1
1
"""Collect tasks."""
2
2
from __future__ import annotations
3
3
4
- import functools
5
4
import subprocess
6
- from types import FunctionType
5
+ import warnings
7
6
from typing import Any
8
- from typing import TYPE_CHECKING
9
7
10
- from pytask import depends_on
11
8
from pytask import has_mark
12
9
from pytask import hookimpl
10
+ from pytask import is_task_function
13
11
from pytask import Mark
14
- from pytask import parse_nodes
15
- from pytask import produces
12
+ from pytask import NodeInfo
13
+ from pytask import parse_dependencies_from_task_function
14
+ from pytask import parse_products_from_task_function
15
+ from pytask import PathNode
16
+ from pytask import PTask
17
+ from pytask import PythonNode
16
18
from pytask import remove_marks
17
19
from pytask import Session
18
20
from pytask import Task
21
+ from pytask import TaskWithoutPath
19
22
from pytask_stata .shared import convert_task_id_to_name_of_log_file
20
23
from pytask_stata .shared import stata
24
+ from pathlib import Path
21
25
22
- if TYPE_CHECKING :
23
- from pathlib import Path
24
26
25
27
26
28
def run_stata_script (
27
- executable : str , script : Path , options : list [str ], log_name : list [str ], cwd : Path
29
+ _executable : str ,
30
+ _script : Path ,
31
+ _options : list [str ],
32
+ _log_name : list [str ],
33
+ _cwd : Path ,
28
34
) -> None :
29
35
"""Run an R script."""
30
- cmd = [executable , "-e" , "do" , script .as_posix (), * options , * log_name ]
36
+ cmd = [_executable , "-e" , "do" , _script .as_posix (), * _options , * _log_name ]
31
37
print ("Executing " + " " .join (cmd ) + "." ) # noqa: T201
32
- subprocess .run (cmd , cwd = cwd , check = True ) # noqa: S603
38
+ subprocess .run (cmd , cwd = _cwd , check = True ) # noqa: S603
33
39
34
40
35
41
@hookimpl
@@ -41,11 +47,11 @@ def pytask_collect_task(
41
47
42
48
if (
43
49
(name .startswith ("task_" ) or has_mark (obj , "task" ))
44
- and callable (obj )
50
+ and is_task_function (obj )
45
51
and has_mark (obj , "stata" )
46
52
):
53
+ # Parse the @pytask.mark.stata decorator.
47
54
obj , marks = remove_marks (obj , "stata" )
48
-
49
55
if len (marks ) > 1 :
50
56
raise ValueError (
51
57
f"Task { name !r} has multiple @pytask.mark.stata marks, but only one is "
@@ -57,47 +63,120 @@ def pytask_collect_task(
57
63
58
64
obj .pytask_meta .markers .append (mark )
59
65
60
- dependencies = parse_nodes ( session , path , name , obj , depends_on )
61
- products = parse_nodes ( session , path , name , obj , produces )
66
+ # Collect the nodes in @pytask.mark.julia and validate them.
67
+ path_nodes = Path . cwd () if path is None else path . parent
62
68
63
- markers = obj .pytask_meta .markers if hasattr (obj , "pytask_meta" ) else []
64
- kwargs = obj .pytask_meta .kwargs if hasattr (obj , "pytask_meta" ) else {}
65
-
66
- task = Task (
67
- base_name = name ,
68
- path = path ,
69
- function = _copy_func (run_stata_script ), # type: ignore[arg-type]
70
- depends_on = dependencies ,
71
- produces = products ,
72
- markers = markers ,
73
- kwargs = kwargs ,
74
- )
69
+ if isinstance (script , str ):
70
+ warnings .warn (
71
+ "Passing a string to the @pytask.mark.stata parameter 'script' is "
72
+ "deprecated. Please, use a pathlib.Path instead." ,
73
+ stacklevel = 1 ,
74
+ )
75
+ script = Path (script )
75
76
76
77
script_node = session .hook .pytask_collect_node (
77
- session = session , path = path , node = script
78
+ session = session ,
79
+ path = path_nodes ,
80
+ node_info = NodeInfo (
81
+ arg_name = "script" , path = (), value = script , task_path = path , task_name = name
82
+ ),
83
+ )
84
+
85
+ if not (isinstance (script_node , PathNode ) and script_node .path .suffix == ".do" ):
86
+ raise ValueError (
87
+ "The 'script' keyword of the @pytask.mark.stata decorator must point "
88
+ f"to a file with the .do suffix, but it is { script_node } ."
89
+ )
90
+
91
+ options_node = session .hook .pytask_collect_node (
92
+ session = session ,
93
+ path = path_nodes ,
94
+ node_info = NodeInfo (
95
+ arg_name = "_options" ,
96
+ path = (),
97
+ value = options ,
98
+ task_path = path ,
99
+ task_name = name ,
100
+ ),
101
+ )
102
+
103
+ executable_node = session .hook .pytask_collect_node (
104
+ session = session ,
105
+ path = path_nodes ,
106
+ node_info = NodeInfo (
107
+ arg_name = "_executable" ,
108
+ path = (),
109
+ value = session .config ["stata" ],
110
+ task_path = path ,
111
+ task_name = name ,
112
+ ),
113
+ )
114
+
115
+ cwd_node = session .hook .pytask_collect_node (
116
+ session = session ,
117
+ path = path_nodes ,
118
+ node_info = NodeInfo (
119
+ arg_name = "_cwd" ,
120
+ path = (),
121
+ value = path .parent ,
122
+ task_path = path ,
123
+ task_name = name ,
124
+ ),
125
+ )
126
+
127
+ dependencies = parse_dependencies_from_task_function (
128
+ session , path , name , path_nodes , obj
78
129
)
130
+ products = parse_products_from_task_function (
131
+ session , path , name , path_nodes , obj
132
+ )
133
+
134
+ # Add script
135
+ dependencies ["_script" ] = script_node
136
+ dependencies ["_options" ] = options_node
137
+ dependencies ["_cwd" ] = cwd_node
138
+ dependencies ["_executable" ] = executable_node
139
+
140
+ markers = obj .pytask_meta .markers if hasattr (obj , "pytask_meta" ) else []
79
141
80
- if isinstance (task .depends_on , dict ):
81
- task .depends_on ["__script" ] = script_node
142
+ task : PTask
143
+ if path is None :
144
+ task = TaskWithoutPath (
145
+ name = name ,
146
+ function = run_stata_script ,
147
+ depends_on = dependencies ,
148
+ produces = products ,
149
+ markers = markers ,
150
+ )
82
151
else :
83
- task .depends_on = {0 : task .depends_on , "__script" : script_node }
152
+ task = Task (
153
+ base_name = name ,
154
+ path = path ,
155
+ function = run_stata_script ,
156
+ depends_on = dependencies ,
157
+ produces = products ,
158
+ markers = markers ,
159
+ )
84
160
161
+ # Add log_name node that depends on the task id.
85
162
if session .config ["platform" ] == "win32" :
86
- log_name = convert_task_id_to_name_of_log_file (task . short_name )
163
+ log_name = convert_task_id_to_name_of_log_file (task )
87
164
log_name_arg = [f"-{ log_name } " ]
88
165
else :
89
166
log_name_arg = []
90
167
91
- stata_function = functools .partial (
92
- task .function ,
93
- executable = session .config ["stata" ],
94
- script = task .depends_on ["__script" ].path ,
95
- options = options ,
96
- log_name = log_name_arg ,
97
- cwd = task .path .parent ,
168
+ log_name_node = session .hook .pytask_collect_node (
169
+ session = session ,
170
+ path = path_nodes ,
171
+ node_info = NodeInfo (
172
+ arg_name = "_log_name" ,
173
+ path = (),
174
+ value = PythonNode (value = log_name_arg ),
175
+ task_path = path ,
176
+ task_name = name ,
177
+ ),
98
178
)
99
-
100
- task .function = stata_function
179
+ task .depends_on ["_log_name" ] = log_name_node
101
180
102
181
return task
103
182
return None
@@ -111,28 +190,3 @@ def _parse_stata_mark(mark: Mark) -> Mark:
111
190
112
191
mark = Mark ("stata" , (), parsed_kwargs )
113
192
return mark
114
-
115
-
116
- def _copy_func (func : FunctionType ) -> FunctionType :
117
- """Create a copy of a function.
118
-
119
- Based on https://stackoverflow.com/a/13503277/7523785.
120
-
121
- Example
122
- -------
123
- >>> def _func(): pass
124
- >>> copied_func = _copy_func(_func)
125
- >>> _func is copied_func
126
- False
127
-
128
- """
129
- new_func = FunctionType (
130
- func .__code__ ,
131
- func .__globals__ ,
132
- name = func .__name__ ,
133
- argdefs = func .__defaults__ ,
134
- closure = func .__closure__ ,
135
- )
136
- new_func = functools .update_wrapper (new_func , func )
137
- new_func .__kwdefaults__ = func .__kwdefaults__
138
- return new_func
0 commit comments