styxkit 0.1.0__tar.gz
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.
- styxkit-0.1.0/PKG-INFO +88 -0
- styxkit-0.1.0/README.md +64 -0
- styxkit-0.1.0/pyproject.toml +25 -0
- styxkit-0.1.0/src/styxkit/__init__.py +217 -0
- styxkit-0.1.0/src/styxkit/py.typed +0 -0
styxkit-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: styxkit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Convenience helpers spanning the Styx runtime backends.
|
|
5
|
+
Author: Florian Rupprecht
|
|
6
|
+
Author-email: Florian Rupprecht <33600480+nx10@users.noreply.github.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Requires-Dist: styxdefs>=0.7.0,<0.8
|
|
9
|
+
Requires-Dist: styxdocker ; extra == 'all'
|
|
10
|
+
Requires-Dist: styxpodman ; extra == 'all'
|
|
11
|
+
Requires-Dist: styxsingularity ; extra == 'all'
|
|
12
|
+
Requires-Dist: styxgraph ; extra == 'all'
|
|
13
|
+
Requires-Dist: styxdocker ; extra == 'docker'
|
|
14
|
+
Requires-Dist: styxgraph ; extra == 'graph'
|
|
15
|
+
Requires-Dist: styxpodman ; extra == 'podman'
|
|
16
|
+
Requires-Dist: styxsingularity ; extra == 'singularity'
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Provides-Extra: all
|
|
19
|
+
Provides-Extra: docker
|
|
20
|
+
Provides-Extra: graph
|
|
21
|
+
Provides-Extra: podman
|
|
22
|
+
Provides-Extra: singularity
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Styxkit - convenience helpers for the Styx runtime
|
|
26
|
+
|
|
27
|
+
`styxkit` is a small convenience layer on top of the Styx runtime. It provides
|
|
28
|
+
one-call runner selection across whichever backend packages you have installed,
|
|
29
|
+
so you do not have to import each runner class and wire up the global runner by
|
|
30
|
+
hand.
|
|
31
|
+
|
|
32
|
+
It sits at the top of the runtime stack:
|
|
33
|
+
|
|
34
|
+
- `styxdefs` is the base contract (the `Runner` protocol, `LocalRunner`,
|
|
35
|
+
`DryRunner`, and `set_global_runner` / `get_global_runner`).
|
|
36
|
+
- Each backend (`styxdocker`, `styxpodman`, `styxsingularity`, `styxgraph`) is an
|
|
37
|
+
independent package depending only on `styxdefs`.
|
|
38
|
+
- `styxkit` depends only on `styxdefs` and imports the backends **lazily**, so
|
|
39
|
+
installing it does not pull in any container backend you do not want.
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install styxkit # base, plus styxdefs
|
|
45
|
+
pip install "styxkit[docker]" # + the Docker backend
|
|
46
|
+
pip install "styxkit[all]" # + every backend (docker, podman, singularity, graph)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Calling a `use_*()` for a backend you have not installed raises a friendly error
|
|
50
|
+
telling you exactly what to install.
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import styxkit
|
|
56
|
+
|
|
57
|
+
# Pick a specific runner:
|
|
58
|
+
styxkit.use_docker()
|
|
59
|
+
styxkit.use_singularity(singularity_executable="apptainer")
|
|
60
|
+
styxkit.use_local()
|
|
61
|
+
|
|
62
|
+
# ...or let styxkit detect the best available container runtime,
|
|
63
|
+
# falling back to local when none is found:
|
|
64
|
+
runner = styxkit.use_auto()
|
|
65
|
+
|
|
66
|
+
# Each use_*() registers the runner as the global runner and returns it,
|
|
67
|
+
# so you can configure it without a get_global_runner() round-trip:
|
|
68
|
+
runner = styxkit.use_docker()
|
|
69
|
+
runner.data_dir = "/tmp/styx"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`use_auto()` prefers, in order, the first container backend that is both
|
|
73
|
+
installed and has its executable on `PATH`: Docker, then Podman, then
|
|
74
|
+
Singularity/Apptainer, otherwise the local runner.
|
|
75
|
+
|
|
76
|
+
## Available helpers
|
|
77
|
+
|
|
78
|
+
- `use_local`, `use_dry` - always available (from `styxdefs`).
|
|
79
|
+
- `use_docker`, `use_podman`, `use_singularity` - lazy, require the matching backend.
|
|
80
|
+
- `use_graph(base=None)` - wraps a runner in a graph recorder; defaults to the
|
|
81
|
+
current global runner.
|
|
82
|
+
- `use_auto()` / `resolve_runner()` - detect and select a runner.
|
|
83
|
+
- The core `styxdefs` symbols (`Runner`, `Execution`, `Metadata`,
|
|
84
|
+
`set_global_runner`, ...) are re-exported for convenience.
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
`styxkit` is released under the MIT License. See the LICENSE file for details.
|
styxkit-0.1.0/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Styxkit - convenience helpers for the Styx runtime
|
|
2
|
+
|
|
3
|
+
`styxkit` is a small convenience layer on top of the Styx runtime. It provides
|
|
4
|
+
one-call runner selection across whichever backend packages you have installed,
|
|
5
|
+
so you do not have to import each runner class and wire up the global runner by
|
|
6
|
+
hand.
|
|
7
|
+
|
|
8
|
+
It sits at the top of the runtime stack:
|
|
9
|
+
|
|
10
|
+
- `styxdefs` is the base contract (the `Runner` protocol, `LocalRunner`,
|
|
11
|
+
`DryRunner`, and `set_global_runner` / `get_global_runner`).
|
|
12
|
+
- Each backend (`styxdocker`, `styxpodman`, `styxsingularity`, `styxgraph`) is an
|
|
13
|
+
independent package depending only on `styxdefs`.
|
|
14
|
+
- `styxkit` depends only on `styxdefs` and imports the backends **lazily**, so
|
|
15
|
+
installing it does not pull in any container backend you do not want.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install styxkit # base, plus styxdefs
|
|
21
|
+
pip install "styxkit[docker]" # + the Docker backend
|
|
22
|
+
pip install "styxkit[all]" # + every backend (docker, podman, singularity, graph)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Calling a `use_*()` for a backend you have not installed raises a friendly error
|
|
26
|
+
telling you exactly what to install.
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import styxkit
|
|
32
|
+
|
|
33
|
+
# Pick a specific runner:
|
|
34
|
+
styxkit.use_docker()
|
|
35
|
+
styxkit.use_singularity(singularity_executable="apptainer")
|
|
36
|
+
styxkit.use_local()
|
|
37
|
+
|
|
38
|
+
# ...or let styxkit detect the best available container runtime,
|
|
39
|
+
# falling back to local when none is found:
|
|
40
|
+
runner = styxkit.use_auto()
|
|
41
|
+
|
|
42
|
+
# Each use_*() registers the runner as the global runner and returns it,
|
|
43
|
+
# so you can configure it without a get_global_runner() round-trip:
|
|
44
|
+
runner = styxkit.use_docker()
|
|
45
|
+
runner.data_dir = "/tmp/styx"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`use_auto()` prefers, in order, the first container backend that is both
|
|
49
|
+
installed and has its executable on `PATH`: Docker, then Podman, then
|
|
50
|
+
Singularity/Apptainer, otherwise the local runner.
|
|
51
|
+
|
|
52
|
+
## Available helpers
|
|
53
|
+
|
|
54
|
+
- `use_local`, `use_dry` - always available (from `styxdefs`).
|
|
55
|
+
- `use_docker`, `use_podman`, `use_singularity` - lazy, require the matching backend.
|
|
56
|
+
- `use_graph(base=None)` - wraps a runner in a graph recorder; defaults to the
|
|
57
|
+
current global runner.
|
|
58
|
+
- `use_auto()` / `resolve_runner()` - detect and select a runner.
|
|
59
|
+
- The core `styxdefs` symbols (`Runner`, `Execution`, `Metadata`,
|
|
60
|
+
`set_global_runner`, ...) are re-exported for convenience.
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
`styxkit` is released under the MIT License. See the LICENSE file for details.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "styxkit"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Convenience helpers spanning the Styx runtime backends."
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Florian Rupprecht", email = "33600480+nx10@users.noreply.github.com"}
|
|
7
|
+
]
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"styxdefs>=0.7.0,<0.8",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
# Backends are imported lazily; install the ones you need (or `styxkit[all]`).
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
docker = ["styxdocker"]
|
|
18
|
+
podman = ["styxpodman"]
|
|
19
|
+
singularity = ["styxsingularity"]
|
|
20
|
+
graph = ["styxgraph"]
|
|
21
|
+
all = ["styxdocker", "styxpodman", "styxsingularity", "styxgraph"]
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["uv_build>=0.8.13,<0.10.0"]
|
|
25
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""Convenience helpers spanning the Styx runtime backends.
|
|
2
|
+
|
|
3
|
+
``styxdefs`` is the base contract; each backend (``styxdocker``, ``styxpodman``,
|
|
4
|
+
``styxsingularity``, ``styxgraph``) is an independent piece. ``styxkit`` sits on
|
|
5
|
+
top and offers one-call runner selection across whichever pieces are installed.
|
|
6
|
+
|
|
7
|
+
Backends are imported lazily, so ``styxkit`` itself only requires ``styxdefs``.
|
|
8
|
+
Calling a ``use_*()`` for a backend that is not installed raises a friendly
|
|
9
|
+
:class:`ModuleNotFoundError` naming the package (and extra) to install.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import importlib
|
|
15
|
+
import importlib.util
|
|
16
|
+
import shutil
|
|
17
|
+
import typing
|
|
18
|
+
|
|
19
|
+
# Re-exported for convenience; the public surface is pinned by ``__all__`` below.
|
|
20
|
+
from styxdefs import (
|
|
21
|
+
DryRunner,
|
|
22
|
+
Execution,
|
|
23
|
+
InputPathType,
|
|
24
|
+
LocalRunner,
|
|
25
|
+
Metadata,
|
|
26
|
+
OutputPathType,
|
|
27
|
+
Runner,
|
|
28
|
+
StyxRuntimeError,
|
|
29
|
+
StyxValidationError,
|
|
30
|
+
get_global_runner,
|
|
31
|
+
set_global_runner,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
RunnerType = typing.Literal["local", "docker", "podman", "singularity"]
|
|
35
|
+
"""A container-runner kind selectable via :func:`resolve_runner` / :func:`use_auto`."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class _Backend(typing.NamedTuple):
|
|
39
|
+
"""A lazily-loaded runner backend: its module, class, exe kwarg, and probes."""
|
|
40
|
+
|
|
41
|
+
module: str
|
|
42
|
+
cls: str
|
|
43
|
+
exe_kwarg: str | None
|
|
44
|
+
executables: tuple[str, ...]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Constructor kwarg names mirror the real backend classes (field-tested in rbc).
|
|
48
|
+
_BACKENDS: dict[str, _Backend] = {
|
|
49
|
+
"docker": _Backend("styxdocker", "DockerRunner", "docker_executable", ("docker",)),
|
|
50
|
+
"podman": _Backend("styxpodman", "PodmanRunner", "podman_executable", ("podman",)),
|
|
51
|
+
"singularity": _Backend(
|
|
52
|
+
"styxsingularity",
|
|
53
|
+
"SingularityRunner",
|
|
54
|
+
"singularity_executable",
|
|
55
|
+
("apptainer", "singularity"),
|
|
56
|
+
),
|
|
57
|
+
"graph": _Backend("styxgraph", "GraphRunner", None, ()),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _runner_factory(kind: str) -> typing.Callable[..., Runner]:
|
|
62
|
+
"""Import a backend's runner class (a Runner factory), or raise a friendly error.
|
|
63
|
+
|
|
64
|
+
Typed as ``Callable[..., Runner]`` rather than ``type[Runner]``: each backend
|
|
65
|
+
constructor has its own keyword signature, none of which the base ``Runner``
|
|
66
|
+
protocol declares, so the constructor kwargs are forwarded opaquely.
|
|
67
|
+
"""
|
|
68
|
+
backend = _BACKENDS[kind]
|
|
69
|
+
try:
|
|
70
|
+
module = importlib.import_module(backend.module)
|
|
71
|
+
except ModuleNotFoundError as exc:
|
|
72
|
+
# Only translate the backend package being absent. A ModuleNotFoundError
|
|
73
|
+
# naming something else means an *installed* backend failed to import a
|
|
74
|
+
# transitive dependency - re-raise that as-is rather than mislabeling it
|
|
75
|
+
# as "install styxkit[...]".
|
|
76
|
+
if exc.name != backend.module:
|
|
77
|
+
raise
|
|
78
|
+
raise ModuleNotFoundError(
|
|
79
|
+
f"The {kind!r} runner needs the {backend.module!r} package. "
|
|
80
|
+
f'Install it with `pip install "styxkit[{kind}]"` '
|
|
81
|
+
f"(or `pip install {backend.module}`)."
|
|
82
|
+
) from exc
|
|
83
|
+
return typing.cast("typing.Callable[..., Runner]", getattr(module, backend.cls))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _use(runner: Runner) -> Runner:
|
|
87
|
+
"""Register ``runner`` as the global runner and return it."""
|
|
88
|
+
set_global_runner(runner)
|
|
89
|
+
return runner
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def use_local(**kwargs: typing.Any) -> Runner:
|
|
93
|
+
"""Register a ``LocalRunner`` as the global runner and return it."""
|
|
94
|
+
return _use(LocalRunner(**kwargs))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def use_dry() -> Runner:
|
|
98
|
+
"""Register a ``DryRunner`` as the global runner and return it.
|
|
99
|
+
|
|
100
|
+
``DryRunner`` takes no configuration, so this helper accepts no arguments.
|
|
101
|
+
"""
|
|
102
|
+
return _use(DryRunner())
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def use_docker(**kwargs: typing.Any) -> Runner:
|
|
106
|
+
"""Register a ``DockerRunner`` as the global runner and return it.
|
|
107
|
+
|
|
108
|
+
Requires the ``styxdocker`` package (``pip install "styxkit[docker]"``).
|
|
109
|
+
"""
|
|
110
|
+
return _use(_runner_factory("docker")(**kwargs))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def use_podman(**kwargs: typing.Any) -> Runner:
|
|
114
|
+
"""Register a ``PodmanRunner`` as the global runner and return it.
|
|
115
|
+
|
|
116
|
+
Requires the ``styxpodman`` package (``pip install "styxkit[podman]"``).
|
|
117
|
+
"""
|
|
118
|
+
return _use(_runner_factory("podman")(**kwargs))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def use_singularity(**kwargs: typing.Any) -> Runner:
|
|
122
|
+
"""Register a ``SingularityRunner`` as the global runner and return it.
|
|
123
|
+
|
|
124
|
+
Requires the ``styxsingularity`` package
|
|
125
|
+
(``pip install "styxkit[singularity]"``).
|
|
126
|
+
"""
|
|
127
|
+
return _use(_runner_factory("singularity")(**kwargs))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def use_graph(base: Runner | None = None, **kwargs: typing.Any) -> Runner:
|
|
131
|
+
"""Wrap a runner in a ``GraphRunner`` and register it as the global runner.
|
|
132
|
+
|
|
133
|
+
Unlike the leaf runners, ``GraphRunner`` decorates a base runner. When
|
|
134
|
+
``base`` is omitted it wraps the current global runner, so ``use_graph()``
|
|
135
|
+
starts recording a graph over whatever runner is already active.
|
|
136
|
+
|
|
137
|
+
Requires the ``styxgraph`` package (``pip install "styxkit[graph]"``).
|
|
138
|
+
"""
|
|
139
|
+
resolved_base = base if base is not None else get_global_runner()
|
|
140
|
+
return _use(_runner_factory("graph")(resolved_base, **kwargs))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _available(kind: str) -> bool:
|
|
144
|
+
"""True iff a backend is importable and one of its executables is on PATH."""
|
|
145
|
+
backend = _BACKENDS[kind]
|
|
146
|
+
if importlib.util.find_spec(backend.module) is None:
|
|
147
|
+
return False
|
|
148
|
+
return any(shutil.which(exe) for exe in backend.executables)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def resolve_runner(runner: RunnerType | typing.Literal["auto"] = "auto") -> RunnerType:
|
|
152
|
+
"""Resolve a runner selection, auto-detecting when ``runner == "auto"``.
|
|
153
|
+
|
|
154
|
+
Auto prefers the first container backend that is both installed and has its
|
|
155
|
+
executable on PATH (docker > podman > singularity), falling back to
|
|
156
|
+
``"local"``.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
runner: An explicit runner kind, or ``"auto"`` to detect one.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
The resolved runner kind.
|
|
163
|
+
"""
|
|
164
|
+
if runner != "auto":
|
|
165
|
+
return runner
|
|
166
|
+
for kind in ("docker", "podman", "singularity"):
|
|
167
|
+
if _available(kind):
|
|
168
|
+
return typing.cast(RunnerType, kind)
|
|
169
|
+
return "local"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def use_auto(**kwargs: typing.Any) -> Runner:
|
|
173
|
+
"""Detect the best available runner, register it as global, and return it.
|
|
174
|
+
|
|
175
|
+
Detection order is described in :func:`resolve_runner`. For a container
|
|
176
|
+
runner, the detected executable (e.g. ``apptainer`` vs ``singularity``) is
|
|
177
|
+
passed through unless the caller already supplied it. Extra keyword
|
|
178
|
+
arguments are forwarded to the selected runner's constructor.
|
|
179
|
+
"""
|
|
180
|
+
kind = resolve_runner("auto")
|
|
181
|
+
if kind == "local":
|
|
182
|
+
return use_local(**kwargs)
|
|
183
|
+
backend = _BACKENDS[kind]
|
|
184
|
+
if backend.exe_kwarg and backend.exe_kwarg not in kwargs:
|
|
185
|
+
exe = next((e for e in backend.executables if shutil.which(e)), None)
|
|
186
|
+
if exe is not None:
|
|
187
|
+
kwargs[backend.exe_kwarg] = exe
|
|
188
|
+
dispatch: dict[str, typing.Callable[..., Runner]] = {
|
|
189
|
+
"docker": use_docker,
|
|
190
|
+
"podman": use_podman,
|
|
191
|
+
"singularity": use_singularity,
|
|
192
|
+
}
|
|
193
|
+
return dispatch[kind](**kwargs)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
__all__ = [
|
|
197
|
+
"DryRunner",
|
|
198
|
+
"Execution",
|
|
199
|
+
"InputPathType",
|
|
200
|
+
"LocalRunner",
|
|
201
|
+
"Metadata",
|
|
202
|
+
"OutputPathType",
|
|
203
|
+
"Runner",
|
|
204
|
+
"RunnerType",
|
|
205
|
+
"StyxRuntimeError",
|
|
206
|
+
"StyxValidationError",
|
|
207
|
+
"get_global_runner",
|
|
208
|
+
"resolve_runner",
|
|
209
|
+
"set_global_runner",
|
|
210
|
+
"use_auto",
|
|
211
|
+
"use_docker",
|
|
212
|
+
"use_dry",
|
|
213
|
+
"use_graph",
|
|
214
|
+
"use_local",
|
|
215
|
+
"use_podman",
|
|
216
|
+
"use_singularity",
|
|
217
|
+
]
|
|
File without changes
|