nextpipe 0.1.0.dev0__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.
nextpipe/__about__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "v0.1.0.dev0"
nextpipe/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ """Framework for Decision Pipeline modeling and execution."""
2
+
3
+ from .__about__ import __version__
4
+ from .decorators import app as app
5
+ from .decorators import needs as needs
6
+ from .decorators import optional as optional
7
+ from .decorators import repeat as repeat
8
+ from .decorators import step as step
9
+ from .flow import FlowGraph as FlowGraph
10
+ from .flow import FlowSpec as FlowSpec
11
+
12
+ VERSION = __version__
13
+ """The version of the nextpipe package."""
nextpipe/decorators.py ADDED
@@ -0,0 +1,166 @@
1
+ from enum import Enum
2
+ from functools import wraps
3
+ from typing import Callable, Dict, List
4
+
5
+ from pathos.multiprocessing import ProcessingPool as Pool
6
+
7
+ from . import utils
8
+
9
+
10
+ class InputType(Enum):
11
+ JSON = 1
12
+ FILES = 2
13
+
14
+
15
+ class StepType(Enum):
16
+ DEFAULT = 1
17
+ APP = 2
18
+
19
+
20
+ class Step:
21
+ def __init__(self, function: callable):
22
+ self.function = function
23
+ self.type = StepType.DEFAULT
24
+ self._inputs = {}
25
+ self._output = None
26
+
27
+ def __repr__(self):
28
+ b = f"Step({self.function.__name__}"
29
+ if hasattr(self, "needs"):
30
+ b += f", {self.needs}"
31
+ if hasattr(self, "repeat"):
32
+ b += f", {self.repeat}"
33
+ if hasattr(self, "app"):
34
+ b += f", {self.app}"
35
+ return b + ")"
36
+
37
+ def get_name(self):
38
+ return self.function.__name__
39
+
40
+ def is_needs(self):
41
+ return hasattr(self, "needs")
42
+
43
+ def skip(self):
44
+ return hasattr(self, "optional") and not self.optional.condition(self)
45
+
46
+ def is_repeat(self):
47
+ return hasattr(self, "repeat")
48
+
49
+ def is_app(self):
50
+ return self.type == StepType.APP
51
+
52
+
53
+ class Needs:
54
+ def __init__(self, predecessors: List[callable]):
55
+ self.predecessors = predecessors
56
+
57
+ def __repr__(self):
58
+ return f"StepNeeds({','.join([p.step.get_name() for p in self.predecessors])})"
59
+
60
+
61
+ class Optional:
62
+ def __init__(self, condition: callable):
63
+ self.condition = condition
64
+
65
+ def __repr__(self):
66
+ return f"StepOnlyIf({self.condition})"
67
+
68
+
69
+ class Repeat:
70
+ def __init__(self, repetitions: int):
71
+ self.repetitions = repetitions
72
+
73
+ def __repr__(self):
74
+ return f"StepRepeat({self.repetitions})"
75
+
76
+
77
+ class App:
78
+ def __init__(
79
+ self,
80
+ app_id: str,
81
+ instance_id: str = "devint",
82
+ input_type: InputType = InputType.JSON,
83
+ parameters: Dict[str, any] = None,
84
+ ):
85
+ self.app_id = app_id
86
+ self.instance_id = instance_id
87
+ self.parameters = parameters if parameters else {}
88
+ self.input_type = input_type
89
+
90
+ def __repr__(self):
91
+ return f"StepRun({self.app_id}, {self.instance_id}, {self.parameters}, {self.input_type})"
92
+
93
+
94
+ def needs(predecessors: List[callable]):
95
+ def decorator(function):
96
+ function.step.needs = Needs(predecessors)
97
+ return function
98
+
99
+ return decorator
100
+
101
+
102
+ def optional(condition: Callable[[Step], bool]):
103
+ def decorator(function):
104
+ function.step.optional = Optional(condition)
105
+ return function
106
+
107
+ return decorator
108
+
109
+
110
+ def repeat(repetitions: int):
111
+ def decorator(function):
112
+ @wraps(function)
113
+ def wrapper(*args, **kwargs):
114
+ inputs = [(args, kwargs) for _ in range(repetitions)]
115
+ outputs = []
116
+ with Pool(repetitions) as pool:
117
+ outputs = pool.map(utils.wrap_func(function), inputs)
118
+ return outputs
119
+
120
+ wrapper.step.repeat = Repeat(repetitions)
121
+
122
+ return wrapper
123
+
124
+ return decorator
125
+
126
+
127
+ def app(
128
+ app_id: str,
129
+ instance_id: str = "default",
130
+ parameters: Dict[str, any] = None,
131
+ input_type: InputType = InputType.JSON,
132
+ ):
133
+ def decorator(function):
134
+ @wraps(function)
135
+ def wrapper(*args, **kwargs):
136
+ utils.log(f"Running {app_id} version {instance_id}")
137
+ return function(*args, **kwargs)
138
+
139
+ # We need to make sure that all values of the parameters are converted to strings,
140
+ # as no other types are allowed in the JSON.
141
+ converted_parameters = utils.convert_to_string_values(parameters if parameters else {})
142
+
143
+ wrapper.step.app = App(
144
+ app_id=app_id,
145
+ instance_id=instance_id,
146
+ parameters=converted_parameters,
147
+ input_type=input_type,
148
+ )
149
+ wrapper.step.type = StepType.APP
150
+
151
+ return wrapper
152
+
153
+ return decorator
154
+
155
+
156
+ def step(function):
157
+ @wraps(function)
158
+ def wrapper(*args, **kwargs):
159
+ utils.log(f"Entering {function.__name__}")
160
+ ret_val = function(*args, **kwargs)
161
+ utils.log(f"Finished {function.__name__}")
162
+ return ret_val
163
+
164
+ wrapper.step = Step(function)
165
+ wrapper.is_step = True
166
+ return wrapper
nextpipe/flow.py ADDED
@@ -0,0 +1,281 @@
1
+ import ast
2
+ import base64
3
+ import collections
4
+ import inspect
5
+ import io
6
+ import time
7
+ from typing import List, Optional, Union
8
+
9
+ from nextmv.cloud import Application, Client, StatusV2
10
+ from pathos.multiprocessing import ProcessingPool as Pool
11
+
12
+ from . import utils
13
+
14
+
15
+ class DAGNode:
16
+ def __init__(self, step_function, step_definition, docstring):
17
+ self.step_function = step_function
18
+ self.step = step_definition
19
+ self.docstring = docstring
20
+ self.successors: List[DAGNode] = []
21
+
22
+ def __repr__(self):
23
+ return f"DAGNode({self.step_function.name})"
24
+
25
+
26
+ def check_cycle(nodes: List[DAGNode]):
27
+ """
28
+ Checks the given DAG for cycles and returns nodes that are part of a cycle.
29
+ """
30
+ # Step 1: Calculate in-degree (number of incoming edges) for each node
31
+ in_degree = {node: 0 for node in nodes}
32
+
33
+ for node in nodes:
34
+ for successor in node.successors:
35
+ in_degree[successor] += 1
36
+
37
+ # Step 2: Initialize a queue with all nodes that have in-degree 0
38
+ queue = collections.deque([node for node in nodes if in_degree[node] == 0])
39
+
40
+ # Number of processed nodes
41
+ processed_count = 0
42
+
43
+ # Step 3: Process nodes with in-degree 0
44
+ while queue:
45
+ current_node = queue.popleft()
46
+ processed_count += 1
47
+
48
+ # Decrease the in-degree of each successor by 1
49
+ for successor in current_node.successors:
50
+ in_degree[successor] -= 1
51
+ # If in-degree becomes 0, add it to the queue
52
+ if in_degree[successor] == 0:
53
+ queue.append(successor)
54
+
55
+ # Step 4: Identify the faulty nodes (those still with in-degree > 0)
56
+ faulty_nodes = [node for node in nodes if in_degree[node] > 0]
57
+
58
+ # If there are faulty nodes, there's a cycle
59
+ if faulty_nodes:
60
+ return True, faulty_nodes
61
+ else:
62
+ return False, None
63
+
64
+
65
+ class FlowSpec:
66
+ def __init__(self, name: str, input: dict, client: Optional[Client] = None):
67
+ self.name = name
68
+ self.graph = FlowGraph(self.__class__)
69
+ self.client = client if client is not None else Client()
70
+ self.input = input
71
+ self.results = {}
72
+
73
+ def __repr__(self):
74
+ return f"Flow({self.name})"
75
+
76
+ def run(self):
77
+ open_nodes = set(self.graph.start_nodes)
78
+ closed_nodes = set()
79
+
80
+ # Run the nodes in parallel
81
+ tasks = {}
82
+ with Pool(8) as pool:
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
+ # Run the node asynchronously
100
+ tasks[node] = pool.apipe(
101
+ self.__run_node,
102
+ node,
103
+ self._get_inputs(node),
104
+ self.client,
105
+ )
106
+
107
+ # Wait until at least one task is done
108
+ task_done = False
109
+ while not task_done:
110
+ time.sleep(0.1)
111
+ # Check if any tasks are done, if not, keep waiting
112
+ for node, task in list(tasks.items()):
113
+ if task.ready():
114
+ # Remove task and mark successors as ready by adding them to the open list.
115
+ result = task.get()
116
+ self.set_result(node, result)
117
+ del tasks[node]
118
+ task_done = True
119
+ closed_nodes.add(node)
120
+ open_nodes.update(node.successors)
121
+
122
+ def set_result(self, step: callable, result: object):
123
+ self.results[step.step] = result
124
+
125
+ def get_result(self, step: callable) -> Union[object, None]:
126
+ return self.results.get(step.step)
127
+
128
+ def _get_inputs(self, node: DAGNode) -> List[object]:
129
+ return (
130
+ [self.get_result(predecessor) for predecessor in node.step.needs.predecessors]
131
+ if node.step.is_needs()
132
+ else [self.input]
133
+ )
134
+
135
+ @staticmethod
136
+ def __run_node(node: DAGNode, inputs: List[object], client: Client) -> Union[List[object], object, None]:
137
+ utils.log(f"Running node {node.step.get_name()}")
138
+
139
+ # Skip the node if it is optional and the condition is not met
140
+ if node.step.skip():
141
+ utils.log(f"Skipping node {node.step.get_name()}")
142
+ return
143
+
144
+ # Run the step
145
+ if node.step.is_app():
146
+ app_step = node.step.app
147
+ repetitions = node.step.repeat.repetitions if node.step.is_repeat() else 1
148
+ # Prepare the input for the app
149
+ # TODO: We only support one predecessor for app steps for now. This may
150
+ # change in the future. We may want to support multiple predecessors for
151
+ # app steps. However, we need to think about how to handle the input and
152
+ # how to expose control over the input to the user.
153
+ if len(inputs) > 1:
154
+ raise Exception(
155
+ f"App steps cannot have more than one predecessor, but {node.step.get_name()} has {len(inputs)}"
156
+ )
157
+ inputs = [
158
+ (
159
+ [], # No nameless arguments
160
+ { # We use the named arguments to pass the user arguments to the run function
161
+ "input": inputs[0],
162
+ "options": app_step.parameters,
163
+ },
164
+ )
165
+ ] * repetitions
166
+ app = Application(client=client, id=app_step.app_id, default_instance_id=app_step.instance_id)
167
+ # Run the app (or multiple runs if it is a repeat step)
168
+ run_ids = [app.new_run(*i[0], **i[1]) for i in inputs]
169
+ outputs = utils.wait_for_runs(app=app, run_ids=run_ids)
170
+ # Check if all runs were successful
171
+ for output in outputs:
172
+ if output.metadata.status_v2 != StatusV2.succeeded:
173
+ raise Exception(
174
+ f"Step {node.step.get_name()} failed with status {output.metadata.status_v2}: "
175
+ + f"{output.error_log}"
176
+ )
177
+ # Unwrap the result and store it
178
+ # TODO: We may want to store the full RunResult object in certain cases.
179
+ # Maybe this can become a parameter of the step decorator.
180
+ outputs = [output.output for output in outputs]
181
+ return outputs if node.step.is_repeat() else outputs[0]
182
+ else:
183
+ spec = inspect.getfullargspec(node.step.function)
184
+ if len(spec.args) == 0:
185
+ output = node.step.function()
186
+ else:
187
+ output = node.step.function(*inputs)
188
+ return output
189
+
190
+
191
+ class FlowGraph:
192
+ def __init__(self, flow_spec):
193
+ self.flow_spec = flow_spec
194
+ self.__create_graph(flow_spec)
195
+ self.__debug_print_graph()
196
+ # Create a Mermaid diagram of the graph and log it
197
+ mermaid = self.__to_mermaid()
198
+ utils.log(mermaid)
199
+ mermaid_url = f'https://mermaid.ink/svg/{base64.b64encode(mermaid.encode("utf8")).decode("ascii")}?theme=dark'
200
+ utils.log(f"Mermaid URL: {mermaid_url}")
201
+
202
+ def __create_graph(self, flow_spec):
203
+ module = __import__(flow_spec.__module__)
204
+ class_name = flow_spec.__name__
205
+ tree = ast.parse(inspect.getsource(module)).body
206
+ root = [n for n in tree if isinstance(n, ast.ClassDef) and n.name == class_name][0]
207
+
208
+ # Build the graph
209
+ self.nodes = []
210
+ visitor = StepVisitor(self.nodes, flow_spec)
211
+ visitor.visit(root)
212
+
213
+ # Init nodes for all steps
214
+ nodes_by_step = {node.step: node for node in self.nodes}
215
+ for node in self.nodes:
216
+ node.predecessors = []
217
+ node.successors = []
218
+
219
+ for node in self.nodes:
220
+ if not node.step.is_needs():
221
+ continue
222
+ for predecessor in node.step.needs.predecessors:
223
+ predecessor_node = nodes_by_step[predecessor.step]
224
+ node.predecessors.append(predecessor_node)
225
+ predecessor_node.successors.append(node)
226
+
227
+ self.start_nodes = [node for node in self.nodes if not node.predecessors]
228
+
229
+ # Make sure that all app steps have at most one predecessor.
230
+ # TODO: This may change in the future. See other comment about it in this file.
231
+ for node in self.nodes:
232
+ if node.step.is_app() and len(node.predecessors) > 1:
233
+ raise Exception(
234
+ "App steps cannot have more than one predecessor, "
235
+ + f"but {node.step.get_name()} has {len(node.predecessors)}"
236
+ )
237
+
238
+ # Check for cycles
239
+ cycle, cycle_nodes = check_cycle(self.nodes)
240
+ if cycle:
241
+ raise Exception(f"Cycle detected in the flow graph, cycle nodes: {cycle_nodes}")
242
+
243
+ def __to_mermaid(self):
244
+ """Convert the graph to a Mermaid diagram."""
245
+ out = io.StringIO()
246
+ out.write("graph TD\n")
247
+ for node in self.nodes:
248
+ node_name = node.step.get_name()
249
+ if node.step.is_repeat():
250
+ out.write(f" {node_name}{{ }}\n")
251
+ out.write(f" {node_name}_join{{ }}\n")
252
+ repetitions = node.step.repeat.repetitions
253
+ for i in range(repetitions):
254
+ out.write(f" {node_name}_{i}({node_name}_{i})\n")
255
+ out.write(f" {node_name} --> {node_name}_{i}\n")
256
+ out.write(f" {node_name}_{i} --> {node_name}_join\n")
257
+ for successor in node.successors:
258
+ out.write(f" {node_name}_join --> {successor.step.get_name()}\n")
259
+ else:
260
+ out.write(f" {node_name}({node_name})\n")
261
+ for successor in node.successors:
262
+ out.write(f" {node_name} --> {successor.step.get_name()}\n")
263
+ return out.getvalue()
264
+
265
+ def __debug_print_graph(self):
266
+ for node in self.nodes:
267
+ utils.log("Node:")
268
+ utils.log(f" Definition: {node.step}")
269
+ utils.log(f" Docstring: {node.docstring}")
270
+
271
+
272
+ class StepVisitor(ast.NodeVisitor):
273
+ def __init__(self, nodes: List[DAGNode], flow: FlowSpec):
274
+ self.nodes = nodes
275
+ self.flow = flow
276
+ super().__init__()
277
+
278
+ def visit_FunctionDef(self, step_function):
279
+ func = getattr(self.flow, step_function.name)
280
+ if hasattr(func, "is_step"):
281
+ self.nodes.append(DAGNode(step_function, func.step, func.__doc__))
nextpipe/utils.py ADDED
@@ -0,0 +1,68 @@
1
+ import sys
2
+ import time
3
+ from functools import wraps
4
+ from typing import Dict, List
5
+
6
+ from nextmv.cloud import Application, RunResult, StatusV2
7
+
8
+
9
+ def log(message: str) -> None:
10
+ """Logs a message using stderr."""
11
+
12
+ print(message, file=sys.stderr)
13
+
14
+
15
+ def wrap_func(function):
16
+ """
17
+ Wraps the given function in a new function that unpacks the arguments given as a tuple.
18
+ """
19
+
20
+ @wraps(function)
21
+ def func_wrapper(args):
22
+ return function(*args[0], **args[1])
23
+
24
+ return func_wrapper
25
+
26
+
27
+ def convert_to_string_values(input_dict: Dict[str, any]) -> Dict[str, str]:
28
+ """
29
+ Converts all values of the given dictionary to strings.
30
+ """
31
+ return {key: str(value) for key, value in input_dict.items()}
32
+
33
+
34
+ _INFINITE_TIMEOUT = sys.maxsize
35
+
36
+
37
+ def wait_for_runs(
38
+ app: Application,
39
+ run_ids: List[str],
40
+ timeout: int = _INFINITE_TIMEOUT,
41
+ max_backoff: int = 30,
42
+ ) -> List[RunResult]:
43
+ """
44
+ Wait until all runs with the given IDs are finished.
45
+ """
46
+ # Wait until all runs are finished or the timeout is reached
47
+ missing = set(run_ids)
48
+ backoff = 1
49
+ start_time = time.time()
50
+ while missing and time.time() - start_time < timeout:
51
+ for run_id in missing.copy():
52
+ run_info = app.run_metadata(run_id=run_id)
53
+ if run_info.metadata.status_v2 == StatusV2.succeeded:
54
+ missing.remove(run_id)
55
+ continue
56
+ if run_info.metadata.status_v2 in [
57
+ StatusV2.failed,
58
+ StatusV2.canceled,
59
+ ]:
60
+ raise RuntimeError(f"Run {run_id} {run_info.metadata.status_v2}")
61
+
62
+ time.sleep(backoff)
63
+ backoff = min(backoff * 2, max_backoff)
64
+
65
+ if missing:
66
+ raise TimeoutError(f"Timeout of {timeout} seconds reached while waiting.")
67
+
68
+ return [app.run_result(run_id=run_id) for run_id in run_ids]
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.3
2
+ Name: nextpipe
3
+ Version: 0.1.0.dev0
4
+ Summary: Framework for Decision Pipeline modeling and execution
5
+ Project-URL: Homepage, https://www.nextmv.io
6
+ Project-URL: Documentation, https://www.nextmv.io/docs
7
+ Project-URL: Repository, https://github.com/nextmv-io/nextpipe
8
+ Author-email: Nextmv <tech@nextmv.io>
9
+ Maintainer-email: Nextmv <tech@nextmv.io>
10
+ License: # LICENSE
11
+
12
+ Business Source License 1.1
13
+
14
+ Parameters
15
+
16
+ Licensor: nextmv.io inc
17
+ Licensed Work: nextpipe
18
+
19
+ Change Date: Four years from the date the Licensed Work is published.
20
+ Change License: GPLv3
21
+
22
+ For information about alternative licensing arrangements for the Software,
23
+ please email info@nextmv.io.
24
+
25
+ Notice
26
+
27
+ The Business Source License (this document, or the “License”) is not an Open
28
+ Source license. However, the Licensed Work will eventually be made available
29
+ under an Open Source License, as stated in this License.
30
+
31
+ License text copyright © 2023 MariaDB plc, All Rights Reserved. “Business Source
32
+ License” is a trademark of MariaDB plc.
33
+
34
+ -----------------------------------------------------------------------------
35
+
36
+ ## Terms
37
+
38
+ The Licensor hereby grants you the right to copy, modify, create derivative
39
+ works, redistribute, and make non-production use of the Licensed Work. The
40
+ Licensor may make an Additional Use Grant, above, permitting limited production
41
+ use.
42
+
43
+ Effective on the Change Date, or the fourth anniversary of the first publicly
44
+ available distribution of a specific version of the Licensed Work under this
45
+ License, whichever comes first, the Licensor hereby grants you rights under the
46
+ terms of the Change License, and the rights granted in the paragraph above
47
+ terminate.
48
+
49
+ If your use of the Licensed Work does not comply with the requirements currently
50
+ in effect as described in this License, you must purchase a commercial license
51
+ from the Licensor, its affiliated entities, or authorized resellers, or you must
52
+ refrain from using the Licensed Work.
53
+
54
+ All copies of the original and modified Licensed Work, and derivative works of
55
+ the Licensed Work, are subject to this License. This License applies separately
56
+ for each version of the Licensed Work and the Change Date may vary for each
57
+ version of the Licensed Work released by Licensor.
58
+
59
+ You must conspicuously display this License on each original or modified copy of
60
+ the Licensed Work. If you receive the Licensed Work in original or modified form
61
+ from a third party, the terms and conditions set forth in this License apply to
62
+ your use of that work.
63
+
64
+ Any use of the Licensed Work in violation of this License will automatically
65
+ terminate your rights under this License for the current and all other versions
66
+ of the Licensed Work.
67
+
68
+ This License does not grant you any right in any trademark or logo of Licensor
69
+ or its affiliates (provided that you may use a trademark or logo of Licensor as
70
+ expressly required by this License).TO THE EXTENT PERMITTED BY APPLICABLE LAW,
71
+ THE LICENSED WORK IS PROVIDED ON AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL
72
+ WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION)
73
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
74
+ NON-INFRINGEMENT, AND TITLE. MariaDB hereby grants you permission to use this
75
+ License’s text to license your works, and to refer to it using the trademark
76
+ “Business Source License”, as long as you comply with the Covenants of Licensor
77
+ below.
78
+
79
+ ## Covenants of Licensor
80
+
81
+ In consideration of the right to use this License’s text and the “Business
82
+ Source License” name and trademark, Licensor covenants to MariaDB, and to all
83
+ other recipients of the licensed work to be provided by Licensor:
84
+
85
+ To specify as the Change License the GPL Version 2.0 or any later version, or a
86
+ license that is compatible with GPL Version 2.0 or a later version, where
87
+ “compatible” means that software provided under the Change License can be
88
+ included in a program with software provided under GPL Version 2.0 or a later
89
+ version. Licensor may specify additional Change Licenses without limitation. To
90
+ either: (a) specify an additional grant of rights to use that does not impose
91
+ any additional restriction on the right granted in this License, as the
92
+ Additional Use Grant; or (b) insert the text “None” to specify a Change Date.
93
+ Not to modify this License in any other way.
94
+
95
+ License text copyright © 2023 MariaDB plc, All Rights Reserved. “Business Source
96
+ License” is a trademark of MariaDB plc.
97
+ License-File: LICENSE.md
98
+ Keywords: decision automation,decision engineering,decision pipelines,decision science,decision workflows,decisions,nextmv,operations research,optimization,pipelines,workflows
99
+ Classifier: Operating System :: OS Independent
100
+ Classifier: Programming Language :: Python :: 3
101
+ Classifier: Programming Language :: Python :: 3.8
102
+ Classifier: Programming Language :: Python :: 3.9
103
+ Classifier: Programming Language :: Python :: 3.10
104
+ Classifier: Programming Language :: Python :: 3.11
105
+ Classifier: Programming Language :: Python :: 3.12
106
+ Requires-Python: >=3.8
107
+ Requires-Dist: nextmv>=0.12.0
108
+ Requires-Dist: pathos>=0.3.2
109
+ Requires-Dist: requests>=2.31.0
110
+ Provides-Extra: dev
111
+ Requires-Dist: ruff>=0.6.4; extra == 'dev'
112
+ Description-Content-Type: text/markdown
113
+
114
+ # nextpipe
115
+
116
+ Framework for Decision Pipeline modeling and execution
@@ -0,0 +1,9 @@
1
+ nextpipe/__about__.py,sha256=uDxRUA_EOvq6G7pRiUDRVgT2IZDZUjnCLbXSNau-_v4,28
2
+ nextpipe/__init__.py,sha256=kP0MhuEjmKG-P6Y0y0qJGrZ25sucWwKkLQoqnFm2xLY,441
3
+ nextpipe/decorators.py,sha256=2AeEaBp4xCe8EicEj3MNQOtp9hUeCt1ZC_R2z9p7MN8,4157
4
+ nextpipe/flow.py,sha256=1d763m4v1f0tWIStw-a83hAG8Ew0Vb8Uc29rB5GW3As,11100
5
+ nextpipe/utils.py,sha256=CmaTPAPZZG6i-C2vwNSxOAYNCon4VraxA4cIlN1czl4,1859
6
+ nextpipe-0.1.0.dev0.dist-info/METADATA,sha256=F-p9g0dyPqV6Xu6HZlHcPBRR_YbeQgjKwrIwPB0zi3g,6001
7
+ nextpipe-0.1.0.dev0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
8
+ nextpipe-0.1.0.dev0.dist-info/licenses/LICENSE.md,sha256=FpVScuRcgdbeBNVsMzoPyBbKL2eCB1vFor1vfNNFEj4,4106
9
+ nextpipe-0.1.0.dev0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.25.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,87 @@
1
+ # LICENSE
2
+
3
+ Business Source License 1.1
4
+
5
+ Parameters
6
+
7
+ Licensor: nextmv.io inc
8
+ Licensed Work: nextpipe
9
+
10
+ Change Date: Four years from the date the Licensed Work is published.
11
+ Change License: GPLv3
12
+
13
+ For information about alternative licensing arrangements for the Software,
14
+ please email info@nextmv.io.
15
+
16
+ Notice
17
+
18
+ The Business Source License (this document, or the “License”) is not an Open
19
+ Source license. However, the Licensed Work will eventually be made available
20
+ under an Open Source License, as stated in this License.
21
+
22
+ License text copyright © 2023 MariaDB plc, All Rights Reserved. “Business Source
23
+ License” is a trademark of MariaDB plc.
24
+
25
+ -----------------------------------------------------------------------------
26
+
27
+ ## Terms
28
+
29
+ The Licensor hereby grants you the right to copy, modify, create derivative
30
+ works, redistribute, and make non-production use of the Licensed Work. The
31
+ Licensor may make an Additional Use Grant, above, permitting limited production
32
+ use.
33
+
34
+ Effective on the Change Date, or the fourth anniversary of the first publicly
35
+ available distribution of a specific version of the Licensed Work under this
36
+ License, whichever comes first, the Licensor hereby grants you rights under the
37
+ terms of the Change License, and the rights granted in the paragraph above
38
+ terminate.
39
+
40
+ If your use of the Licensed Work does not comply with the requirements currently
41
+ in effect as described in this License, you must purchase a commercial license
42
+ from the Licensor, its affiliated entities, or authorized resellers, or you must
43
+ refrain from using the Licensed Work.
44
+
45
+ All copies of the original and modified Licensed Work, and derivative works of
46
+ the Licensed Work, are subject to this License. This License applies separately
47
+ for each version of the Licensed Work and the Change Date may vary for each
48
+ version of the Licensed Work released by Licensor.
49
+
50
+ You must conspicuously display this License on each original or modified copy of
51
+ the Licensed Work. If you receive the Licensed Work in original or modified form
52
+ from a third party, the terms and conditions set forth in this License apply to
53
+ your use of that work.
54
+
55
+ Any use of the Licensed Work in violation of this License will automatically
56
+ terminate your rights under this License for the current and all other versions
57
+ of the Licensed Work.
58
+
59
+ This License does not grant you any right in any trademark or logo of Licensor
60
+ or its affiliates (provided that you may use a trademark or logo of Licensor as
61
+ expressly required by this License).TO THE EXTENT PERMITTED BY APPLICABLE LAW,
62
+ THE LICENSED WORK IS PROVIDED ON AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL
63
+ WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION)
64
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
65
+ NON-INFRINGEMENT, AND TITLE. MariaDB hereby grants you permission to use this
66
+ License’s text to license your works, and to refer to it using the trademark
67
+ “Business Source License”, as long as you comply with the Covenants of Licensor
68
+ below.
69
+
70
+ ## Covenants of Licensor
71
+
72
+ In consideration of the right to use this License’s text and the “Business
73
+ Source License” name and trademark, Licensor covenants to MariaDB, and to all
74
+ other recipients of the licensed work to be provided by Licensor:
75
+
76
+ To specify as the Change License the GPL Version 2.0 or any later version, or a
77
+ license that is compatible with GPL Version 2.0 or a later version, where
78
+ “compatible” means that software provided under the Change License can be
79
+ included in a program with software provided under GPL Version 2.0 or a later
80
+ version. Licensor may specify additional Change Licenses without limitation. To
81
+ either: (a) specify an additional grant of rights to use that does not impose
82
+ any additional restriction on the right granted in this License, as the
83
+ Additional Use Grant; or (b) insert the text “None” to specify a Change Date.
84
+ Not to modify this License in any other way.
85
+
86
+ License text copyright © 2023 MariaDB plc, All Rights Reserved. “Business Source
87
+ License” is a trademark of MariaDB plc.