linkinpy 0.0.1__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.
- linkinpy/__init__.py +63 -0
- linkinpy/_shm.py +30 -0
- linkinpy/_testing.py +15 -0
- linkinpy/_worker.py +68 -0
- linkinpy/cli.py +133 -0
- linkinpy/colab.py +438 -0
- linkinpy/environment.py +237 -0
- linkinpy/gui.py +175 -0
- linkinpy/images.py +134 -0
- linkinpy/integrations/__init__.py +1 -0
- linkinpy/integrations/image_io.py +12 -0
- linkinpy/integrations/nanopyx.py +20 -0
- linkinpy/linkinpy.py +56 -0
- linkinpy/runner.py +160 -0
- linkinpy/spec.py +284 -0
- linkinpy-0.0.1.dist-info/METADATA +95 -0
- linkinpy-0.0.1.dist-info/RECORD +19 -0
- linkinpy-0.0.1.dist-info/WHEEL +4 -0
- linkinpy-0.0.1.dist-info/entry_points.txt +2 -0
linkinpy/__init__.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from .colab import (
|
|
2
|
+
choose_colab_spec,
|
|
3
|
+
install_colab_spec,
|
|
4
|
+
installed_colab_specs,
|
|
5
|
+
launch_colab,
|
|
6
|
+
mount_google_drive,
|
|
7
|
+
run_installed_colab_method,
|
|
8
|
+
)
|
|
9
|
+
from .environment import EnvironmentManager, ensure_uv, find_uv
|
|
10
|
+
from .images import (
|
|
11
|
+
SharedArray,
|
|
12
|
+
attach_shared_array,
|
|
13
|
+
create_shared_array,
|
|
14
|
+
read_image,
|
|
15
|
+
write_image,
|
|
16
|
+
)
|
|
17
|
+
from .linkinpy import LinkinPy
|
|
18
|
+
from .runner import (
|
|
19
|
+
get_callable,
|
|
20
|
+
resolve_callable,
|
|
21
|
+
run_callable_in_process,
|
|
22
|
+
run_spec_callable,
|
|
23
|
+
run_spec_callable_in_environment,
|
|
24
|
+
)
|
|
25
|
+
from .spec import (
|
|
26
|
+
CallableSpec,
|
|
27
|
+
InputSpec,
|
|
28
|
+
LinkinPySpec,
|
|
29
|
+
PackageSpec,
|
|
30
|
+
SpecError,
|
|
31
|
+
load_spec,
|
|
32
|
+
parse_spec,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"CallableSpec",
|
|
37
|
+
"EnvironmentManager",
|
|
38
|
+
"InputSpec",
|
|
39
|
+
"LinkinPy",
|
|
40
|
+
"LinkinPySpec",
|
|
41
|
+
"PackageSpec",
|
|
42
|
+
"SharedArray",
|
|
43
|
+
"SpecError",
|
|
44
|
+
"attach_shared_array",
|
|
45
|
+
"choose_colab_spec",
|
|
46
|
+
"create_shared_array",
|
|
47
|
+
"ensure_uv",
|
|
48
|
+
"find_uv",
|
|
49
|
+
"get_callable",
|
|
50
|
+
"install_colab_spec",
|
|
51
|
+
"installed_colab_specs",
|
|
52
|
+
"launch_colab",
|
|
53
|
+
"load_spec",
|
|
54
|
+
"mount_google_drive",
|
|
55
|
+
"parse_spec",
|
|
56
|
+
"read_image",
|
|
57
|
+
"resolve_callable",
|
|
58
|
+
"run_callable_in_process",
|
|
59
|
+
"run_installed_colab_method",
|
|
60
|
+
"run_spec_callable",
|
|
61
|
+
"run_spec_callable_in_environment",
|
|
62
|
+
"write_image",
|
|
63
|
+
]
|
linkinpy/_shm.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from .images import create_empty_untracked_shared_array
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main(argv: Optional[list[str]] = None) -> int:
|
|
11
|
+
parser = argparse.ArgumentParser(description="Internal LinkinPy shared-memory helper.")
|
|
12
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
13
|
+
|
|
14
|
+
allocate = subparsers.add_parser("allocate", help="Allocate an untracked shared-memory array.")
|
|
15
|
+
allocate.add_argument("--shape", required=True, help="JSON list of dimensions.")
|
|
16
|
+
allocate.add_argument("--dtype", required=True)
|
|
17
|
+
allocate.add_argument("--axes")
|
|
18
|
+
|
|
19
|
+
args = parser.parse_args(argv)
|
|
20
|
+
if args.command == "allocate":
|
|
21
|
+
shape = json.loads(args.shape)
|
|
22
|
+
descriptor, block = create_empty_untracked_shared_array(shape, args.dtype, axes=args.axes)
|
|
23
|
+
block.close()
|
|
24
|
+
print(json.dumps(descriptor.to_descriptor()), flush=True)
|
|
25
|
+
return 0
|
|
26
|
+
return 1
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if __name__ == "__main__":
|
|
30
|
+
raise SystemExit(main())
|
linkinpy/_testing.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def add_one(x: float) -> float:
|
|
7
|
+
return x + 1
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def image_plus_one(image: np.ndarray) -> np.ndarray:
|
|
11
|
+
return image + 1
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def make_image() -> np.ndarray:
|
|
15
|
+
return np.arange(4, dtype=np.float32).reshape(2, 2)
|
linkinpy/_worker.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from .images import create_untracked_shared_array, write_image
|
|
12
|
+
from .runner import parse_cli_values, run_spec_callable
|
|
13
|
+
from .spec import load_spec
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main(argv: Optional[list[str]] = None) -> int:
|
|
17
|
+
parser = argparse.ArgumentParser(description="Internal LinkinPy worker.")
|
|
18
|
+
parser.add_argument("spec")
|
|
19
|
+
parser.add_argument("callable_name")
|
|
20
|
+
parser.add_argument("--output")
|
|
21
|
+
parser.add_argument("--transport", choices=("shared_memory", "disk"), default="shared_memory")
|
|
22
|
+
parser.add_argument("--wait-for-shm-release", action="store_true")
|
|
23
|
+
parser.add_argument("values", nargs="*")
|
|
24
|
+
args = parser.parse_args(argv)
|
|
25
|
+
|
|
26
|
+
spec = load_spec(args.spec)
|
|
27
|
+
values = parse_cli_values(args.values)
|
|
28
|
+
result = run_spec_callable(
|
|
29
|
+
spec,
|
|
30
|
+
args.callable_name,
|
|
31
|
+
values,
|
|
32
|
+
interface="cli",
|
|
33
|
+
output_path=args.output if args.transport == "disk" else None,
|
|
34
|
+
ensure_environment=False,
|
|
35
|
+
)
|
|
36
|
+
if args.transport == "shared_memory" and _is_image_like(result):
|
|
37
|
+
try:
|
|
38
|
+
shared, _block = create_untracked_shared_array(result)
|
|
39
|
+
print(json.dumps(shared.to_descriptor()))
|
|
40
|
+
sys.stdout.flush()
|
|
41
|
+
if args.wait_for_shm_release:
|
|
42
|
+
sys.stdin.readline()
|
|
43
|
+
except Exception as exc:
|
|
44
|
+
print(f"linkinpy: shared-memory output failed, falling back to disk: {exc}", file=sys.stderr)
|
|
45
|
+
if args.output is None:
|
|
46
|
+
raise
|
|
47
|
+
print(str(write_image(args.output, result)))
|
|
48
|
+
elif args.output is None:
|
|
49
|
+
print(json.dumps(_jsonable(result)))
|
|
50
|
+
else:
|
|
51
|
+
print(str(Path(args.output).expanduser().resolve()))
|
|
52
|
+
return 0
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _jsonable(value: object) -> object:
|
|
56
|
+
if hasattr(value, "tolist"):
|
|
57
|
+
return value.tolist()
|
|
58
|
+
if isinstance(value, Path):
|
|
59
|
+
return str(value)
|
|
60
|
+
return value
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _is_image_like(value: object) -> bool:
|
|
64
|
+
return isinstance(value, np.ndarray) or (hasattr(value, "shape") and hasattr(value, "dtype"))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
raise SystemExit(main())
|
linkinpy/cli.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from .environment import EnvironmentError, EnvironmentManager, format_requirements
|
|
9
|
+
from .runner import RunnerError
|
|
10
|
+
from .spec import SpecError, load_spec
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main(argv: Optional[list[str]] = None) -> int:
|
|
14
|
+
parser = argparse.ArgumentParser(prog="linkinpy")
|
|
15
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
16
|
+
|
|
17
|
+
install = subparsers.add_parser("install", help="Create/update the uv environment for a spec.")
|
|
18
|
+
install.add_argument("spec")
|
|
19
|
+
install.add_argument("--interface", default="cli")
|
|
20
|
+
|
|
21
|
+
list_cmd = subparsers.add_parser("list", help="List callable workflows declared by a spec.")
|
|
22
|
+
list_cmd.add_argument("spec")
|
|
23
|
+
|
|
24
|
+
run = subparsers.add_parser("run", help="Run a callable workflow inside its spec-owned environment.")
|
|
25
|
+
run.add_argument("spec")
|
|
26
|
+
run.add_argument("callable_name")
|
|
27
|
+
run.add_argument("--interface", default="cli")
|
|
28
|
+
run.add_argument("--output")
|
|
29
|
+
run.add_argument("--transport", choices=("shared_memory", "disk"), default="shared_memory")
|
|
30
|
+
run.add_argument("--wait-for-shm-release", action="store_true")
|
|
31
|
+
run.add_argument("values", nargs="*", help="Callable arguments as name=value.")
|
|
32
|
+
|
|
33
|
+
gui = subparsers.add_parser("gui", help="Launch a generated magicgui interface.")
|
|
34
|
+
gui.add_argument("spec")
|
|
35
|
+
gui.add_argument("callable_name")
|
|
36
|
+
|
|
37
|
+
args = parser.parse_args(argv)
|
|
38
|
+
try:
|
|
39
|
+
if args.command == "install":
|
|
40
|
+
return _install(args.spec, args.interface)
|
|
41
|
+
if args.command == "list":
|
|
42
|
+
return _list(args.spec)
|
|
43
|
+
if args.command == "run":
|
|
44
|
+
return _run(
|
|
45
|
+
args.spec,
|
|
46
|
+
args.callable_name,
|
|
47
|
+
args.interface,
|
|
48
|
+
args.output,
|
|
49
|
+
args.transport,
|
|
50
|
+
args.wait_for_shm_release,
|
|
51
|
+
args.values,
|
|
52
|
+
)
|
|
53
|
+
if args.command == "gui":
|
|
54
|
+
return _gui(args.spec, args.callable_name)
|
|
55
|
+
except (EnvironmentError, RunnerError, RuntimeError, SpecError) as exc:
|
|
56
|
+
print(f"linkinpy: {exc}", file=sys.stderr)
|
|
57
|
+
return 2
|
|
58
|
+
return 0
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _install(spec_source: str, interface: str) -> int:
|
|
62
|
+
spec = load_spec(spec_source)
|
|
63
|
+
manager = EnvironmentManager(spec)
|
|
64
|
+
env_path = manager.ensure(interface, force=True)
|
|
65
|
+
requirements = format_requirements(spec.python_packages())
|
|
66
|
+
print(f"Environment: {env_path}")
|
|
67
|
+
print(f"Installed for {interface}: {', '.join(requirements) if requirements else '(LinkinPy core only)'}")
|
|
68
|
+
return 0
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _list(spec_source: str) -> int:
|
|
72
|
+
spec = load_spec(spec_source)
|
|
73
|
+
print(spec.display_name)
|
|
74
|
+
for callable_spec in spec.callables.values():
|
|
75
|
+
display = callable_spec.display_name or callable_spec.name
|
|
76
|
+
print(f"- {callable_spec.name}: {display} ({callable_spec.callable})")
|
|
77
|
+
return 0
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _run(
|
|
81
|
+
spec_source: str,
|
|
82
|
+
callable_name: str,
|
|
83
|
+
interface: str,
|
|
84
|
+
output: Optional[str],
|
|
85
|
+
transport: str,
|
|
86
|
+
wait_for_shm_release: bool,
|
|
87
|
+
values: list[str],
|
|
88
|
+
) -> int:
|
|
89
|
+
spec = load_spec(spec_source)
|
|
90
|
+
manager = EnvironmentManager(spec)
|
|
91
|
+
manager.ensure(interface)
|
|
92
|
+
worker_args = ["-m", "linkinpy._worker", spec_source, callable_name, "--transport", transport, *values]
|
|
93
|
+
if output is not None:
|
|
94
|
+
worker_args[4:4] = ["--output", output]
|
|
95
|
+
if wait_for_shm_release:
|
|
96
|
+
worker_args[4:4] = ["--wait-for-shm-release"]
|
|
97
|
+
return _run_worker_with_release_handshake(manager, worker_args)
|
|
98
|
+
completed = manager.run_python(worker_args)
|
|
99
|
+
print(completed.stdout, end="")
|
|
100
|
+
if completed.stderr:
|
|
101
|
+
print(completed.stderr, file=sys.stderr, end="")
|
|
102
|
+
return completed.returncode
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _run_worker_with_release_handshake(manager: EnvironmentManager, worker_args: list[str]) -> int:
|
|
106
|
+
command = [str(manager.python_executable), *worker_args]
|
|
107
|
+
process = subprocess.Popen(
|
|
108
|
+
command,
|
|
109
|
+
stdin=subprocess.PIPE,
|
|
110
|
+
stdout=subprocess.PIPE,
|
|
111
|
+
stderr=subprocess.STDOUT,
|
|
112
|
+
text=True,
|
|
113
|
+
)
|
|
114
|
+
assert process.stdout is not None
|
|
115
|
+
for line in process.stdout:
|
|
116
|
+
print(line, end="", flush=True)
|
|
117
|
+
if '"transport"' in line and '"shared_memory"' in line and process.stdin is not None:
|
|
118
|
+
sys.stdin.readline()
|
|
119
|
+
process.stdin.write("\n")
|
|
120
|
+
process.stdin.flush()
|
|
121
|
+
return process.wait()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _gui(spec_source: str, callable_name: str) -> int:
|
|
125
|
+
from .gui import launch_magicgui
|
|
126
|
+
|
|
127
|
+
spec = load_spec(spec_source)
|
|
128
|
+
launch_magicgui(spec, callable_name)
|
|
129
|
+
return 0
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
raise SystemExit(main())
|