nextpipe 0.1.0.dev6__tar.gz → 0.1.0.dev8__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.dev6 → nextpipe-0.1.0.dev8}/.github/workflows/test.yml +4 -2
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/PKG-INFO +2 -2
- nextpipe-0.1.0.dev8/examples/apps/echo/input.json +1 -0
- {nextpipe-0.1.0.dev6/examples/pipeline-complex → nextpipe-0.1.0.dev8/examples/pipeline-chain}/app.yaml +2 -3
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/pipeline-chain/main.py +0 -5
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/pipeline-complex/README.md +1 -1
- {nextpipe-0.1.0.dev6/examples/pipeline-ensemble → nextpipe-0.1.0.dev8/examples/pipeline-complex}/app.yaml +2 -3
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/pipeline-complex/main.py +26 -16
- nextpipe-0.1.0.dev8/examples/pipeline-ensemble/app.yaml +6 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/pipeline-ensemble/main.py +0 -5
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/pipeline-foreach/README.md +1 -1
- nextpipe-0.1.0.dev8/examples/pipeline-foreach/app.yaml +6 -0
- nextpipe-0.1.0.dev8/examples/pipeline-foreach/main.py +58 -0
- nextpipe-0.1.0.dev8/examples/pipeline-preprocess/app.yaml +6 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/pipeline-preprocess/main.py +0 -5
- nextpipe-0.1.0.dev8/nextpipe/__about__.py +1 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/nextpipe/config.py +21 -1
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/nextpipe/flow.py +2 -2
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/nextpipe/uplink.py +2 -2
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/nextpipe/utils.py +3 -3
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/pyproject.toml +1 -1
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/pipelines/chain.py +0 -5
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/pipelines/complex.py +0 -5
- nextpipe-0.1.0.dev8/tests/pipelines/foreach-2-pred.json +3 -0
- nextpipe-0.1.0.dev8/tests/pipelines/foreach-2-pred.json.golden +65 -0
- nextpipe-0.1.0.dev8/tests/pipelines/foreach-2-pred.py +58 -0
- nextpipe-0.1.0.dev8/tests/pipelines/foreach.json +3 -0
- nextpipe-0.1.0.dev8/tests/pipelines/foreach.json.golden +56 -0
- nextpipe-0.1.0.dev6/examples/pipeline-foreach/main.py → nextpipe-0.1.0.dev8/tests/pipelines/foreach.py +0 -5
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/test_integration.py +36 -4
- nextpipe-0.1.0.dev6/examples/pipeline-foreach/app.yaml +0 -7
- nextpipe-0.1.0.dev6/examples/pipeline-preprocess/app.yaml +0 -7
- nextpipe-0.1.0.dev6/nextpipe/__about__.py +0 -1
- nextpipe-0.1.0.dev6/tests/deploy/app.yaml +0 -7
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/.github/workflows/lint.yml +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/.github/workflows/release.yml +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/.gitignore +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/.prettierrc.yml +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/LICENSE.md +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/README.md +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/README.md +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/apps/echo/.gitignore +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/apps/echo/README.md +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/apps/echo/app.yaml +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/apps/echo/main.py +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/apps/echo/requirements.txt +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/pipeline-chain/README.md +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/pipeline-chain/requirements.txt +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/pipeline-complex/requirements.txt +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/pipeline-ensemble/README.md +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/pipeline-ensemble/requirements.txt +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/pipeline-foreach/requirements.txt +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/pipeline-preprocess/README.md +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/examples/pipeline-preprocess/requirements.txt +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/nextpipe/__init__.py +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/nextpipe/decorators.py +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/nextpipe/graph.py +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/nextpipe/schema.py +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/nextpipe/threads.py +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/nextpipe.code-workspace +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/.gitignore +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/README.md +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/__init__.py +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/apps/echo/.gitignore +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/apps/echo/README.md +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/apps/echo/app.yaml +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/apps/echo/main.py +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/apps/echo/requirements.txt +0 -0
- {nextpipe-0.1.0.dev6/examples/pipeline-chain → nextpipe-0.1.0.dev8/tests/deploy}/app.yaml +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/deploy/main.py +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/deploy/requirements.txt +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/pipelines/chain.json +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/pipelines/chain.json.golden +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/pipelines/complex.json +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/pipelines/complex.json.golden +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/pipelines/fail.py +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/test_graph.py +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/test_threads.py +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/test_uplink.py +0 -0
- {nextpipe-0.1.0.dev6 → nextpipe-0.1.0.dev8}/tests/test_version.py +0 -0
|
@@ -5,7 +5,7 @@ jobs:
|
|
|
5
5
|
runs-on: ubuntu-latest
|
|
6
6
|
strategy:
|
|
7
7
|
matrix:
|
|
8
|
-
python-version: ["3.9", "3.
|
|
8
|
+
python-version: ["3.9", "3.11", "3.13"]
|
|
9
9
|
steps:
|
|
10
10
|
- name: git clone
|
|
11
11
|
uses: actions/checkout@v4
|
|
@@ -23,4 +23,6 @@ jobs:
|
|
|
23
23
|
- name: unit tests
|
|
24
24
|
env:
|
|
25
25
|
NEXTMV_API_KEY_NEXTPIPE: ${{ secrets.NEXTMV_API_KEY_NEXTPIPE }}
|
|
26
|
-
run:
|
|
26
|
+
run: |
|
|
27
|
+
export NEXTMV_API_KEY=$NEXTMV_API_KEY_NEXTPIPE
|
|
28
|
+
python -m unittest
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nextpipe
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev8
|
|
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
|
|
@@ -109,7 +109,7 @@ Requires-Dist: nextmv>=0.13.1
|
|
|
109
109
|
Requires-Dist: pathos>=0.3.2
|
|
110
110
|
Requires-Dist: requests>=2.31.0
|
|
111
111
|
Provides-Extra: dev
|
|
112
|
-
Requires-Dist: goldie>=0.1.
|
|
112
|
+
Requires-Dist: goldie>=0.1.7; extra == 'dev'
|
|
113
113
|
Requires-Dist: ruff>=0.6.4; extra == 'dev'
|
|
114
114
|
Description-Content-Type: text/markdown
|
|
115
115
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "hello": "world!" }
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import json
|
|
2
|
-
import os
|
|
3
2
|
|
|
4
3
|
import nextmv
|
|
5
4
|
|
|
@@ -31,10 +30,6 @@ class Flow(FlowSpec):
|
|
|
31
30
|
|
|
32
31
|
|
|
33
32
|
def main():
|
|
34
|
-
# Read API key from file (until secrets management support)
|
|
35
|
-
with open("key.json") as f:
|
|
36
|
-
os.environ["NEXTMV_API_KEY"] = json.load(f)["nextmv_api_key"]
|
|
37
|
-
|
|
38
33
|
# Load input data
|
|
39
34
|
input = nextmv.load_local()
|
|
40
35
|
|
|
@@ -40,5 +40,5 @@ graph LR
|
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
nextmv app push -a <app-id>
|
|
43
|
-
|
|
43
|
+
curl "https://gist.githubusercontent.com/merschformann/a90959b87d1360b604e4a9f6457340ca/raw/661e631376bdf78a07548a3cd136c1fc6e47c639/muenster.json" | nextmv app run -a <app-id>
|
|
44
44
|
```
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
+
import copy
|
|
1
2
|
import json
|
|
2
|
-
import os
|
|
3
3
|
|
|
4
4
|
import nextmv
|
|
5
|
-
import requests
|
|
6
5
|
|
|
7
6
|
from nextpipe import FlowSpec, app, needs, repeat, step
|
|
8
7
|
|
|
@@ -10,29 +9,39 @@ from nextpipe import FlowSpec, app, needs, repeat, step
|
|
|
10
9
|
# >>> Workflow definition
|
|
11
10
|
class Flow(FlowSpec):
|
|
12
11
|
@step
|
|
13
|
-
def
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
response = requests.get(file_url)
|
|
17
|
-
return response.json()
|
|
12
|
+
def prepare(input: dict):
|
|
13
|
+
"""Prepares the data."""
|
|
14
|
+
return input
|
|
18
15
|
|
|
19
|
-
@
|
|
16
|
+
@needs(predecessors=[prepare])
|
|
17
|
+
@step
|
|
18
|
+
def convert(input: dict):
|
|
19
|
+
"""Converts the data."""
|
|
20
|
+
clone = copy.deepcopy(input)
|
|
21
|
+
if "defaults" in clone and "stops" in clone["defaults"] and "quantity" in clone["defaults"]["stops"]:
|
|
22
|
+
clone["defaults"]["stops"]["quantity"] *= -1
|
|
23
|
+
for stop in clone["stops"]:
|
|
24
|
+
if "quantity" in stop:
|
|
25
|
+
stop["quantity"] *= -1
|
|
26
|
+
return clone
|
|
27
|
+
|
|
28
|
+
@repeat(repetitions=2)
|
|
20
29
|
@app(app_id="routing-nextroute")
|
|
21
|
-
@needs(predecessors=[
|
|
30
|
+
@needs(predecessors=[prepare])
|
|
22
31
|
@step
|
|
23
32
|
def run_nextroute():
|
|
24
33
|
"""Runs the model."""
|
|
25
34
|
pass
|
|
26
35
|
|
|
27
36
|
@app(app_id="routing-ortools")
|
|
28
|
-
@needs(predecessors=[
|
|
37
|
+
@needs(predecessors=[convert])
|
|
29
38
|
@step
|
|
30
39
|
def run_ortools():
|
|
31
40
|
"""Runs the model."""
|
|
32
41
|
pass
|
|
33
42
|
|
|
34
43
|
@app(app_id="routing-pyvroom")
|
|
35
|
-
@needs(predecessors=[
|
|
44
|
+
@needs(predecessors=[convert])
|
|
36
45
|
@step
|
|
37
46
|
def run_pyvroom():
|
|
38
47
|
"""Runs the model."""
|
|
@@ -56,16 +65,17 @@ class Flow(FlowSpec):
|
|
|
56
65
|
values.sort()
|
|
57
66
|
nextmv.log(f"Values: {values}")
|
|
58
67
|
|
|
59
|
-
return
|
|
68
|
+
# For test stability reasons, we always return the or-tools result
|
|
69
|
+
_ = results.pop(best_solution_idx)
|
|
70
|
+
return result_ortools
|
|
60
71
|
|
|
61
72
|
|
|
62
73
|
def main():
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
os.environ["NEXTMV_API_KEY"] = json.load(f)["nextmv_api_key"]
|
|
74
|
+
# Load input data
|
|
75
|
+
input = nextmv.load_local()
|
|
66
76
|
|
|
67
77
|
# Run workflow
|
|
68
|
-
flow = Flow("DecisionFlow",
|
|
78
|
+
flow = Flow("DecisionFlow", input.data)
|
|
69
79
|
flow.run()
|
|
70
80
|
result = flow.get_result(flow.pick_best)
|
|
71
81
|
print(json.dumps(result))
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import json
|
|
2
|
-
import os
|
|
3
2
|
|
|
4
3
|
import nextmv
|
|
5
4
|
|
|
@@ -37,10 +36,6 @@ class Flow(FlowSpec):
|
|
|
37
36
|
|
|
38
37
|
|
|
39
38
|
def main():
|
|
40
|
-
# Read API key from file (until secrets management support)
|
|
41
|
-
with open("key.json") as f:
|
|
42
|
-
os.environ["NEXTMV_API_KEY"] = json.load(f)["nextmv_api_key"]
|
|
43
|
-
|
|
44
39
|
# Load input data
|
|
45
40
|
input = nextmv.load_local()
|
|
46
41
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import nextmv
|
|
5
|
+
import nextmv.cloud
|
|
6
|
+
|
|
7
|
+
from nextpipe import AppOption, AppRunConfig, FlowSpec, app, foreach, join, needs, step
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Flow(FlowSpec):
|
|
11
|
+
@foreach() # Run the successor step for each item in the result list of this step
|
|
12
|
+
@step
|
|
13
|
+
def fanout(data: dict):
|
|
14
|
+
"""
|
|
15
|
+
Creates 3 copies of the input and configures them for 3 different app parameters.
|
|
16
|
+
"""
|
|
17
|
+
inputs = [copy.deepcopy(data) for _ in range(3)]
|
|
18
|
+
run_configs = [AppRunConfig(input, [AppOption("param", i)]) for i, input in enumerate(inputs)]
|
|
19
|
+
return run_configs
|
|
20
|
+
|
|
21
|
+
@step
|
|
22
|
+
def stats(data: dict):
|
|
23
|
+
"""
|
|
24
|
+
Calculates some statistics to put on the output as well.
|
|
25
|
+
"""
|
|
26
|
+
return {"stats": {"count": len(data)}}
|
|
27
|
+
|
|
28
|
+
@app(app_id="echo")
|
|
29
|
+
@needs(predecessors=[fanout])
|
|
30
|
+
@step
|
|
31
|
+
def solve():
|
|
32
|
+
"""
|
|
33
|
+
Runs the model.
|
|
34
|
+
"""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
@needs(predecessors=[solve, stats])
|
|
38
|
+
@join() # Collect the results from the previous 'foreach' step and combine them into a list passed as the arg
|
|
39
|
+
@step
|
|
40
|
+
def merge(results: list):
|
|
41
|
+
"""Merges the results."""
|
|
42
|
+
return results
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def main():
|
|
46
|
+
# Load input data
|
|
47
|
+
input = nextmv.load_local()
|
|
48
|
+
|
|
49
|
+
# Run workflow
|
|
50
|
+
flow = Flow("DecisionFlow", input.data)
|
|
51
|
+
flow.run()
|
|
52
|
+
|
|
53
|
+
# Write out the result
|
|
54
|
+
print(json.dumps(flow.get_result(flow.merge)))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
main()
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import csv
|
|
2
2
|
import json
|
|
3
|
-
import os
|
|
4
3
|
|
|
5
4
|
import requests
|
|
6
5
|
from nextmv.logger import log
|
|
@@ -90,10 +89,6 @@ class Flow(FlowSpec):
|
|
|
90
89
|
|
|
91
90
|
|
|
92
91
|
def main():
|
|
93
|
-
# Read API key from file (until secrets management support)
|
|
94
|
-
with open("key.json") as f:
|
|
95
|
-
os.environ["NEXTMV_API_KEY"] = json.load(f)["nextmv_api_key"]
|
|
96
|
-
|
|
97
92
|
# Run workflow
|
|
98
93
|
flow = Flow("DecisionFlow", None)
|
|
99
94
|
flow.run()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "v0.1.0.dev8"
|
|
@@ -1,8 +1,24 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
|
|
3
3
|
from dataclasses_json import dataclass_json
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
@dataclass_json
|
|
7
|
+
@dataclass
|
|
8
|
+
class AppPollingOptions:
|
|
9
|
+
"""Options for polling the platform for the status of an app."""
|
|
10
|
+
|
|
11
|
+
timeout: float = 1800
|
|
12
|
+
"""
|
|
13
|
+
Timeout in seconds for polling the platform.
|
|
14
|
+
This is used for example when waiting for results of an app run.
|
|
15
|
+
"""
|
|
16
|
+
max_backoff: float = 30
|
|
17
|
+
"""
|
|
18
|
+
Maximum backoff time in seconds.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
@dataclass_json
|
|
7
23
|
@dataclass
|
|
8
24
|
class Configuration:
|
|
@@ -20,3 +36,7 @@ class Configuration:
|
|
|
20
36
|
of inputs used when a step has multiple predecessors which are themselves repeated or
|
|
21
37
|
foreach steps.
|
|
22
38
|
"""
|
|
39
|
+
app_polling: AppPollingOptions = field(default_factory=AppPollingOptions)
|
|
40
|
+
"""
|
|
41
|
+
Options for polling the platform for the status of an app.
|
|
42
|
+
"""
|
|
@@ -288,8 +288,8 @@ class Runner:
|
|
|
288
288
|
inputs = [list(item) for item in product(*predecessor_inputs.values())]
|
|
289
289
|
# If the steps is a 'join' step, we need to combine the inputs from all predecessors.
|
|
290
290
|
if step.definition.is_join():
|
|
291
|
-
#
|
|
292
|
-
inputs = [
|
|
291
|
+
# Make sure that we only pass one list as the input.
|
|
292
|
+
inputs = [[inputs]]
|
|
293
293
|
# If the step is a 'repeat' step, repeat the inputs for each repetition.
|
|
294
294
|
if step.definition.is_repeat():
|
|
295
295
|
inputs = inputs * step.definition.get_repetitions()
|
|
@@ -12,7 +12,7 @@ from nextpipe.utils import log
|
|
|
12
12
|
|
|
13
13
|
FAILED_UPDATES_THRESHOLD = 10
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
ENV_APP_ID = "NEXTMV_APP_ID"
|
|
16
16
|
ENV_RUN_ID = "NEXTMV_RUN_ID"
|
|
17
17
|
|
|
18
18
|
|
|
@@ -96,7 +96,7 @@ class UplinkClient:
|
|
|
96
96
|
if config is None:
|
|
97
97
|
# Load config from environment
|
|
98
98
|
config = UplinkConfig(
|
|
99
|
-
application_id=os.environ.get(
|
|
99
|
+
application_id=os.environ.get(ENV_APP_ID),
|
|
100
100
|
run_id=os.environ.get(ENV_RUN_ID),
|
|
101
101
|
)
|
|
102
102
|
self.config = config
|
|
@@ -36,15 +36,15 @@ _INFINITE_TIMEOUT = sys.maxsize
|
|
|
36
36
|
def wait_for_runs(
|
|
37
37
|
app: Application,
|
|
38
38
|
run_ids: list[str],
|
|
39
|
-
timeout:
|
|
40
|
-
max_backoff:
|
|
39
|
+
timeout: float = _INFINITE_TIMEOUT,
|
|
40
|
+
max_backoff: float = 30,
|
|
41
41
|
) -> list[RunResult]:
|
|
42
42
|
"""
|
|
43
43
|
Wait until all runs with the given IDs are finished.
|
|
44
44
|
"""
|
|
45
45
|
# Wait until all runs are finished or the timeout is reached
|
|
46
46
|
missing = set(run_ids)
|
|
47
|
-
backoff =
|
|
47
|
+
backoff = 2
|
|
48
48
|
start_time = time.time()
|
|
49
49
|
while missing and time.time() - start_time < timeout:
|
|
50
50
|
time.sleep(backoff)
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import json
|
|
2
|
-
import os
|
|
3
2
|
|
|
4
3
|
import nextmv
|
|
5
4
|
|
|
@@ -31,10 +30,6 @@ class Flow(FlowSpec):
|
|
|
31
30
|
|
|
32
31
|
|
|
33
32
|
def main():
|
|
34
|
-
# Read API key from file (until secrets management support)
|
|
35
|
-
with open("key.json") as f:
|
|
36
|
-
os.environ["NEXTMV_API_KEY"] = json.load(f)["nextmv_api_key"]
|
|
37
|
-
|
|
38
33
|
# Load input data
|
|
39
34
|
input = nextmv.load_local()
|
|
40
35
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import json
|
|
2
|
-
import os
|
|
3
2
|
|
|
4
3
|
import nextmv
|
|
5
4
|
|
|
@@ -59,10 +58,6 @@ class Flow(FlowSpec):
|
|
|
59
58
|
|
|
60
59
|
|
|
61
60
|
def main():
|
|
62
|
-
# Read API key from file (until secrets management support)
|
|
63
|
-
with open("key.json") as f:
|
|
64
|
-
os.environ["NEXTMV_API_KEY"] = json.load(f)["nextmv_api_key"]
|
|
65
|
-
|
|
66
61
|
# Load input data
|
|
67
62
|
input = nextmv.load_local()
|
|
68
63
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"solution": {
|
|
4
|
+
"echo": {
|
|
5
|
+
"data": {
|
|
6
|
+
"hello": "world!"
|
|
7
|
+
},
|
|
8
|
+
"args": [
|
|
9
|
+
"-param=0"
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"statistics": {
|
|
14
|
+
"run": {
|
|
15
|
+
"duration": 0.123
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"assets": [],
|
|
19
|
+
"stats": {
|
|
20
|
+
"count": 1
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"solution": {
|
|
25
|
+
"echo": {
|
|
26
|
+
"data": {
|
|
27
|
+
"hello": "world!"
|
|
28
|
+
},
|
|
29
|
+
"args": [
|
|
30
|
+
"-param=1"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"statistics": {
|
|
35
|
+
"run": {
|
|
36
|
+
"duration": 0.123
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"assets": [],
|
|
40
|
+
"stats": {
|
|
41
|
+
"count": 1
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"solution": {
|
|
46
|
+
"echo": {
|
|
47
|
+
"data": {
|
|
48
|
+
"hello": "world!"
|
|
49
|
+
},
|
|
50
|
+
"args": [
|
|
51
|
+
"-param=2"
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"statistics": {
|
|
56
|
+
"run": {
|
|
57
|
+
"duration": 0.123
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"assets": [],
|
|
61
|
+
"stats": {
|
|
62
|
+
"count": 1
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import nextmv
|
|
5
|
+
import nextmv.cloud
|
|
6
|
+
|
|
7
|
+
from nextpipe import AppOption, AppRunConfig, FlowSpec, app, foreach, join, needs, step
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Flow(FlowSpec):
|
|
11
|
+
@foreach() # Run the successor step for each item in the result list of this step
|
|
12
|
+
@step
|
|
13
|
+
def fanout(data: dict):
|
|
14
|
+
"""
|
|
15
|
+
Creates 3 copies of the input and configures them for 3 different app parameters.
|
|
16
|
+
"""
|
|
17
|
+
inputs = [copy.deepcopy(data) for _ in range(3)]
|
|
18
|
+
run_configs = [AppRunConfig(input, [AppOption("param", i)]) for i, input in enumerate(inputs)]
|
|
19
|
+
return run_configs
|
|
20
|
+
|
|
21
|
+
@step
|
|
22
|
+
def stats(data: dict):
|
|
23
|
+
"""
|
|
24
|
+
Calculates some statistics to put on the output as well.
|
|
25
|
+
"""
|
|
26
|
+
return {"stats": {"count": len(data)}}
|
|
27
|
+
|
|
28
|
+
@app(app_id="echo")
|
|
29
|
+
@needs(predecessors=[fanout])
|
|
30
|
+
@step
|
|
31
|
+
def solve():
|
|
32
|
+
"""
|
|
33
|
+
Runs the model.
|
|
34
|
+
"""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
@needs(predecessors=[solve, stats])
|
|
38
|
+
@join() # Collect the results from the previous 'foreach' step and combine them into a list passed as the arg
|
|
39
|
+
@step
|
|
40
|
+
def merge(results: list):
|
|
41
|
+
"""Merges the results."""
|
|
42
|
+
return results
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def main():
|
|
46
|
+
# Load input data
|
|
47
|
+
input = nextmv.load_local()
|
|
48
|
+
|
|
49
|
+
# Run workflow
|
|
50
|
+
flow = Flow("DecisionFlow", input.data)
|
|
51
|
+
flow.run()
|
|
52
|
+
|
|
53
|
+
# Write out the result
|
|
54
|
+
print(json.dumps(flow.get_result(flow.merge)))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
main()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"solution": {
|
|
4
|
+
"echo": {
|
|
5
|
+
"data": {
|
|
6
|
+
"hello": "world!"
|
|
7
|
+
},
|
|
8
|
+
"args": [
|
|
9
|
+
"-param=0"
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"statistics": {
|
|
14
|
+
"run": {
|
|
15
|
+
"duration": 0.123
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"assets": []
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"solution": {
|
|
22
|
+
"echo": {
|
|
23
|
+
"data": {
|
|
24
|
+
"hello": "world!"
|
|
25
|
+
},
|
|
26
|
+
"args": [
|
|
27
|
+
"-param=1"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"statistics": {
|
|
32
|
+
"run": {
|
|
33
|
+
"duration": 0.123
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"assets": []
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"solution": {
|
|
40
|
+
"echo": {
|
|
41
|
+
"data": {
|
|
42
|
+
"hello": "world!"
|
|
43
|
+
},
|
|
44
|
+
"args": [
|
|
45
|
+
"-param=2"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"statistics": {
|
|
50
|
+
"run": {
|
|
51
|
+
"duration": 0.123
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"assets": []
|
|
55
|
+
}
|
|
56
|
+
]
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
import json
|
|
3
|
-
import os
|
|
4
3
|
|
|
5
4
|
import nextmv
|
|
6
5
|
import nextmv.cloud
|
|
@@ -37,10 +36,6 @@ class Flow(FlowSpec):
|
|
|
37
36
|
|
|
38
37
|
|
|
39
38
|
def main():
|
|
40
|
-
# Read API key from file (until secrets management support)
|
|
41
|
-
with open("key.json") as f:
|
|
42
|
-
os.environ["NEXTMV_API_KEY"] = json.load(f)["nextmv_api_key"]
|
|
43
|
-
|
|
44
39
|
# Load input data
|
|
45
40
|
input = nextmv.load_local()
|
|
46
41
|
|
|
@@ -2,7 +2,6 @@ import os
|
|
|
2
2
|
import os.path
|
|
3
3
|
import random
|
|
4
4
|
import unittest
|
|
5
|
-
from dataclasses import replace
|
|
6
5
|
|
|
7
6
|
import goldie
|
|
8
7
|
from nextmv import cloud
|
|
@@ -91,9 +90,42 @@ class TestExample(unittest.TestCase):
|
|
|
91
90
|
configuration=config,
|
|
92
91
|
)
|
|
93
92
|
|
|
93
|
+
# FOREACH
|
|
94
|
+
config.comparison_configuration.json_processing_config = goldie.ConfigProcessJson(
|
|
95
|
+
replacements=[
|
|
96
|
+
goldie.JsonReplacement(path="$[0].statistics.run.duration", value=0.123),
|
|
97
|
+
goldie.JsonReplacement(path="$[1].statistics.run.duration", value=0.123),
|
|
98
|
+
goldie.JsonReplacement(path="$[2].statistics.run.duration", value=0.123),
|
|
99
|
+
],
|
|
100
|
+
)
|
|
101
|
+
goldie.run_file_unittest(
|
|
102
|
+
test=self,
|
|
103
|
+
td=goldie.TestDefinition(
|
|
104
|
+
input_file=os.path.join(path, "foreach.json"),
|
|
105
|
+
extra_args=[("pipeline", os.path.join(path, "foreach.py"))],
|
|
106
|
+
),
|
|
107
|
+
configuration=config,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# FOREACH 2 PREDECESSORS
|
|
111
|
+
config.comparison_configuration.json_processing_config = goldie.ConfigProcessJson(
|
|
112
|
+
replacements=[
|
|
113
|
+
goldie.JsonReplacement(path="$[0][0].statistics.run.duration", value=0.123),
|
|
114
|
+
goldie.JsonReplacement(path="$[1][0].statistics.run.duration", value=0.123),
|
|
115
|
+
goldie.JsonReplacement(path="$[2][0].statistics.run.duration", value=0.123),
|
|
116
|
+
],
|
|
117
|
+
)
|
|
118
|
+
goldie.run_file_unittest(
|
|
119
|
+
test=self,
|
|
120
|
+
td=goldie.TestDefinition(
|
|
121
|
+
input_file=os.path.join(path, "foreach-2-pred.json"),
|
|
122
|
+
extra_args=[("pipeline", os.path.join(path, "foreach-2-pred.py"))],
|
|
123
|
+
),
|
|
124
|
+
configuration=config,
|
|
125
|
+
)
|
|
126
|
+
|
|
94
127
|
# COMPLEX
|
|
95
|
-
|
|
96
|
-
config_complex.comparison_configuration.json_processing_config = goldie.ConfigProcessJson(
|
|
128
|
+
config.comparison_configuration.json_processing_config = goldie.ConfigProcessJson(
|
|
97
129
|
replacements=[
|
|
98
130
|
goldie.JsonReplacement(path="$.statistics.result.duration", value="0.123"),
|
|
99
131
|
goldie.JsonReplacement(path="$.statistics.run.duration", value="0.123"),
|
|
@@ -105,7 +137,7 @@ class TestExample(unittest.TestCase):
|
|
|
105
137
|
input_file=os.path.join(path, "complex.json"),
|
|
106
138
|
extra_args=[("pipeline", os.path.join(path, "complex.py"))],
|
|
107
139
|
),
|
|
108
|
-
configuration=
|
|
140
|
+
configuration=config,
|
|
109
141
|
)
|
|
110
142
|
|
|
111
143
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "v0.1.0.dev6"
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|