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.
- dxpipe_sdk-0.1.0/PKG-INFO +136 -0
- dxpipe_sdk-0.1.0/README.md +112 -0
- dxpipe_sdk-0.1.0/pyproject.toml +42 -0
- dxpipe_sdk-0.1.0/setup.cfg +4 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk/__init__.py +36 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk/_expression.py +50 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk/auth.py +113 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk/client.py +81 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk/component.py +215 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk/connection.py +42 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk/exceptions.py +28 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk/http.py +97 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk/job.py +207 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk/meta.py +76 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk/model.py +419 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk/result.py +325 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk/types.py +97 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk.egg-info/PKG-INFO +136 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk.egg-info/SOURCES.txt +23 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk.egg-info/dependency_links.txt +1 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk.egg-info/requires.txt +6 -0
- dxpipe_sdk-0.1.0/src/dxpipe_sdk.egg-info/top_level.txt +1 -0
- dxpipe_sdk-0.1.0/tests/test_model.py +160 -0
- dxpipe_sdk-0.1.0/tests/test_public_api.py +57 -0
- dxpipe_sdk-0.1.0/tests/test_result.py +102 -0
|
@@ -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,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)
|