dv-flow-mgr 0.0.1.13644197178a1__py3-none-any.whl → 0.0.1.13657597614a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dv_flow/mgr/package_def.py +11 -93
- dv_flow/mgr/std/exec.py +19 -0
- dv_flow/mgr/std/fileset.py +54 -57
- dv_flow/mgr/std/flow.dv +11 -5
- dv_flow/mgr/std/message.py +4 -5
- dv_flow/mgr/std/task_null.py +6 -8
- dv_flow/mgr/task_data.py +1 -0
- dv_flow/mgr/task_def.py +1 -0
- dv_flow/mgr/task_graph_builder.py +7 -7
- dv_flow/mgr/task_listener_log.py +15 -0
- dv_flow/mgr/task_node.py +23 -10
- dv_flow/mgr/task_runner.py +85 -6
- {dv_flow_mgr-0.0.1.13644197178a1.dist-info → dv_flow_mgr-0.0.1.13657597614a1.dist-info}/METADATA +1 -1
- {dv_flow_mgr-0.0.1.13644197178a1.dist-info → dv_flow_mgr-0.0.1.13657597614a1.dist-info}/RECORD +18 -16
- {dv_flow_mgr-0.0.1.13644197178a1.dist-info → dv_flow_mgr-0.0.1.13657597614a1.dist-info}/LICENSE +0 -0
- {dv_flow_mgr-0.0.1.13644197178a1.dist-info → dv_flow_mgr-0.0.1.13657597614a1.dist-info}/WHEEL +0 -0
- {dv_flow_mgr-0.0.1.13644197178a1.dist-info → dv_flow_mgr-0.0.1.13657597614a1.dist-info}/entry_points.txt +0 -0
- {dv_flow_mgr-0.0.1.13644197178a1.dist-info → dv_flow_mgr-0.0.1.13657597614a1.dist-info}/top_level.txt +0 -0
dv_flow/mgr/package_def.py
CHANGED
@@ -36,7 +36,7 @@ from .package_import_spec import PackageImportSpec, PackageSpec
|
|
36
36
|
from .task_node import TaskNodeCtor, TaskNodeCtorProxy, TaskNodeCtorTask
|
37
37
|
from .task_ctor import TaskCtor
|
38
38
|
from .task_def import TaskDef, TaskSpec
|
39
|
-
from .std.task_null import TaskNull
|
39
|
+
from .std.task_null import TaskNull, TaskNullParams
|
40
40
|
from .type_def import TypeDef
|
41
41
|
|
42
42
|
|
@@ -122,96 +122,13 @@ class PackageDef(BaseModel):
|
|
122
122
|
ctor_t = tasks_m[task_name][2]
|
123
123
|
return ctor_t
|
124
124
|
|
125
|
-
def handleParams(self, task, ctor_t):
|
126
|
-
self._log.debug("--> handleParams %s params=%s" % (task.name, str(task.params)))
|
127
|
-
|
128
|
-
if task.params is not None and len(task.params) > 0:
|
129
|
-
decl_params = False
|
130
|
-
|
131
|
-
# First, add in a parameter-setting stage
|
132
|
-
ctor_t = TaskCtorParam(
|
133
|
-
name=ctor_t.name,
|
134
|
-
uses=ctor_t,
|
135
|
-
srcdir=ctor_t.srcdir)
|
136
|
-
# ctor_t.params.update(task.params)
|
137
|
-
|
138
|
-
for value in task.params.values():
|
139
|
-
self._log.debug("value: %s" % str(value))
|
140
|
-
if type(value) == dict and "type" in value.keys():
|
141
|
-
decl_params = True
|
142
|
-
break
|
143
|
-
|
144
|
-
field_m = {}
|
145
|
-
# First, add parameters from the base class
|
146
|
-
base_o = ctor_t.mkParams()
|
147
|
-
for fname,info in base_o.model_fields.items():
|
148
|
-
self._log.debug("Field: %s (%s)" % (fname, info.default))
|
149
|
-
field_m[fname] = (info.annotation, info.default)
|
150
|
-
|
151
|
-
if decl_params:
|
152
|
-
self._log.debug("Type declares new parameters")
|
153
|
-
# We need to combine base parameters with new parameters
|
154
|
-
|
155
|
-
for p in task.params.keys():
|
156
|
-
param = task.params[p]
|
157
|
-
self._log.debug("param: %s" % str(param))
|
158
|
-
if type(param) == dict and "type" in param.keys():
|
159
|
-
ptype_s = param["type"]
|
160
|
-
if ptype_s not in ptype_m.keys():
|
161
|
-
raise Exception("Unknown type %s" % ptype_s)
|
162
|
-
ptype = ptype_m[ptype_s]
|
163
|
-
|
164
|
-
if p in field_m.keys():
|
165
|
-
raise Exception("Duplicate field %s" % p)
|
166
|
-
if "value" in param.keys():
|
167
|
-
field_m[p] = (ptype, param["value"])
|
168
|
-
else:
|
169
|
-
field_m[p] = (ptype, pdflt_m[ptype_s])
|
170
|
-
else:
|
171
|
-
if p not in field_m.keys():
|
172
|
-
raise Exception("Field %s not found" % p)
|
173
|
-
if type(param) != dict:
|
174
|
-
value = param
|
175
|
-
elif "value" in param.keys():
|
176
|
-
value = param["value"]
|
177
|
-
else:
|
178
|
-
raise Exception("No value specified for param %s: %s" % (
|
179
|
-
p, str(param)))
|
180
|
-
field_m[p] = (field_m[p][0], value)
|
181
|
-
self._log.debug("field_m: %s" % str(field_m))
|
182
|
-
param_t = pydantic.create_model(
|
183
|
-
"Task%sParams" % task.name, **field_m)
|
184
|
-
ctor_t = TaskCtorParamCls(
|
185
|
-
name=ctor_t.name,
|
186
|
-
uses=ctor_t,
|
187
|
-
params_ctor=param_t)
|
188
|
-
else: # no new parameters declared
|
189
|
-
self._log.debug("Type only overrides existing parameters")
|
190
|
-
for p in task.params.keys():
|
191
|
-
param = task.params[p]
|
192
|
-
if p not in field_m.keys():
|
193
|
-
raise Exception("Field %s not found" % p)
|
194
|
-
if type(param) != dict:
|
195
|
-
value = param
|
196
|
-
elif "value" in param.keys():
|
197
|
-
value = param["value"]
|
198
|
-
else:
|
199
|
-
raise Exception("No value specified for param %s: %s" % (
|
200
|
-
p, str(param)))
|
201
|
-
field_m[p] = (field_m[p][0], value)
|
202
|
-
self._log.debug("Set param=%s to %s" % (p, str(value)))
|
203
|
-
ctor_t.params[p] = value
|
204
|
-
|
205
|
-
self._log.debug("<-- handleParams %s" % task.name)
|
206
|
-
|
207
|
-
return ctor_t
|
208
|
-
|
209
125
|
def mkTaskCtor(self, session, task, srcdir, tasks_m) -> TaskCtor:
|
210
126
|
self._log.debug("--> %s::mkTaskCtor %s (srcdir: %s)" % (self.name, task.name, srcdir))
|
211
127
|
base_ctor_t : TaskCtor = None
|
212
128
|
ctor_t : TaskCtor = None
|
213
129
|
base_params : BaseModel = None
|
214
130
|
callable = None
|
131
|
+
passthrough = False
|
215
132
|
needs = [] if task.needs is None else task.needs.copy()
|
216
133
|
|
217
134
|
if task.uses is not None:
|
@@ -239,7 +156,7 @@ class PackageDef(BaseModel):
|
|
239
156
|
modname, self.basedir, str(e)))
|
240
157
|
|
241
158
|
if not hasattr(mod, clsname):
|
242
|
-
raise Exception("
|
159
|
+
raise Exception("Method %s not found in module %s" % (clsname, modname))
|
243
160
|
callable = getattr(mod, clsname)
|
244
161
|
|
245
162
|
# Determine if we need to use a new
|
@@ -250,6 +167,7 @@ class PackageDef(BaseModel):
|
|
250
167
|
name=task.name,
|
251
168
|
srcdir=srcdir,
|
252
169
|
paramT=paramT, # TODO: need to determine the parameter type
|
170
|
+
passthrough=passthrough,
|
253
171
|
needs=needs, # TODO: need to determine the needs
|
254
172
|
task=callable)
|
255
173
|
elif base_ctor_t is not None:
|
@@ -258,18 +176,18 @@ class PackageDef(BaseModel):
|
|
258
176
|
name=task.name,
|
259
177
|
srcdir=srcdir,
|
260
178
|
paramT=paramT, # TODO: need to determine the parameter type
|
179
|
+
passthrough=passthrough,
|
261
180
|
needs=needs,
|
262
181
|
uses=base_ctor_t)
|
263
182
|
else:
|
264
183
|
self._log.debug("Use 'Null' as the class implementation")
|
265
|
-
ctor_t =
|
184
|
+
ctor_t = TaskNodeCtorTask(
|
266
185
|
name=task.name,
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
# ctor_t.depends.extend(task.depends)
|
186
|
+
srcdir=srcdir,
|
187
|
+
paramT=TaskNullParams,
|
188
|
+
passthrough=passthrough,
|
189
|
+
needs=needs,
|
190
|
+
task=TaskNull)
|
273
191
|
|
274
192
|
self._log.debug("<-- %s::mkTaskCtor %s" % (self.name, task.name))
|
275
193
|
return ctor_t
|
dv_flow/mgr/std/exec.py
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
import asyncio
|
2
|
+
import logging
|
3
|
+
from dv_flow.mgr import TaskDataResult
|
4
|
+
|
5
|
+
_log = logging.getLogger("Exec")
|
6
|
+
|
7
|
+
async def Exec(runner, input) -> TaskDataResult:
|
8
|
+
_log.debug("TaskExec run: %s: cmd=%s" % (input.name, input.params.command))
|
9
|
+
|
10
|
+
|
11
|
+
proc = await asyncio.create_subprocess_shell(
|
12
|
+
input.params.command,
|
13
|
+
stdout=asyncio.subprocess.PIPE,
|
14
|
+
stderr=asyncio.subprocess.PIPE)
|
15
|
+
|
16
|
+
await proc.wait()
|
17
|
+
|
18
|
+
return TaskDataResult()
|
19
|
+
|
dv_flow/mgr/std/fileset.py
CHANGED
@@ -4,75 +4,72 @@ import fnmatch
|
|
4
4
|
import glob
|
5
5
|
import logging
|
6
6
|
import pydantic.dataclasses as dc
|
7
|
+
from pydantic import BaseModel
|
7
8
|
from typing import ClassVar, List, Tuple
|
8
|
-
from dv_flow.mgr import
|
9
|
+
from dv_flow.mgr import TaskDataResult
|
9
10
|
from dv_flow.mgr import FileSet as _FileSet
|
10
11
|
|
11
|
-
class TaskFileSetMemento(
|
12
|
+
class TaskFileSetMemento(BaseModel):
|
12
13
|
files : List[Tuple[str,float]] = dc.Field(default_factory=list)
|
13
14
|
|
14
|
-
|
15
|
+
_log = logging.getLogger("FileSet")
|
15
16
|
|
16
|
-
|
17
|
+
async def FileSet(runner, input) -> TaskDataResult:
|
18
|
+
_log.debug("TaskFileSet run: %s: basedir=%s, base=%s type=%s include=%s" % (
|
19
|
+
input.name,
|
20
|
+
input.srcdir,
|
21
|
+
input.params.base, input.params.type, str(input.params.include)
|
22
|
+
))
|
17
23
|
|
18
|
-
async def run(self, input : TaskData) -> TaskData:
|
19
|
-
self._log.debug("TaskFileSet run: %s: basedir=%s, base=%s type=%s include=%s" % (
|
20
|
-
self.name,
|
21
|
-
self.srcdir,
|
22
|
-
self.params.base, self.params.type, str(self.params.include)
|
23
|
-
))
|
24
24
|
|
25
|
+
changed = False
|
26
|
+
ex_memento = input.memento
|
27
|
+
memento = TaskFileSetMemento()
|
25
28
|
|
26
|
-
|
27
|
-
|
29
|
+
_log.debug("ex_memento: %s" % str(ex_memento))
|
30
|
+
_log.debug("params: %s" % str(input.params))
|
28
31
|
|
29
|
-
|
30
|
-
|
32
|
+
if input.params is not None:
|
33
|
+
glob_root = os.path.join(input.srcdir, input.params.base)
|
34
|
+
glob_root = glob_root.strip()
|
31
35
|
|
32
|
-
if
|
33
|
-
glob_root =
|
34
|
-
glob_root = glob_root.strip()
|
36
|
+
if glob_root[-1] == '/' or glob_root == '\\':
|
37
|
+
glob_root = glob_root[:-1]
|
35
38
|
|
36
|
-
|
37
|
-
glob_root = glob_root[:-1]
|
39
|
+
_log.debug("glob_root: %s" % glob_root)
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
src=self.name,
|
43
|
-
type=self.params.type,
|
41
|
+
fs = _FileSet(
|
42
|
+
src=input.name,
|
43
|
+
type=input.params.type,
|
44
44
|
basedir=glob_root)
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
return input
|
77
|
-
|
78
|
-
pass
|
46
|
+
if not isinstance(input.params.include, list):
|
47
|
+
input.params.include = [input.params.include]
|
48
|
+
|
49
|
+
included_files = []
|
50
|
+
for pattern in input.params.include:
|
51
|
+
included_files.extend(glob.glob(os.path.join(glob_root, pattern), recursive=False))
|
52
|
+
|
53
|
+
_log.debug("included_files: %s" % str(included_files))
|
54
|
+
|
55
|
+
for file in included_files:
|
56
|
+
if not any(glob.fnmatch.fnmatch(file, os.path.join(glob_root, pattern)) for pattern in input.params.exclude):
|
57
|
+
memento.files.append((file, os.path.getmtime(os.path.join(glob_root, file))))
|
58
|
+
fs.files.append(file[len(glob_root)+1:])
|
59
|
+
|
60
|
+
# Check to see if the filelist or fileset have changed
|
61
|
+
# Only bother doing this if the upstream task data has not changed
|
62
|
+
if ex_memento is not None and not input.changed:
|
63
|
+
ex_memento.files.sort(key=lambda x: x[0])
|
64
|
+
memento.files.sort(key=lambda x: x[0])
|
65
|
+
_log.debug("ex_memento.files: %s" % str(ex_memento.files))
|
66
|
+
_log.debug("memento.files: %s" % str(memento.files))
|
67
|
+
changed = ex_memento != memento
|
68
|
+
else:
|
69
|
+
changed = True
|
70
|
+
|
71
|
+
return TaskDataResult(
|
72
|
+
memento=memento,
|
73
|
+
changed=changed,
|
74
|
+
output=[fs]
|
75
|
+
)
|
dv_flow/mgr/std/flow.dv
CHANGED
@@ -1,16 +1,19 @@
|
|
1
1
|
|
2
|
+
# yaml-language-server: $schema=https://dv-flow.github.io/dv-flow.schema.json
|
3
|
+
|
2
4
|
package:
|
3
5
|
name: std
|
4
6
|
|
5
7
|
tasks:
|
6
8
|
- name: Message
|
7
|
-
|
9
|
+
pytask: dv_flow.mgr.std.message.Message
|
8
10
|
with:
|
9
11
|
msg:
|
10
12
|
type: str
|
11
13
|
value: ""
|
12
14
|
- name: FileSet
|
13
|
-
|
15
|
+
pytask: dv_flow.mgr.std.fileset.FileSet
|
16
|
+
passthrough: true
|
14
17
|
with:
|
15
18
|
base:
|
16
19
|
type: str
|
@@ -24,9 +27,12 @@ package:
|
|
24
27
|
exclude:
|
25
28
|
type: str
|
26
29
|
value: ""
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
+
- name: Exec
|
31
|
+
pytask: dv_flow.mgr.std.exec.Exec
|
32
|
+
with:
|
33
|
+
command:
|
34
|
+
type: str
|
35
|
+
value: ""
|
30
36
|
types:
|
31
37
|
# - name: TaskDataItem
|
32
38
|
# doc: |
|
dv_flow/mgr/std/message.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
|
2
|
-
from dv_flow.mgr import Task,
|
2
|
+
from dv_flow.mgr import Task, TaskDataResult
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
return input
|
4
|
+
async def Message(runner, input) -> TaskDataResult:
|
5
|
+
print("%s: %s" % (input.name, input.params.msg))
|
6
|
+
return TaskDataResult()
|
dv_flow/mgr/std/task_null.py
CHANGED
@@ -1,11 +1,9 @@
|
|
1
|
-
from
|
2
|
-
from ..task_data import
|
1
|
+
from pydantic import BaseModel
|
2
|
+
from ..task_data import TaskDataResult
|
3
3
|
|
4
|
-
class
|
5
|
-
|
4
|
+
class TaskNullParams(BaseModel):
|
5
|
+
pass
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
self._log.debug("%s: TaskNull.run" % self.name)
|
10
|
-
return input
|
7
|
+
async def TaskNull(runner, input) -> TaskDataResult:
|
8
|
+
return TaskDataResult()
|
11
9
|
|
dv_flow/mgr/task_data.py
CHANGED
dv_flow/mgr/task_def.py
CHANGED
@@ -40,6 +40,7 @@ class TaskDef(BaseModel):
|
|
40
40
|
doc : str = dc.Field(default="")
|
41
41
|
needs : List[Union[str,TaskSpec]] = dc.Field(default_factory=list, alias="needs")
|
42
42
|
params: Dict[str,Union[str,ParamDef]] = dc.Field(default_factory=dict, alias="with")
|
43
|
+
passthrough: bool = dc.Field(default=False)
|
43
44
|
# out: List[TaskOutput] = dc.Field(default_factory=list)
|
44
45
|
|
45
46
|
def copy(self) -> 'TaskDef':
|
@@ -26,6 +26,7 @@ from .package import Package
|
|
26
26
|
from .package_def import PackageDef, PackageSpec
|
27
27
|
from .pkg_rgy import PkgRgy
|
28
28
|
from .task import Task
|
29
|
+
from .task_node import TaskNodeCtor
|
29
30
|
from typing import Dict, List
|
30
31
|
|
31
32
|
@dc.dataclass
|
@@ -92,18 +93,17 @@ class TaskGraphBuilder(object):
|
|
92
93
|
|
93
94
|
self._pkg_s.append(pkg)
|
94
95
|
|
95
|
-
ctor_t :
|
96
|
+
ctor_t : TaskNodeCtor = pkg.getTaskCtor(task_name)
|
96
97
|
|
97
98
|
self._logger.debug("ctor_t: %s" % ctor_t.name)
|
98
99
|
|
99
100
|
needs = []
|
100
101
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
# depends.append(self._task_m[dep])
|
102
|
+
for need_def in ctor_t.getNeeds():
|
103
|
+
if not need_def in self._task_m.keys():
|
104
|
+
task = self._mkTaskGraph(need_def, rundir)
|
105
|
+
self._task_m[need_def] = task
|
106
|
+
needs.append(self._task_m[need_def])
|
107
107
|
|
108
108
|
# The returned task should have all param references resolved
|
109
109
|
params = ctor_t.mkTaskParams()
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
|
3
|
+
class TaskListenerLog(object):
|
4
|
+
|
5
|
+
def event(self, task : 'Task', reason : 'Reason'):
|
6
|
+
if reason == 'enter':
|
7
|
+
print("> Task %s" % task.name, flush=True)
|
8
|
+
elif reason == 'leave':
|
9
|
+
for m in task.result.markers:
|
10
|
+
print(" %s" % m)
|
11
|
+
print("< Task %s" % task.name, flush=True)
|
12
|
+
else:
|
13
|
+
print("- Task %s" % task.name, flush=True)
|
14
|
+
pass
|
15
|
+
|
dv_flow/mgr/task_node.py
CHANGED
@@ -22,9 +22,11 @@ class TaskNode(object):
|
|
22
22
|
|
23
23
|
# Runtime fields -- these get populated during execution
|
24
24
|
changed : bool = False
|
25
|
+
passthrough : bool = False
|
25
26
|
needs : List['TaskNode'] = dc.field(default_factory=list)
|
26
27
|
rundir : str = dc.field(default=None)
|
27
28
|
output : TaskDataOutput = dc.field(default=None)
|
29
|
+
result : TaskDataResult = dc.field(default=None)
|
28
30
|
|
29
31
|
_log : ClassVar = logging.getLogger("TaskNode")
|
30
32
|
|
@@ -46,7 +48,7 @@ class TaskNode(object):
|
|
46
48
|
for need in self.needs:
|
47
49
|
in_params.extend(need.output.output)
|
48
50
|
|
49
|
-
#
|
51
|
+
# Create an evaluator for substituting param values
|
50
52
|
eval = ParamRefEval()
|
51
53
|
|
52
54
|
eval.setVar("in", in_params)
|
@@ -66,6 +68,7 @@ class TaskNode(object):
|
|
66
68
|
raise Exception("Unhandled param type: %s" % str(value))
|
67
69
|
|
68
70
|
input = TaskDataInput(
|
71
|
+
name=self.name,
|
69
72
|
changed=changed,
|
70
73
|
srcdir=self.srcdir,
|
71
74
|
rundir=rundir,
|
@@ -73,7 +76,7 @@ class TaskNode(object):
|
|
73
76
|
memento=memento)
|
74
77
|
|
75
78
|
# TODO: notify of task start
|
76
|
-
|
79
|
+
self.result : TaskDataResult = await self.task(self, input)
|
77
80
|
# TODO: notify of task complete
|
78
81
|
|
79
82
|
# TODO: form a dep map from the outgoing param sets
|
@@ -81,13 +84,13 @@ class TaskNode(object):
|
|
81
84
|
|
82
85
|
# Store the result
|
83
86
|
self.output = TaskDataOutput(
|
84
|
-
changed=
|
87
|
+
changed=self.result.changed,
|
85
88
|
dep_m=dep_m,
|
86
|
-
output=
|
89
|
+
output=self.result.output.copy())
|
87
90
|
|
88
91
|
# TODO:
|
89
92
|
|
90
|
-
return
|
93
|
+
return self.result
|
91
94
|
|
92
95
|
def __hash__(self):
|
93
96
|
return id(self)
|
@@ -103,6 +106,7 @@ class TaskNodeCtor(object):
|
|
103
106
|
name : str
|
104
107
|
srcdir : str
|
105
108
|
paramT : Any
|
109
|
+
passthrough : bool
|
106
110
|
|
107
111
|
def getNeeds(self) -> List[str]:
|
108
112
|
return []
|
@@ -163,6 +167,7 @@ class TaskNodeCtorProxy(TaskNodeCtorDefBase):
|
|
163
167
|
if srcdir is None:
|
164
168
|
srcdir = self.srcdir
|
165
169
|
node = self.uses.mkTaskNode(params=params, srcdir=srcdir, name=name, needs=needs)
|
170
|
+
node.passthrough = self.passthrough
|
166
171
|
return node
|
167
172
|
|
168
173
|
@dc.dataclass
|
@@ -174,6 +179,7 @@ class TaskNodeCtorTask(TaskNodeCtorDefBase):
|
|
174
179
|
srcdir = self.srcdir
|
175
180
|
|
176
181
|
node = TaskNode(name, srcdir, params, self.task, needs=needs)
|
182
|
+
node.passthrough = self.passthrough
|
177
183
|
node.task = self.task
|
178
184
|
|
179
185
|
return node
|
@@ -187,6 +193,7 @@ class TaskNodeCtorWrapper(TaskNodeCtor):
|
|
187
193
|
srcdir=None,
|
188
194
|
params=None,
|
189
195
|
needs=None,
|
196
|
+
passthrough=None,
|
190
197
|
**kwargs):
|
191
198
|
"""Convenience method for direct creation of tasks"""
|
192
199
|
if params is None:
|
@@ -197,11 +204,16 @@ class TaskNodeCtorWrapper(TaskNodeCtor):
|
|
197
204
|
params=params,
|
198
205
|
name=name,
|
199
206
|
needs=needs)
|
207
|
+
if passthrough is not None:
|
208
|
+
node.passthrough = passthrough
|
209
|
+
else:
|
210
|
+
node.passthrough = self.passthrough
|
200
211
|
|
201
212
|
return node
|
202
213
|
|
203
214
|
def mkTaskNode(self, params, srcdir=None, name=None, needs=None) -> TaskNode:
|
204
215
|
node = TaskNode(name, srcdir, params, self.T, needs=needs)
|
216
|
+
node.passthrough = self.passthrough
|
205
217
|
return node
|
206
218
|
|
207
219
|
def mkTaskParams(self, params : Dict = None) -> Any:
|
@@ -231,16 +243,17 @@ class TaskNodeCtorWrapper(TaskNodeCtor):
|
|
231
243
|
setattr(obj, key, value)
|
232
244
|
return obj
|
233
245
|
|
234
|
-
def task(paramT):
|
246
|
+
def task(paramT,passthrough=False):
|
235
247
|
"""Decorator to wrap a task method as a TaskNodeCtor"""
|
236
248
|
def wrapper(T):
|
237
249
|
task_mname = T.__module__
|
238
250
|
task_module = sys.modules[task_mname]
|
239
251
|
ctor = TaskNodeCtorWrapper(
|
240
|
-
T.__name__,
|
241
|
-
os.path.dirname(os.path.abspath(task_module.__file__)),
|
242
|
-
paramT,
|
243
|
-
|
252
|
+
name=T.__name__,
|
253
|
+
srcdir=os.path.dirname(os.path.abspath(task_module.__file__)),
|
254
|
+
paramT=paramT,
|
255
|
+
passthrough=passthrough,
|
256
|
+
T=T)
|
244
257
|
return ctor
|
245
258
|
return wrapper
|
246
259
|
|
dv_flow/mgr/task_runner.py
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
import asyncio
|
2
|
+
import json
|
3
|
+
import os
|
4
|
+
import re
|
2
5
|
import dataclasses as dc
|
6
|
+
import logging
|
3
7
|
from toposort import toposort
|
4
|
-
from typing import Any, Callable, List, Tuple, Union
|
8
|
+
from typing import Any, Callable, ClassVar, List, Tuple, Union
|
5
9
|
from .task_data import TaskDataInput, TaskDataOutput, TaskDataResult
|
6
10
|
from .task_node import TaskNode
|
7
11
|
|
@@ -12,6 +16,15 @@ class TaskRunner(object):
|
|
12
16
|
# List of [Listener:Callable[Task],Recurisve:bool]
|
13
17
|
listeners : List[Tuple[Callable['Task','Reason'], bool]] = dc.field(default_factory=list)
|
14
18
|
|
19
|
+
_log : ClassVar = logging.getLogger("TaskRunner")
|
20
|
+
|
21
|
+
def add_listener(self, l, recursive=False):
|
22
|
+
self.listeners.append((l, recursive))
|
23
|
+
|
24
|
+
def _notify(self, task : 'Task', reason : 'Reason'):
|
25
|
+
for listener in self.listeners:
|
26
|
+
listener[0](task, reason)
|
27
|
+
|
15
28
|
async def do_run(self,
|
16
29
|
task : 'Task',
|
17
30
|
memento : Any = None) -> 'TaskDataResult':
|
@@ -26,17 +39,52 @@ class TaskRunner(object):
|
|
26
39
|
class TaskSetRunner(TaskRunner):
|
27
40
|
nproc : int = 8
|
28
41
|
|
42
|
+
_anon_tid : int = 1
|
43
|
+
|
44
|
+
_log : ClassVar = logging.getLogger("TaskSetRunner")
|
45
|
+
|
29
46
|
async def run(self, task : Union[TaskNode,List[TaskNode]]):
|
47
|
+
# Ensure that the rundir exists or can be created
|
48
|
+
|
49
|
+
if not os.path.isdir(self.rundir):
|
50
|
+
os.makedirs(self.rundir)
|
51
|
+
|
52
|
+
if not os.path.isdir(os.path.join(self.rundir, "cache")):
|
53
|
+
os.makedirs(os.path.join(self.rundir, "cache"))
|
54
|
+
|
55
|
+
src_memento = None
|
56
|
+
dst_memento = {}
|
57
|
+
if os.path.isfile(os.path.join(self.rundir, "cache", "mementos.json")):
|
58
|
+
try:
|
59
|
+
with open(os.path.join(self.rundir, "cache", "mementos.json"), "r") as f:
|
60
|
+
src_memento = json.load(f)
|
61
|
+
except Exception as e:
|
62
|
+
src_memento = {}
|
63
|
+
else:
|
64
|
+
src_memento = {}
|
65
|
+
|
66
|
+
|
30
67
|
# First, build a depedency map
|
31
68
|
tasks = task if isinstance(task, list) else [task]
|
32
69
|
dep_m = {}
|
70
|
+
self._anon_tid = 1
|
33
71
|
for t in tasks:
|
34
72
|
self._buildDepMap(dep_m, t)
|
35
73
|
|
36
|
-
|
74
|
+
if self._log.isEnabledFor(logging.DEBUG):
|
75
|
+
self._log.debug("Deps:")
|
76
|
+
for t,value in dep_m.items():
|
77
|
+
self._log.debug(" Task: %s", str(t.name))
|
78
|
+
for v in value:
|
79
|
+
self._log.debug(" - %s", str(v.name))
|
37
80
|
|
38
81
|
order = list(toposort(dep_m))
|
39
82
|
|
83
|
+
if self._log.isEnabledFor(logging.DEBUG):
|
84
|
+
self._log.debug("Order:")
|
85
|
+
for active_s in order:
|
86
|
+
self._log.debug("- {%s}", ",".join(t.name for t in active_s))
|
87
|
+
|
40
88
|
active_task_l = []
|
41
89
|
done_task_s = set()
|
42
90
|
for active_s in order:
|
@@ -49,25 +97,56 @@ class TaskSetRunner(TaskRunner):
|
|
49
97
|
for i in range(len(active_task_l)):
|
50
98
|
if active_task_l[i][1] == d:
|
51
99
|
tt = active_task_l[i][0]
|
100
|
+
if tt.result.memento is not None:
|
101
|
+
dst_memento[tt.name] = tt.result.memento.model_dump()
|
102
|
+
else:
|
103
|
+
dst_memento[tt.name] = None
|
104
|
+
self._notify(tt, "leave")
|
52
105
|
done_task_s.add(tt)
|
53
106
|
active_task_l.pop(i)
|
54
107
|
break
|
55
108
|
if t not in done_task_s:
|
109
|
+
memento = src_memento.get(t.name, None)
|
110
|
+
dirname = t.name
|
111
|
+
invalid_chars_pattern = r'[\/:*?"<>|#%&{}\$\\!\'`;=@+]'
|
112
|
+
# Replace invalid characters with the replacement string.
|
113
|
+
dirname = re.sub(invalid_chars_pattern, '_', dirname)
|
114
|
+
|
115
|
+
rundir = os.path.join(self.rundir, dirname)
|
116
|
+
|
117
|
+
if not os.path.isdir(rundir):
|
118
|
+
os.makedirs(rundir, exist_ok=True)
|
119
|
+
|
120
|
+
self._notify(t, "enter")
|
56
121
|
coro = asyncio.Task(t.do_run(
|
57
122
|
self,
|
58
|
-
|
59
|
-
|
123
|
+
rundir,
|
124
|
+
memento))
|
60
125
|
active_task_l.append((t, coro))
|
61
126
|
|
62
127
|
# Now, wait for tasks to complete
|
63
128
|
if len(active_task_l):
|
129
|
+
# TODO: Shouldn't gather here -- reach to each completion
|
64
130
|
coros = list(at[1] for at in active_task_l)
|
65
131
|
res = await asyncio.gather(*coros)
|
132
|
+
for tt in active_task_l:
|
133
|
+
if tt[0].result.memento is not None:
|
134
|
+
dst_memento[tt[0].name] = tt[0].result.memento.model_dump()
|
135
|
+
else:
|
136
|
+
dst_memento[tt[0].name] = None
|
137
|
+
self._notify(tt[0], "leave")
|
138
|
+
active_task_l.clear()
|
66
139
|
|
140
|
+
with open(os.path.join(self.rundir, "cache", "mementos.json"), "w") as f:
|
141
|
+
json.dump(dst_memento, f)
|
67
142
|
|
68
143
|
pass
|
69
144
|
|
70
145
|
def _buildDepMap(self, dep_m, task : TaskNode):
|
146
|
+
if task.name is None:
|
147
|
+
task.name = "anon_%d" % self._anon_tid
|
148
|
+
self._anon_tid += 1
|
149
|
+
|
71
150
|
if task not in dep_m.keys():
|
72
151
|
dep_m[task] = set(task.needs)
|
73
152
|
for need in task.needs:
|
@@ -86,8 +165,8 @@ class SingleTaskRunner(TaskRunner):
|
|
86
165
|
# TODO: create an evaluator for substituting param values
|
87
166
|
eval = None
|
88
167
|
|
89
|
-
for field in dc.fields(task.params):
|
90
|
-
print("Field: %s" % field.name)
|
168
|
+
# for field in dc.fields(task.params):
|
169
|
+
# print("Field: %s" % field.name)
|
91
170
|
|
92
171
|
input = TaskDataInput(
|
93
172
|
changed=changed,
|
{dv_flow_mgr-0.0.1.13644197178a1.dist-info → dv_flow_mgr-0.0.1.13657597614a1.dist-info}/RECORD
RENAMED
@@ -7,7 +7,7 @@ dv_flow/mgr/fileset.py,sha256=FNvC5sU2ArxJ0OO3v8dXTv8zX-bZ5t0a0ljne0fQQ1o,1150
|
|
7
7
|
dv_flow/mgr/fragment_def.py,sha256=cyzp1XeWtNOaagScmeS-BPsoXj9j2LTBbKq5ZUioz8I,1641
|
8
8
|
dv_flow/mgr/out,sha256=d8GGBi3J43fhdLBlnsUbzBfRe0TD0QTP3nOTz54l2bI,200
|
9
9
|
dv_flow/mgr/package.py,sha256=878twhPD-E1pFlDNUtuyeFEgJ_Y89b560og4St-Iwrs,1679
|
10
|
-
dv_flow/mgr/package_def.py,sha256=
|
10
|
+
dv_flow/mgr/package_def.py,sha256=EdKwyxqvWT8xQmALGa6BbNv0PMbDLZTr3A27L9coIzc,13761
|
11
11
|
dv_flow/mgr/package_import_spec.py,sha256=ah3r15v5Jdub2poc3sgi6Uar1L3oGoYsCPPNiOHV-a4,1760
|
12
12
|
dv_flow/mgr/param.py,sha256=3BY-ucig6JRw73FhjyJQL-vpd57qhAzixgZ8I5FoUpw,553
|
13
13
|
dv_flow/mgr/param_def.py,sha256=gLua-EQiY8V2CFX-2svLRIlrs8PEeGh4-EPtn4a2Mng,712
|
@@ -16,30 +16,32 @@ dv_flow/mgr/parsetab.py,sha256=enSOnMQ-woIsMEzHyeYiefvhAl8enxfX9Ct_o8-jkqs,3780
|
|
16
16
|
dv_flow/mgr/pkg_rgy.py,sha256=2R_EaeBDJn5qUq9DzSnLc37wUP36MWSv-p0LgUjJRAg,4471
|
17
17
|
dv_flow/mgr/task.py,sha256=kLQSvnVwj9ROIDtxq8lLu-4mJizTxOqvUeogmgN6QAA,5976
|
18
18
|
dv_flow/mgr/task_ctor.py,sha256=hlfl-UVvyjzLFN6D0Oel9eBs0xUQPqCX7gQ0uEHoL7o,1382
|
19
|
-
dv_flow/mgr/task_data.py,sha256=
|
20
|
-
dv_flow/mgr/task_def.py,sha256=
|
19
|
+
dv_flow/mgr/task_data.py,sha256=gzs7BfwTLPKUEpaiGMwvA3MNfebTKHv_wDFppvWHo8A,11413
|
20
|
+
dv_flow/mgr/task_def.py,sha256=PORXrUBoynoj_oYAVISR5NW53OZevZ6hL4T7TutkkHo,1879
|
21
21
|
dv_flow/mgr/task_exec_data.py,sha256=aT__kmVmNxqnS_GbTRS1vrYgKiv8cH-HUSmRb6YYKsI,640
|
22
|
-
dv_flow/mgr/task_graph_builder.py,sha256=
|
22
|
+
dv_flow/mgr/task_graph_builder.py,sha256=sswBPUZg71dFT8kaB0towbWIadhNdbTy5gLgvC2uiVA,7276
|
23
23
|
dv_flow/mgr/task_graph_runner.py,sha256=jUGI49QvxUCfQoKQDDk2psbeapIcCg72qNOW1JipHzM,2182
|
24
24
|
dv_flow/mgr/task_graph_runner_local.py,sha256=OrydPwtQ8E7hYWvSXx0h7lI3nfUNFyklULhsyMwz9dA,4687
|
25
25
|
dv_flow/mgr/task_impl_data.py,sha256=bFPijoKrh9x7fZN2DsvRJp0UHo-gGM0VjtDQISyfhFk,321
|
26
|
+
dv_flow/mgr/task_listener_log.py,sha256=B9-LEgSF2QwcUREBoUjEsS0rRYg1_ilFHBUKlNeT5YA,444
|
26
27
|
dv_flow/mgr/task_memento.py,sha256=C7VTQpBhDEoYuDmE6YTM-6TLMLnqHp6Y0Vat1aTgtCs,1096
|
27
|
-
dv_flow/mgr/task_node.py,sha256=
|
28
|
+
dv_flow/mgr/task_node.py,sha256=iQKiDTC8Oz8tQwLzJF4NpsREg8JPXI-rju5tG7P6dfw,8448
|
28
29
|
dv_flow/mgr/task_output.py,sha256=l-W-FvVo6YDah1RQS-I9N0KUtB3vp-kl7lxIdmNz0l4,178
|
29
30
|
dv_flow/mgr/task_params_ctor.py,sha256=aXgB8o9xFPjaEjGW_xYkEC0N0apzGzGUPDj7g2ZLvus,1112
|
30
|
-
dv_flow/mgr/task_runner.py,sha256=
|
31
|
+
dv_flow/mgr/task_runner.py,sha256=Xv5bPwAKV793R9W5Ksu1u_WBoRUzlpfHpGtGLXdsIlY,6940
|
31
32
|
dv_flow/mgr/type.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
32
33
|
dv_flow/mgr/type_def.py,sha256=KdhuNlfw-NKU-4VZFCnMPyj775yEB7cpr5tz73a9yuQ,259
|
33
34
|
dv_flow/mgr/util.py,sha256=06eVyURF4ga-s8C9Sd3ZSDebwO4QS0XXaB8xADVbWRc,1437
|
34
35
|
dv_flow/mgr/cmds/cmd_run.py,sha256=eths8kT7mBmpZqwOuMtpKAaux4rg-f7hPBxxTHbpKT4,2903
|
35
36
|
dv_flow/mgr/share/flow.json,sha256=lNmZex9NXkYbyb2aZseQfUOkV9CMyfH0iLODEI7EPBw,5096
|
36
|
-
dv_flow/mgr/std/
|
37
|
-
dv_flow/mgr/std/
|
38
|
-
dv_flow/mgr/std/
|
39
|
-
dv_flow/mgr/std/
|
40
|
-
|
41
|
-
dv_flow_mgr-0.0.1.
|
42
|
-
dv_flow_mgr-0.0.1.
|
43
|
-
dv_flow_mgr-0.0.1.
|
44
|
-
dv_flow_mgr-0.0.1.
|
45
|
-
dv_flow_mgr-0.0.1.
|
37
|
+
dv_flow/mgr/std/exec.py,sha256=ETx9xSxhdCD_iw6pcmhrafDCJ-41AneyEAPwQf3q-3w,452
|
38
|
+
dv_flow/mgr/std/fileset.py,sha256=r2s2H45FuBhTLsjvjqn26Zb6EsR-psvV00ObMIyEGNA,2486
|
39
|
+
dv_flow/mgr/std/flow.dv,sha256=jlFOh3xVECOzHws7x6YvJ9eCIGHM5gsPeEnheiGOukY,1553
|
40
|
+
dv_flow/mgr/std/message.py,sha256=CWrBKImbXKe2d7hJ223U3Ifuxo54zLpFPJviE8BUJvk,188
|
41
|
+
dv_flow/mgr/std/task_null.py,sha256=UKwUnqwFPBY8BO44ZAPcgehQB59kHZFa1qyZc1TwUqE,196
|
42
|
+
dv_flow_mgr-0.0.1.13657597614a1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
43
|
+
dv_flow_mgr-0.0.1.13657597614a1.dist-info/METADATA,sha256=HVQUNoijJOwkTY6DfEDvnWzRDQ3JVzQFeR-9brAOQus,13276
|
44
|
+
dv_flow_mgr-0.0.1.13657597614a1.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
45
|
+
dv_flow_mgr-0.0.1.13657597614a1.dist-info/entry_points.txt,sha256=1roy8wAFM48LabOvr6jiOw0MUs-qE8X3Vf8YykPazxk,50
|
46
|
+
dv_flow_mgr-0.0.1.13657597614a1.dist-info/top_level.txt,sha256=amfVTkggzYPtWwLqNmRukfz1Buu0pGS2SrYBBLhXm04,8
|
47
|
+
dv_flow_mgr-0.0.1.13657597614a1.dist-info/RECORD,,
|
{dv_flow_mgr-0.0.1.13644197178a1.dist-info → dv_flow_mgr-0.0.1.13657597614a1.dist-info}/LICENSE
RENAMED
File without changes
|
{dv_flow_mgr-0.0.1.13644197178a1.dist-info → dv_flow_mgr-0.0.1.13657597614a1.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|
File without changes
|