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.
Files changed (74) hide show
  1. spl/__init__.py +14 -0
  2. spl/client.py +1364 -0
  3. spl/core/__init__.py +23 -0
  4. spl/core/common.py +350 -0
  5. spl/core/entities/__init__.py +0 -0
  6. spl/core/entities/adapter.py +210 -0
  7. spl/core/entities/artifact.py +141 -0
  8. spl/core/entities/control.py +45 -0
  9. spl/core/entities/distribution.py +65 -0
  10. spl/core/entities/function.py +254 -0
  11. spl/core/entities/local_function.py +286 -0
  12. spl/core/entities/misc.py +14 -0
  13. spl/core/entities/module.py +88 -0
  14. spl/core/entities/node.py +286 -0
  15. spl/core/entities/node_function.py +79 -0
  16. spl/core/entities/node_remote.py +295 -0
  17. spl/core/entities/pipeline.py +436 -0
  18. spl/core/entities/scalar.py +55 -0
  19. spl/core/ir/__init__.py +0 -0
  20. spl/core/ir/common.py +34 -0
  21. spl/core/ir/parse.py +79 -0
  22. spl/core/ir/unparse.py +29 -0
  23. spl/core/ir/utils.py +163 -0
  24. spl/daemon/__init__.py +23 -0
  25. spl/daemon/__main__.py +11 -0
  26. spl/daemon/cli.py +582 -0
  27. spl/daemon/client.py +43 -0
  28. spl/daemon/docker_environment.py +329 -0
  29. spl/daemon/docker_pool.py +516 -0
  30. spl/daemon/environment.py +228 -0
  31. spl/daemon/environment_base.py +479 -0
  32. spl/daemon/heartbeat_service.py +119 -0
  33. spl/daemon/metadata.py +427 -0
  34. spl/daemon/remote_client.py +457 -0
  35. spl/daemon/repositories/__init__.py +17 -0
  36. spl/daemon/repositories/env.py +323 -0
  37. spl/daemon/repositories/library.py +181 -0
  38. spl/daemon/repositories/object.py +997 -0
  39. spl/daemon/repositories/run.py +279 -0
  40. spl/daemon/repositories/server_connection.py +657 -0
  41. spl/daemon/repositories/sync_event.py +129 -0
  42. spl/daemon/routes/__init__.py +1 -0
  43. spl/daemon/routes/_helpers.py +147 -0
  44. spl/daemon/routes/artifacts.py +77 -0
  45. spl/daemon/routes/diagnostics.py +114 -0
  46. spl/daemon/routes/envs.py +82 -0
  47. spl/daemon/routes/libraries.py +129 -0
  48. spl/daemon/routes/objects.py +174 -0
  49. spl/daemon/routes/remote.py +56 -0
  50. spl/daemon/routes/runs.py +96 -0
  51. spl/daemon/routes/server_connections.py +86 -0
  52. spl/daemon/runtime_backend.py +368 -0
  53. spl/daemon/runtime_config.py +133 -0
  54. spl/daemon/runtime_dependencies.py +459 -0
  55. spl/daemon/secret_store.py +187 -0
  56. spl/daemon/server.py +2224 -0
  57. spl/daemon/server_connection.py +267 -0
  58. spl/daemon/services/__init__.py +1 -0
  59. spl/daemon/services/sync.py +76 -0
  60. spl/daemon/signature.py +376 -0
  61. spl/daemon/storage_base.py +542 -0
  62. spl/daemon/store.py +436 -0
  63. spl/daemon/worker.py +526 -0
  64. spl/daemon_client.py +945 -0
  65. spl/pipeline_widget.py +1452 -0
  66. spl/py.typed +0 -0
  67. spl/server_client.py +787 -0
  68. splime-0.1.2.dist-info/METADATA +189 -0
  69. splime-0.1.2.dist-info/RECORD +74 -0
  70. splime-0.1.2.dist-info/WHEEL +5 -0
  71. splime-0.1.2.dist-info/entry_points.txt +2 -0
  72. splime-0.1.2.dist-info/licenses/LICENSE +201 -0
  73. splime-0.1.2.dist-info/licenses/NOTICE +8 -0
  74. 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))