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 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())