Skip to content

Commit 3e45e58

Browse files
authored
Merge 3940aec into e525c6f
2 parents e525c6f + 3940aec commit 3e45e58

File tree

4 files changed

+87
-181
lines changed

4 files changed

+87
-181
lines changed

docs/source/changes.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and
1414
caused flaky tests.
1515
- {pull}`486` adds default names to {class}`~pytask.PPathNode`.
1616
- {pull}`488` raises an error when an invalid value is used in a return annotation.
17+
- {pull}`489` simplifies parsing products and does not raise an error when a product
18+
annotation is used with the argument name `produces`. And, allow `produces` to intake
19+
any node.
1720

1821
## 0.4.2 - 2023-11-8
1922

src/_pytask/collect_utils.py

Lines changed: 15 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ def parse_dependencies_from_task_function(
264264
kwargs = {**signature_defaults, **task_kwargs}
265265
kwargs.pop("produces", None)
266266

267-
# Parse products from task decorated with @task and that uses produces.
267+
# Parse dependencies from task when @task is used.
268268
if "depends_on" in kwargs:
269269
has_depends_on_argument = True
270270
dependencies["depends_on"] = tree_map(
@@ -375,7 +375,7 @@ def _find_args_with_node_annotation(func: Callable[..., Any]) -> dict[str, PNode
375375
_ERROR_MULTIPLE_PRODUCT_DEFINITIONS = """The task uses multiple ways to define \
376376
products. Products should be defined with either
377377
378-
- 'typing.Annotated[Path(...), Product]' (recommended)
378+
- 'typing.Annotated[Path, Product] = Path(...)' (recommended)
379379
- '@pytask.mark.task(kwargs={'produces': Path(...)})'
380380
- as a default argument for 'produces': 'produces = Path(...)'
381381
- '@pytask.mark.produces(Path(...))' (deprecated)
@@ -384,7 +384,7 @@ def _find_args_with_node_annotation(func: Callable[..., Any]) -> dict[str, PNode
384384
"""
385385

386386

387-
def parse_products_from_task_function(
387+
def parse_products_from_task_function( # noqa: C901
388388
session: Session, task_path: Path | None, task_name: str, node_path: Path, obj: Any
389389
) -> dict[str, Any]:
390390
"""Parse products from task function.
@@ -415,26 +415,14 @@ def parse_products_from_task_function(
415415
parameters_with_product_annot = _find_args_with_product_annotation(obj)
416416
parameters_with_node_annot = _find_args_with_node_annotation(obj)
417417

418-
# Parse products from task decorated with @task and that uses produces.
418+
# Allow to collect products from 'produces'.
419419
if "produces" in kwargs:
420-
has_produces_argument = True
421-
collected_products = tree_map_with_path(
422-
lambda p, x: _collect_product(
423-
session,
424-
node_path,
425-
task_name,
426-
NodeInfo(
427-
arg_name="produces",
428-
path=p,
429-
value=x,
430-
task_path=task_path,
431-
task_name=task_name,
432-
),
433-
is_string_allowed=True,
434-
),
435-
kwargs["produces"],
436-
)
437-
out = {"produces": collected_products}
420+
if "produces" not in parameters_with_product_annot:
421+
parameters_with_product_annot.append("produces")
422+
# If there are more parameters with a product annotation, we want to raise an
423+
# error later to warn about mixing different interfaces.
424+
if set(parameters_with_product_annot) - {"produces"}:
425+
has_produces_argument = True
438426

439427
if parameters_with_product_annot:
440428
out = {}
@@ -473,7 +461,7 @@ def parse_products_from_task_function(
473461
task_path=task_path,
474462
task_name=task_name,
475463
),
476-
is_string_allowed=False,
464+
convert_string_to_path=parameter_name == "produces", # noqa: B023
477465
),
478466
value,
479467
)
@@ -493,7 +481,7 @@ def parse_products_from_task_function(
493481
task_path=task_path,
494482
task_name=task_name,
495483
),
496-
is_string_allowed=False,
484+
convert_string_to_path=False,
497485
),
498486
parameters_with_node_annot["return"],
499487
)
@@ -514,7 +502,7 @@ def parse_products_from_task_function(
514502
task_path=task_path,
515503
task_name=task_name,
516504
),
517-
is_string_allowed=False,
505+
convert_string_to_path=False,
518506
),
519507
task_produces,
520508
)
@@ -641,7 +629,7 @@ def _collect_product(
641629
path: Path,
642630
task_name: str,
643631
node_info: NodeInfo,
644-
is_string_allowed: bool = False,
632+
convert_string_to_path: bool = False,
645633
) -> PNode:
646634
"""Collect products for a task.
647635
@@ -655,17 +643,9 @@ def _collect_product(
655643
656644
"""
657645
node = node_info.value
658-
# For historical reasons, task.kwargs is like the deco and supports str and Path.
659-
if not isinstance(node, (str, Path)) and is_string_allowed:
660-
msg = (
661-
f"`@pytask.mark.task(kwargs={{'produces': ...}}` can only accept values of "
662-
"type 'str' and 'pathlib.Path' or the same values nested in tuples, lists, "
663-
f"and dictionaries. Here, {node} has type {type(node)}."
664-
)
665-
raise ValueError(msg)
666646

667647
# If we encounter a string and it is allowed, convert it to a path.
668-
if isinstance(node, str) and is_string_allowed:
648+
if isinstance(node, str) and convert_string_to_path:
669649
node = Path(node)
670650
node_info = node_info._replace(value=node)
671651

0 commit comments

Comments
 (0)