-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(fixtures): Replace fixture represenation with a class #12473
Conversation
bf90fca
to
d25a8d9
Compare
3d2ec33
to
84cc5b7
Compare
Hey @The-Compiler @RonnyPfannschmidt I rebased the PR could you please take a look? |
testing/code/test_source.py
Outdated
@@ -478,12 +478,13 @@ def deco_mark(): | |||
def deco_fixture(): | |||
assert False | |||
|
|||
src = inspect.getsource(deco_fixture) | |||
# Since deco_fixture is now an instance of FixtureFunctionDef the getsource function will not work on it. | |||
with pytest.raises(Exception): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you assert against a more specific exception? As I understand, this is not testing that something went wrong but it's not clear what exactly. Also, it's usually a good idea to use the match=
regex with this helper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes will apply. This tries to document the behavior more than testing anything. Before introducing the FixtureFunctionDefinition it was possible to run inspect.getsource on fixtures but now it's not. This is just describing the behavior. I'm open to removing it as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed it to a more specific error. Do you think we can remove the test? This just tests that you cannot use inspect on fixture functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can remove this test. I removed it.
f0324a5
to
9d2a916
Compare
Hey @Glyphack, could you resolve the merge conflicts / rebase this? |
Yes will do today. |
641a3a1
to
bc0a0e5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Iove where this is going
It opens up nice follow ups
src/_pytest/compat.py
Outdated
def get_real_func(obj): | ||
"""Get the real function object of the (possibly) wrapped object by | ||
functools.wraps or functools.partial.""" | ||
:func:`functools.wraps`, or :func:`functools.partial`, or :func:`pytest.fixture`.""" | ||
from _pytest.fixtures import FixtureFunctionDefinition |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we can drop fixture definition from that , we should pass the definitions attribute
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you suggesting to pass the FixtureFunctionDefinition object to this function? If I understand correctly this function is expected to take any object and return the real object according to this test testing the behavior:
https://github.com/Glyphack/pytest/blob/3d7416c2dd5dcf2d60a8b30636dcc90978c64215/testing/test_compat.py#L63
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As fixture definitions are no longer wrapping functions into other functions
The suggestion is to no longer consider them wrappers and passing the function directly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RonnyPfannschmidt Sorry I didn't fully understand your comment.
The current unwrapping behavior here is only for two cases:
- The pytest fixture is applied on a function so I can display this case with Fixture(f)
- The pytest fixture is decorated so we have deco(Fixture(f))
The first case is handled by obj = obj._get_wrapped_function()
line and second case is handled by obj = inspect.unwrap(obj, stop=lambda x: type(x) is FixtureFunctionDefinition)
.
If we drop any of them we cannot handle one of those two cases.
But I feel I am missing your point but unwrapping to me are these two lines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Decorators cannot apply to a fixture definition
So that should trigger a error
It's wrong even now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wrong about that but the behavior is still needed. For example this test fails if we don't do unwrapping in this function:
https://github.com/Glyphack/pytest/blob/a6318c944f907bc350922c3961365e9ecd8d3e81/testing/python/integration.py#L11
The reason is that getfslineno
is using this function to unwrap a decorator:
https://github.com/Glyphack/pytest/blob/a6318c944f907bc350922c3961365e9ecd8d3e81/src/_pytest/_code/code.py#L1341
I think the solution is to make a separate function for the cases when we want to perform the unwrapping? What is your suggestion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I meant is that we no longer ought to pass fixture definition to anything that unwrapped
Definition is no longer a function
Perhaps we should drop the call altogether
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks I realized the reason my code required this check to be here was that I was getting objects using the fixture name instead of their actual name and mistakenly I calling this on non fixture objects. After fixing that I could remove the get_real_method and simplify this function.
Thank you both for the review. I left some TODO comments I will address them in a follow up PR. |
Co-authored-by: Bruno Oliveira <[email protected]>
Co-authored-by: Bruno Oliveira <[email protected]>
Co-authored-by: Bruno Oliveira <[email protected]>
Co-authored-by: Ran Benita <[email protected]>
Thank you so much for the reviews. Please take another look and let me know if there are more improvements. I was busy last week I will be quicker in resolving them this time. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update! Please see my comments.
@nicoddemus I like to help with the PR you created. I'll take a look there once this is done.
I think @nicoddemus's intention is for you to incorporate his suggestions in this PR, this way we can review & merge everything together.
"https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly about how to update your code." | ||
) | ||
|
||
@functools.wraps(function) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the best thing would be to use functools.update_wrapper
in FixtureFunctionDefinition.__init__
, unless there is something preventing it.
src/_pytest/fixtures.py
Outdated
@@ -1352,7 +1360,7 @@ def fixture( | |||
def yield_fixture( | |||
fixture_function=None, | |||
*args, | |||
scope="function", | |||
scope: _ScopeName = "function", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is still relevant
src/_pytest/fixtures.py
Outdated
self.__name__ = self.name | ||
self._fixture_function_marker = fixture_function_marker | ||
self._fixture_function = function | ||
self._instance = instance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it wouldn't be simpler to keep self._fixture_function
bound to instance
instead of keeping _instance
and binding it lazily? Is there a reason why it needs to be lazy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I applied this and it's simpler. I don't think there's any reason to do it when we get the fixture.
src/_pytest/fixtures.py
Outdated
@@ -1271,7 +1279,7 @@ def fixture( | |||
autouse: bool = ..., | |||
ids: Sequence[object | None] | Callable[[Any], object | None] | None = ..., | |||
name: str | None = None, | |||
) -> FixtureFunctionMarker: ... | |||
) -> FixtureFunctionDefinition: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is still relevant
@bluetech not really, I meant to apply those later (I only created a Draft PR to not lose the local commits I made). |
Ah sorry for the assumption. The reason I thought it would be better to combine is that merging as-is would introduce a typing regression (fixture functions would lose their signature). But it's only temporary so not a problem. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @Glyphack!
* Carry around parameters and return value in `FixtureFunctionDefinition`. * Add `FixtureParams` to `FixtureDef`. Follow up to pytest-dev#12473.
* Carry around parameters and return value in `FixtureFunctionDefinition`. * Add `FixtureParams` to `FixtureDef`. Follow up to pytest-dev#12473.
FYI, this breaks pytest-factoryboy, but that's something I have to deal with. That being said, |
Closes #11525
During the sprint we discussed fixing the above issue and thought maybe it's a better idea to add a better represenation to fixtures. To do this without patching the object more, this PR refactors fixtures to have a class with attributes.
The main rational behind that is:
Example
Previously we had:
where fixt is a pytest fixture function that is not replaced by it's value(directly used)
Now we print: