splime 0.1.2__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.
- spl/__init__.py +14 -0
- spl/client.py +1364 -0
- spl/core/__init__.py +23 -0
- spl/core/common.py +350 -0
- spl/core/entities/__init__.py +0 -0
- spl/core/entities/adapter.py +210 -0
- spl/core/entities/artifact.py +141 -0
- spl/core/entities/control.py +45 -0
- spl/core/entities/distribution.py +65 -0
- spl/core/entities/function.py +254 -0
- spl/core/entities/local_function.py +286 -0
- spl/core/entities/misc.py +14 -0
- spl/core/entities/module.py +88 -0
- spl/core/entities/node.py +286 -0
- spl/core/entities/node_function.py +79 -0
- spl/core/entities/node_remote.py +295 -0
- spl/core/entities/pipeline.py +436 -0
- spl/core/entities/scalar.py +55 -0
- spl/core/ir/__init__.py +0 -0
- spl/core/ir/common.py +34 -0
- spl/core/ir/parse.py +79 -0
- spl/core/ir/unparse.py +29 -0
- spl/core/ir/utils.py +163 -0
- spl/daemon/__init__.py +23 -0
- spl/daemon/__main__.py +11 -0
- spl/daemon/cli.py +582 -0
- spl/daemon/client.py +43 -0
- spl/daemon/docker_environment.py +329 -0
- spl/daemon/docker_pool.py +516 -0
- spl/daemon/environment.py +228 -0
- spl/daemon/environment_base.py +479 -0
- spl/daemon/heartbeat_service.py +119 -0
- spl/daemon/metadata.py +427 -0
- spl/daemon/remote_client.py +457 -0
- spl/daemon/repositories/__init__.py +17 -0
- spl/daemon/repositories/env.py +323 -0
- spl/daemon/repositories/library.py +181 -0
- spl/daemon/repositories/object.py +997 -0
- spl/daemon/repositories/run.py +279 -0
- spl/daemon/repositories/server_connection.py +657 -0
- spl/daemon/repositories/sync_event.py +129 -0
- spl/daemon/routes/__init__.py +1 -0
- spl/daemon/routes/_helpers.py +147 -0
- spl/daemon/routes/artifacts.py +77 -0
- spl/daemon/routes/diagnostics.py +114 -0
- spl/daemon/routes/envs.py +82 -0
- spl/daemon/routes/libraries.py +129 -0
- spl/daemon/routes/objects.py +174 -0
- spl/daemon/routes/remote.py +56 -0
- spl/daemon/routes/runs.py +96 -0
- spl/daemon/routes/server_connections.py +86 -0
- spl/daemon/runtime_backend.py +368 -0
- spl/daemon/runtime_config.py +133 -0
- spl/daemon/runtime_dependencies.py +459 -0
- spl/daemon/secret_store.py +187 -0
- spl/daemon/server.py +2224 -0
- spl/daemon/server_connection.py +267 -0
- spl/daemon/services/__init__.py +1 -0
- spl/daemon/services/sync.py +76 -0
- spl/daemon/signature.py +376 -0
- spl/daemon/storage_base.py +542 -0
- spl/daemon/store.py +436 -0
- spl/daemon/worker.py +526 -0
- spl/daemon_client.py +945 -0
- spl/pipeline_widget.py +1452 -0
- spl/py.typed +0 -0
- spl/server_client.py +787 -0
- splime-0.1.2.dist-info/METADATA +189 -0
- splime-0.1.2.dist-info/RECORD +74 -0
- splime-0.1.2.dist-info/WHEEL +5 -0
- splime-0.1.2.dist-info/entry_points.txt +2 -0
- splime-0.1.2.dist-info/licenses/LICENSE +201 -0
- splime-0.1.2.dist-info/licenses/NOTICE +8 -0
- splime-0.1.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from types import FunctionType
|
|
5
|
+
from typing import Generator
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
from spl.core.entities.function import get_function_metadata
|
|
11
|
+
from spl.core.entities.node import Node
|
|
12
|
+
from spl.core.ir.common import DBase
|
|
13
|
+
from spl.core.ir.parse import _branch, ir_parse
|
|
14
|
+
from spl.core.ir.unparse import ir_unparse
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen = True)
|
|
18
|
+
class NodeFunction(Node):
|
|
19
|
+
func: FunctionType
|
|
20
|
+
|
|
21
|
+
def __init__(self, func, uuid: UUID | None = None):
|
|
22
|
+
md = get_function_metadata(func)
|
|
23
|
+
super().__init__(
|
|
24
|
+
inputs = md.inputs,
|
|
25
|
+
outputs = md.outputs,
|
|
26
|
+
uuid = uuid)
|
|
27
|
+
object.__setattr__(self, 'func', func)
|
|
28
|
+
|
|
29
|
+
def __repr__(self):
|
|
30
|
+
return '<{}>'.format(self.func.__name__)
|
|
31
|
+
|
|
32
|
+
def __hash__(self):
|
|
33
|
+
return hash(self.uuid)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen = True)
|
|
37
|
+
class DNodeFunction(DBase):
|
|
38
|
+
uuid: str
|
|
39
|
+
func: str
|
|
40
|
+
|
|
41
|
+
yaml.add_representer(
|
|
42
|
+
DNodeFunction,
|
|
43
|
+
lambda dumper, data: dumper.represent_mapping('!DNodeFunction', data.__dict__))
|
|
44
|
+
|
|
45
|
+
yaml.add_constructor(
|
|
46
|
+
'!DNodeFunction',
|
|
47
|
+
lambda loader, node: DNodeFunction(**loader.construct_mapping(node)))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@ir_parse.register(
|
|
51
|
+
lambda x: isinstance(x, NodeFunction))
|
|
52
|
+
def _ir_parse__node_function(
|
|
53
|
+
x: NodeFunction,
|
|
54
|
+
name: str | None = None):
|
|
55
|
+
|
|
56
|
+
return _branch(
|
|
57
|
+
x,
|
|
58
|
+
lambda: DNodeFunction(
|
|
59
|
+
uuid = str(x.uuid),
|
|
60
|
+
func = get_function_metadata(x.func).name),
|
|
61
|
+
lambda frame_offset: [ir_parse(x.func, name = name)])
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@ir_unparse.register(
|
|
65
|
+
lambda x: isinstance(x, DNodeFunction))
|
|
66
|
+
def _ir_unparse__node_function(x: DNodeFunction, source: Path) -> Generator[ast.stmt]:
|
|
67
|
+
yield ast.Assign(
|
|
68
|
+
targets = [ast.Name(id = '_node', ctx = ast.Store())],
|
|
69
|
+
value = ast.Call(
|
|
70
|
+
func = ast.Name(id = 'NodeFunction', ctx = ast.Load()),
|
|
71
|
+
keywords = [
|
|
72
|
+
ast.keyword(
|
|
73
|
+
arg = 'uuid',
|
|
74
|
+
value = ast.Call(
|
|
75
|
+
func = ast.Name(id = 'UUID', ctx = ast.Load()),
|
|
76
|
+
args = [ast.Constant(value = x.uuid)])),
|
|
77
|
+
ast.keyword(
|
|
78
|
+
arg = 'func',
|
|
79
|
+
value = ast.Name(id = x.func, ctx = ast.Load()))]))
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Generator
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
from spl.core.entities.node import InputPort, Node, OutputPort
|
|
10
|
+
from spl.core.ir.common import DBase
|
|
11
|
+
from spl.core.ir.parse import _branch, ir_parse
|
|
12
|
+
from spl.core.ir.unparse import ir_unparse
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen = True)
|
|
16
|
+
class NodeRemote(Node):
|
|
17
|
+
url: str
|
|
18
|
+
name: str
|
|
19
|
+
version: str
|
|
20
|
+
owner_id: str | None = None
|
|
21
|
+
library: str | None = None
|
|
22
|
+
target_machine: str | None = None
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
url = None,
|
|
27
|
+
name = None,
|
|
28
|
+
version = 'latest',
|
|
29
|
+
inputs = None,
|
|
30
|
+
outputs = None,
|
|
31
|
+
uuid: UUID | None = None,
|
|
32
|
+
*,
|
|
33
|
+
pipeline = None,
|
|
34
|
+
function = None,
|
|
35
|
+
owner = None,
|
|
36
|
+
owner_id = None,
|
|
37
|
+
library = None,
|
|
38
|
+
target_machine = None):
|
|
39
|
+
|
|
40
|
+
if pipeline is not None:
|
|
41
|
+
if name is not None:
|
|
42
|
+
raise TypeError('pass either name or pipeline/function, not both')
|
|
43
|
+
name = _remote_name(pipeline, function)
|
|
44
|
+
elif function is not None:
|
|
45
|
+
if name is None:
|
|
46
|
+
raise TypeError('function requires name or pipeline')
|
|
47
|
+
name = _remote_name(name, function)
|
|
48
|
+
if name is None:
|
|
49
|
+
if url is None:
|
|
50
|
+
raise TypeError('NodeRemote requires object name')
|
|
51
|
+
name = url
|
|
52
|
+
url = None
|
|
53
|
+
url = '' if url is None else str(url)
|
|
54
|
+
name = str(name)
|
|
55
|
+
version = 'latest' if version is None else str(version)
|
|
56
|
+
owner_id = _normalize_owner(owner, owner_id)
|
|
57
|
+
library = None if library is None else str(library)
|
|
58
|
+
target_machine = None if target_machine is None else str(target_machine)
|
|
59
|
+
|
|
60
|
+
if inputs is None or outputs is None:
|
|
61
|
+
resolved_inputs, resolved_outputs = _resolve_remote_ports(
|
|
62
|
+
url = url,
|
|
63
|
+
name = name,
|
|
64
|
+
version = version,
|
|
65
|
+
owner_id = owner_id,
|
|
66
|
+
library = library,
|
|
67
|
+
target_machine = target_machine)
|
|
68
|
+
if inputs is None:
|
|
69
|
+
inputs = resolved_inputs
|
|
70
|
+
if outputs is None:
|
|
71
|
+
outputs = resolved_outputs
|
|
72
|
+
|
|
73
|
+
super().__init__(
|
|
74
|
+
inputs = inputs,
|
|
75
|
+
outputs = outputs,
|
|
76
|
+
uuid = uuid)
|
|
77
|
+
object.__setattr__(self, 'url', url)
|
|
78
|
+
object.__setattr__(self, 'name', name)
|
|
79
|
+
object.__setattr__(self, 'version', version)
|
|
80
|
+
object.__setattr__(self, 'owner_id', owner_id)
|
|
81
|
+
object.__setattr__(self, 'library', library)
|
|
82
|
+
object.__setattr__(self, 'target_machine', target_machine)
|
|
83
|
+
|
|
84
|
+
def __repr__(self):
|
|
85
|
+
return '<{}/{}:{}>'.format(self.url, self.name, self.version)
|
|
86
|
+
|
|
87
|
+
def __hash__(self):
|
|
88
|
+
return hash(self.uuid)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _remote_name(object_name, function = None) -> str:
|
|
92
|
+
object_name = str(object_name)
|
|
93
|
+
if function is None:
|
|
94
|
+
return object_name
|
|
95
|
+
function = str(function)
|
|
96
|
+
if '::' in object_name:
|
|
97
|
+
parent, current_function = object_name.split('::', 1)
|
|
98
|
+
if not parent or not current_function:
|
|
99
|
+
raise ValueError('remote function reference must look like object::function')
|
|
100
|
+
if current_function != function:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
'function was provided twice with different values: '
|
|
103
|
+
f'{current_function!r} and {function!r}')
|
|
104
|
+
return object_name
|
|
105
|
+
return f'{object_name}::{function}'
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _normalize_owner(owner, owner_id):
|
|
109
|
+
if owner is not None and owner_id is not None and str(owner) != str(owner_id):
|
|
110
|
+
raise ValueError(
|
|
111
|
+
'owner and owner_id were both provided with different values: '
|
|
112
|
+
f'{owner!r} and {owner_id!r}')
|
|
113
|
+
value = owner_id if owner_id is not None else owner
|
|
114
|
+
return None if value is None else str(value)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _remote_ref(
|
|
118
|
+
*,
|
|
119
|
+
url: str,
|
|
120
|
+
name: str,
|
|
121
|
+
version: str,
|
|
122
|
+
owner_id: str | None = None,
|
|
123
|
+
library: str | None = None,
|
|
124
|
+
target_machine: str | None = None) -> dict[str, Any]:
|
|
125
|
+
ref: dict[str, Any] = {
|
|
126
|
+
'url': url,
|
|
127
|
+
'name': name,
|
|
128
|
+
'version': version}
|
|
129
|
+
if owner_id is not None:
|
|
130
|
+
ref['owner_id'] = owner_id
|
|
131
|
+
if library is not None:
|
|
132
|
+
ref['library'] = library
|
|
133
|
+
if target_machine is not None:
|
|
134
|
+
ref['target_machine'] = target_machine
|
|
135
|
+
return ref
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _resolve_remote_ports(
|
|
139
|
+
url: str,
|
|
140
|
+
name: str,
|
|
141
|
+
version: str,
|
|
142
|
+
*,
|
|
143
|
+
owner_id: str | None = None,
|
|
144
|
+
library: str | None = None,
|
|
145
|
+
target_machine: str | None = None):
|
|
146
|
+
"""Resolve a remote node signature through the local daemon."""
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
from spl.daemon_client import Client
|
|
150
|
+
|
|
151
|
+
payload = Client().resolve_remote_signature(_remote_ref(
|
|
152
|
+
url = url,
|
|
153
|
+
name = name,
|
|
154
|
+
version = version,
|
|
155
|
+
owner_id = owner_id,
|
|
156
|
+
library = library,
|
|
157
|
+
target_machine = target_machine))
|
|
158
|
+
except Exception as exc:
|
|
159
|
+
raise RuntimeError(
|
|
160
|
+
'NodeRemote inputs/outputs were omitted, but the local SPL daemon '
|
|
161
|
+
'could not resolve the remote signature. Connect the daemon to '
|
|
162
|
+
'SPLime or pass url, inputs, and outputs explicitly. '
|
|
163
|
+
f'Remote: {name}:{version}; error: {exc}') from exc
|
|
164
|
+
|
|
165
|
+
signature = payload.get('signature') if isinstance(payload, dict) else payload
|
|
166
|
+
if not isinstance(signature, dict):
|
|
167
|
+
raise RuntimeError(
|
|
168
|
+
'NodeRemote signature resolver returned an invalid response for '
|
|
169
|
+
f'{name}:{version}')
|
|
170
|
+
return (
|
|
171
|
+
[_signature_input_to_port(item) for item in signature.get('inputs') or []],
|
|
172
|
+
_signature_outputs_to_ports(signature.get('outputs') or []))
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _signature_input_to_port(item: dict[str, Any]) -> InputPort:
|
|
176
|
+
return InputPort(
|
|
177
|
+
name = str(item.get('name') or 'default'),
|
|
178
|
+
typ_ = item.get('type'),
|
|
179
|
+
default = item.get('default'))
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _signature_outputs_to_ports(outputs: list[dict[str, Any]]) -> list[OutputPort]:
|
|
183
|
+
ports = []
|
|
184
|
+
seen = set()
|
|
185
|
+
for item in outputs:
|
|
186
|
+
raw_ports = item.get('ports')
|
|
187
|
+
candidates = raw_ports or [{
|
|
188
|
+
'name': item.get('name') or 'default',
|
|
189
|
+
'type': item.get('type')}]
|
|
190
|
+
for port in candidates:
|
|
191
|
+
name = str(port.get('name') or 'default')
|
|
192
|
+
if name in seen:
|
|
193
|
+
continue
|
|
194
|
+
seen.add(name)
|
|
195
|
+
ports.append(OutputPort(
|
|
196
|
+
name = name,
|
|
197
|
+
typ_ = port.get('type')))
|
|
198
|
+
return ports or [OutputPort(name = 'default', typ_ = None)]
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@dataclass(frozen = True)
|
|
202
|
+
class DNodeRemote(DBase):
|
|
203
|
+
uuid: str
|
|
204
|
+
url: str
|
|
205
|
+
name: str
|
|
206
|
+
version: str
|
|
207
|
+
owner_id: str | None = None
|
|
208
|
+
library: str | None = None
|
|
209
|
+
target_machine: str | None = None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _dnode_remote_mapping(data: DNodeRemote):
|
|
213
|
+
payload = {
|
|
214
|
+
'uuid': data.uuid,
|
|
215
|
+
'url': data.url,
|
|
216
|
+
'name': data.name,
|
|
217
|
+
'version': data.version}
|
|
218
|
+
if data.owner_id is not None:
|
|
219
|
+
payload['owner_id'] = data.owner_id
|
|
220
|
+
if data.library is not None:
|
|
221
|
+
payload['library'] = data.library
|
|
222
|
+
if data.target_machine is not None:
|
|
223
|
+
payload['target_machine'] = data.target_machine
|
|
224
|
+
return payload
|
|
225
|
+
|
|
226
|
+
yaml.add_representer(
|
|
227
|
+
DNodeRemote,
|
|
228
|
+
lambda dumper, data: dumper.represent_mapping('!DNodeRemote', _dnode_remote_mapping(data)))
|
|
229
|
+
|
|
230
|
+
yaml.add_constructor(
|
|
231
|
+
'!DNodeRemote',
|
|
232
|
+
lambda loader, node: DNodeRemote(**loader.construct_mapping(node)))
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@ir_parse.register(
|
|
236
|
+
lambda x: isinstance(x, NodeRemote))
|
|
237
|
+
def _ir_parse__node_remote(
|
|
238
|
+
x: NodeRemote,
|
|
239
|
+
name: str | None = None):
|
|
240
|
+
|
|
241
|
+
return _branch(
|
|
242
|
+
x,
|
|
243
|
+
lambda: DNodeRemote(
|
|
244
|
+
uuid = str(x.uuid),
|
|
245
|
+
url = x.url,
|
|
246
|
+
name = x.name,
|
|
247
|
+
version = x.version,
|
|
248
|
+
owner_id = x.owner_id,
|
|
249
|
+
library = x.library,
|
|
250
|
+
target_machine = x.target_machine),
|
|
251
|
+
lambda _: [])
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@ir_unparse.register(
|
|
255
|
+
lambda x: isinstance(x, DNodeRemote))
|
|
256
|
+
def _ir_unparse__node_function(x: DNodeRemote, source: Path) -> Generator[ast.stmt]:
|
|
257
|
+
keywords = [
|
|
258
|
+
ast.keyword(
|
|
259
|
+
arg = 'uuid',
|
|
260
|
+
value = ast.Call(
|
|
261
|
+
func = ast.Name(id = 'UUID', ctx = ast.Load()),
|
|
262
|
+
args = [ast.Constant(value = x.uuid)])),
|
|
263
|
+
|
|
264
|
+
ast.keyword(
|
|
265
|
+
arg = 'url',
|
|
266
|
+
value = ast.Constant(value = x.url)),
|
|
267
|
+
|
|
268
|
+
ast.keyword(
|
|
269
|
+
arg = 'name',
|
|
270
|
+
value = ast.Constant(value = x.name)),
|
|
271
|
+
|
|
272
|
+
ast.keyword(
|
|
273
|
+
arg = 'version',
|
|
274
|
+
value = ast.Constant(value = x.version)),
|
|
275
|
+
|
|
276
|
+
# TODO: from context
|
|
277
|
+
ast.keyword(
|
|
278
|
+
arg = 'inputs',
|
|
279
|
+
value = ast.List(elts = [], ctx = ast.Load())),
|
|
280
|
+
|
|
281
|
+
ast.keyword(
|
|
282
|
+
arg = 'outputs',
|
|
283
|
+
value = ast.List(elts = [], ctx = ast.Load()))]
|
|
284
|
+
for attr in ('owner_id', 'library', 'target_machine'):
|
|
285
|
+
value = getattr(x, attr)
|
|
286
|
+
if value is not None:
|
|
287
|
+
keywords.append(ast.keyword(
|
|
288
|
+
arg = attr,
|
|
289
|
+
value = ast.Constant(value = value)))
|
|
290
|
+
|
|
291
|
+
yield ast.Assign(
|
|
292
|
+
targets = [ast.Name(id = '_node', ctx = ast.Store())],
|
|
293
|
+
value = ast.Call(
|
|
294
|
+
func = ast.Name(id = 'NodeRemote', ctx = ast.Load()),
|
|
295
|
+
keywords = keywords))
|