Mimiry 0.2.1__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.
- mimiry-0.2.1/PKG-INFO +130 -0
- mimiry-0.2.1/README.md +109 -0
- mimiry-0.2.1/pyproject.toml +44 -0
- mimiry-0.2.1/setup.cfg +4 -0
- mimiry-0.2.1/src/Mimiry.egg-info/PKG-INFO +130 -0
- mimiry-0.2.1/src/Mimiry.egg-info/SOURCES.txt +22 -0
- mimiry-0.2.1/src/Mimiry.egg-info/dependency_links.txt +1 -0
- mimiry-0.2.1/src/Mimiry.egg-info/entry_points.txt +2 -0
- mimiry-0.2.1/src/Mimiry.egg-info/requires.txt +6 -0
- mimiry-0.2.1/src/Mimiry.egg-info/top_level.txt +1 -0
- mimiry-0.2.1/src/mimiry/__init__.py +35 -0
- mimiry-0.2.1/src/mimiry/__main__.py +8 -0
- mimiry-0.2.1/src/mimiry/_auth.py +188 -0
- mimiry-0.2.1/src/mimiry/_cli.py +101 -0
- mimiry-0.2.1/src/mimiry/_client.py +122 -0
- mimiry-0.2.1/src/mimiry/_config.py +58 -0
- mimiry-0.2.1/src/mimiry/_serialization.py +250 -0
- mimiry-0.2.1/src/mimiry/_session.py +199 -0
- mimiry-0.2.1/src/mimiry/_setup.py +212 -0
- mimiry-0.2.1/src/mimiry/_ssh.py +320 -0
- mimiry-0.2.1/src/mimiry/exceptions.py +47 -0
- mimiry-0.2.1/src/mimiry/function.py +290 -0
- mimiry-0.2.1/src/mimiry/image.py +72 -0
- mimiry-0.2.1/src/mimiry/run.py +217 -0
mimiry-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: Mimiry
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Python SDK for serverless compute
|
|
5
|
+
Author-email: Oliver <oliversorensen39@gmail.com>
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Project-URL: Homepage, https://mimiry.com
|
|
8
|
+
Project-URL: Repository, https://github.com/OTSorensen/mimiry-python-sdk
|
|
9
|
+
Project-URL: Issues, https://github.com/OTSorensen/mimiry-python-sdk/issues
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: httpx>=0.27
|
|
17
|
+
Requires-Dist: cloudpickle>=3.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
20
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
21
|
+
|
|
22
|
+
# mimiry — Python SDK for Mimiry GPU compute
|
|
23
|
+
|
|
24
|
+
**Status:** alpha — early access
|
|
25
|
+
**Backend:** softlaunch.mimiry.com (beta)
|
|
26
|
+
|
|
27
|
+
Python-native interface for running serverless cloud GPU jobs on Mimiry, with full control over locality and providers.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install mimiry
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Or, for local development from a clone of this repo (editable install):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install -e .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Auth
|
|
42
|
+
|
|
43
|
+
Running jobs requires a Mimiry account. The SDK authenticates with SSH-JWT — the same SSH key you register on your account at the [Mimiry portal](https://softlaunch.mimiry.com). The fastest way to get set up is the interactive wizard, which generates a key (if needed), walks you through registering it in the portal, writes `MIMIRY_SSH_KEY` to your shell profile, and verifies the connection:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
mimiry setup # alias: mimiry init
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This is a one-time step — you're set going forward.
|
|
50
|
+
|
|
51
|
+
To configure auth manually instead, point the SDK at your private key:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
export MIMIRY_SSH_KEY=~/.ssh/mimiry
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or pass `ssh_key_path=` explicitly to `mimiry.configure()`.
|
|
58
|
+
|
|
59
|
+
## GPU types and providers
|
|
60
|
+
|
|
61
|
+
Mimiry sources GPUs from both local datacenters and cloud providers across Europe and the US, spanning entry-level cards up to the latest high-end accelerators. You control locality and hardware requirements, as well as which providers to use.
|
|
62
|
+
|
|
63
|
+
Always check what's currently available before selecting hardware:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
mimiry availability
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
To filter to a single GPU family, add `--gpu-family <FAMILY>`.
|
|
70
|
+
|
|
71
|
+
## Python version
|
|
72
|
+
|
|
73
|
+
Your local Python `major.minor` must match the Python inside your container
|
|
74
|
+
image. The SDK ships your function to the GPU with `cloudpickle`, which can't
|
|
75
|
+
move code objects across Python versions — e.g. a function pickled on 3.12
|
|
76
|
+
won't load on 3.10.
|
|
77
|
+
|
|
78
|
+
You don't need to think about this with the default image: it ships Python
|
|
79
|
+
3.12, matching recent Ubuntu / Debian / Fedora. It only matters if you set
|
|
80
|
+
`image=` yourself — pick one whose `python3` matches your local interpreter.
|
|
81
|
+
Confirm with `python3 --version` locally and inside the image; a mismatch
|
|
82
|
+
shows up as a failure to deserialize your function.
|
|
83
|
+
|
|
84
|
+
## Quickstart — one-shot function
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import mimiry
|
|
88
|
+
|
|
89
|
+
@mimiry.function(
|
|
90
|
+
# Uses default hardware; run `mimiry availability` to choose a GPU/provider.
|
|
91
|
+
image="nvcr.io/nvidia/cuda:12.6.2-runtime-ubuntu24.04",
|
|
92
|
+
)
|
|
93
|
+
def gpu_info():
|
|
94
|
+
import subprocess
|
|
95
|
+
return subprocess.check_output(
|
|
96
|
+
["nvidia-smi", "--query-gpu=name,memory.total", "--format=csv"],
|
|
97
|
+
text=True,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
print(gpu_info.remote())
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Quickstart — raw bash command
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
import mimiry
|
|
107
|
+
|
|
108
|
+
result = mimiry.run(
|
|
109
|
+
image="nvcr.io/nvidia/cuda:12.6.2-runtime-ubuntu24.04",
|
|
110
|
+
command="nvidia-smi",
|
|
111
|
+
)
|
|
112
|
+
print(result.logs)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## What works in this version
|
|
116
|
+
|
|
117
|
+
- `@mimiry.function(gpu=..., image=...)` decorator
|
|
118
|
+
- `.remote(*args, **kwargs)` — sync call, returns the function's return value
|
|
119
|
+
- `.map(iterable)` — runs the function over an iterable, sequentially
|
|
120
|
+
- `Image.from_registry(uri).pip_install(...).apt_install(...)` — basic image customisation (installs at container start; no real Dockerfile build)
|
|
121
|
+
- `mimiry.run(image, gpu, command)` — raw bash entrypoint
|
|
122
|
+
- SSH-JWT auth via existing key
|
|
123
|
+
|
|
124
|
+
## Examples
|
|
125
|
+
|
|
126
|
+
See `examples/`:
|
|
127
|
+
|
|
128
|
+
- `01_hello.py` — minimal `nvidia-smi` on a GPU
|
|
129
|
+
- `02_cuda_probe.py` — probe the GPU (driver, CUDA, device count) and return a structured Python dict
|
|
130
|
+
- `03_bash_command.py` — run an arbitrary shell command with `mimiry.run()`
|
mimiry-0.2.1/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# mimiry — Python SDK for Mimiry GPU compute
|
|
2
|
+
|
|
3
|
+
**Status:** alpha — early access
|
|
4
|
+
**Backend:** softlaunch.mimiry.com (beta)
|
|
5
|
+
|
|
6
|
+
Python-native interface for running serverless cloud GPU jobs on Mimiry, with full control over locality and providers.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install mimiry
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Or, for local development from a clone of this repo (editable install):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install -e .
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Auth
|
|
21
|
+
|
|
22
|
+
Running jobs requires a Mimiry account. The SDK authenticates with SSH-JWT — the same SSH key you register on your account at the [Mimiry portal](https://softlaunch.mimiry.com). The fastest way to get set up is the interactive wizard, which generates a key (if needed), walks you through registering it in the portal, writes `MIMIRY_SSH_KEY` to your shell profile, and verifies the connection:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
mimiry setup # alias: mimiry init
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This is a one-time step — you're set going forward.
|
|
29
|
+
|
|
30
|
+
To configure auth manually instead, point the SDK at your private key:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
export MIMIRY_SSH_KEY=~/.ssh/mimiry
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or pass `ssh_key_path=` explicitly to `mimiry.configure()`.
|
|
37
|
+
|
|
38
|
+
## GPU types and providers
|
|
39
|
+
|
|
40
|
+
Mimiry sources GPUs from both local datacenters and cloud providers across Europe and the US, spanning entry-level cards up to the latest high-end accelerators. You control locality and hardware requirements, as well as which providers to use.
|
|
41
|
+
|
|
42
|
+
Always check what's currently available before selecting hardware:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
mimiry availability
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
To filter to a single GPU family, add `--gpu-family <FAMILY>`.
|
|
49
|
+
|
|
50
|
+
## Python version
|
|
51
|
+
|
|
52
|
+
Your local Python `major.minor` must match the Python inside your container
|
|
53
|
+
image. The SDK ships your function to the GPU with `cloudpickle`, which can't
|
|
54
|
+
move code objects across Python versions — e.g. a function pickled on 3.12
|
|
55
|
+
won't load on 3.10.
|
|
56
|
+
|
|
57
|
+
You don't need to think about this with the default image: it ships Python
|
|
58
|
+
3.12, matching recent Ubuntu / Debian / Fedora. It only matters if you set
|
|
59
|
+
`image=` yourself — pick one whose `python3` matches your local interpreter.
|
|
60
|
+
Confirm with `python3 --version` locally and inside the image; a mismatch
|
|
61
|
+
shows up as a failure to deserialize your function.
|
|
62
|
+
|
|
63
|
+
## Quickstart — one-shot function
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import mimiry
|
|
67
|
+
|
|
68
|
+
@mimiry.function(
|
|
69
|
+
# Uses default hardware; run `mimiry availability` to choose a GPU/provider.
|
|
70
|
+
image="nvcr.io/nvidia/cuda:12.6.2-runtime-ubuntu24.04",
|
|
71
|
+
)
|
|
72
|
+
def gpu_info():
|
|
73
|
+
import subprocess
|
|
74
|
+
return subprocess.check_output(
|
|
75
|
+
["nvidia-smi", "--query-gpu=name,memory.total", "--format=csv"],
|
|
76
|
+
text=True,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
print(gpu_info.remote())
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Quickstart — raw bash command
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
import mimiry
|
|
86
|
+
|
|
87
|
+
result = mimiry.run(
|
|
88
|
+
image="nvcr.io/nvidia/cuda:12.6.2-runtime-ubuntu24.04",
|
|
89
|
+
command="nvidia-smi",
|
|
90
|
+
)
|
|
91
|
+
print(result.logs)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## What works in this version
|
|
95
|
+
|
|
96
|
+
- `@mimiry.function(gpu=..., image=...)` decorator
|
|
97
|
+
- `.remote(*args, **kwargs)` — sync call, returns the function's return value
|
|
98
|
+
- `.map(iterable)` — runs the function over an iterable, sequentially
|
|
99
|
+
- `Image.from_registry(uri).pip_install(...).apt_install(...)` — basic image customisation (installs at container start; no real Dockerfile build)
|
|
100
|
+
- `mimiry.run(image, gpu, command)` — raw bash entrypoint
|
|
101
|
+
- SSH-JWT auth via existing key
|
|
102
|
+
|
|
103
|
+
## Examples
|
|
104
|
+
|
|
105
|
+
See `examples/`:
|
|
106
|
+
|
|
107
|
+
- `01_hello.py` — minimal `nvidia-smi` on a GPU
|
|
108
|
+
- `02_cuda_probe.py` — probe the GPU (driver, CUDA, device count) and return a structured Python dict
|
|
109
|
+
- `03_bash_command.py` — run an arbitrary shell command with `mimiry.run()`
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "Mimiry"
|
|
7
|
+
version = "0.2.1"
|
|
8
|
+
description = "Python SDK for serverless compute"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "Proprietary" }
|
|
12
|
+
authors = [{ name = "Oliver", email = "oliversorensen39@gmail.com" }]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Programming Language :: Python :: 3.10",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
dependencies = [
|
|
21
|
+
"httpx>=0.27",
|
|
22
|
+
"cloudpickle>=3.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = [
|
|
27
|
+
"pytest>=8",
|
|
28
|
+
"ruff>=0.5",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://mimiry.com"
|
|
33
|
+
Repository = "https://github.com/OTSorensen/mimiry-python-sdk"
|
|
34
|
+
Issues = "https://github.com/OTSorensen/mimiry-python-sdk/issues"
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
mimiry = "mimiry._cli:main"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
|
41
|
+
|
|
42
|
+
[tool.ruff]
|
|
43
|
+
line-length = 100
|
|
44
|
+
target-version = "py310"
|
mimiry-0.2.1/setup.cfg
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: Mimiry
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Python SDK for serverless compute
|
|
5
|
+
Author-email: Oliver <oliversorensen39@gmail.com>
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Project-URL: Homepage, https://mimiry.com
|
|
8
|
+
Project-URL: Repository, https://github.com/OTSorensen/mimiry-python-sdk
|
|
9
|
+
Project-URL: Issues, https://github.com/OTSorensen/mimiry-python-sdk/issues
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: httpx>=0.27
|
|
17
|
+
Requires-Dist: cloudpickle>=3.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
20
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
21
|
+
|
|
22
|
+
# mimiry — Python SDK for Mimiry GPU compute
|
|
23
|
+
|
|
24
|
+
**Status:** alpha — early access
|
|
25
|
+
**Backend:** softlaunch.mimiry.com (beta)
|
|
26
|
+
|
|
27
|
+
Python-native interface for running serverless cloud GPU jobs on Mimiry, with full control over locality and providers.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install mimiry
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Or, for local development from a clone of this repo (editable install):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install -e .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Auth
|
|
42
|
+
|
|
43
|
+
Running jobs requires a Mimiry account. The SDK authenticates with SSH-JWT — the same SSH key you register on your account at the [Mimiry portal](https://softlaunch.mimiry.com). The fastest way to get set up is the interactive wizard, which generates a key (if needed), walks you through registering it in the portal, writes `MIMIRY_SSH_KEY` to your shell profile, and verifies the connection:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
mimiry setup # alias: mimiry init
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This is a one-time step — you're set going forward.
|
|
50
|
+
|
|
51
|
+
To configure auth manually instead, point the SDK at your private key:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
export MIMIRY_SSH_KEY=~/.ssh/mimiry
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or pass `ssh_key_path=` explicitly to `mimiry.configure()`.
|
|
58
|
+
|
|
59
|
+
## GPU types and providers
|
|
60
|
+
|
|
61
|
+
Mimiry sources GPUs from both local datacenters and cloud providers across Europe and the US, spanning entry-level cards up to the latest high-end accelerators. You control locality and hardware requirements, as well as which providers to use.
|
|
62
|
+
|
|
63
|
+
Always check what's currently available before selecting hardware:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
mimiry availability
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
To filter to a single GPU family, add `--gpu-family <FAMILY>`.
|
|
70
|
+
|
|
71
|
+
## Python version
|
|
72
|
+
|
|
73
|
+
Your local Python `major.minor` must match the Python inside your container
|
|
74
|
+
image. The SDK ships your function to the GPU with `cloudpickle`, which can't
|
|
75
|
+
move code objects across Python versions — e.g. a function pickled on 3.12
|
|
76
|
+
won't load on 3.10.
|
|
77
|
+
|
|
78
|
+
You don't need to think about this with the default image: it ships Python
|
|
79
|
+
3.12, matching recent Ubuntu / Debian / Fedora. It only matters if you set
|
|
80
|
+
`image=` yourself — pick one whose `python3` matches your local interpreter.
|
|
81
|
+
Confirm with `python3 --version` locally and inside the image; a mismatch
|
|
82
|
+
shows up as a failure to deserialize your function.
|
|
83
|
+
|
|
84
|
+
## Quickstart — one-shot function
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import mimiry
|
|
88
|
+
|
|
89
|
+
@mimiry.function(
|
|
90
|
+
# Uses default hardware; run `mimiry availability` to choose a GPU/provider.
|
|
91
|
+
image="nvcr.io/nvidia/cuda:12.6.2-runtime-ubuntu24.04",
|
|
92
|
+
)
|
|
93
|
+
def gpu_info():
|
|
94
|
+
import subprocess
|
|
95
|
+
return subprocess.check_output(
|
|
96
|
+
["nvidia-smi", "--query-gpu=name,memory.total", "--format=csv"],
|
|
97
|
+
text=True,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
print(gpu_info.remote())
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Quickstart — raw bash command
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
import mimiry
|
|
107
|
+
|
|
108
|
+
result = mimiry.run(
|
|
109
|
+
image="nvcr.io/nvidia/cuda:12.6.2-runtime-ubuntu24.04",
|
|
110
|
+
command="nvidia-smi",
|
|
111
|
+
)
|
|
112
|
+
print(result.logs)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## What works in this version
|
|
116
|
+
|
|
117
|
+
- `@mimiry.function(gpu=..., image=...)` decorator
|
|
118
|
+
- `.remote(*args, **kwargs)` — sync call, returns the function's return value
|
|
119
|
+
- `.map(iterable)` — runs the function over an iterable, sequentially
|
|
120
|
+
- `Image.from_registry(uri).pip_install(...).apt_install(...)` — basic image customisation (installs at container start; no real Dockerfile build)
|
|
121
|
+
- `mimiry.run(image, gpu, command)` — raw bash entrypoint
|
|
122
|
+
- SSH-JWT auth via existing key
|
|
123
|
+
|
|
124
|
+
## Examples
|
|
125
|
+
|
|
126
|
+
See `examples/`:
|
|
127
|
+
|
|
128
|
+
- `01_hello.py` — minimal `nvidia-smi` on a GPU
|
|
129
|
+
- `02_cuda_probe.py` — probe the GPU (driver, CUDA, device count) and return a structured Python dict
|
|
130
|
+
- `03_bash_command.py` — run an arbitrary shell command with `mimiry.run()`
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/Mimiry.egg-info/PKG-INFO
|
|
4
|
+
src/Mimiry.egg-info/SOURCES.txt
|
|
5
|
+
src/Mimiry.egg-info/dependency_links.txt
|
|
6
|
+
src/Mimiry.egg-info/entry_points.txt
|
|
7
|
+
src/Mimiry.egg-info/requires.txt
|
|
8
|
+
src/Mimiry.egg-info/top_level.txt
|
|
9
|
+
src/mimiry/__init__.py
|
|
10
|
+
src/mimiry/__main__.py
|
|
11
|
+
src/mimiry/_auth.py
|
|
12
|
+
src/mimiry/_cli.py
|
|
13
|
+
src/mimiry/_client.py
|
|
14
|
+
src/mimiry/_config.py
|
|
15
|
+
src/mimiry/_serialization.py
|
|
16
|
+
src/mimiry/_session.py
|
|
17
|
+
src/mimiry/_setup.py
|
|
18
|
+
src/mimiry/_ssh.py
|
|
19
|
+
src/mimiry/exceptions.py
|
|
20
|
+
src/mimiry/function.py
|
|
21
|
+
src/mimiry/image.py
|
|
22
|
+
src/mimiry/run.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mimiry
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""mimiry — Python SDK for Mimiry GPU compute (softlaunch).
|
|
2
|
+
|
|
3
|
+
v0.2.0 — wraps the existing /api/compute/v1/sessions API. See README.md.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from mimiry._config import configure, get_config
|
|
7
|
+
from mimiry.exceptions import (
|
|
8
|
+
MimiryError,
|
|
9
|
+
AuthError,
|
|
10
|
+
SessionError,
|
|
11
|
+
SessionFailed,
|
|
12
|
+
SessionTimeout,
|
|
13
|
+
ResultParseError,
|
|
14
|
+
)
|
|
15
|
+
from mimiry.function import function, Function
|
|
16
|
+
from mimiry.image import Image
|
|
17
|
+
from mimiry.run import run
|
|
18
|
+
|
|
19
|
+
__version__ = "0.2.0"
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"__version__",
|
|
23
|
+
"configure",
|
|
24
|
+
"get_config",
|
|
25
|
+
"function",
|
|
26
|
+
"Function",
|
|
27
|
+
"Image",
|
|
28
|
+
"run",
|
|
29
|
+
"MimiryError",
|
|
30
|
+
"AuthError",
|
|
31
|
+
"SessionError",
|
|
32
|
+
"SessionFailed",
|
|
33
|
+
"SessionTimeout",
|
|
34
|
+
"ResultParseError",
|
|
35
|
+
]
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""SSH-JWT authentication against softlaunch.mimiry.com.
|
|
2
|
+
|
|
3
|
+
Ports the algorithm from .claude/skills/mimiry-softlaunch/scripts/mimiry-auth.sh.
|
|
4
|
+
We shell out to ``ssh-keygen`` for signing — implementing the SSH signature
|
|
5
|
+
format in pure Python would mean re-deriving an OpenSSH-compatible format from
|
|
6
|
+
the ``cryptography`` library, and ``ssh-keygen`` is universally available on
|
|
7
|
+
the systems Mimiry users actually run on.
|
|
8
|
+
|
|
9
|
+
Tokens last 1 hour (Mimiry default). The Token class refreshes itself when
|
|
10
|
+
within 5 minutes of expiry.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import base64
|
|
16
|
+
import os
|
|
17
|
+
import secrets
|
|
18
|
+
import shutil
|
|
19
|
+
import subprocess
|
|
20
|
+
import tempfile
|
|
21
|
+
import time
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
import httpx
|
|
26
|
+
|
|
27
|
+
from mimiry.exceptions import AuthError
|
|
28
|
+
|
|
29
|
+
_TOKEN_REFRESH_BUFFER_SECONDS = 300 # refresh if < 5 min left
|
|
30
|
+
_DEFAULT_TOKEN_TTL_SECONDS = 3600
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class Token:
|
|
35
|
+
"""A short-lived JWT issued by Mimiry. Self-refreshes when near expiry."""
|
|
36
|
+
|
|
37
|
+
access_token: str
|
|
38
|
+
expires_at: float # unix seconds
|
|
39
|
+
fingerprint: str
|
|
40
|
+
ssh_key_path: Path
|
|
41
|
+
api_base: str
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def is_expired(self) -> bool:
|
|
45
|
+
return time.time() >= self.expires_at
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def is_near_expiry(self) -> bool:
|
|
49
|
+
return time.time() + _TOKEN_REFRESH_BUFFER_SECONDS >= self.expires_at
|
|
50
|
+
|
|
51
|
+
def refresh(self) -> "Token":
|
|
52
|
+
"""Re-exchange the SSH signature for a fresh JWT. Mutates self."""
|
|
53
|
+
new = exchange_ssh_for_token(self.ssh_key_path, self.api_base)
|
|
54
|
+
self.access_token = new.access_token
|
|
55
|
+
self.expires_at = new.expires_at
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
def get(self) -> str:
|
|
59
|
+
"""Return the bearer string, refreshing if near expiry."""
|
|
60
|
+
if self.is_near_expiry:
|
|
61
|
+
self.refresh()
|
|
62
|
+
return self.access_token
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _normalize_key_path(key_path: str | Path) -> Path:
|
|
66
|
+
"""Accept either the private key path or the .pub path; return the private path."""
|
|
67
|
+
p = Path(key_path).expanduser()
|
|
68
|
+
if p.suffix == ".pub":
|
|
69
|
+
p = p.with_suffix("")
|
|
70
|
+
if not p.is_file():
|
|
71
|
+
raise AuthError(f"SSH private key not found: {p}")
|
|
72
|
+
if not p.with_suffix(p.suffix + ".pub").is_file() and not Path(f"{p}.pub").is_file():
|
|
73
|
+
raise AuthError(f"SSH public key not found: {p}.pub")
|
|
74
|
+
return p
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _fingerprint(public_key_path: Path) -> str:
|
|
78
|
+
"""Run ``ssh-keygen -lf`` to get the SHA256 fingerprint of a public key."""
|
|
79
|
+
if shutil.which("ssh-keygen") is None:
|
|
80
|
+
raise AuthError("ssh-keygen not found on PATH — required for Mimiry auth")
|
|
81
|
+
try:
|
|
82
|
+
out = subprocess.run(
|
|
83
|
+
["ssh-keygen", "-lf", str(public_key_path)],
|
|
84
|
+
capture_output=True,
|
|
85
|
+
text=True,
|
|
86
|
+
check=True,
|
|
87
|
+
)
|
|
88
|
+
except subprocess.CalledProcessError as e:
|
|
89
|
+
raise AuthError(f"ssh-keygen -lf failed: {e.stderr.strip()}") from e
|
|
90
|
+
# Format: "<bits> <fingerprint> <comment> (<type>)"
|
|
91
|
+
parts = out.stdout.strip().split()
|
|
92
|
+
if len(parts) < 2:
|
|
93
|
+
raise AuthError(f"unexpected ssh-keygen output: {out.stdout!r}")
|
|
94
|
+
return parts[1]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _sign(message: bytes, private_key_path: Path) -> bytes:
|
|
98
|
+
"""Sign ``message`` with the SSH key under the ``mimiry-auth`` namespace.
|
|
99
|
+
|
|
100
|
+
Returns the raw .sig file contents (OpenSSH SSHSIG format), as Mimiry expects.
|
|
101
|
+
"""
|
|
102
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
103
|
+
msg_path = Path(tmpdir) / "msg"
|
|
104
|
+
msg_path.write_bytes(message)
|
|
105
|
+
try:
|
|
106
|
+
subprocess.run(
|
|
107
|
+
["ssh-keygen", "-Y", "sign", "-f", str(private_key_path), "-n", "mimiry-auth",
|
|
108
|
+
str(msg_path)],
|
|
109
|
+
capture_output=True,
|
|
110
|
+
check=True,
|
|
111
|
+
)
|
|
112
|
+
except subprocess.CalledProcessError as e:
|
|
113
|
+
raise AuthError(
|
|
114
|
+
f"ssh-keygen -Y sign failed: {e.stderr.decode(errors='replace').strip()}"
|
|
115
|
+
) from e
|
|
116
|
+
sig_path = Path(f"{msg_path}.sig")
|
|
117
|
+
if not sig_path.is_file():
|
|
118
|
+
raise AuthError("ssh-keygen did not produce a .sig file")
|
|
119
|
+
return sig_path.read_bytes()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def exchange_ssh_for_token(
|
|
123
|
+
ssh_key_path: str | Path,
|
|
124
|
+
api_base: str = "https://softlaunch.mimiry.com",
|
|
125
|
+
) -> Token:
|
|
126
|
+
"""Do the SSH-signature → JWT exchange. Returns a Token."""
|
|
127
|
+
api_base = api_base.rstrip("/")
|
|
128
|
+
priv = _normalize_key_path(ssh_key_path)
|
|
129
|
+
pub = Path(f"{priv}.pub")
|
|
130
|
+
|
|
131
|
+
fingerprint = _fingerprint(pub)
|
|
132
|
+
timestamp = str(int(time.time()))
|
|
133
|
+
nonce = secrets.token_hex(16)
|
|
134
|
+
|
|
135
|
+
message = f"{fingerprint}\n{timestamp}\n{nonce}".encode()
|
|
136
|
+
signature_bytes = _sign(message, priv)
|
|
137
|
+
signature_b64 = base64.b64encode(signature_bytes).decode()
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
resp = httpx.post(
|
|
141
|
+
f"{api_base}/api/v1/auth/token",
|
|
142
|
+
headers={
|
|
143
|
+
"X-SSH-Fingerprint": fingerprint,
|
|
144
|
+
"X-SSH-Signature": signature_b64,
|
|
145
|
+
"X-SSH-Timestamp": timestamp,
|
|
146
|
+
"X-SSH-Nonce": nonce,
|
|
147
|
+
"Content-Type": "application/json",
|
|
148
|
+
},
|
|
149
|
+
json={"expires_in": _DEFAULT_TOKEN_TTL_SECONDS},
|
|
150
|
+
timeout=30.0,
|
|
151
|
+
)
|
|
152
|
+
except httpx.HTTPError as e:
|
|
153
|
+
raise AuthError(f"token exchange request failed: {e}") from e
|
|
154
|
+
|
|
155
|
+
if resp.status_code != 200:
|
|
156
|
+
raise AuthError(
|
|
157
|
+
f"token exchange returned {resp.status_code}: {resp.text[:500]}"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
body = resp.json()
|
|
161
|
+
access_token = body.get("access_token")
|
|
162
|
+
if not access_token:
|
|
163
|
+
raise AuthError(f"token exchange response missing access_token: {body}")
|
|
164
|
+
|
|
165
|
+
expires_in = body.get("expires_in", _DEFAULT_TOKEN_TTL_SECONDS)
|
|
166
|
+
return Token(
|
|
167
|
+
access_token=access_token,
|
|
168
|
+
expires_at=time.time() + float(expires_in),
|
|
169
|
+
fingerprint=fingerprint,
|
|
170
|
+
ssh_key_path=priv,
|
|
171
|
+
api_base=api_base,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_token(
|
|
176
|
+
ssh_key_path: str | Path | None = None,
|
|
177
|
+
api_base: str = "https://softlaunch.mimiry.com",
|
|
178
|
+
) -> Token:
|
|
179
|
+
"""Get a fresh Token. Falls back to ``MIMIRY_SSH_KEY`` env var when ``ssh_key_path`` is None."""
|
|
180
|
+
if ssh_key_path is None:
|
|
181
|
+
env_key = os.environ.get("MIMIRY_SSH_KEY")
|
|
182
|
+
if not env_key:
|
|
183
|
+
raise AuthError(
|
|
184
|
+
"no SSH key provided — pass ssh_key_path=, set MIMIRY_SSH_KEY env var, "
|
|
185
|
+
"or call mimiry.configure(ssh_key_path=...) first"
|
|
186
|
+
)
|
|
187
|
+
ssh_key_path = env_key
|
|
188
|
+
return exchange_ssh_for_token(ssh_key_path, api_base)
|