Skip to content

Commit 9231432

Browse files
committed
Simplify deferred annotation handling code
- Remove redundant _snapshot_annotation_locals function (was just a copy) - Use _caller_locals directly instead of double-copying - Fix priority order in _build_evaluation_locals: snapshot locals now have highest priority over default arguments - Use public API inspect.Parameter.empty instead of inspect._empty - Clean up duplicate docstring entries for id, kwargs, produces - Remove unnecessary C901 noqa (function simplified)
1 parent 525a9ef commit 9231432

2 files changed

Lines changed: 14 additions & 44 deletions

File tree

src/_pytask/_inspect.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,14 @@ def get_annotations(
7373
def _build_evaluation_locals(
7474
obj: Callable[..., Any], provided_locals: dict[str, Any] | None
7575
) -> dict[str, Any]:
76+
# Order matters: later updates override earlier ones.
77+
# Default arguments are lowest priority (fallbacks), then provided_locals,
78+
# then snapshot_locals (captured loop variables) have highest priority.
7679
evaluation_locals: dict[str, Any] = {}
80+
evaluation_locals.update(_get_default_argument_locals(obj))
7781
if provided_locals:
7882
evaluation_locals.update(provided_locals)
7983
evaluation_locals.update(_get_snapshot_locals(obj))
80-
evaluation_locals.update(_get_default_argument_locals(obj))
8184
return evaluation_locals
8285

8386

@@ -95,7 +98,7 @@ def _get_default_argument_locals(obj: Callable[..., Any]) -> dict[str, Any]:
9598

9699
defaults = {}
97100
for parameter in parameters:
98-
if parameter.default is not inspect._empty:
101+
if parameter.default is not inspect.Parameter.empty:
99102
defaults[parameter.name] = parameter.default
100103
return defaults
101104

src/_pytask/task_utils.py

Lines changed: 9 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"""
4545

4646

47-
def task( # noqa: PLR0913, C901
47+
def task( # noqa: PLR0913
4848
name: str | None = None,
4949
*,
5050
after: str | Callable[..., Any] | list[Callable[..., Any]] | None = None,
@@ -72,30 +72,18 @@ def task( # noqa: PLR0913, C901
7272
information.
7373
is_generator
7474
An indicator whether this task is a task generator.
75-
id
76-
An id for the task if it is part of a parametrization. Otherwise, an automatic
77-
id will be generated. See
78-
:doc:`this tutorial <../tutorials/repeating_tasks_with_different_inputs>` for
79-
more information.
80-
kwargs
81-
A dictionary containing keyword arguments which are passed to the task when it
82-
is executed.
83-
produces
84-
Definition of products to parse the function returns and store them. See
85-
:doc:`this how-to guide <../how_to_guides/using_task_returns>` for more
8675
id
8776
An id for the task if it is part of a repetition. Otherwise, an automatic id
8877
will be generated. See :ref:`how-to-repeat-a-task-with-different-inputs-the-id`
8978
for more information.
9079
kwargs
91-
Use a dictionary to pass any keyword arguments to the task function which can be
92-
dependencies or products of the task. Read :ref:`task-kwargs` for more
93-
information.
94-
produces
95-
Use this argument if you want to parse the return of the task function as a
96-
product, but you cannot annotate the return of the function. See :doc:`this
97-
how-to guide <../how_to_guides/using_task_returns>` or :ref:`task-produces` for
80+
A dictionary containing keyword arguments which are passed to the task function.
81+
These can be dependencies or products of the task. Read :ref:`task-kwargs` for
9882
more information.
83+
produces
84+
Use this argument to parse the return of the task function as a product. See
85+
:doc:`this how-to guide <../how_to_guides/using_task_returns>` or
86+
:ref:`task-produces` for more information.
9987
10088
Examples
10189
--------
@@ -150,8 +138,6 @@ def wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
150138
parsed_name = _parse_name(unwrapped, name)
151139
parsed_after = _parse_after(after)
152140

153-
annotation_locals = _snapshot_annotation_locals(_caller_locals)
154-
155141
if hasattr(unwrapped, "pytask_meta"):
156142
unwrapped.pytask_meta.after = parsed_after
157143
unwrapped.pytask_meta.is_generator = is_generator
@@ -160,11 +146,11 @@ def wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
160146
unwrapped.pytask_meta.markers.append(Mark("task", (), {}))
161147
unwrapped.pytask_meta.name = parsed_name
162148
unwrapped.pytask_meta.produces = produces
163-
unwrapped.pytask_meta.after = parsed_after
149+
unwrapped.pytask_meta.annotation_locals = _caller_locals
164150
else:
165151
unwrapped.pytask_meta = CollectionMetadata( # type: ignore[attr-defined]
166152
after=parsed_after,
167-
annotation_locals=annotation_locals,
153+
annotation_locals=_caller_locals,
168154
is_generator=is_generator,
169155
id_=id,
170156
kwargs=parsed_kwargs,
@@ -173,9 +159,6 @@ def wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
173159
produces=produces,
174160
)
175161

176-
if annotation_locals is not None and hasattr(unwrapped, "pytask_meta"):
177-
unwrapped.pytask_meta.annotation_locals = annotation_locals
178-
179162
if coiled_kwargs and hasattr(unwrapped, "pytask_meta"):
180163
unwrapped.pytask_meta.attributes["coiled_kwargs"] = coiled_kwargs
181164

@@ -314,22 +297,6 @@ def parse_keyword_arguments_from_signature_defaults(
314297
return kwargs
315298

316299

317-
def _snapshot_annotation_locals(
318-
caller_locals: dict[str, Any] | None,
319-
) -> dict[str, Any] | None:
320-
"""Capture caller's frame locals at decoration time for deferred annotation eval.
321-
322-
This function captures variables that may be referenced in type annotations but
323-
won't be available when annotations are evaluated later (e.g., loop variables in
324-
task generators under Python 3.14's PEP 649 deferred annotations).
325-
326-
We capture the caller's frame locals - variables in the scope where @task is
327-
applied (e.g., loop variables like `path` that are only referenced in annotations).
328-
329-
"""
330-
return caller_locals.copy() if caller_locals else None
331-
332-
333300
def _generate_ids_for_tasks(
334301
tasks: list[tuple[str, Callable[..., Any]]],
335302
) -> dict[str, Callable[..., Any]]:

0 commit comments

Comments
 (0)