nexusflow-sdk 0.3.0__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.
Files changed (27) hide show
  1. nexusflow_sdk-0.3.0/PKG-INFO +180 -0
  2. nexusflow_sdk-0.3.0/README.md +156 -0
  3. nexusflow_sdk-0.3.0/pyproject.toml +42 -0
  4. nexusflow_sdk-0.3.0/setup.cfg +4 -0
  5. nexusflow_sdk-0.3.0/src/nexusflow_sdk/__init__.py +36 -0
  6. nexusflow_sdk-0.3.0/src/nexusflow_sdk/_constraints.py +61 -0
  7. nexusflow_sdk-0.3.0/src/nexusflow_sdk/_expression.py +137 -0
  8. nexusflow_sdk-0.3.0/src/nexusflow_sdk/auth.py +113 -0
  9. nexusflow_sdk-0.3.0/src/nexusflow_sdk/client.py +81 -0
  10. nexusflow_sdk-0.3.0/src/nexusflow_sdk/component.py +261 -0
  11. nexusflow_sdk-0.3.0/src/nexusflow_sdk/connection.py +42 -0
  12. nexusflow_sdk-0.3.0/src/nexusflow_sdk/exceptions.py +28 -0
  13. nexusflow_sdk-0.3.0/src/nexusflow_sdk/http.py +97 -0
  14. nexusflow_sdk-0.3.0/src/nexusflow_sdk/job.py +306 -0
  15. nexusflow_sdk-0.3.0/src/nexusflow_sdk/meta.py +76 -0
  16. nexusflow_sdk-0.3.0/src/nexusflow_sdk/model.py +448 -0
  17. nexusflow_sdk-0.3.0/src/nexusflow_sdk/result.py +333 -0
  18. nexusflow_sdk-0.3.0/src/nexusflow_sdk/types.py +112 -0
  19. nexusflow_sdk-0.3.0/src/nexusflow_sdk.egg-info/PKG-INFO +180 -0
  20. nexusflow_sdk-0.3.0/src/nexusflow_sdk.egg-info/SOURCES.txt +25 -0
  21. nexusflow_sdk-0.3.0/src/nexusflow_sdk.egg-info/dependency_links.txt +1 -0
  22. nexusflow_sdk-0.3.0/src/nexusflow_sdk.egg-info/requires.txt +6 -0
  23. nexusflow_sdk-0.3.0/src/nexusflow_sdk.egg-info/top_level.txt +1 -0
  24. nexusflow_sdk-0.3.0/tests/test_job.py +174 -0
  25. nexusflow_sdk-0.3.0/tests/test_model.py +300 -0
  26. nexusflow_sdk-0.3.0/tests/test_public_api.py +57 -0
  27. nexusflow_sdk-0.3.0/tests/test_result.py +132 -0
@@ -0,0 +1,180 @@
1
+ Metadata-Version: 2.4
2
+ Name: nexusflow-sdk
3
+ Version: 0.3.0
4
+ Summary: Python SDK for NexusFlow model editing and simulation workflows
5
+ Author: NexusFlow Team
6
+ License-Expression: LicenseRef-Proprietary
7
+ Keywords: nexusflow,pipeline-simulation,oil-gas,sdk,digital-twin
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Scientific/Engineering
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: requests>=2.31.0
20
+ Requires-Dist: websocket-client>=1.8.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: build>=1.2.2; extra == "dev"
23
+ Requires-Dist: twine>=5.1.1; extra == "dev"
24
+
25
+ # nexusflow-sdk
26
+
27
+ nexusflow-sdk is a Python SDK for NexusFlow model editing, simulation execution, streaming result retrieval, and runtime command control.
28
+
29
+ ## Features
30
+
31
+ - Token-based authentication against the NexusFlow backend
32
+ - Create models for supported workspace types and open existing models
33
+ - Add, remove, connect, and configure components in the model graph
34
+ - Save model snapshots and start simulation jobs
35
+ - Poll or stream logs, plots, tables, inspect data, and container messages
36
+ - Send runtime commands to components during online or transient execution
37
+
38
+ ## Requirements
39
+
40
+ - Python 3.10 or later
41
+ - A reachable NexusFlow service endpoint
42
+ - A NexusFlow account that can obtain access tokens
43
+
44
+ ## Install
45
+
46
+ Install from a built wheel or from PyPI after publication:
47
+
48
+ ```bash
49
+ pip install nexusflow-sdk
50
+ ```
51
+
52
+ For local development in this repository:
53
+
54
+ ```bash
55
+ pip install -e .
56
+ ```
57
+
58
+ ## Quick Start
59
+
60
+ ```python
61
+ from nexusflow_sdk import NexusFlowClient
62
+
63
+ client = NexusFlowClient(
64
+ base_url="http://localhost:8000",
65
+ username="admin",
66
+ password="your-password",
67
+ )
68
+
69
+ model = client.create_model(
70
+ name="sdk-demo-model",
71
+ workspace_type="lps_online",
72
+ description="Created by nexusflow-sdk",
73
+ )
74
+
75
+ source = model.add_component("Source", position=(0, 0))
76
+ pipe = model.add_component("Pipe", position=(240, 0))
77
+ load = model.add_component("Load", position=(480, 0))
78
+
79
+ model.connect(source, "0", pipe, "0")
80
+ model.connect(pipe, "1", load, "0")
81
+
82
+ model.save()
83
+ job = model.run(timeout=120, image="nexusflow")
84
+
85
+ for message in job.stream_results(timeout=2):
86
+ print(message.get("type"), message.get("key"))
87
+ ```
88
+
89
+ ## Public API
90
+
91
+ The package currently exports these main entry points:
92
+
93
+ - `NexusFlowClient`
94
+ - `Model`
95
+ - `Component`
96
+ - `Connection`
97
+ - `Job`
98
+ - `MetaService`
99
+ - `ResultStream`
100
+ - `LogMessage`
101
+ - `PlotMessage`
102
+ - `TableMessage`
103
+ - `InspectMessage`
104
+ - `ContainerMessage`
105
+ - `NexusFlowError`
106
+ - `AuthError`
107
+ - `APIError`
108
+ - `NotFoundError`
109
+ - `ValidationError`
110
+
111
+ ## Examples
112
+
113
+ - `examples/quickstart.py`: create, save, run, and fetch results
114
+ - `examples/transient_streaming.py`: stream transient online results and send commands during execution
115
+ - `examples/run_existing_model.py`: open an existing model by ID and run it
116
+
117
+ ## Release Validation
118
+
119
+ Build and metadata check:
120
+
121
+ ```bash
122
+ python -m build
123
+ python -m twine check dist/*
124
+ ```
125
+
126
+ Minimal install smoke test with the built wheel:
127
+
128
+ ```bash
129
+ python -m venv .smoke-venv
130
+ .smoke-venv\Scripts\python -m pip install dist\nexusflow_sdk-0.1.2-py3-none-any.whl
131
+ .smoke-venv\Scripts\python -c "from nexusflow_sdk import NexusFlowClient, Model, Job, ResultStream; print('smoke ok')"
132
+ ```
133
+
134
+ ## Release Workflow
135
+
136
+ If you want to publish a new version yourself, the repository now includes a reusable PowerShell script:
137
+
138
+ ```powershell
139
+ .\scripts\release.ps1 -Repository pypi -Token "pypi-..."
140
+ ```
141
+
142
+ Recommended process:
143
+
144
+ 1. Update `version` in `pyproject.toml`.
145
+ 2. Run the release script from the `nexusflow-sdk` directory.
146
+ 3. Wait for PyPI index propagation if the published install smoke test fails on the first try.
147
+
148
+ The script performs these steps automatically:
149
+
150
+ - clean `dist/`
151
+ - `python -m build`
152
+ - `python -m twine check dist/*`
153
+ - `python -m unittest tests.test_public_api`
154
+ - install the built wheel into a temporary virtual environment for a local smoke test
155
+ - upload to PyPI or TestPyPI
156
+ - create a fresh temporary virtual environment and install the published package for a post-publish smoke test
157
+
158
+ Examples:
159
+
160
+ ```powershell
161
+ # Full publish to PyPI
162
+ .\scripts\release.ps1 -Repository pypi -Token "pypi-..."
163
+
164
+ # Full publish to TestPyPI
165
+ .\scripts\release.ps1 -Repository testpypi -Token "pypi-..."
166
+
167
+ # Local validation only, without upload
168
+ .\scripts\release.ps1 -SkipUpload -SkipSmokeTest
169
+ ```
170
+
171
+ You can also avoid passing tokens on the command line by using environment variables:
172
+
173
+ ```powershell
174
+ $env:PYPI_TOKEN = "pypi-..."
175
+ .\scripts\release.ps1 -Repository pypi
176
+ ```
177
+
178
+ ## License
179
+
180
+ This package is currently distributed as proprietary software.
@@ -0,0 +1,156 @@
1
+ # nexusflow-sdk
2
+
3
+ nexusflow-sdk is a Python SDK for NexusFlow model editing, simulation execution, streaming result retrieval, and runtime command control.
4
+
5
+ ## Features
6
+
7
+ - Token-based authentication against the NexusFlow backend
8
+ - Create models for supported workspace types and open existing models
9
+ - Add, remove, connect, and configure components in the model graph
10
+ - Save model snapshots and start simulation jobs
11
+ - Poll or stream logs, plots, tables, inspect data, and container messages
12
+ - Send runtime commands to components during online or transient execution
13
+
14
+ ## Requirements
15
+
16
+ - Python 3.10 or later
17
+ - A reachable NexusFlow service endpoint
18
+ - A NexusFlow account that can obtain access tokens
19
+
20
+ ## Install
21
+
22
+ Install from a built wheel or from PyPI after publication:
23
+
24
+ ```bash
25
+ pip install nexusflow-sdk
26
+ ```
27
+
28
+ For local development in this repository:
29
+
30
+ ```bash
31
+ pip install -e .
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ```python
37
+ from nexusflow_sdk import NexusFlowClient
38
+
39
+ client = NexusFlowClient(
40
+ base_url="http://localhost:8000",
41
+ username="admin",
42
+ password="your-password",
43
+ )
44
+
45
+ model = client.create_model(
46
+ name="sdk-demo-model",
47
+ workspace_type="lps_online",
48
+ description="Created by nexusflow-sdk",
49
+ )
50
+
51
+ source = model.add_component("Source", position=(0, 0))
52
+ pipe = model.add_component("Pipe", position=(240, 0))
53
+ load = model.add_component("Load", position=(480, 0))
54
+
55
+ model.connect(source, "0", pipe, "0")
56
+ model.connect(pipe, "1", load, "0")
57
+
58
+ model.save()
59
+ job = model.run(timeout=120, image="nexusflow")
60
+
61
+ for message in job.stream_results(timeout=2):
62
+ print(message.get("type"), message.get("key"))
63
+ ```
64
+
65
+ ## Public API
66
+
67
+ The package currently exports these main entry points:
68
+
69
+ - `NexusFlowClient`
70
+ - `Model`
71
+ - `Component`
72
+ - `Connection`
73
+ - `Job`
74
+ - `MetaService`
75
+ - `ResultStream`
76
+ - `LogMessage`
77
+ - `PlotMessage`
78
+ - `TableMessage`
79
+ - `InspectMessage`
80
+ - `ContainerMessage`
81
+ - `NexusFlowError`
82
+ - `AuthError`
83
+ - `APIError`
84
+ - `NotFoundError`
85
+ - `ValidationError`
86
+
87
+ ## Examples
88
+
89
+ - `examples/quickstart.py`: create, save, run, and fetch results
90
+ - `examples/transient_streaming.py`: stream transient online results and send commands during execution
91
+ - `examples/run_existing_model.py`: open an existing model by ID and run it
92
+
93
+ ## Release Validation
94
+
95
+ Build and metadata check:
96
+
97
+ ```bash
98
+ python -m build
99
+ python -m twine check dist/*
100
+ ```
101
+
102
+ Minimal install smoke test with the built wheel:
103
+
104
+ ```bash
105
+ python -m venv .smoke-venv
106
+ .smoke-venv\Scripts\python -m pip install dist\nexusflow_sdk-0.1.2-py3-none-any.whl
107
+ .smoke-venv\Scripts\python -c "from nexusflow_sdk import NexusFlowClient, Model, Job, ResultStream; print('smoke ok')"
108
+ ```
109
+
110
+ ## Release Workflow
111
+
112
+ If you want to publish a new version yourself, the repository now includes a reusable PowerShell script:
113
+
114
+ ```powershell
115
+ .\scripts\release.ps1 -Repository pypi -Token "pypi-..."
116
+ ```
117
+
118
+ Recommended process:
119
+
120
+ 1. Update `version` in `pyproject.toml`.
121
+ 2. Run the release script from the `nexusflow-sdk` directory.
122
+ 3. Wait for PyPI index propagation if the published install smoke test fails on the first try.
123
+
124
+ The script performs these steps automatically:
125
+
126
+ - clean `dist/`
127
+ - `python -m build`
128
+ - `python -m twine check dist/*`
129
+ - `python -m unittest tests.test_public_api`
130
+ - install the built wheel into a temporary virtual environment for a local smoke test
131
+ - upload to PyPI or TestPyPI
132
+ - create a fresh temporary virtual environment and install the published package for a post-publish smoke test
133
+
134
+ Examples:
135
+
136
+ ```powershell
137
+ # Full publish to PyPI
138
+ .\scripts\release.ps1 -Repository pypi -Token "pypi-..."
139
+
140
+ # Full publish to TestPyPI
141
+ .\scripts\release.ps1 -Repository testpypi -Token "pypi-..."
142
+
143
+ # Local validation only, without upload
144
+ .\scripts\release.ps1 -SkipUpload -SkipSmokeTest
145
+ ```
146
+
147
+ You can also avoid passing tokens on the command line by using environment variables:
148
+
149
+ ```powershell
150
+ $env:PYPI_TOKEN = "pypi-..."
151
+ .\scripts\release.ps1 -Repository pypi
152
+ ```
153
+
154
+ ## License
155
+
156
+ This package is currently distributed as proprietary software.
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "nexusflow-sdk"
7
+ version = "0.3.0"
8
+ description = "Python SDK for NexusFlow model editing and simulation workflows"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "LicenseRef-Proprietary"
12
+ authors = [
13
+ {name = "NexusFlow Team"},
14
+ ]
15
+ keywords = ["nexusflow", "pipeline-simulation", "oil-gas", "sdk", "digital-twin"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Topic :: Scientific/Engineering",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ ]
27
+ dependencies = [
28
+ "requests>=2.31.0",
29
+ "websocket-client>=1.8.0",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ dev = [
34
+ "build>=1.2.2",
35
+ "twine>=5.1.1",
36
+ ]
37
+
38
+ [tool.setuptools]
39
+ package-dir = {"" = "src"}
40
+
41
+ [tool.setuptools.packages.find]
42
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,36 @@
1
+ from .client import NexusFlowClient
2
+ from .component import Component, PortRef
3
+ from .connection import Connection
4
+ from .exceptions import APIError, AuthError, NexusFlowError, NotFoundError, ValidationError
5
+ from .job import Job
6
+ from .meta import MetaService
7
+ from .model import Model
8
+ from .result import (
9
+ ContainerMessage,
10
+ InspectMessage,
11
+ LogMessage,
12
+ PlotMessage,
13
+ ResultStream,
14
+ TableMessage,
15
+ )
16
+
17
+ __all__ = [
18
+ "APIError",
19
+ "AuthError",
20
+ "Component",
21
+ "Connection",
22
+ "ContainerMessage",
23
+ "NexusFlowClient",
24
+ "NexusFlowError",
25
+ "InspectMessage",
26
+ "Job",
27
+ "LogMessage",
28
+ "MetaService",
29
+ "Model",
30
+ "NotFoundError",
31
+ "PlotMessage",
32
+ "PortRef",
33
+ "ResultStream",
34
+ "TableMessage",
35
+ "ValidationError",
36
+ ]
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Mapping
4
+
5
+ from ._expression import evaluate_condition, evaluate_expression
6
+
7
+
8
+ def _constraint_scope(
9
+ *,
10
+ value: Any,
11
+ params: Mapping[str, Any] | None,
12
+ global_params: Mapping[str, Any] | None,
13
+ context: Mapping[str, Any] | None,
14
+ ) -> dict[str, Any]:
15
+ return {
16
+ "value": value,
17
+ "params": dict(params or {}),
18
+ "global": dict(global_params or params or {}),
19
+ "context": dict(context or {}),
20
+ }
21
+
22
+
23
+ def validate_constraints(
24
+ arg_def: Mapping[str, Any],
25
+ value: Any,
26
+ *,
27
+ params: Mapping[str, Any] | None,
28
+ global_params: Mapping[str, Any] | None = None,
29
+ context: Mapping[str, Any] | None = None,
30
+ ) -> list[str]:
31
+ constraints = arg_def.get("constraints", [])
32
+ if not isinstance(constraints, list):
33
+ return []
34
+
35
+ issues: list[str] = []
36
+ scope = _constraint_scope(
37
+ value=value,
38
+ params=params,
39
+ global_params=global_params,
40
+ context={
41
+ **dict(context or {}),
42
+ "key": arg_def.get("key", ""),
43
+ "type": arg_def.get("type", ""),
44
+ },
45
+ )
46
+ for constraint in constraints:
47
+ if not isinstance(constraint, Mapping):
48
+ continue
49
+ expr = str(constraint.get("expr", "")).strip()
50
+ if not expr:
51
+ continue
52
+ message = str(constraint.get("message") or "constraint failed")
53
+ when = str(constraint.get("when", "")).strip()
54
+ try:
55
+ if when and not evaluate_condition(when, scope):
56
+ continue
57
+ if not bool(evaluate_expression(expr, scope)):
58
+ issues.append(message)
59
+ except Exception as exc:
60
+ issues.append(f"constraint rule failed: {message} ({exc})")
61
+ return issues
@@ -0,0 +1,137 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from types import SimpleNamespace
5
+ from typing import Any, Mapping
6
+
7
+
8
+ _NOT_PATTERN = re.compile(r"(?<![=!<>])!(?!=)")
9
+
10
+
11
+ def _to_namespace(value: Any) -> Any:
12
+ if isinstance(value, Mapping):
13
+ return SimpleNamespace(**{key: _to_namespace(item) for key, item in value.items()})
14
+ if isinstance(value, list):
15
+ return [_to_namespace(item) for item in value]
16
+ return value
17
+
18
+
19
+ def _equal_text(left: Any, right: Any) -> bool:
20
+ return str(left) == str(right)
21
+
22
+
23
+ def _as_list(value: Any) -> list[Any]:
24
+ if value is None:
25
+ return []
26
+ if isinstance(value, list):
27
+ return value
28
+ if isinstance(value, tuple):
29
+ return list(value)
30
+ return [value]
31
+
32
+
33
+ def _stable_marker(value: Any) -> str:
34
+ if isinstance(value, Mapping):
35
+ return "dict:" + repr(sorted((str(key), _stable_marker(item)) for key, item in value.items()))
36
+ if isinstance(value, (list, tuple)):
37
+ return "list:" + repr([_stable_marker(item) for item in value])
38
+ return f"{type(value).__name__}:{value!r}"
39
+
40
+
41
+ def _column(rows: Any, index: Any) -> list[Any]:
42
+ col_index = int(index)
43
+ result: list[Any] = []
44
+ for row in _as_list(rows):
45
+ if isinstance(row, Mapping):
46
+ result.append(row.get(col_index, row.get(str(col_index))))
47
+ continue
48
+ if isinstance(row, (list, tuple)) and -len(row) <= col_index < len(row):
49
+ result.append(row[col_index])
50
+ else:
51
+ result.append(None)
52
+ return result
53
+
54
+
55
+ def _unique(items: Any) -> bool:
56
+ seen: set[str] = set()
57
+ for item in _as_list(items):
58
+ marker = _stable_marker(item)
59
+ if marker in seen:
60
+ return False
61
+ seen.add(marker)
62
+ return True
63
+
64
+
65
+ def _empty(value: Any) -> bool:
66
+ if value is None:
67
+ return True
68
+ if isinstance(value, (str, list, tuple, dict, set)):
69
+ return len(value) == 0
70
+ return False
71
+
72
+
73
+ def _not_empty(value: Any) -> bool:
74
+ return not _empty(value)
75
+
76
+
77
+ def _matches(value: Any, pattern: Any) -> bool:
78
+ return re.search(str(pattern), "" if value is None else str(value)) is not None
79
+
80
+
81
+ def _number(value: Any) -> float:
82
+ return float(value)
83
+
84
+
85
+ def _string(value: Any) -> str:
86
+ return "" if value is None else str(value)
87
+
88
+
89
+ def _transform(expr: str) -> str:
90
+ transformed = expr.strip()
91
+ transformed = transformed.replace("&&", " and ")
92
+ transformed = transformed.replace("||", " or ")
93
+ transformed = re.sub(r"\btrue\b", "True", transformed, flags=re.IGNORECASE)
94
+ transformed = re.sub(r"\bfalse\b", "False", transformed, flags=re.IGNORECASE)
95
+ transformed = re.sub(r"\bnull\b", "None", transformed, flags=re.IGNORECASE)
96
+ transformed = re.sub(r"\bglobal\b", "global_", transformed)
97
+ transformed = _NOT_PATTERN.sub(" not ", transformed)
98
+ return transformed
99
+
100
+
101
+ def evaluate_expression(expr: str | None, scope: Mapping[str, Any] | None = None) -> Any:
102
+ if expr is None:
103
+ raise ValueError("expression is empty")
104
+ text = expr.strip()
105
+ if not text:
106
+ raise ValueError("expression is empty")
107
+ transformed = _transform(text)
108
+ locals_scope = {key: _to_namespace(value) for key, value in (scope or {}).items()}
109
+ if "global" in locals_scope and "global_" not in locals_scope:
110
+ locals_scope["global_"] = locals_scope["global"]
111
+ return eval(
112
+ transformed,
113
+ {
114
+ "__builtins__": {},
115
+ "equalText": _equal_text,
116
+ "min": min,
117
+ "max": max,
118
+ "abs": abs,
119
+ "len": len,
120
+ "empty": _empty,
121
+ "notEmpty": _not_empty,
122
+ "column": _column,
123
+ "unique": _unique,
124
+ "matches": _matches,
125
+ "number": _number,
126
+ "string": _string,
127
+ },
128
+ locals_scope,
129
+ )
130
+
131
+
132
+ def evaluate_condition(expr: str | None, scope: Mapping[str, Any] | None = None) -> bool:
133
+ try:
134
+ result = evaluate_expression(expr, scope)
135
+ except Exception:
136
+ return False
137
+ return bool(result)