nextpipe 0.1.0.dev4__tar.gz → 0.1.0.dev5__tar.gz
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.
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/PKG-INFO +9 -4
- nextpipe-0.1.0.dev5/README.md +7 -0
- nextpipe-0.1.0.dev5/examples/README.md +3 -0
- nextpipe-0.1.0.dev5/examples/apps/echo/.gitignore +1 -0
- nextpipe-0.1.0.dev5/examples/apps/echo/README.md +17 -0
- nextpipe-0.1.0.dev5/examples/apps/echo/app.yaml +6 -0
- nextpipe-0.1.0.dev5/examples/apps/echo/main.py +11 -0
- nextpipe-0.1.0.dev5/examples/apps/echo/requirements.txt +1 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/examples/pipeline-chain/README.md +4 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/examples/pipeline-complex/README.md +7 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/examples/pipeline-ensemble/README.md +6 -1
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/examples/pipeline-ensemble/main.py +7 -1
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/examples/pipeline-preprocess/README.md +7 -0
- nextpipe-0.1.0.dev5/nextpipe/__about__.py +1 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/nextpipe/decorators.py +14 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/nextpipe/flow.py +84 -60
- nextpipe-0.1.0.dev5/nextpipe/schema.py +50 -0
- nextpipe-0.1.0.dev5/nextpipe/threads.py +75 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/pyproject.toml +2 -1
- nextpipe-0.1.0.dev4/README.md +0 -3
- nextpipe-0.1.0.dev4/nextpipe/__about__.py +0 -1
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/.github/workflows/python-lint.yml +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/.github/workflows/python-test.yml +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/.github/workflows/release.yml +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/.gitignore +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/.prettierrc.yml +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/LICENSE.md +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/examples/pipeline-chain/app.yaml +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/examples/pipeline-chain/main.py +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/examples/pipeline-chain/requirements.txt +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/examples/pipeline-complex/app.yaml +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/examples/pipeline-complex/main.py +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/examples/pipeline-complex/requirements.txt +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/examples/pipeline-ensemble/app.yaml +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/examples/pipeline-ensemble/requirements.txt +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/examples/pipeline-preprocess/app.yaml +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/examples/pipeline-preprocess/main.py +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/examples/pipeline-preprocess/requirements.txt +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/nextpipe/__init__.py +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/nextpipe/utils.py +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/nextpipe.code-workspace +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/tests/__init__.py +0 -0
- {nextpipe-0.1.0.dev4 → nextpipe-0.1.0.dev5}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: nextpipe
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev5
|
|
4
4
|
Summary: Framework for Decision Pipeline modeling and execution
|
|
5
5
|
Project-URL: Homepage, https://www.nextmv.io
|
|
6
6
|
Project-URL: Documentation, https://www.nextmv.io/docs
|
|
@@ -104,7 +104,8 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
104
104
|
Classifier: Programming Language :: Python :: 3.11
|
|
105
105
|
Classifier: Programming Language :: Python :: 3.12
|
|
106
106
|
Requires-Python: >=3.8
|
|
107
|
-
Requires-Dist:
|
|
107
|
+
Requires-Dist: dataclasses-json>=0.6.7
|
|
108
|
+
Requires-Dist: nextmv>=0.13.1
|
|
108
109
|
Requires-Dist: pathos>=0.3.2
|
|
109
110
|
Requires-Dist: requests>=2.31.0
|
|
110
111
|
Provides-Extra: dev
|
|
@@ -113,4 +114,8 @@ Description-Content-Type: text/markdown
|
|
|
113
114
|
|
|
114
115
|
# nextpipe
|
|
115
116
|
|
|
116
|
-
Framework for Decision Pipeline modeling and execution
|
|
117
|
+
Framework for Decision Pipeline modeling and execution.
|
|
118
|
+
|
|
119
|
+
## Examples
|
|
120
|
+
|
|
121
|
+
You can find examples of how to use `nextpipe` in the [examples](./examples) directory.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
This directory contains examples of how to use `nextpipe`. Each example typically makes use of other apps that get executed in the example pipeline. If these apps are not already available via [community-apps](https://github.com/nextmv-io/community-apps) (via CLI: `nextmv community clone -a <app-id>`), you can find them in the `./apps` directory.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*.json
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# JSON echo
|
|
2
|
+
|
|
3
|
+
This is a sample app that reads JSON from the input and writes it to the output.
|
|
4
|
+
Furthermore, it adds a statistics section to the output.
|
|
5
|
+
|
|
6
|
+
## Usage
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
echo '{"hello": "world!"}' | python main.py
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Push to Nextmv
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
nextmv app create -a echo -n "JSON echo" -d "This is a sample app that reads JSON from the input and writes it to the output."
|
|
16
|
+
nextmv app push -a echo
|
|
17
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nextmv==0.13.1
|
|
@@ -29,6 +29,13 @@ graph TD
|
|
|
29
29
|
pick_best(pick_best)
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
## Pre-requisites
|
|
33
|
+
|
|
34
|
+
- Subscribe to the following marketplace apps and name them as follows:
|
|
35
|
+
- _Nextmv Routing_: `routing-nextroute`
|
|
36
|
+
- _OR-Tools Routing_: `routing-ortools`
|
|
37
|
+
- _PyVroom Routing_: `routing-pyvroom`
|
|
38
|
+
|
|
32
39
|
## Usage
|
|
33
40
|
|
|
34
41
|
```bash
|
|
@@ -21,9 +21,14 @@ graph TD
|
|
|
21
21
|
pick_best(pick_best)
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
## Pre-requisites
|
|
25
|
+
|
|
26
|
+
- Subscribe to the following marketplace apps and name them as follows:
|
|
27
|
+
- _Nextmv Routing_: `routing-nextroute`
|
|
28
|
+
|
|
24
29
|
## Usage
|
|
25
30
|
|
|
26
31
|
```bash
|
|
27
32
|
nextmv app push -a <app-id>
|
|
28
|
-
cat /path/to/routing/input.json | nextmv app run -a <app-id> -e "8c16gb12h"
|
|
33
|
+
cat /path/to/routing/input.json | nextmv app run -a <app-id> -e "8c16gb12h" -o 'instance=v171-5s'
|
|
29
34
|
```
|
|
@@ -5,12 +5,18 @@ import nextmv
|
|
|
5
5
|
|
|
6
6
|
from nextpipe import FlowSpec, app, needs, repeat, step
|
|
7
7
|
|
|
8
|
+
# Define the options for the workflow
|
|
9
|
+
parameters = [
|
|
10
|
+
nextmv.Parameter("instance", str, "devint", "App instance to use. Default is devint.", False),
|
|
11
|
+
]
|
|
12
|
+
options = nextmv.Options(*parameters)
|
|
13
|
+
|
|
8
14
|
|
|
9
15
|
# >>> Workflow definition
|
|
10
16
|
class Flow(FlowSpec):
|
|
11
17
|
@app(
|
|
12
18
|
app_id="routing-nextroute",
|
|
13
|
-
instance_id=
|
|
19
|
+
instance_id=options.instance,
|
|
14
20
|
parameters={"model.constraints.enable.cluster": True},
|
|
15
21
|
)
|
|
16
22
|
@repeat(repetitions=3)
|
|
@@ -31,6 +31,13 @@ graph TD
|
|
|
31
31
|
pick_best(pick_best)
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
## Pre-requisites
|
|
35
|
+
|
|
36
|
+
- Subscribe to the following marketplace apps and name them as follows:
|
|
37
|
+
- _Nextmv Routing_: `routing-nextroute`
|
|
38
|
+
- _OR-Tools Routing_: `routing-ortools`
|
|
39
|
+
- _PyVroom Routing_: `routing-pyvroom`
|
|
40
|
+
|
|
34
41
|
## Usage
|
|
35
42
|
|
|
36
43
|
```bash
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "v0.1.0.dev5"
|
|
@@ -21,6 +21,8 @@ class Step:
|
|
|
21
21
|
def __init__(self, function: callable):
|
|
22
22
|
self.function = function
|
|
23
23
|
self.type = StepType.DEFAULT
|
|
24
|
+
self.state = "pending"
|
|
25
|
+
self.run_ids = []
|
|
24
26
|
self._inputs = {}
|
|
25
27
|
self._output = None
|
|
26
28
|
|
|
@@ -37,6 +39,12 @@ class Step:
|
|
|
37
39
|
def get_name(self):
|
|
38
40
|
return self.function.__name__
|
|
39
41
|
|
|
42
|
+
def set_state(self, state: str):
|
|
43
|
+
self.state = state
|
|
44
|
+
|
|
45
|
+
def get_state(self):
|
|
46
|
+
return self.state
|
|
47
|
+
|
|
40
48
|
def is_needs(self):
|
|
41
49
|
return hasattr(self, "needs")
|
|
42
50
|
|
|
@@ -49,6 +57,12 @@ class Step:
|
|
|
49
57
|
def is_app(self):
|
|
50
58
|
return self.type == StepType.APP
|
|
51
59
|
|
|
60
|
+
def set_run_ids(self, run_ids: List[str]):
|
|
61
|
+
self.run_ids = run_ids
|
|
62
|
+
|
|
63
|
+
def get_run_ids(self):
|
|
64
|
+
return self.run_ids
|
|
65
|
+
|
|
52
66
|
|
|
53
67
|
class Needs:
|
|
54
68
|
def __init__(self, predecessors: List[callable]):
|
|
@@ -4,26 +4,16 @@ import collections
|
|
|
4
4
|
import inspect
|
|
5
5
|
import io
|
|
6
6
|
import time
|
|
7
|
+
from importlib.metadata import version
|
|
7
8
|
from typing import List, Optional, Union
|
|
8
9
|
|
|
9
|
-
import nextmv
|
|
10
10
|
from nextmv.cloud import Application, Client, StatusV2
|
|
11
|
-
from pathos.multiprocessing import ProcessingPool as Pool
|
|
12
11
|
|
|
13
|
-
from . import utils
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class FlowParameter(nextmv.Parameter):
|
|
17
|
-
"""
|
|
18
|
-
Parameter that is used in a `FlowSpec`.
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
def __init__(self, name, type, default=None, description=None, required=False):
|
|
22
|
-
super().__init__(name, type, default, description, required)
|
|
12
|
+
from . import decorators, schema, threads, utils
|
|
23
13
|
|
|
24
14
|
|
|
25
15
|
class DAGNode:
|
|
26
|
-
def __init__(self, step_function, step_definition, docstring):
|
|
16
|
+
def __init__(self, step_function: callable, step_definition: decorators.Step, docstring: str):
|
|
27
17
|
self.step_function = step_function
|
|
28
18
|
self.step = step_definition
|
|
29
19
|
self.docstring = docstring
|
|
@@ -89,45 +79,51 @@ class FlowSpec:
|
|
|
89
79
|
|
|
90
80
|
# Run the nodes in parallel
|
|
91
81
|
tasks = {}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
82
|
+
pool = threads.Pool(8)
|
|
83
|
+
while open_nodes:
|
|
84
|
+
while True:
|
|
85
|
+
# Get the first node from the open nodes which has all its predecessors done
|
|
86
|
+
node = next(
|
|
87
|
+
iter(
|
|
88
|
+
filter(
|
|
89
|
+
lambda n: all(p in closed_nodes for p in n.predecessors),
|
|
90
|
+
open_nodes,
|
|
91
|
+
)
|
|
92
|
+
),
|
|
93
|
+
None,
|
|
94
|
+
)
|
|
95
|
+
if node is None:
|
|
96
|
+
# No more nodes to run at this point. Wait for the remaining tasks to finish.
|
|
97
|
+
break
|
|
98
|
+
open_nodes.remove(node)
|
|
99
|
+
# Skip the node if it is optional and the condition is not met
|
|
100
|
+
if node.step.skip():
|
|
101
|
+
utils.log(f"Skipping node {node.step.get_name()}")
|
|
102
|
+
node.step.set_state("skipped")
|
|
103
|
+
utils.log("NEXTPIPE_DAG_UPDATE=" + self.graph._persist_dag_update(node))
|
|
104
|
+
continue
|
|
105
|
+
# Run the node asynchronously
|
|
106
|
+
job = threads.Job(self.__run_node, (node, self._get_inputs(node), self.client))
|
|
107
|
+
pool.run(job)
|
|
108
|
+
tasks[node] = job
|
|
109
|
+
node.step.set_state("running")
|
|
110
|
+
utils.log("NEXTPIPE_DAG_UPDATE=" + self.graph._persist_dag_update(node))
|
|
111
|
+
|
|
112
|
+
# Wait until at least one task is done
|
|
113
|
+
task_done = False
|
|
114
|
+
while not task_done:
|
|
115
|
+
time.sleep(0.1)
|
|
116
|
+
# Check if any tasks are done, if not, keep waiting
|
|
117
|
+
for node, job in list(tasks.items()):
|
|
118
|
+
if job.done:
|
|
119
|
+
# Remove task and mark successors as ready by adding them to the open list.
|
|
120
|
+
self.set_result(node, job.result)
|
|
121
|
+
node.step.set_state("succeeded")
|
|
122
|
+
utils.log("NEXTPIPE_DAG_UPDATE=" + self.graph._persist_dag_update(node))
|
|
123
|
+
del tasks[node]
|
|
124
|
+
task_done = True
|
|
125
|
+
closed_nodes.add(node)
|
|
126
|
+
open_nodes.update(node.successors)
|
|
131
127
|
|
|
132
128
|
def set_result(self, step: callable, result: object):
|
|
133
129
|
self.results[step.step] = result
|
|
@@ -146,11 +142,6 @@ class FlowSpec:
|
|
|
146
142
|
def __run_node(node: DAGNode, inputs: List[object], client: Client) -> Union[List[object], object, None]:
|
|
147
143
|
utils.log(f"Running node {node.step.get_name()}")
|
|
148
144
|
|
|
149
|
-
# Skip the node if it is optional and the condition is not met
|
|
150
|
-
if node.step.skip():
|
|
151
|
-
utils.log(f"Skipping node {node.step.get_name()}")
|
|
152
|
-
return
|
|
153
|
-
|
|
154
145
|
# Run the step
|
|
155
146
|
if node.step.is_app():
|
|
156
147
|
app_step = node.step.app
|
|
@@ -176,6 +167,7 @@ class FlowSpec:
|
|
|
176
167
|
app = Application(client=client, id=app_step.app_id, default_instance_id=app_step.instance_id)
|
|
177
168
|
# Run the app (or multiple runs if it is a repeat step)
|
|
178
169
|
run_ids = [app.new_run(*i[0], **i[1]) for i in inputs]
|
|
170
|
+
node.step.set_run_ids(run_ids)
|
|
179
171
|
outputs = utils.wait_for_runs(app=app, run_ids=run_ids)
|
|
180
172
|
# Check if all runs were successful
|
|
181
173
|
for output in outputs:
|
|
@@ -202,11 +194,14 @@ class FlowGraph:
|
|
|
202
194
|
def __init__(self, flow_spec):
|
|
203
195
|
self.flow_spec = flow_spec
|
|
204
196
|
self.__create_graph(flow_spec)
|
|
197
|
+
self.__debug_print_head()
|
|
205
198
|
self.__debug_print_graph()
|
|
199
|
+
# Print the DAG in persistence format
|
|
200
|
+
utils.log("NEXTPIPE_DAG=" + self._persist_dag())
|
|
206
201
|
# Create a Mermaid diagram of the graph and log it
|
|
207
|
-
mermaid = self.
|
|
202
|
+
mermaid = self._to_mermaid()
|
|
208
203
|
utils.log(mermaid)
|
|
209
|
-
mermaid_url = f
|
|
204
|
+
mermaid_url = f"https://mermaid.ink/svg/{base64.b64encode(mermaid.encode('utf8')).decode('ascii')}?theme=dark"
|
|
210
205
|
utils.log(f"Mermaid URL: {mermaid_url}")
|
|
211
206
|
|
|
212
207
|
def __create_graph(self, flow_spec):
|
|
@@ -250,7 +245,30 @@ class FlowGraph:
|
|
|
250
245
|
if cycle:
|
|
251
246
|
raise Exception(f"Cycle detected in the flow graph, cycle nodes: {cycle_nodes}")
|
|
252
247
|
|
|
253
|
-
def
|
|
248
|
+
def _persist_dag(self) -> str:
|
|
249
|
+
dto = schema.DAGDTO(
|
|
250
|
+
nodes=[
|
|
251
|
+
schema.NodeDTO(
|
|
252
|
+
id=node.step.get_name(),
|
|
253
|
+
app_id=node.step.app.app_id if node.step.is_app() else "",
|
|
254
|
+
step_name=node.step.get_name(),
|
|
255
|
+
docs=node.docstring,
|
|
256
|
+
successors=[s.step.get_name() for s in node.successors],
|
|
257
|
+
)
|
|
258
|
+
for node in self.nodes
|
|
259
|
+
]
|
|
260
|
+
)
|
|
261
|
+
return schema.serialize_dag(dto)
|
|
262
|
+
|
|
263
|
+
def _persist_dag_update(self, node: DAGNode) -> str:
|
|
264
|
+
dto = schema.NodeUpdateDTO(
|
|
265
|
+
node_id=node.step.get_name(),
|
|
266
|
+
state=node.step.get_state(),
|
|
267
|
+
run_ids=node.step.get_run_ids(),
|
|
268
|
+
)
|
|
269
|
+
return schema.serialize_node_update(dto)
|
|
270
|
+
|
|
271
|
+
def _to_mermaid(self):
|
|
254
272
|
"""Convert the graph to a Mermaid diagram."""
|
|
255
273
|
out = io.StringIO()
|
|
256
274
|
out.write("graph TD\n")
|
|
@@ -272,6 +290,12 @@ class FlowGraph:
|
|
|
272
290
|
out.write(f" {node_name} --> {successor.step.get_name()}\n")
|
|
273
291
|
return out.getvalue()
|
|
274
292
|
|
|
293
|
+
def __debug_print_head(self):
|
|
294
|
+
utils.log(f"Flow: {self.flow_spec.__name__}")
|
|
295
|
+
utils.log(f"nextpipe: {version('nextpipe')}")
|
|
296
|
+
utils.log(f"nextmv: {version('nextmv')}")
|
|
297
|
+
utils.log("Flow graph nodes:")
|
|
298
|
+
|
|
275
299
|
def __debug_print_graph(self):
|
|
276
300
|
for node in self.nodes:
|
|
277
301
|
utils.log("Node:")
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import dataclasses
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
import dataclasses_json
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclasses_json.dataclass_json
|
|
9
|
+
@dataclasses.dataclass
|
|
10
|
+
class NodeUpdateDTO:
|
|
11
|
+
node_id: str
|
|
12
|
+
"""The ID of the node to update."""
|
|
13
|
+
state: str
|
|
14
|
+
"""The state of the node."""
|
|
15
|
+
run_ids: List[str] = dataclasses.field(default_factory=list)
|
|
16
|
+
"""The ID of the associated run (if any)."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclasses_json.dataclass_json
|
|
20
|
+
@dataclasses.dataclass
|
|
21
|
+
class NodeDTO:
|
|
22
|
+
id: str
|
|
23
|
+
"""The ID of the node."""
|
|
24
|
+
app_id: str
|
|
25
|
+
"""The ID of the app this step represents (if any)."""
|
|
26
|
+
step_name: str
|
|
27
|
+
"""The name of the step (func)."""
|
|
28
|
+
docs: str
|
|
29
|
+
"""The doc string of the step."""
|
|
30
|
+
successors: List[str]
|
|
31
|
+
"""The IDs of the nodes that depend on this node."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclasses_json.dataclass_json
|
|
35
|
+
@dataclasses.dataclass
|
|
36
|
+
class DAGDTO:
|
|
37
|
+
nodes: List[NodeDTO]
|
|
38
|
+
"""The nodes in the DAG."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def serialize_dag(dag: DAGDTO) -> str:
|
|
42
|
+
"""Serialize the DAG for transmission."""
|
|
43
|
+
j_str = dag.to_json(separators=(",", ":"))
|
|
44
|
+
return base64.b64encode(j_str.encode()).decode()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def serialize_node_update(node_update: NodeUpdateDTO) -> str:
|
|
48
|
+
"""Serialize the node update for transmission."""
|
|
49
|
+
j_str = node_update.to_json(separators=(",", ":"))
|
|
50
|
+
return base64.b64encode(j_str.encode()).decode()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import time
|
|
3
|
+
from typing import Callable, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Job:
|
|
7
|
+
def __init__(self, target: Callable, args: Optional[tuple] = None):
|
|
8
|
+
self.target = target
|
|
9
|
+
self.args = args
|
|
10
|
+
self.done = False
|
|
11
|
+
self.result = None
|
|
12
|
+
|
|
13
|
+
def run(self):
|
|
14
|
+
if self.args:
|
|
15
|
+
self.result = self.target(*self.args)
|
|
16
|
+
else:
|
|
17
|
+
self.result = self.target()
|
|
18
|
+
self.done = True
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Pool:
|
|
22
|
+
def __init__(self, max_threads: int):
|
|
23
|
+
self.max_threads = max_threads
|
|
24
|
+
self.counter = 0 # Used to assign unique IDs to threads
|
|
25
|
+
self.waiting = {}
|
|
26
|
+
self.running = {}
|
|
27
|
+
self.lock = threading.Lock()
|
|
28
|
+
self.cond = threading.Condition(self.lock)
|
|
29
|
+
|
|
30
|
+
def run(self, job: Job) -> None:
|
|
31
|
+
with self.lock:
|
|
32
|
+
self.counter += 1
|
|
33
|
+
thread_id = self.counter
|
|
34
|
+
self.waiting[thread_id] = job
|
|
35
|
+
|
|
36
|
+
def worker(job: Job, thread_id: int):
|
|
37
|
+
try:
|
|
38
|
+
job.run()
|
|
39
|
+
finally:
|
|
40
|
+
with self.lock:
|
|
41
|
+
self.running.pop(thread_id, None)
|
|
42
|
+
self.cond.notify_all() # Notify others that a thread is available
|
|
43
|
+
|
|
44
|
+
while True:
|
|
45
|
+
with self.lock:
|
|
46
|
+
if len(self.running) < self.max_threads:
|
|
47
|
+
# Move job from waiting to running
|
|
48
|
+
thread = threading.Thread(target=worker, args=(job, thread_id))
|
|
49
|
+
self.running[thread_id] = thread
|
|
50
|
+
self.waiting.pop(thread_id, None)
|
|
51
|
+
thread.start()
|
|
52
|
+
break # Successfully assigned the job to a thread
|
|
53
|
+
else:
|
|
54
|
+
self.cond.wait() # Wait until a thread is available
|
|
55
|
+
|
|
56
|
+
def join(self) -> None:
|
|
57
|
+
with self.cond:
|
|
58
|
+
while self.waiting or self.running:
|
|
59
|
+
self.cond.wait() # Wait until all jobs are finished
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_pool():
|
|
63
|
+
def target(*args):
|
|
64
|
+
print(f"Running job with args: {args}")
|
|
65
|
+
time.sleep(0.5) # Simulate work
|
|
66
|
+
|
|
67
|
+
pool = Pool(2)
|
|
68
|
+
for i in range(1, 7): # Submit 6 jobs
|
|
69
|
+
pool.run(Job(target, (i,)))
|
|
70
|
+
pool.join()
|
|
71
|
+
print("All jobs completed.")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
test_pool()
|
nextpipe-0.1.0.dev4/README.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "v0.1.0.dev4"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|