1
1
"""This module provides utility functions for :mod:`_pytask.collect`."""
2
2
from __future__ import annotations
3
3
4
- import inspect
5
4
import itertools
6
5
import uuid
7
6
from pathlib import Path
@@ -79,9 +78,15 @@ def parse_nodes(
79
78
session : Session , path : Path , name : str , obj : Any , parser : Callable [..., Any ]
80
79
) -> Any :
81
80
"""Parse nodes from object."""
81
+ arg_name = parser .__name__
82
82
objects = _extract_nodes_from_function_markers (obj , parser )
83
- nodes = _convert_objects_to_node_dictionary (objects , parser .__name__ )
84
- nodes = tree_map (lambda x : _collect_old_dependencies (session , path , name , x ), nodes )
83
+ nodes = _convert_objects_to_node_dictionary (objects , arg_name )
84
+ nodes = tree_map (
85
+ lambda x : _collect_decorator_nodes (
86
+ session , path , name , NodeInfo (arg_name , (), x )
87
+ ),
88
+ nodes ,
89
+ )
85
90
return nodes
86
91
87
92
@@ -211,10 +216,23 @@ def _merge_dictionaries(list_of_dicts: list[dict[Any, Any]]) -> dict[Any, Any]:
211
216
return out
212
217
213
218
214
- def parse_dependencies_from_task_function (
219
+ _ERROR_MULTIPLE_DEPENDENCY_DEFINITIONS = """"Dependencies are defined via \
220
+ '@pytask.mark.depends_on' and as default arguments for the argument 'depends_on'. Use \
221
+ only one way and not both.
222
+
223
+ Hint: You do not need to use 'depends_on' since pytask v0.4. Every function argument \
224
+ that is not a product is treated as a dependency. Read more about dependencies in the \
225
+ documentation: https://tinyurl.com/yrezszr4.
226
+ """
227
+
228
+
229
+ def parse_dependencies_from_task_function ( # noqa: C901
215
230
session : Session , path : Path , name : str , obj : Any
216
231
) -> dict [str , Any ]:
217
232
"""Parse dependencies from task function."""
233
+ has_depends_on_decorator = False
234
+ has_depends_on_argument = False
235
+
218
236
if has_mark (obj , "depends_on" ):
219
237
nodes = parse_nodes (session , path , name , obj , depends_on )
220
238
return {"depends_on" : nodes }
@@ -224,14 +242,30 @@ def parse_dependencies_from_task_function(
224
242
kwargs = {** signature_defaults , ** task_kwargs }
225
243
kwargs .pop ("produces" , None )
226
244
245
+ dependencies = {}
246
+ # Parse products from task decorated with @task and that uses produces.
247
+ if "depends_on" in kwargs :
248
+ has_depends_on_argument = True
249
+ dependencies ["depends_on" ] = tree_map (
250
+ lambda x : _collect_decorator_nodes (
251
+ session , path , name , NodeInfo (arg_name = "depends_on" , path = (), value = x )
252
+ ),
253
+ kwargs ["depends_on" ],
254
+ )
255
+
256
+ if has_depends_on_decorator and has_depends_on_argument :
257
+ raise NodeNotCollectedError (_ERROR_MULTIPLE_PRODUCT_DEFINITIONS )
258
+
227
259
parameters_with_product_annot = _find_args_with_product_annotation (obj )
228
260
parameters_with_node_annot = _find_args_with_node_annotation (obj )
229
261
230
- dependencies = {}
231
262
for parameter_name , value in kwargs .items ():
232
263
if parameter_name in parameters_with_product_annot :
233
264
continue
234
265
266
+ if parameter_name == "depends_on" :
267
+ continue
268
+
235
269
if parameter_name in parameters_with_node_annot :
236
270
237
271
def _evolve (x : Any ) -> Any :
@@ -316,8 +350,7 @@ def parse_products_from_task_function(
316
350
317
351
"""
318
352
has_produces_decorator = False
319
- has_task_decorator = False
320
- has_signature_default = False
353
+ has_produces_argument = False
321
354
has_annotation = False
322
355
out = {}
323
356
@@ -333,6 +366,7 @@ def parse_products_from_task_function(
333
366
334
367
# Parse products from task decorated with @task and that uses produces.
335
368
if "produces" in kwargs :
369
+ has_produces_argument = True
336
370
collected_products = tree_map_with_path (
337
371
lambda p , x : _collect_product (
338
372
session ,
@@ -345,52 +379,26 @@ def parse_products_from_task_function(
345
379
)
346
380
out = {"produces" : collected_products }
347
381
348
- parameters = inspect .signature (obj ).parameters
349
-
350
- # Parse products from default arguments
351
- if not has_mark (obj , "task" ) and "produces" in parameters :
352
- parameter = parameters ["produces" ]
353
- if parameter .default is not parameter .empty :
354
- has_signature_default = True
355
- # Use _collect_new_node to not collect strings.
356
- collected_products = tree_map_with_path (
357
- lambda p , x : _collect_product (
358
- session ,
359
- path ,
360
- name ,
361
- NodeInfo (arg_name = "produces" , path = p , value = x ),
362
- is_string_allowed = False ,
363
- ),
364
- parameter .default ,
365
- )
366
- out = {"produces" : collected_products }
367
-
368
382
parameters_with_product_annot = _find_args_with_product_annotation (obj )
369
383
if parameters_with_product_annot :
370
384
has_annotation = True
371
385
for parameter_name in parameters_with_product_annot :
372
- # Use _collect_new_node to not collect strings.
373
- collected_products = tree_map_with_path (
374
- lambda p , x : _collect_product (
375
- session ,
376
- path ,
377
- name ,
378
- NodeInfo (parameter_name , p , x ), # noqa: B023
379
- is_string_allowed = False ,
380
- ),
381
- kwargs [parameter_name ],
382
- )
383
- out = {parameter_name : collected_products }
386
+ if parameter_name in kwargs :
387
+ # Use _collect_new_node to not collect strings.
388
+ collected_products = tree_map_with_path (
389
+ lambda p , x : _collect_product (
390
+ session ,
391
+ path ,
392
+ name ,
393
+ NodeInfo (parameter_name , p , x ), # noqa: B023
394
+ is_string_allowed = False ,
395
+ ),
396
+ kwargs [parameter_name ],
397
+ )
398
+ out = {parameter_name : collected_products }
384
399
385
400
if (
386
- sum (
387
- (
388
- has_produces_decorator ,
389
- has_task_decorator ,
390
- has_signature_default ,
391
- has_annotation ,
392
- )
393
- )
401
+ sum ((has_produces_decorator , has_produces_argument , has_annotation ))
394
402
>= 2 # noqa: PLR2004
395
403
):
396
404
raise NodeNotCollectedError (_ERROR_MULTIPLE_PRODUCT_DEFINITIONS )
@@ -416,8 +424,15 @@ def _find_args_with_product_annotation(func: Callable[..., Any]) -> list[str]:
416
424
return args_with_product_annot
417
425
418
426
419
- def _collect_old_dependencies (
420
- session : Session , path : Path , name : str , node : str | Path
427
+ _ERROR_WRONG_TYPE_DECORATOR = """'@pytask.mark.depends_on', '@pytask.mark.produces', \
428
+ and their function arguments can only accept values of type 'str' and 'pathlib.Path' \
429
+ or the same values nested in tuples, lists, and dictionaries. Here, {node} has type \
430
+ {node_type}.
431
+ """
432
+
433
+
434
+ def _collect_decorator_nodes (
435
+ session : Session , path : Path , name : str , node_info : NodeInfo
421
436
) -> dict [str , MetaNode ]:
422
437
"""Collect nodes for a task.
423
438
@@ -427,22 +442,26 @@ def _collect_old_dependencies(
427
442
If the node could not collected.
428
443
429
444
"""
445
+ node = node_info .value
446
+
430
447
if not isinstance (node , (str , Path )):
431
- raise ValueError (
432
- "'@pytask.mark.depends_on' and '@pytask.mark.produces' can only accept "
433
- "values of type 'str' and 'pathlib.Path' or the same values nested in "
434
- f"tuples, lists, and dictionaries. Here, { node } has type { type (node )} ."
448
+ raise NodeNotCollectedError (
449
+ _ERROR_WRONG_TYPE_DECORATOR .format (node = node , node_type = type (node ))
435
450
)
436
451
437
452
if isinstance (node , str ):
438
453
node = Path (node )
454
+ node_info = node_info ._replace (value = node )
439
455
440
456
collected_node = session .hook .pytask_collect_node (
441
- session = session , path = path , node_info = NodeInfo ( "produces" , (), node )
457
+ session = session , path = path , node_info = node_info
442
458
)
443
459
if collected_node is None :
460
+ kind = {"depends_on" : "dependency" , "produces" : "product" }.get (
461
+ node_info .arg_name
462
+ )
444
463
raise NodeNotCollectedError (
445
- f"{ node !r} cannot be parsed as a dependency for task { name !r} in { path !r} ."
464
+ f"{ node !r} cannot be parsed as a { kind } for task { name !r} in { path !r} ."
446
465
)
447
466
448
467
return collected_node
@@ -462,7 +481,7 @@ def _collect_dependencies(
462
481
node = node_info .value
463
482
464
483
collected_node = session .hook .pytask_collect_node (
465
- session = session , path = path , node_info = node_info , node = node
484
+ session = session , path = path , node_info = node_info
466
485
)
467
486
if collected_node is None :
468
487
raise NodeNotCollectedError (
@@ -509,9 +528,10 @@ def _collect_product(
509
528
510
529
if isinstance (node , str ):
511
530
node = Path (node )
531
+ node_info = node_info ._replace (value = node )
512
532
513
533
collected_node = session .hook .pytask_collect_node (
514
- session = session , path = path , node_info = node_info , node = node
534
+ session = session , path = path , node_info = node_info
515
535
)
516
536
if collected_node is None :
517
537
raise NodeNotCollectedError (
0 commit comments