dxpipe-sdk 0.1.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.
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: dxpipe-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for DxPipe model editing and simulation workflows
5
+ Author: DxPipe Team
6
+ License-Expression: LicenseRef-Proprietary
7
+ Keywords: dxpipe,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
+ # dxpipe-sdk
26
+
27
+ dxpipe-sdk is a Python SDK for DxPipe model editing, simulation execution, streaming result retrieval, and runtime command control.
28
+
29
+ ## Features
30
+
31
+ - Token-based authentication against the DxPipe 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 DxPipe service endpoint
42
+ - A DxPipe 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 dxpipe-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 dxpipe_sdk import DxPipeClient
62
+
63
+ client = DxPipeClient(
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 dxpipe-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="dxpipe")
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
+ - `DxPipeClient`
94
+ - `Model`
95
+ - `Component`
96
+ - `Connection`
97
+ - `Job`
98
+ - `MetaService`
99
+ - `ResultStream`
100
+ - `LogMessage`
101
+ - `PlotMessage`
102
+ - `TableMessage`
103
+ - `InspectMessage`
104
+ - `ContainerMessage`
105
+ - `DxPipeError`
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\dxpipe_sdk-0.1.0-py3-none-any.whl
131
+ .smoke-venv\Scripts\python -c "from dxpipe_sdk import DxPipeClient, Model, Job, ResultStream; print('smoke ok')"
132
+ ```
133
+
134
+ ## License
135
+
136
+ This package is currently distributed as proprietary software.
@@ -0,0 +1,112 @@
1
+ # dxpipe-sdk
2
+
3
+ dxpipe-sdk is a Python SDK for DxPipe model editing, simulation execution, streaming result retrieval, and runtime command control.
4
+
5
+ ## Features
6
+
7
+ - Token-based authentication against the DxPipe 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 DxPipe service endpoint
18
+ - A DxPipe 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 dxpipe-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 dxpipe_sdk import DxPipeClient
38
+
39
+ client = DxPipeClient(
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 dxpipe-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="dxpipe")
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
+ - `DxPipeClient`
70
+ - `Model`
71
+ - `Component`
72
+ - `Connection`
73
+ - `Job`
74
+ - `MetaService`
75
+ - `ResultStream`
76
+ - `LogMessage`
77
+ - `PlotMessage`
78
+ - `TableMessage`
79
+ - `InspectMessage`
80
+ - `ContainerMessage`
81
+ - `DxPipeError`
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\dxpipe_sdk-0.1.0-py3-none-any.whl
107
+ .smoke-venv\Scripts\python -c "from dxpipe_sdk import DxPipeClient, Model, Job, ResultStream; print('smoke ok')"
108
+ ```
109
+
110
+ ## License
111
+
112
+ 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 = "dxpipe-sdk"
7
+ version = "0.1.0"
8
+ description = "Python SDK for DxPipe model editing and simulation workflows"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "LicenseRef-Proprietary"
12
+ authors = [
13
+ {name = "DxPipe Team"},
14
+ ]
15
+ keywords = ["dxpipe", "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 DxPipeClient
2
+ from .component import Component, PortRef
3
+ from .connection import Connection
4
+ from .exceptions import APIError, AuthError, DxPipeError, 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
+ "DxPipeClient",
24
+ "DxPipeError",
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,50 @@
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 _transform(expr: str) -> str:
24
+ transformed = expr.strip()
25
+ transformed = transformed.replace("&&", " and ")
26
+ transformed = transformed.replace("||", " or ")
27
+ transformed = re.sub(r"\btrue\b", "True", transformed, flags=re.IGNORECASE)
28
+ transformed = re.sub(r"\bfalse\b", "False", transformed, flags=re.IGNORECASE)
29
+ transformed = re.sub(r"\bnull\b", "None", transformed, flags=re.IGNORECASE)
30
+ transformed = _NOT_PATTERN.sub(" not ", transformed)
31
+ return transformed
32
+
33
+
34
+ def evaluate_condition(expr: str | None, scope: Mapping[str, Any] | None = None) -> bool:
35
+ if expr is None:
36
+ return False
37
+ text = expr.strip()
38
+ if not text:
39
+ return False
40
+ transformed = _transform(text)
41
+ locals_scope = {key: _to_namespace(value) for key, value in (scope or {}).items()}
42
+ try:
43
+ result = eval(
44
+ transformed,
45
+ {"__builtins__": {}, "equalText": _equal_text, "min": min, "max": max, "abs": abs, "len": len},
46
+ locals_scope,
47
+ )
48
+ except Exception:
49
+ return False
50
+ return bool(result)
@@ -0,0 +1,113 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ import requests
7
+
8
+ from .exceptions import AuthError
9
+
10
+
11
+ @dataclass(slots=True)
12
+ class TokenPair:
13
+ access_token: str
14
+ refresh_token: str | None = None
15
+
16
+
17
+ class TokenAuth:
18
+ def __init__(
19
+ self,
20
+ *,
21
+ base_url: str,
22
+ session: requests.Session,
23
+ access_token: str | None = None,
24
+ refresh_token: str | None = None,
25
+ username: str | None = None,
26
+ password: str | None = None,
27
+ timeout: float = 30.0,
28
+ ) -> None:
29
+ self._base_url = base_url.rstrip("/")
30
+ self._session = session
31
+ self._timeout = timeout
32
+ self._username = username
33
+ self._password = password
34
+ self._tokens = TokenPair(access_token or "", refresh_token)
35
+
36
+ @property
37
+ def access_token(self) -> str | None:
38
+ return self._tokens.access_token or None
39
+
40
+ @property
41
+ def refresh_token(self) -> str | None:
42
+ return self._tokens.refresh_token
43
+
44
+ @property
45
+ def authorization_header(self) -> dict[str, str]:
46
+ if not self.access_token:
47
+ return {}
48
+ return {"Authorization": f"Bearer {self.access_token}"}
49
+
50
+ @property
51
+ def can_refresh(self) -> bool:
52
+ return bool(self.refresh_token or (self._username and self._password))
53
+
54
+ def ensure_login(self) -> None:
55
+ if self.access_token:
56
+ return
57
+ if not (self._username and self._password):
58
+ raise AuthError("No access token or username/password was provided.")
59
+ self.login(self._username, self._password)
60
+
61
+ def login(self, username: str, password: str) -> TokenPair:
62
+ response = self._session.post(
63
+ f"{self._base_url}/api/token/",
64
+ json={"username": username, "password": password},
65
+ timeout=self._timeout,
66
+ )
67
+ payload = _parse_json(response)
68
+ if response.status_code >= 400:
69
+ raise AuthError(_extract_error_message(payload, response.text))
70
+
71
+ access = payload.get("access") or payload.get("access_token")
72
+ refresh = payload.get("refresh") or payload.get("refresh_token")
73
+ if not access:
74
+ raise AuthError("Token endpoint did not return an access token.")
75
+ self._username = username
76
+ self._password = password
77
+ self._tokens = TokenPair(access, refresh)
78
+ return self._tokens
79
+
80
+ def refresh(self) -> TokenPair:
81
+ if self.refresh_token:
82
+ response = self._session.post(
83
+ f"{self._base_url}/api/token/refresh/",
84
+ json={"refresh": self.refresh_token},
85
+ timeout=self._timeout,
86
+ )
87
+ payload = _parse_json(response)
88
+ if response.status_code < 400:
89
+ access = payload.get("access") or payload.get("access_token")
90
+ refresh = payload.get("refresh") or payload.get("refresh_token") or self.refresh_token
91
+ if access:
92
+ self._tokens = TokenPair(access, refresh)
93
+ return self._tokens
94
+
95
+ if self._username and self._password:
96
+ return self.login(self._username, self._password)
97
+ raise AuthError("Unable to refresh access token.")
98
+
99
+
100
+ def _parse_json(response: requests.Response) -> dict[str, Any]:
101
+ try:
102
+ payload = response.json()
103
+ except ValueError:
104
+ return {}
105
+ return payload if isinstance(payload, dict) else {}
106
+
107
+
108
+ def _extract_error_message(payload: dict[str, Any], fallback: str) -> str:
109
+ for key in ("detail", "message", "error", "msg"):
110
+ value = payload.get(key)
111
+ if isinstance(value, str) and value.strip():
112
+ return value
113
+ return fallback or "Authentication failed."
@@ -0,0 +1,81 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import requests
6
+
7
+ from .auth import TokenAuth
8
+ from .http import HttpClient
9
+ from .job import Job
10
+ from .meta import MetaService
11
+ from .model import Model
12
+
13
+
14
+ class DxPipeClient:
15
+ def __init__(
16
+ self,
17
+ *,
18
+ base_url: str,
19
+ token: str | None = None,
20
+ refresh_token: str | None = None,
21
+ username: str | None = None,
22
+ password: str | None = None,
23
+ timeout: float = 30.0,
24
+ session: requests.Session | None = None,
25
+ ) -> None:
26
+ self.base_url = base_url.rstrip("/")
27
+ self.timeout = timeout
28
+ self.session = session or requests.Session()
29
+ self.auth = TokenAuth(
30
+ base_url=self.base_url,
31
+ session=self.session,
32
+ access_token=token,
33
+ refresh_token=refresh_token,
34
+ username=username,
35
+ password=password,
36
+ timeout=timeout,
37
+ )
38
+ self.http = HttpClient(base_url=self.base_url, auth=self.auth, timeout=timeout, session=self.session)
39
+ self.meta = MetaService(self.http)
40
+
41
+ def __enter__(self) -> "DxPipeClient":
42
+ return self
43
+
44
+ def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
45
+ self.close()
46
+
47
+ def close(self) -> None:
48
+ self.session.close()
49
+
50
+ def login(self, username: str, password: str) -> None:
51
+ self.auth.login(username, password)
52
+
53
+ def list_models(self, **params: Any) -> list[Model]:
54
+ return Model.list(self, **params)
55
+
56
+ def get_model(self, model_id: str) -> Model:
57
+ return Model.get(self, model_id)
58
+
59
+ def create_model(
60
+ self,
61
+ *,
62
+ name: str,
63
+ workspace_type: str,
64
+ description: str = "",
65
+ publicity: str = "private",
66
+ global_params: dict[str, Any] | None = None,
67
+ ) -> Model:
68
+ return Model.create(
69
+ self,
70
+ name=name,
71
+ workspace_type=workspace_type,
72
+ description=description,
73
+ publicity=publicity,
74
+ global_params=global_params,
75
+ )
76
+
77
+ def list_jobs(self, **params: Any) -> list[Job]:
78
+ return Job.list(self, **params)
79
+
80
+ def get_job(self, job_id: str) -> Job:
81
+ return Job.get(self, job_id)