33
44import itertools
55import uuid
6+ import warnings
67from pathlib import Path
78from typing import Any
89from typing import Callable
2122from _pytask .nodes import PythonNode
2223from _pytask .shared import find_duplicates
2324from _pytask .task_utils import parse_keyword_arguments_from_signature_defaults
25+ from _pytask .tree_util import PyTree
2426from _pytask .tree_util import tree_leaves
2527from _pytask .tree_util import tree_map
2628from _pytask .tree_util import tree_map_with_path
4244]
4345
4446
45- def depends_on (
46- objects : Any | Iterable [Any ] | dict [Any , Any ]
47- ) -> Any | Iterable [Any ] | dict [Any , Any ]:
47+ def depends_on (objects : PyTree [Any ]) -> PyTree [Any ]:
4848 """Specify dependencies for a task.
4949
5050 Parameters
@@ -58,9 +58,7 @@ def depends_on(
5858 return objects
5959
6060
61- def produces (
62- objects : Any | Iterable [Any ] | dict [Any , Any ]
63- ) -> Any | Iterable [Any ] | dict [Any , Any ]:
61+ def produces (objects : PyTree [Any ]) -> PyTree [Any ]:
6462 """Specify products of a task.
6563
6664 Parameters
@@ -342,6 +340,18 @@ def _find_args_with_node_annotation(func: Callable[..., Any]) -> dict[str, MetaN
342340Read more about products in the documentation: https://tinyurl.com/yrezszr4.
343341"""
344342
343+ _WARNING_PRODUCES_AS_KWARG = """Using 'produces' as an argument name to specify \
344+ products is deprecated and won't be available in pytask v0.5. Instead, use the product \
345+ annotation, described in this tutorial: https://tinyurl.com/yrezszr4.
346+
347+ from typing_extensions import Annotated
348+ from pytask import Product
349+
350+ def task_example(produces: Annotated[..., Product]):
351+ ...
352+
353+ """
354+
345355
346356def parse_products_from_task_function (
347357 session : Session , path : Path , name : str , obj : Any
@@ -369,8 +379,14 @@ def parse_products_from_task_function(
369379 signature_defaults = parse_keyword_arguments_from_signature_defaults (obj )
370380 kwargs = {** signature_defaults , ** task_kwargs }
371381
382+ parameters_with_product_annot = _find_args_with_product_annotation (obj )
383+
372384 # Parse products from task decorated with @task and that uses produces.
373385 if "produces" in kwargs :
386+ if "produces" not in parameters_with_product_annot :
387+ warnings .warn (
388+ _WARNING_PRODUCES_AS_KWARG , category = FutureWarning , stacklevel = 1
389+ )
374390 has_produces_argument = True
375391 collected_products = tree_map_with_path (
376392 lambda p , x : _collect_product (
@@ -384,7 +400,6 @@ def parse_products_from_task_function(
384400 )
385401 out = {"produces" : collected_products }
386402
387- parameters_with_product_annot = _find_args_with_product_annotation (obj )
388403 if parameters_with_product_annot :
389404 has_annotation = True
390405 for parameter_name in parameters_with_product_annot :
@@ -436,6 +451,11 @@ def _find_args_with_product_annotation(func: Callable[..., Any]) -> list[str]:
436451"""
437452
438453
454+ _WARNING_STRING_DEPRECATED = """Using strings to specify a {kind} is deprecated. Pass \
455+ a 'pathlib.Path' instead with 'Path("{node}")'.
456+ """
457+
458+
439459def _collect_decorator_nodes (
440460 session : Session , path : Path , name : str , node_info : NodeInfo
441461) -> dict [str , MetaNode ]:
@@ -448,23 +468,26 @@ def _collect_decorator_nodes(
448468
449469 """
450470 node = node_info .value
471+ kind = {"depends_on" : "dependency" , "produces" : "product" }.get (node_info .arg_name )
451472
452473 if not isinstance (node , (str , Path )):
453474 raise NodeNotCollectedError (
454475 _ERROR_WRONG_TYPE_DECORATOR .format (node = node , node_type = type (node ))
455476 )
456477
457478 if isinstance (node , str ):
479+ warnings .warn (
480+ _WARNING_STRING_DEPRECATED .format (kind = kind , node = node ),
481+ category = FutureWarning ,
482+ stacklevel = 1 ,
483+ )
458484 node = Path (node )
459485 node_info = node_info ._replace (value = node )
460486
461487 collected_node = session .hook .pytask_collect_node (
462488 session = session , path = path , node_info = node_info
463489 )
464490 if collected_node is None :
465- kind = {"depends_on" : "dependency" , "produces" : "product" }.get (
466- node_info .arg_name
467- )
468491 raise NodeNotCollectedError (
469492 f"{ node !r} cannot be parsed as a { kind } for task { name !r} in { path !r} ."
470493 )
@@ -525,10 +548,9 @@ def _collect_product(
525548 # The parameter defaults only support Path objects.
526549 if not isinstance (node , Path ) and not is_string_allowed :
527550 raise ValueError (
528- "If you use 'produces' as a function argument of a task and pass values as "
529- "function defaults, it can only accept values of type 'pathlib.Path' or "
530- "the same value nested in tuples, lists, and dictionaries. Here, "
531- f"{ node !r} has type { type (node )} ."
551+ "If you declare products with 'Annotated[..., Product]', only values of "
552+ "type 'pathlib.Path' optionally nested in tuples, lists, and "
553+ f"dictionaries are allowed. Here, { node !r} has type { type (node )} ."
532554 )
533555
534556 if isinstance (node , str ):
0 commit comments