Refactoring:
When using IDE built in refactoring tools it’ll not refactor strings. With the current solution you’ll need to manually edit those strings while in the proposed solution you don’t. This saves a lot of time and prevents bugs. @patch(module.function) will be refactored while @patch("module.function") will not.
Hi Lukas, and welcome! Marry Christmas, happy holidays, and best wishes for the new year!
Thank you for giving an example, but unfortunately, you will have to tell us what your proposed feature actually is, we can’t read your mind. Please explain in words what your change is, and how it will work.
What is @patch? Is it part of your proposal? Is your proposal specific to just patch, or all decorators, or all strings, or what?
Okay, that’s my fault. When I said “words”, I meant written words, not a video.
I tried to watch your video, I really did. It is impossible for me to read the text in on the screen, the text is too small (shrunk to about 4pt high) and the contrast is too low (looks like grey text on a dark grey background).
In your video, you said “As you can see…” – no, I can’t see. This is what it looks like in my browser:
So I have no clue what you were typing, or what you were doing. My guess is that you are using some sort of IDE but it is impossible for me to see which one.
Lukas is proposing that patch() also accept unquoted targets, like @patch(__main__.SomeClass).
Is that correct Lukas?
If so, please explain how this can work in the general case.
For example @patch('sys.stdout', new_callable=StringIO). If we remove the quoting, sys.stdout is evaluated and returns (usually) a _io.TextIOWrapper object, which is all that the patch function receives. How is patch supposed to know it is supposed to patch the sys module, just from a TextIOWrapper instance?
In case that problem is not clear, let’s say we want to patch math.pi to be a different value:
from unittest.mock import patch
@patch('math.pi', new_callable=lambda: 3.0)
def func(pi):
import math
return 2*math.pi
func() # returns 6.0
If I write it as @patch(math.pi, ...) the patch function receives the float 3.1415… how does it know which module to patch?
It’s not inherently a bad idea, it’s just not workable in the way you originally suggested for the reasons @steven.daprano mentioned.
However there is another way that your original goals could be achieved. Currently the patch methods first argument is annotated as a plain string. We could introduce a new type hint class to the typing module that inherits from str, but indicates that the string is a Python module/attribute reference?
I.e
from typing import Reference
def patch(target: Refrerence, …):
…
This could be used in third party libraries to annotate functions that also accept string references, and it would simplify IDE support for refactoring them as these argument types would be clearly delineated and wouldn’t need to be stored in some global allowlist of “string arguments that are actually not just any string and are Python references instead”.
This would be useful for Django for example, which uses these string reference types heavily in project settings.