dv-flow-mgr 0.0.1.14013608039a1__py3-none-any.whl → 0.0.1.14097297609a1__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.
Files changed (31) hide show
  1. dv_flow/mgr/__init__.py +2 -2
  2. dv_flow/mgr/__main__.py +26 -1
  3. dv_flow/mgr/cmds/cmd_graph.py +82 -0
  4. dv_flow/mgr/cmds/cmd_show.py +107 -0
  5. dv_flow/mgr/fileset.py +1 -0
  6. dv_flow/mgr/package.py +3 -3
  7. dv_flow/mgr/package_def.py +82 -12
  8. dv_flow/mgr/std/message.py +1 -1
  9. dv_flow/mgr/task_data.py +24 -20
  10. dv_flow/mgr/task_def.py +0 -1
  11. dv_flow/mgr/task_graph_builder.py +121 -12
  12. dv_flow/mgr/task_graph_dot_writer.py +78 -0
  13. dv_flow/mgr/task_node.py +3 -326
  14. dv_flow/mgr/task_node_compound.py +13 -1
  15. dv_flow/mgr/task_node_ctor.py +118 -0
  16. dv_flow/mgr/task_node_ctor_compound.py +117 -0
  17. dv_flow/mgr/task_node_ctor_compound_proxy.py +65 -0
  18. dv_flow/mgr/task_node_ctor_def_base.py +47 -0
  19. dv_flow/mgr/task_node_ctor_proxy.py +56 -0
  20. dv_flow/mgr/task_node_ctor_task.py +64 -0
  21. dv_flow/mgr/task_node_ctor_wrapper.py +96 -0
  22. dv_flow/mgr/task_node_leaf.py +170 -0
  23. dv_flow/mgr/task_runner.py +11 -6
  24. {dv_flow_mgr-0.0.1.14013608039a1.dist-info → dv_flow_mgr-0.0.1.14097297609a1.dist-info}/METADATA +1 -1
  25. {dv_flow_mgr-0.0.1.14013608039a1.dist-info → dv_flow_mgr-0.0.1.14097297609a1.dist-info}/RECORD +29 -20
  26. {dv_flow_mgr-0.0.1.14013608039a1.dist-info → dv_flow_mgr-0.0.1.14097297609a1.dist-info}/WHEEL +1 -1
  27. dv_flow/mgr/task.py +0 -181
  28. dv_flow/mgr/task_ctor.py +0 -64
  29. {dv_flow_mgr-0.0.1.14013608039a1.dist-info → dv_flow_mgr-0.0.1.14097297609a1.dist-info}/entry_points.txt +0 -0
  30. {dv_flow_mgr-0.0.1.14013608039a1.dist-info → dv_flow_mgr-0.0.1.14097297609a1.dist-info}/licenses/LICENSE +0 -0
  31. {dv_flow_mgr-0.0.1.14013608039a1.dist-info → dv_flow_mgr-0.0.1.14097297609a1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,118 @@
1
+ #****************************************************************************
2
+ #* task_node_ctor.py
3
+ #*
4
+ #* Copyright 2023-2025 Matthew Ballance and Contributors
5
+ #*
6
+ #* Licensed under the Apache License, Version 2.0 (the "License"); you may
7
+ #* not use this file except in compliance with the License.
8
+ #* You may obtain a copy of the License at:
9
+ #*
10
+ #* http://www.apache.org/licenses/LICENSE-2.0
11
+ #*
12
+ #* Unless required by applicable law or agreed to in writing, software
13
+ #* distributed under the License is distributed on an "AS IS" BASIS,
14
+ #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ #* See the License for the specific language governing permissions and
16
+ #* limitations under the License.
17
+ #*
18
+ #* Created on:
19
+ #* Author:
20
+ #*
21
+ #****************************************************************************
22
+ import enum
23
+ import os
24
+ import sys
25
+ import dataclasses as dc
26
+ import pydantic.dataclasses as pdc
27
+ import logging
28
+ import toposort
29
+ from typing import Any, Callable, ClassVar, Dict, List, Tuple
30
+ from .task_node import TaskNode
31
+ from .param import Param
32
+
33
+ @dc.dataclass
34
+ class TaskNodeCtor(object):
35
+ """
36
+ Factory for a specific task type
37
+ - Produces a task parameters object, applying value-setting instructions
38
+ - Produces a TaskNode
39
+ """
40
+ name : str
41
+ srcdir : str
42
+ paramT : Any
43
+ passthrough : bool
44
+ consumes : List[Any]
45
+ needs : List[str]
46
+
47
+ def __call__(self,
48
+ builder=None,
49
+ name=None,
50
+ srcdir=None,
51
+ params=None,
52
+ needs=None,
53
+ passthrough=None,
54
+ consumes=None,
55
+ **kwargs):
56
+ """Convenience method for direct creation of tasks"""
57
+ if params is None:
58
+ params = self.mkTaskParams(kwargs)
59
+
60
+ node = self.mkTaskNode(
61
+ builder=builder,
62
+ srcdir=srcdir,
63
+ params=params,
64
+ name=name,
65
+ needs=needs)
66
+ if passthrough is not None:
67
+ node.passthrough = passthrough
68
+ else:
69
+ node.passthrough = self.passthrough
70
+ if consumes is not None:
71
+ if node.consumes is None:
72
+ node.consumes = consumes
73
+ else:
74
+ node.consumes.extend(consumes)
75
+ else:
76
+ if node.consumes is None:
77
+ node.consumes = self.consumes
78
+
79
+ return node
80
+
81
+ def getNeeds(self) -> List[str]:
82
+ return []
83
+
84
+ def mkTaskNode(self,
85
+ builder,
86
+ params,
87
+ srcdir=None,
88
+ name=None,
89
+ needs=None) -> TaskNode:
90
+ raise NotImplementedError("mkTaskNode in type %s" % str(type(self)))
91
+
92
+ def mkTaskParams(self, params : Dict = None) -> Any:
93
+ obj = self.paramT()
94
+
95
+ # Apply user-specified params
96
+ if params is not None:
97
+ for key,value in params.items():
98
+ if not hasattr(obj, key):
99
+ raise Exception("Parameters class %s does not contain field %s" % (
100
+ str(type(obj)),
101
+ key))
102
+ else:
103
+ if isinstance(value, Param):
104
+ if value.append is not None:
105
+ ex_value = getattr(obj, key, [])
106
+ ex_value.extend(value.append)
107
+ setattr(obj, key, ex_value)
108
+ elif value.prepend is not None:
109
+ ex_value = getattr(obj, key, [])
110
+ value = value.copy()
111
+ value.extend(ex_value)
112
+ setattr(obj, key, value)
113
+ pass
114
+ else:
115
+ raise Exception("Unhandled value spec: %s" % str(value))
116
+ else:
117
+ setattr(obj, key, value)
118
+ return obj
@@ -0,0 +1,117 @@
1
+ #****************************************************************************
2
+ #* task_node_ctor_compound.py
3
+ #*
4
+ #* Copyright 2023-2025 Matthew Ballance and Contributors
5
+ #*
6
+ #* Licensed under the Apache License, Version 2.0 (the "License"); you may
7
+ #* not use this file except in compliance with the License.
8
+ #* You may obtain a copy of the License at:
9
+ #*
10
+ #* http://www.apache.org/licenses/LICENSE-2.0
11
+ #*
12
+ #* Unless required by applicable law or agreed to in writing, software
13
+ #* distributed under the License is distributed on an "AS IS" BASIS,
14
+ #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ #* See the License for the specific language governing permissions and
16
+ #* limitations under the License.
17
+ #*
18
+ #* Created on:
19
+ #* Author:
20
+ #*
21
+ #****************************************************************************
22
+ import os
23
+ import json
24
+ import dataclasses as dc
25
+ import logging
26
+ from pydantic import BaseModel
27
+ from typing import Any, Callable, ClassVar, Dict, List, Tuple
28
+ from .task_def import TaskDef
29
+ from .task_data import TaskDataOutput, TaskDataResult
30
+ from .task_node import TaskNode
31
+ from .task_node_ctor import TaskNodeCtor
32
+ from .task_node_compound import TaskNodeCompound
33
+
34
+ @dc.dataclass
35
+ class TaskNodeCtorCompound(TaskNodeCtor):
36
+ task_def : TaskDef
37
+ tasks : List[TaskNodeCtor] = dc.field(default_factory=list)
38
+
39
+ _log : ClassVar = logging.getLogger("TaskNodeCtorCompound")
40
+
41
+ def mkTaskNode(self, builder, params, srcdir=None, name=None, needs=None) -> 'TaskNode':
42
+ """Creates a task object without a base task"""
43
+ self._log.debug("--> mkTaskNode %s (%d)" % (name, len(self.tasks)))
44
+ if srcdir is None:
45
+ srcdir = self.srcdir
46
+
47
+ node = TaskNodeCompound(
48
+ name=name,
49
+ srcdir=srcdir,
50
+ params=params,
51
+ needs=needs)
52
+
53
+
54
+ builder.enter_compound(node)
55
+ builder.addTask("in", node.input)
56
+
57
+ self._buildSubGraph(builder, node)
58
+
59
+ builder.leave_compound(node)
60
+
61
+ self._log.debug("<-- mkTaskNode %s (%d)" % (name, len(node.needs)))
62
+ return node
63
+
64
+ def _buildSubGraph(self, builder, node):
65
+ nodes = []
66
+
67
+ for t in self.tasks:
68
+ # Need to get the parent name
69
+ needs = []
70
+ for n in t.needs:
71
+ need_name = "%s.%s" % (builder.package().name, n)
72
+ task = builder.findTask(n)
73
+ if task is None:
74
+ task = builder.findTask(need_name)
75
+
76
+ if task is None:
77
+ raise Exception("Failed to find task %s (%s)" % (n, need_name))
78
+ self._log.debug("Add %s as dependency of %s" % (
79
+ task.name, t.name
80
+ ))
81
+ needs.append(task)
82
+ sn = t.mkTaskNode(
83
+ builder=builder,
84
+ params=t.mkTaskParams(),
85
+ name=t.name,
86
+ needs=needs)
87
+ nodes.append(sn)
88
+ builder.addTask(t.name, sn)
89
+ in_t = builder.findTask("in")
90
+
91
+
92
+ for n in nodes:
93
+
94
+ # If this node references one of the others, then
95
+ # it takes input from that node, and not the 'in' node
96
+ has_ref = False
97
+ for nt in n.needs:
98
+ self._log.debug("nt: %s %s" % (nt[0].name, str(n.needs)))
99
+ if nt[0] in nodes or nt[0] is in_t:
100
+ has_ref = True
101
+ break
102
+ if not has_ref:
103
+ n.needs.append([builder.findTask("in"), False])
104
+
105
+ # Only add a dependency on the node if no other node references it
106
+ is_ref = False
107
+ for nt in nodes:
108
+ for nn in nt.needs:
109
+ if nn[0] == n:
110
+ is_ref = True
111
+ break
112
+ if not is_ref:
113
+ node.needs.append([n, False])
114
+
115
+ self._log.debug("nodes: %d (%d %d)" % (len(nodes), len(self.tasks), len(node.needs)))
116
+
117
+ pass
@@ -0,0 +1,65 @@
1
+ import os
2
+ import json
3
+ import dataclasses as dc
4
+ import logging
5
+ from pydantic import BaseModel
6
+ from typing import Any, Callable, ClassVar, Dict, List, Tuple
7
+ from .task_data import TaskDataOutput, TaskDataResult
8
+ from .task_node import TaskNode
9
+ from .task_node_compound import TaskNodeCompound
10
+ from .task_node_ctor import TaskNodeCtor
11
+ from .task_node_ctor_compound import TaskNodeCtorCompound
12
+
13
+ # TaskParamsCtor accepts an evaluation context and returns a task parameter object
14
+ TaskParamsCtor = Callable[[object], Any]
15
+
16
+ @dc.dataclass
17
+ class TaskNodeCtorCompoundProxy(TaskNodeCtorCompound):
18
+ """Task has a 'uses' clause, so we delegate creation of the node"""
19
+ uses : TaskNodeCtor = dc.field(default=None)
20
+
21
+ _log : ClassVar = logging.getLogger("TaskNodeCtorCompoundProxy")
22
+
23
+ def mkTaskNode(self, builder, params, srcdir=None, name=None, needs=None) -> 'TaskNode':
24
+ """Creates a task object without a base task"""
25
+ self._log.debug("--> mkTaskNode: %s", name)
26
+ if srcdir is None:
27
+ srcdir = self.srcdir
28
+
29
+ is_compound_uses = builder.is_compound_uses()
30
+
31
+ if not is_compound_uses:
32
+ # We're at the leaf level
33
+ node = TaskNodeCompound(
34
+ name=name,
35
+ srcdir=srcdir,
36
+ params=params)
37
+
38
+ builder.enter_compound(node)
39
+ builder.addTask("in", node.input)
40
+ else:
41
+ node = None
42
+
43
+ builder.enter_compound_uses()
44
+
45
+ # Construct the base-task node
46
+ base_node = self.uses.mkTaskNode(
47
+ builder=builder,
48
+ params=params,
49
+ srcdir=srcdir,
50
+ name=name + ".super",
51
+ needs=[node.input])
52
+ builder.addTask("super", base_node)
53
+
54
+ # Build 'uses' node
55
+ self._buildSubGraph(builder, node)
56
+
57
+ builder.leave_compound_uses()
58
+
59
+ if not is_compound_uses:
60
+ builder.leave_compound(node)
61
+
62
+
63
+ self._log.debug("<-- mkTaskNode: %s", name)
64
+ return node
65
+
@@ -0,0 +1,47 @@
1
+ #****************************************************************************
2
+ #* task_node_ctor_def_base.py
3
+ #*
4
+ #* Copyright 2023-2025 Matthew Ballance and Contributors
5
+ #*
6
+ #* Licensed under the Apache License, Version 2.0 (the "License"); you may
7
+ #* not use this file except in compliance with the License.
8
+ #* You may obtain a copy of the License at:
9
+ #*
10
+ #* http://www.apache.org/licenses/LICENSE-2.0
11
+ #*
12
+ #* Unless required by applicable law or agreed to in writing, software
13
+ #* distributed under the License is distributed on an "AS IS" BASIS,
14
+ #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ #* See the License for the specific language governing permissions and
16
+ #* limitations under the License.
17
+ #*
18
+ #* Created on:
19
+ #* Author:
20
+ #*
21
+ #****************************************************************************
22
+ import enum
23
+ import os
24
+ import sys
25
+ import dataclasses as dc
26
+ import pydantic.dataclasses as pdc
27
+ import logging
28
+ import toposort
29
+ from typing import Any, Callable, ClassVar, Dict, List, Tuple
30
+ from .task_data import TaskDataInput, TaskDataOutput, TaskDataResult
31
+ from .task_params_ctor import TaskParamsCtor
32
+ from .param_ref_eval import ParamRefEval
33
+ from .param import Param
34
+ from .task_node_ctor import TaskNodeCtor
35
+
36
+ @dc.dataclass
37
+ class TaskNodeCtorDefBase(TaskNodeCtor):
38
+ """Task defines its own needs, that will need to be filled in"""
39
+ needs : List['str']
40
+
41
+ def __post_init__(self):
42
+ if self.needs is None:
43
+ self.needs = []
44
+
45
+ def getNeeds(self) -> List[str]:
46
+ return self.needs
47
+
@@ -0,0 +1,56 @@
1
+ #****************************************************************************
2
+ #* task_node_ctor_def_base.py
3
+ #*
4
+ #* Copyright 2023-2025 Matthew Ballance and Contributors
5
+ #*
6
+ #* Licensed under the Apache License, Version 2.0 (the "License"); you may
7
+ #* not use this file except in compliance with the License.
8
+ #* You may obtain a copy of the License at:
9
+ #*
10
+ #* http://www.apache.org/licenses/LICENSE-2.0
11
+ #*
12
+ #* Unless required by applicable law or agreed to in writing, software
13
+ #* distributed under the License is distributed on an "AS IS" BASIS,
14
+ #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ #* See the License for the specific language governing permissions and
16
+ #* limitations under the License.
17
+ #*
18
+ #* Created on:
19
+ #* Author:
20
+ #*
21
+ #****************************************************************************
22
+ import enum
23
+ import os
24
+ import sys
25
+ import dataclasses as dc
26
+ import pydantic.dataclasses as pdc
27
+ import logging
28
+ import toposort
29
+ from typing import Any, Callable, ClassVar, Dict, List, Tuple
30
+ from .task_data import TaskDataInput, TaskDataOutput, TaskDataResult
31
+ from .task_params_ctor import TaskParamsCtor
32
+ from .param_ref_eval import ParamRefEval
33
+ from .param import Param
34
+ from .task_node import TaskNode
35
+ from .task_node_ctor import TaskNodeCtor
36
+ from .task_node_ctor_def_base import TaskNodeCtorDefBase
37
+
38
+ @dc.dataclass
39
+ class TaskNodeCtorProxy(TaskNodeCtorDefBase):
40
+ """Task has a 'uses' clause, so we delegate creation of the node"""
41
+ uses : TaskNodeCtor
42
+
43
+ def mkTaskNode(self, builder, params, srcdir=None, name=None, needs=None) -> TaskNode:
44
+ if srcdir is None:
45
+ srcdir = self.srcdir
46
+ builder.enter_uses()
47
+ node = self.uses.mkTaskNode(
48
+ builder=builder, params=params, srcdir=srcdir, name=name, needs=needs)
49
+ node.passthrough = self.passthrough
50
+ node.consumes = self.consumes
51
+ builder.leave_uses()
52
+
53
+ if not builder.in_uses():
54
+ builder.addTask(name, node)
55
+ return node
56
+
@@ -0,0 +1,64 @@
1
+ #****************************************************************************
2
+ #* task_node_ctor_task.py
3
+ #*
4
+ #* Copyright 2023-2025 Matthew Ballance and Contributors
5
+ #*
6
+ #* Licensed under the Apache License, Version 2.0 (the "License"); you may
7
+ #* not use this file except in compliance with the License.
8
+ #* You may obtain a copy of the License at:
9
+ #*
10
+ #* http://www.apache.org/licenses/LICENSE-2.0
11
+ #*
12
+ #* Unless required by applicable law or agreed to in writing, software
13
+ #* distributed under the License is distributed on an "AS IS" BASIS,
14
+ #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ #* See the License for the specific language governing permissions and
16
+ #* limitations under the License.
17
+ #*
18
+ #* Created on:
19
+ #* Author:
20
+ #*
21
+ #****************************************************************************
22
+ import enum
23
+ import os
24
+ import sys
25
+ import dataclasses as dc
26
+ import pydantic.dataclasses as pdc
27
+ import logging
28
+ import toposort
29
+ from typing import Any, Callable, ClassVar, Dict, List, Tuple
30
+ from .task_data import TaskDataInput, TaskDataOutput, TaskDataResult
31
+ from .task_params_ctor import TaskParamsCtor
32
+ from .param_ref_eval import ParamRefEval
33
+ from .param import Param
34
+ from .task_node import TaskNode
35
+ from .task_node_leaf import TaskNodeLeaf
36
+ from .task_node_ctor import TaskNodeCtor
37
+ from .task_node_ctor_def_base import TaskNodeCtorDefBase
38
+
39
+ @dc.dataclass
40
+ class TaskNodeCtorTask(TaskNodeCtorDefBase):
41
+ task : Callable[['TaskRunner','TaskDataInput'],'TaskDataResult']
42
+
43
+ _log : ClassVar[logging.Logger] = logging.getLogger("TaskNodeCtorTask")
44
+
45
+ def mkTaskNode(self, builder, params, srcdir=None, name=None, needs=None) -> TaskNode:
46
+ self._log.debug("--> mkTaskNode needs=%d" % (
47
+ (len(needs) if needs is not None else -1),
48
+ ))
49
+ if srcdir is None:
50
+ srcdir = self.srcdir
51
+
52
+ node = TaskNodeLeaf(
53
+ name=name,
54
+ srcdir=srcdir,
55
+ params=params,
56
+ task=self.task,
57
+ needs=needs)
58
+ node.passthrough = self.passthrough
59
+ node.consumes = self.consumes
60
+ node.task = self.task
61
+ builder.addTask(name, node)
62
+
63
+ self._log.debug("<-- mkTaskNode")
64
+ return node
@@ -0,0 +1,96 @@
1
+ #****************************************************************************
2
+ #* task_node_ctor_task.py
3
+ #*
4
+ #* Copyright 2023-2025 Matthew Ballance and Contributors
5
+ #*
6
+ #* Licensed under the Apache License, Version 2.0 (the "License"); you may
7
+ #* not use this file except in compliance with the License.
8
+ #* You may obtain a copy of the License at:
9
+ #*
10
+ #* http://www.apache.org/licenses/LICENSE-2.0
11
+ #*
12
+ #* Unless required by applicable law or agreed to in writing, software
13
+ #* distributed under the License is distributed on an "AS IS" BASIS,
14
+ #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ #* See the License for the specific language governing permissions and
16
+ #* limitations under the License.
17
+ #*
18
+ #* Created on:
19
+ #* Author:
20
+ #*
21
+ #****************************************************************************
22
+ import enum
23
+ import os
24
+ import sys
25
+ import dataclasses as dc
26
+ import pydantic.dataclasses as pdc
27
+ import logging
28
+ import toposort
29
+ from typing import Any, Callable, ClassVar, Dict, List, Tuple
30
+ from .task_data import TaskDataInput, TaskDataOutput, TaskDataResult
31
+ from .task_params_ctor import TaskParamsCtor
32
+ from .param_ref_eval import ParamRefEval
33
+ from .param import Param
34
+ from .task_node import TaskNode
35
+ from .task_node_leaf import TaskNodeLeaf
36
+ from .task_node_ctor import TaskNodeCtor
37
+ from .task_node_ctor_def_base import TaskNodeCtorDefBase
38
+
39
+ @dc.dataclass
40
+ class TaskNodeCtorWrapper(TaskNodeCtor):
41
+ T : Any
42
+
43
+ def mkTaskNode(self, builder, params, srcdir=None, name=None, needs=None) -> TaskNode:
44
+ node = TaskNodeLeaf(
45
+ name=name,
46
+ srcdir=srcdir,
47
+ params=params,
48
+ task=self.T,
49
+ needs=needs)
50
+ node.passthrough = self.passthrough
51
+ node.consumes = self.consumes
52
+ return node
53
+
54
+ def mkTaskParams(self, params : Dict = None) -> Any:
55
+ obj = self.paramT()
56
+
57
+ # Apply user-specified params
58
+ for key,value in params.items():
59
+ if not hasattr(obj, key):
60
+ raise Exception("Parameters class %s does not contain field %s" % (
61
+ str(type(obj)),
62
+ key))
63
+ else:
64
+ if isinstance(value, Param):
65
+ if value.append is not None:
66
+ ex_value = getattr(obj, key, [])
67
+ ex_value.extend(value.append)
68
+ setattr(obj, key, ex_value)
69
+ elif value.prepend is not None:
70
+ ex_value = getattr(obj, key, [])
71
+ value = value.copy()
72
+ value.extend(ex_value)
73
+ setattr(obj, key, value)
74
+ pass
75
+ else:
76
+ raise Exception("Unhandled value spec: %s" % str(value))
77
+ else:
78
+ setattr(obj, key, value)
79
+ return obj
80
+
81
+ def task(paramT,passthrough=False,consumes=None):
82
+ """Decorator to wrap a task method as a TaskNodeCtor"""
83
+ def wrapper(T):
84
+ task_mname = T.__module__
85
+ task_module = sys.modules[task_mname]
86
+ ctor = TaskNodeCtorWrapper(
87
+ name=T.__name__,
88
+ srcdir=os.path.dirname(os.path.abspath(task_module.__file__)),
89
+ paramT=paramT,
90
+ passthrough=passthrough,
91
+ consumes=consumes,
92
+ needs=[],
93
+ T=T)
94
+ return ctor
95
+ return wrapper
96
+