Skip to content
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

Type annotate fixtures.py & related #6736

Closed
wants to merge 1 commit into from

Conversation

bluetech
Copy link
Member

@bluetech bluetech commented Feb 14, 2020

Extracted from #6717.

Depends/based on #6737.

fixturedefs = self._arg2fixturedefs.get(argname, None)
if fixturedefs is None:
# we arrive here because of a dynamic call to
# getfixturevalue(argname) usage which was naturally
# not known at parsing/collection time
assert self._pyfuncitem.parent is not None
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is immediately dereferenced below so should be fine.

""" Dynamically run a named fixture function.

Declaring fixtures via function argument is recommended where possible.
But if you can only decide whether to use another fixture at test
setup time, you may use this function to retrieve it inside a fixture
or test function body.
"""
return self._get_active_fixturedef(argname).cached_result[0]
fixturedef = self._get_active_fixturedef(argname)
assert fixturedef.cached_result is not None
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Immediately dereferenced below.

while 1:
fixturedef = getattr(current, "_fixturedef", None)
if fixturedef is None:
values.reverse()
return values
values.append(fixturedef)
assert isinstance(current, SubRequest)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This attribute is only set on SubRequest.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would make a helpful in-code comment for future maintainers 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know, once the assert is in, the implication is reversed -- the access is legal because of the assert, not the other way :)

@@ -669,20 +718,21 @@ def _schedule_finalizers(self, fixturedef, subrequest):
super()._schedule_finalizers(fixturedef, subrequest)


scopes = "session package module class function".split()
scopes = ["session", "package", "module", "class", "function"] # type: List[_Scope]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to make the type annotation work (_Scope is a Literal).

"""Look up the index of ``scope`` and raise a descriptive value error
if not defined.
"""
strscopes = scopes # type: Sequence[str]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a hack to expand the type of scopes to allow checking against an str rather than _Scope.

@@ -871,6 +927,7 @@ def finish(self, request):
exceptions.append(sys.exc_info())
if exceptions:
_, val, tb = exceptions[0]
assert val is not None
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dereferenced just below.

yield
if request.config.option.setupshow:
if hasattr(request, "param"):
# Save the fixture parameter so ._show_fixture_action() can
# display it now and during the teardown (in .finish()).
if fixturedef.ids:
if callable(fixturedef.ids):
fixturedef.cached_param = fixturedef.ids(request.param)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed so I only need to ignore once.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a helper method FixtureDef.set_cached_param would be appropriate? Though I can see an argument that it's not worth the trouble when we have to maintain the current option for backwards-compatibility.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm not sure it's worth it.

@bluetech bluetech changed the title Type annotate fixtures.py & related WIP: Type annotate fixtures.py & related Feb 14, 2020
@bluetech bluetech changed the title WIP: Type annotate fixtures.py & related Type annotate fixtures.py & related Feb 14, 2020
Copy link
Member

@Zac-HD Zac-HD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @bluetech! A few questions and suggestions for further annotations below, but overall this looks great to me 😁

yield
if request.config.option.setupshow:
if hasattr(request, "param"):
# Save the fixture parameter so ._show_fixture_action() can
# display it now and during the teardown (in .finish()).
if fixturedef.ids:
if callable(fixturedef.ids):
fixturedef.cached_param = fixturedef.ids(request.param)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a helper method FixtureDef.set_cached_param would be appropriate? Though I can see an argument that it's not worth the trouble when we have to maintain the current option for backwards-compatibility.

@@ -111,7 +137,9 @@ def get_scope_node(node, scope):
return node.getparent(cls)


def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager):
def add_funcarg_pseudo_fixture_def(
collector, metafunc: "Metafunc", fixturemanager: "FixtureManager"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth annotating collector? AFAICT it's an Optional[Type[nodes.Node]].

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I'm not sure of something, and too lazy to check/verify, I leave it unannotated. Usually some later PR adds it :)

src/_pytest/fixtures.py Outdated Show resolved Hide resolved
while 1:
fixturedef = getattr(current, "_fixturedef", None)
if fixturedef is None:
values.reverse()
return values
values.append(fixturedef)
assert isinstance(current, SubRequest)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would make a helpful in-code comment for future maintainers 😄

src/_pytest/fixtures.py Outdated Show resolved Hide resolved
self,
request: "FixtureRequest",
scope: "_Scope",
param,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

param: object, ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, this one can also be NOTSET, and I'd like to distinguish it in the type annotation from just an object (although object subsumes everything) just for clarity.

I think there is another PR that does that although I sort of lost track myself already 👣

@@ -805,7 +855,7 @@ def _teardown_yield_fixture(fixturefunc, it):
)


def _eval_scope_callable(scope_callable, fixture_name, config):
def _eval_scope_callable(scope_callable, fixture_name: str, config: Config) -> str:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scope_callable: Callable[..., str] - speccing keyword arguments is pretty fiddly, but even without the arguments specified this could catch a fair few issues.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to leave this one for for later.

@@ -1317,7 +1379,9 @@ def _getautousenames(self, nodeid):
autousenames.extend(basenames)
return autousenames

def getfixtureclosure(self, fixturenames, parentnode, ignore_args=()):
def getfixtureclosure(
self, fixturenames: Tuple[str, ...], parentnode, ignore_args: Sequence[str] = ()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parentnode: nodes.Node

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, I prefer to leave this one.

Copy link
Member Author

@bluetech bluetech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review @Zac-HD. I applied (some) of your suggestions. Also rebased.

@@ -111,7 +137,9 @@ def get_scope_node(node, scope):
return node.getparent(cls)


def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager):
def add_funcarg_pseudo_fixture_def(
collector, metafunc: "Metafunc", fixturemanager: "FixtureManager"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I'm not sure of something, and too lazy to check/verify, I leave it unannotated. Usually some later PR adds it :)

src/_pytest/fixtures.py Outdated Show resolved Hide resolved
while 1:
fixturedef = getattr(current, "_fixturedef", None)
if fixturedef is None:
values.reverse()
return values
values.append(fixturedef)
assert isinstance(current, SubRequest)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know, once the assert is in, the implication is reversed -- the access is legal because of the assert, not the other way :)

src/_pytest/fixtures.py Outdated Show resolved Hide resolved
self,
request: "FixtureRequest",
scope: "_Scope",
param,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, this one can also be NOTSET, and I'd like to distinguish it in the type annotation from just an object (although object subsumes everything) just for clarity.

I think there is another PR that does that although I sort of lost track myself already 👣

@@ -805,7 +855,7 @@ def _teardown_yield_fixture(fixturefunc, it):
)


def _eval_scope_callable(scope_callable, fixture_name, config):
def _eval_scope_callable(scope_callable, fixture_name: str, config: Config) -> str:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to leave this one for for later.

@@ -1317,7 +1379,9 @@ def _getautousenames(self, nodeid):
autousenames.extend(basenames)
return autousenames

def getfixtureclosure(self, fixturenames, parentnode, ignore_args=()):
def getfixtureclosure(
self, fixturenames: Tuple[str, ...], parentnode, ignore_args: Sequence[str] = ()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, I prefer to leave this one.

yield
if request.config.option.setupshow:
if hasattr(request, "param"):
# Save the fixture parameter so ._show_fixture_action() can
# display it now and during the teardown (in .finish()).
if fixturedef.ids:
if callable(fixturedef.ids):
fixturedef.cached_param = fixturedef.ids(request.param)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm not sure it's worth it.

@bluetech bluetech closed this Feb 28, 2020
@bluetech bluetech deleted the fixtures-annotate branch February 28, 2020 12:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants