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,141 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import hashlib
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Generator
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
from spl.core.ir.common import DBase
|
|
10
|
+
from spl.core.ir.parse import _branch, ir_parse
|
|
11
|
+
from spl.core.ir.unparse import ir_unparse
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_HASH_CHUNK_SIZE = 1024 * 1024
|
|
15
|
+
_SHA256_HEX_DIGITS = set('0123456789abcdefABCDEF')
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _validate_non_empty_string(name: str, value: str) -> None:
|
|
19
|
+
if not isinstance(value, str):
|
|
20
|
+
raise TypeError('artifact ref {} must be a string'.format(name))
|
|
21
|
+
if not value:
|
|
22
|
+
raise ValueError('artifact ref {} must be a non-empty string'.format(name))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _validate_sha256(value: str) -> None:
|
|
26
|
+
_validate_non_empty_string('sha256', value)
|
|
27
|
+
if len(value) != 64 or any(c not in _SHA256_HEX_DIGITS for c in value):
|
|
28
|
+
raise ValueError('artifact ref sha256 must be a 64-character hex string')
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _validate_size(value: int) -> None:
|
|
32
|
+
if type(value) is not int:
|
|
33
|
+
raise TypeError('artifact ref size must be an integer')
|
|
34
|
+
if value < 0:
|
|
35
|
+
raise ValueError('artifact ref size must be non-negative')
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _validate_artifact_ref(key: str, uri: str, sha256: str, size: int) -> None:
|
|
39
|
+
_validate_non_empty_string('key', key)
|
|
40
|
+
_validate_non_empty_string('uri', uri)
|
|
41
|
+
_validate_sha256(sha256)
|
|
42
|
+
_validate_size(size)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen = True)
|
|
46
|
+
class ArtifactRef:
|
|
47
|
+
"""Reference to a materialized artifact on the local filesystem."""
|
|
48
|
+
|
|
49
|
+
key: str
|
|
50
|
+
uri: str
|
|
51
|
+
sha256: str
|
|
52
|
+
size: int
|
|
53
|
+
|
|
54
|
+
def __post_init__(self) -> None:
|
|
55
|
+
_validate_artifact_ref(
|
|
56
|
+
key = self.key,
|
|
57
|
+
uri = self.uri,
|
|
58
|
+
sha256 = self.sha256,
|
|
59
|
+
size = self.size)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen = True)
|
|
63
|
+
class DArtifactRef(DBase):
|
|
64
|
+
"""Serialized ArtifactRef value for pipeline YAML."""
|
|
65
|
+
|
|
66
|
+
key: str
|
|
67
|
+
uri: str
|
|
68
|
+
sha256: str
|
|
69
|
+
size: int
|
|
70
|
+
|
|
71
|
+
def __post_init__(self) -> None:
|
|
72
|
+
_validate_artifact_ref(
|
|
73
|
+
key = self.key,
|
|
74
|
+
uri = self.uri,
|
|
75
|
+
sha256 = self.sha256,
|
|
76
|
+
size = self.size)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
yaml.add_representer(
|
|
80
|
+
DArtifactRef,
|
|
81
|
+
lambda dumper, data: dumper.represent_mapping('!DArtifactRef', data.__dict__))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _construct_dartifact_ref(loader: Any, node: Any) -> DArtifactRef:
|
|
85
|
+
return DArtifactRef(**loader.construct_mapping(node))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
yaml.add_constructor(
|
|
89
|
+
'!DArtifactRef',
|
|
90
|
+
_construct_dartifact_ref)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def compute_sha256(path: Path) -> str:
|
|
94
|
+
"""Compute a file's SHA-256 digest with chunked reads."""
|
|
95
|
+
|
|
96
|
+
digest = hashlib.sha256()
|
|
97
|
+
with path.open('rb') as f:
|
|
98
|
+
for chunk in iter(lambda: f.read(_HASH_CHUNK_SIZE), b''):
|
|
99
|
+
digest.update(chunk)
|
|
100
|
+
return digest.hexdigest()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _mk_empty_dependencies(frame_offset: int) -> Generator[DBase]:
|
|
104
|
+
yield from ()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@ir_parse.register(
|
|
108
|
+
lambda x: isinstance(x, ArtifactRef))
|
|
109
|
+
def _ir_parse__artifact_ref(
|
|
110
|
+
x: ArtifactRef,
|
|
111
|
+
name: str | None = None) -> _branch:
|
|
112
|
+
return _branch(
|
|
113
|
+
x,
|
|
114
|
+
lambda: DArtifactRef(
|
|
115
|
+
key = x.key,
|
|
116
|
+
uri = x.uri,
|
|
117
|
+
sha256 = x.sha256,
|
|
118
|
+
size = x.size),
|
|
119
|
+
_mk_empty_dependencies)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@ir_unparse.register(
|
|
123
|
+
lambda x: isinstance(x, DArtifactRef))
|
|
124
|
+
def _ir_unparse__artifact_ref(x: DArtifactRef, source: Path) -> Generator[ast.stmt]:
|
|
125
|
+
yield ast.Assign(
|
|
126
|
+
targets = [ast.Name(id = '_link_to', ctx = ast.Store())],
|
|
127
|
+
value = ast.Call(
|
|
128
|
+
func = ast.Name(id = 'ArtifactRef', ctx = ast.Load()),
|
|
129
|
+
keywords = [
|
|
130
|
+
ast.keyword(
|
|
131
|
+
arg = 'key',
|
|
132
|
+
value = ast.Constant(value = x.key)),
|
|
133
|
+
ast.keyword(
|
|
134
|
+
arg = 'uri',
|
|
135
|
+
value = ast.Constant(value = x.uri)),
|
|
136
|
+
ast.keyword(
|
|
137
|
+
arg = 'sha256',
|
|
138
|
+
value = ast.Constant(value = x.sha256)),
|
|
139
|
+
ast.keyword(
|
|
140
|
+
arg = 'size',
|
|
141
|
+
value = ast.Constant(value = x.size))]))
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from spl.core.ir.common import DBase
|
|
7
|
+
from spl.core.ir.unparse import ir_unparse
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen = True)
|
|
11
|
+
class DSPLSelfImport(DBase):
|
|
12
|
+
name: str
|
|
13
|
+
|
|
14
|
+
yaml.add_representer(
|
|
15
|
+
DSPLSelfImport,
|
|
16
|
+
lambda dumper, data: dumper.represent_mapping('!DSPLSelfImport', data.__dict__))
|
|
17
|
+
|
|
18
|
+
yaml.add_constructor(
|
|
19
|
+
'!DSPLSelfImport',
|
|
20
|
+
lambda loader, node: DSPLSelfImport(**loader.construct_mapping(node)))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen = True)
|
|
24
|
+
class DSPLImport(DBase):
|
|
25
|
+
path: str
|
|
26
|
+
name: str
|
|
27
|
+
|
|
28
|
+
yaml.add_representer(
|
|
29
|
+
DSPLImport,
|
|
30
|
+
lambda dumper, data: dumper.represent_mapping('!DSPLImport', data.__dict__))
|
|
31
|
+
|
|
32
|
+
yaml.add_constructor(
|
|
33
|
+
'!DSPLImport',
|
|
34
|
+
lambda loader, node: DSPLImport(**loader.construct_mapping(node)))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@ir_unparse.register(lambda x: isinstance(x, DSPLSelfImport))
|
|
38
|
+
def _ir_unparse__spl_self_import(x: DSPLSelfImport, source: Path):
|
|
39
|
+
yield from []
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@ir_unparse.register(lambda x: isinstance(x, DSPLImport))
|
|
43
|
+
def _ir_unparse__spl_import(x: DSPLImport, source: Path):
|
|
44
|
+
yield from []
|
|
45
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import logging
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from importlib.metadata import PackageNotFoundError, packages_distributions
|
|
5
|
+
from itertools import chain
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from types import ModuleType
|
|
8
|
+
from typing import Generator
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
|
|
12
|
+
from spl.core.ir.common import DBase
|
|
13
|
+
from spl.core.ir.unparse import ir_unparse
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen = True)
|
|
17
|
+
class DDistribution(DBase):
|
|
18
|
+
package: str
|
|
19
|
+
version: str
|
|
20
|
+
|
|
21
|
+
def __lt__(self, other):
|
|
22
|
+
return (self.package, self.version) < (other.package, other.version)
|
|
23
|
+
|
|
24
|
+
yaml.add_representer(
|
|
25
|
+
DDistribution,
|
|
26
|
+
lambda dumper, data: dumper.represent_mapping('!DDistribution', data.__dict__))
|
|
27
|
+
|
|
28
|
+
yaml.add_constructor(
|
|
29
|
+
'!DDistribution',
|
|
30
|
+
lambda loader, node: DDistribution(**loader.construct_mapping(node)))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_dependencies_from_distribution(module: ModuleType) -> Generator[DDistribution]:
|
|
34
|
+
distributions = packages_distributions()
|
|
35
|
+
if (package := module.__package__):
|
|
36
|
+
for x in set(distributions[package.split('.')[0]]):
|
|
37
|
+
yield DDistribution(
|
|
38
|
+
package = x,
|
|
39
|
+
version = importlib.metadata.version(x))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def validate_distributions(deps, source) -> None:
|
|
43
|
+
distributions = sorted(set(filter(
|
|
44
|
+
lambda x: isinstance(x, DDistribution),
|
|
45
|
+
chain.from_iterable([dependencies for (_, dependencies) in deps]))))
|
|
46
|
+
|
|
47
|
+
for x in distributions:
|
|
48
|
+
try:
|
|
49
|
+
if (version := importlib.metadata.version(x.package)) != x.version:
|
|
50
|
+
logging.warning(
|
|
51
|
+
'{}: distribution mismatch: {} == {} (actual {})'.format(
|
|
52
|
+
source,
|
|
53
|
+
x.package,
|
|
54
|
+
x.version, version))
|
|
55
|
+
except PackageNotFoundError:
|
|
56
|
+
logging.warning(
|
|
57
|
+
'{}: distribution is not found: {} == {}'.format(
|
|
58
|
+
source,
|
|
59
|
+
x.package,
|
|
60
|
+
x.version))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@ir_unparse.register(lambda x: isinstance(x, DDistribution))
|
|
64
|
+
def _ir_unparse__distribution(x: DDistribution, source: Path):
|
|
65
|
+
yield from []
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import builtins
|
|
3
|
+
import dis
|
|
4
|
+
import inspect
|
|
5
|
+
import sys
|
|
6
|
+
import typing
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from itertools import chain
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from types import CodeType, FunctionType
|
|
11
|
+
from typing import Annotated, Generator
|
|
12
|
+
|
|
13
|
+
import yaml
|
|
14
|
+
|
|
15
|
+
import spl.core.entities.node as m_node
|
|
16
|
+
from spl.core.entities.control import DSPLImport
|
|
17
|
+
from spl.core.entities.node import DEFAULT_PORT, InputPort, OutputPort
|
|
18
|
+
from spl.core.ir.common import DBase
|
|
19
|
+
from spl.core.ir.parse import _attach, _branch, ir_parse
|
|
20
|
+
from spl.core.ir.unparse import ir_unparse
|
|
21
|
+
|
|
22
|
+
LOCATION_DUNDER_NAME = '__spl_location__'
|
|
23
|
+
METADATA_DUNDER_NAME = '__spl_metadata__'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class _body: # noqa: N801
|
|
28
|
+
value: str
|
|
29
|
+
|
|
30
|
+
yaml.add_representer(
|
|
31
|
+
_body,
|
|
32
|
+
lambda dumper, data: dumper.represent_scalar('tag:yaml.org,2002:str', data.value, style = '|'))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen = True)
|
|
36
|
+
class DFunction(DBase):
|
|
37
|
+
name: str
|
|
38
|
+
body: str
|
|
39
|
+
inputs: list[InputPort]
|
|
40
|
+
outputs: list[OutputPort] | None
|
|
41
|
+
|
|
42
|
+
def __hash__(self):
|
|
43
|
+
return hash((self.name, self.body, tuple(self.inputs), tuple(self.outputs)))
|
|
44
|
+
|
|
45
|
+
def __repr__(self):
|
|
46
|
+
return '{}({})'.format(self.__class__.__name__, repr(self.name))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
yaml.add_representer(
|
|
50
|
+
DFunction,
|
|
51
|
+
lambda dumper, data: dumper.represent_mapping('!DFunction', {
|
|
52
|
+
'name': data.name,
|
|
53
|
+
'inputs': list(map(
|
|
54
|
+
lambda x: {
|
|
55
|
+
'name': x.name,
|
|
56
|
+
'type': x.typ_,
|
|
57
|
+
'default': x.default}, data.inputs)),
|
|
58
|
+
'outputs': None if data.outputs is None else list(map(
|
|
59
|
+
lambda x: {
|
|
60
|
+
'name': x.name,
|
|
61
|
+
'type': x.typ_}, data.outputs)),
|
|
62
|
+
'body': _body(data.body)}))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
yaml.add_constructor(
|
|
66
|
+
'!DFunction',
|
|
67
|
+
lambda loader, node: (lambda data: DFunction(
|
|
68
|
+
name = data['name'],
|
|
69
|
+
body = data['body'],
|
|
70
|
+
inputs = [
|
|
71
|
+
InputPort(
|
|
72
|
+
name = x['name'],
|
|
73
|
+
typ_ = x['type'],
|
|
74
|
+
default = x.get('default'))
|
|
75
|
+
for x in data['inputs']],
|
|
76
|
+
outputs = [
|
|
77
|
+
OutputPort(
|
|
78
|
+
name = x['name'],
|
|
79
|
+
typ_ = x['type'])
|
|
80
|
+
for x in data['outputs']]))
|
|
81
|
+
(loader.construct_mapping(node, deep = True)))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_dependency_names_from_bytecode(f: FunctionType | CodeType):
|
|
85
|
+
for x in dis.Bytecode(f):
|
|
86
|
+
match x.opname:
|
|
87
|
+
case 'LOAD_GLOBAL':
|
|
88
|
+
yield x.argval
|
|
89
|
+
case 'LOAD_NAME':
|
|
90
|
+
yield x.argval
|
|
91
|
+
case 'LOAD_CONST':
|
|
92
|
+
if isinstance(x.argval, CodeType):
|
|
93
|
+
yield from get_dependency_names_from_bytecode(x.argval)
|
|
94
|
+
|
|
95
|
+
def get_dependencies_from_bytecode(
|
|
96
|
+
frame_offset,
|
|
97
|
+
f: FunctionType | CodeType):
|
|
98
|
+
g = sys._getframe(1 + frame_offset).f_globals
|
|
99
|
+
|
|
100
|
+
names = sorted(filter(
|
|
101
|
+
lambda x: x not in vars(builtins),
|
|
102
|
+
set(get_dependency_names_from_bytecode(f))))
|
|
103
|
+
|
|
104
|
+
if missing_names := (set(names) - set(g.keys())):
|
|
105
|
+
raise ValueError('missing names: {}'.format(', '.join(sorted(missing_names))))
|
|
106
|
+
|
|
107
|
+
for name in names:
|
|
108
|
+
yield ir_parse(g[name], name)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_dependencies_from_ast(frame_offset, tree: ast.FunctionDef):
|
|
112
|
+
for value in map(ast.unparse, filter(None, [x.annotation for x in tree.args.args])):
|
|
113
|
+
yield from get_dependencies_from_bytecode(
|
|
114
|
+
frame_offset + 1,
|
|
115
|
+
value)
|
|
116
|
+
for value in map(ast.unparse, tree.args.defaults):
|
|
117
|
+
yield from get_dependencies_from_bytecode(
|
|
118
|
+
frame_offset + 1,
|
|
119
|
+
value)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def serialize_function_output(func: FunctionType, tree: ast.FunctionDef):
|
|
123
|
+
name = DEFAULT_PORT
|
|
124
|
+
|
|
125
|
+
return_type = func.__annotations__.get('return')
|
|
126
|
+
if (return_type is not None) and (typing.get_origin(return_type) == Annotated):
|
|
127
|
+
(_, name, *_) = typing.get_args(return_type)
|
|
128
|
+
|
|
129
|
+
return [
|
|
130
|
+
OutputPort(
|
|
131
|
+
name = name,
|
|
132
|
+
typ_ = ast.unparse(tree.returns) if (tree.returns is not None) else None)]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def serialize_function(func: FunctionType, tree: ast.FunctionDef | None = None):
|
|
136
|
+
if tree is None:
|
|
137
|
+
[tree] = ast.parse(inspect.getsource(func)).body
|
|
138
|
+
|
|
139
|
+
args = tree.args
|
|
140
|
+
|
|
141
|
+
return DFunction(
|
|
142
|
+
name = tree.name,
|
|
143
|
+
body = ast.unparse(tree.body),
|
|
144
|
+
inputs = [
|
|
145
|
+
InputPort(
|
|
146
|
+
name = a.arg,
|
|
147
|
+
typ_ = ast.unparse(a.annotation) if a.annotation is not None else None,
|
|
148
|
+
default = ast.unparse(d) if d is not None else None)
|
|
149
|
+
for a, d in zip(
|
|
150
|
+
args.args,
|
|
151
|
+
[*[None] * (len(args.args) - len(args.defaults)), *args.defaults],
|
|
152
|
+
strict = True)],
|
|
153
|
+
outputs = serialize_function_output(func, tree))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@ir_parse.register(
|
|
157
|
+
lambda x: (isinstance(x, FunctionType) and (getattr(x, '__module__', None) == '__main__')))
|
|
158
|
+
def _ir_parse__function(
|
|
159
|
+
x: FunctionType,
|
|
160
|
+
name: str | None = None):
|
|
161
|
+
|
|
162
|
+
if hasattr(x, LOCATION_DUNDER_NAME):
|
|
163
|
+
# We imported this function using SPL, using it's metadata.
|
|
164
|
+
return _attach(chain([DSPLImport(*getattr(x, LOCATION_DUNDER_NAME))]))
|
|
165
|
+
|
|
166
|
+
[tree] = ast.parse(inspect.getsource(x)).body
|
|
167
|
+
return _branch(
|
|
168
|
+
x,
|
|
169
|
+
lambda: serialize_function(x, tree),
|
|
170
|
+
lambda frame_offset: chain(
|
|
171
|
+
get_dependencies_from_bytecode(frame_offset, x),
|
|
172
|
+
get_dependencies_from_ast(frame_offset, tree)))
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_function_metadata(func: FunctionType):
|
|
176
|
+
if hasattr(func, METADATA_DUNDER_NAME):
|
|
177
|
+
# We imported this function using SPL, using it's metadata.
|
|
178
|
+
return getattr(func, METADATA_DUNDER_NAME)
|
|
179
|
+
return serialize_function(func)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@ir_unparse.register(lambda x: isinstance(x, DFunction))
|
|
183
|
+
def _ir_unparse__function(x: DFunction, source: Path) -> Generator[ast.stmt]:
|
|
184
|
+
yield ast.FunctionDef(
|
|
185
|
+
name = x.name,
|
|
186
|
+
body = ast.parse(x.body).body,
|
|
187
|
+
# TODO: add support for multiple outputs
|
|
188
|
+
returns = ast.parse(x.outputs[0].typ_, mode = 'eval').body if x.outputs[0].typ_ is not None else None,
|
|
189
|
+
args = ast.arguments(
|
|
190
|
+
args = [
|
|
191
|
+
ast.arg(
|
|
192
|
+
arg = port.name,
|
|
193
|
+
annotation = ast.parse(port.typ_, mode = 'eval').body if port.typ_ is not None else None)
|
|
194
|
+
for port in x.inputs],
|
|
195
|
+
defaults = [
|
|
196
|
+
ast.parse(port.default, mode = 'eval').body
|
|
197
|
+
for port in x.inputs
|
|
198
|
+
if port.default is not None]))
|
|
199
|
+
|
|
200
|
+
# Importing helpers
|
|
201
|
+
yield ast.ImportFrom(
|
|
202
|
+
module = m_node.__name__,
|
|
203
|
+
names = [
|
|
204
|
+
ast.alias(name = 'InputPort'),
|
|
205
|
+
ast.alias(name = 'OutputPort')])
|
|
206
|
+
|
|
207
|
+
yield ast.ImportFrom(
|
|
208
|
+
module = __name__,
|
|
209
|
+
names = [
|
|
210
|
+
ast.alias(name = 'DFunction')])
|
|
211
|
+
|
|
212
|
+
# Marking function as spl-imported
|
|
213
|
+
yield ast.Expr(value = ast.Call(
|
|
214
|
+
func = ast.Name(id = 'setattr', ctx = ast.Load()),
|
|
215
|
+
args = [
|
|
216
|
+
ast.Name(id = x.name, ctx = ast.Load()),
|
|
217
|
+
ast.Constant(value = LOCATION_DUNDER_NAME),
|
|
218
|
+
ast.Tuple(elts = [
|
|
219
|
+
ast.Constant(value = str(source.absolute())),
|
|
220
|
+
ast.Constant(value = x.name)])]))
|
|
221
|
+
|
|
222
|
+
# Adding metadata
|
|
223
|
+
yield ast.Expr(value = ast.Call(
|
|
224
|
+
func = ast.Name(id = 'setattr', ctx = ast.Load()),
|
|
225
|
+
args = [
|
|
226
|
+
ast.Name(id = x.name, ctx = ast.Load()),
|
|
227
|
+
ast.Constant(value = METADATA_DUNDER_NAME),
|
|
228
|
+
ast.Call(
|
|
229
|
+
func = ast.Name(id = 'DFunction', ctx = ast.Load()),
|
|
230
|
+
keywords = [
|
|
231
|
+
ast.keyword(arg = 'name', value = ast.Constant(value = x.name)),
|
|
232
|
+
ast.keyword(arg = 'body', value = ast.Constant(value = x.body)),
|
|
233
|
+
ast.keyword(arg = 'inputs', value = ast.List(
|
|
234
|
+
elts = [
|
|
235
|
+
ast.Call(
|
|
236
|
+
func = ast.Name(id = 'InputPort', ctx = ast.Load()),
|
|
237
|
+
keywords = [
|
|
238
|
+
ast.keyword(arg = 'name', value = ast.Constant(value = port.name)),
|
|
239
|
+
ast.keyword(arg = 'typ_', value = ast.Constant(value = port.typ_)),
|
|
240
|
+
ast.keyword(
|
|
241
|
+
arg = 'default',
|
|
242
|
+
value = ast.Constant(value = port.default))])
|
|
243
|
+
for port in x.inputs],
|
|
244
|
+
ctx = ast.Load())),
|
|
245
|
+
|
|
246
|
+
ast.keyword(arg = 'outputs', value = ast.List(
|
|
247
|
+
elts = [
|
|
248
|
+
ast.Call(
|
|
249
|
+
func = ast.Name(id = 'OutputPort', ctx = ast.Load()),
|
|
250
|
+
keywords = [
|
|
251
|
+
ast.keyword(arg = 'name', value = ast.Constant(value = port.name)),
|
|
252
|
+
ast.keyword(arg = 'typ_', value = ast.Constant(value = port.typ_))])
|
|
253
|
+
for port in x.outputs],
|
|
254
|
+
ctx = ast.Load()))])]))
|