urun-cli 0.6.2__tar.gz → 0.7.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.
- {urun_cli-0.6.2 → urun_cli-0.7.0}/CHANGELOG.md +9 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/PKG-INFO +110 -2
- {urun_cli-0.6.2 → urun_cli-0.7.0}/README.md +106 -1
- {urun_cli-0.6.2 → urun_cli-0.7.0}/pyproject.toml +13 -3
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/catalog.py +60 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/cli.py +137 -30
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/deps.py +394 -10
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/dev.py +174 -20
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/discovery.py +450 -2
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/manifest.py +63 -0
- urun_cli-0.7.0/src/urun/rust.py +1497 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/scratch.py +438 -33
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/serve.py +127 -5
- {urun_cli-0.6.2 → urun_cli-0.7.0}/.gitignore +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/LICENSE +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/SECURITY.md +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/hatch_build.py +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/__init__.py +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/_bin/README.md +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/api.py +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/buildlogs.py +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/client_tokens.py +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/config.py +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/devconsole.py +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/devstream.py +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/errors.py +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/fleet.py +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/frontend_launch.py +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/launch_auth.py +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/nodes.py +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/openai_proxy_bin.py +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/repo_deps.py +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/retry.py +0 -0
- {urun_cli-0.6.2 → urun_cli-0.7.0}/src/urun/runtime_health.py +0 -0
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## 0.6.3
|
|
2
|
+
|
|
3
|
+
- Bump the vendored `urun-openai` binary pin to `@urun-sh/openai@0.5.0` — the
|
|
4
|
+
**`omp` agent launcher** (`urun compat omp -p "..."`, the direct
|
|
5
|
+
extension lane: no proxy subprocess, omp loads `@urun-sh/openai/pi-extension`
|
|
6
|
+
via its own `-e` mechanism and selects the app with `--model urun/<slug>`)
|
|
7
|
+
ships through the installed CLI's vendored bundle. Pin + integrity advanced
|
|
8
|
+
in the same commit (supply-chain contract, see `scripts/openai_proxy.pin`).
|
|
9
|
+
|
|
1
10
|
# Changelog
|
|
2
11
|
|
|
3
12
|
## 0.5.4
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: urun-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: End-user CLI for deploying apps to urun
|
|
5
5
|
Project-URL: Homepage, https://urun.sh
|
|
6
6
|
Project-URL: Repository, https://github.com/urun-sh/urun-cli
|
|
@@ -18,11 +18,14 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Classifier: Topic :: Software Development :: Build Tools
|
|
20
20
|
Requires-Python: >=3.11
|
|
21
|
+
Provides-Extra: rust
|
|
22
|
+
Requires-Dist: tree-sitter-rust==0.24.0; extra == 'rust'
|
|
23
|
+
Requires-Dist: tree-sitter==0.25.2; extra == 'rust'
|
|
21
24
|
Description-Content-Type: text/markdown
|
|
22
25
|
|
|
23
26
|
# urun CLI
|
|
24
27
|
|
|
25
|
-
Deploy Python apps to urun from your terminal.
|
|
28
|
+
Deploy Python and Rust (native SDK) apps to urun from your terminal.
|
|
26
29
|
|
|
27
30
|
[](https://pypi.org/project/urun-cli/)
|
|
28
31
|
[](https://pypi.org/project/urun-cli/)
|
|
@@ -35,6 +38,15 @@ uv tool install urun-cli
|
|
|
35
38
|
pip install urun-cli
|
|
36
39
|
```
|
|
37
40
|
|
|
41
|
+
For Rust (native SDK) deploys, install with the `[rust]` extra — see
|
|
42
|
+
[Deploy a Rust app (native SDK)](#deploy-a-rust-app-native-sdk):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv tool install "urun-cli[rust]"
|
|
46
|
+
# or
|
|
47
|
+
pip install "urun-cli[rust]"
|
|
48
|
+
```
|
|
49
|
+
|
|
38
50
|
The package installs the `urun` command:
|
|
39
51
|
|
|
40
52
|
```bash
|
|
@@ -154,6 +166,100 @@ Override the examples checkout with `--examples-root` or `URUN_EXAMPLES_ROOT`;
|
|
|
154
166
|
override the session API with `--session-url` or `URUN_SESSION_BASE_URL`; pin
|
|
155
167
|
the function with `--function` and the environment with `--environment`.
|
|
156
168
|
|
|
169
|
+
## Deploy a Rust app (native SDK)
|
|
170
|
+
|
|
171
|
+
Rust apps use the native SDK crate `urun` and are declared by an inline module
|
|
172
|
+
owning `#[urun::app(name = "...")]` with nested `#[urun::function]` handlers.
|
|
173
|
+
|
|
174
|
+
The executable calls `urun::main!(app)` from `fn main()`. Each handler crosses
|
|
175
|
+
the `ctx.self_test(...)` barrier before accessing session state. The runnable
|
|
176
|
+
`native-echo` example below includes the complete lifecycle.
|
|
177
|
+
|
|
178
|
+
Install the CLI with the Rust parser extra (a plain install stays
|
|
179
|
+
dependency-free; the extra pulls the pinned `tree-sitter`/`tree-sitter-rust`
|
|
180
|
+
wheels used for static declaration extraction — no Rust toolchain needed):
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
uv tool install "urun-cli[rust]"
|
|
184
|
+
# or
|
|
185
|
+
pip install "urun-cli[rust]"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Deploy by pointing at the project's `Cargo.toml` (a `src/main.rs` or the
|
|
189
|
+
project directory also work):
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
urun deploy path/to/project/Cargo.toml
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
- **Vendor the SDK in-root.** The `urun` SDK crate is not published to
|
|
196
|
+
crates.io and the build environment has no private git credentials, so the
|
|
197
|
+
crate must ship inside your deploy root: reference it by a relative path
|
|
198
|
+
dependency (`urun = { path = "vendor/urun" }`) or as a member of the Cargo
|
|
199
|
+
workspace rooted there. Credential-free registry/git dependencies are fine.
|
|
200
|
+
- **`Cargo.lock` is mandatory.** The builder builds with `--locked`; a missing
|
|
201
|
+
lock file fails the deploy.
|
|
202
|
+
- The bundle is the full in-root Cargo workspace / path-dependency / resource
|
|
203
|
+
closure, minus `target/`, `.git`, and `.cargo/credentials{,.toml}`.
|
|
204
|
+
|
|
205
|
+
A runnable starting point is the SDK checkout's `examples/native-echo` app,
|
|
206
|
+
which vendors the SDK via a path dependency and deploys as-is:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
git clone git@github.com:urun-sh/urun-rs.git
|
|
210
|
+
urun deploy urun-rs/examples/native-echo
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Deploy a mixed Python + Rust app
|
|
214
|
+
|
|
215
|
+
ONE app can hold functions implemented in BOTH languages: one identity, one
|
|
216
|
+
release, the full function set replaced atomically on every deploy. Declare
|
|
217
|
+
the sources explicitly in the project's `pyproject.toml` — nothing is scanned
|
|
218
|
+
or inferred, so vendored SDK examples are never pulled into your app:
|
|
219
|
+
|
|
220
|
+
```toml
|
|
221
|
+
# pyproject.toml
|
|
222
|
+
[tool.urun]
|
|
223
|
+
entrypoints = ["app.py", "native/src/main.rs"]
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Python functions register on one `App("mixed-app")`. Rust functions use
|
|
227
|
+
`#[urun::function(...)]` inside `#[urun::app(name = "mixed-app")]`.
|
|
228
|
+
The builder assembles both into one function registry: the language belongs
|
|
229
|
+
to the function, not to a separate app. See the runnable
|
|
230
|
+
[`examples/mixed-app`](https://github.com/urun-sh/urun-rs/tree/main/examples/mixed-app)
|
|
231
|
+
for complete Python and Rust implementations, including self-tests and calls
|
|
232
|
+
in both directions.
|
|
233
|
+
|
|
234
|
+
Deploy the whole app with any of these — each resolves the ENTIRE declared
|
|
235
|
+
app, so one component invocation never accidentally retires its sibling
|
|
236
|
+
functions:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
urun deploy . # the config directory
|
|
240
|
+
urun deploy pyproject.toml # the declaration itself
|
|
241
|
+
urun deploy app.py # a declared component
|
|
242
|
+
urun deploy native/src/main.rs
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
- **One identity across all sources.** Python helpers may import the same
|
|
246
|
+
`App` object; Rust declarations use the same app-name literal. A different
|
|
247
|
+
App object, mismatched name, or duplicate function name is a hard error.
|
|
248
|
+
- **Declared paths stay in the config directory** (root-relative, no `..`
|
|
249
|
+
escapes, every declared file present). The bundle root may widen through
|
|
250
|
+
ordinary Cargo workspace resolution — e.g. a crate that is a member of a
|
|
251
|
+
wider workspace ships that whole workspace closure — and every entrypoint
|
|
252
|
+
and `manifest_path` rebases onto it.
|
|
253
|
+
- **Python dependencies stay Python.** The manifest's `deps` block is
|
|
254
|
+
resolved from the Python source exactly like a pure Python deploy
|
|
255
|
+
(`Dependencies(python=...)`, `requirements.txt`, `python_version`); the
|
|
256
|
+
Rust sources' `apt`/`build_deps` function options fold into the same lists.
|
|
257
|
+
- **Resources ride with their code** in both halves: the Python import/data
|
|
258
|
+
closure plus each Rust crate's full workspace/path closure and every
|
|
259
|
+
referenced `Cargo.lock`.
|
|
260
|
+
|
|
261
|
+
A runnable starting point is the SDK checkout's `examples/mixed-app` app.
|
|
262
|
+
|
|
157
263
|
## Serve a model from the catalog
|
|
158
264
|
|
|
159
265
|
`urun serve` deploys a model straight from the urun model catalog — no app code
|
|
@@ -449,6 +555,8 @@ would fire kubectl at a cluster you are not looking at.
|
|
|
449
555
|
| Entrypoint | Included source |
|
|
450
556
|
| --- | --- |
|
|
451
557
|
| `urun deploy app.py` | `app.py` and local Python files it imports |
|
|
558
|
+
| `urun deploy Cargo.toml` | The in-root Cargo workspace / path-dependency closure (see [Deploy a Rust app (native SDK)](#deploy-a-rust-app-native-sdk)). |
|
|
559
|
+
| `urun deploy .` / `pyproject.toml` | The whole explicitly-declared `[tool.urun]` app — Python + Rust sources, both closures (see [Deploy a mixed Python + Rust app](#deploy-a-mixed-python--rust-app)). A declared component (`app.py`, `native/src/main.rs`) resolves the same entire app. |
|
|
452
560
|
|
|
453
561
|
Dependencies are declared in your urun app code. Project-level files such as
|
|
454
562
|
`pyproject.toml` and `requirements.txt` are not uploaded as dependency
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# urun CLI
|
|
2
2
|
|
|
3
|
-
Deploy Python apps to urun from your terminal.
|
|
3
|
+
Deploy Python and Rust (native SDK) apps to urun from your terminal.
|
|
4
4
|
|
|
5
5
|
[](https://pypi.org/project/urun-cli/)
|
|
6
6
|
[](https://pypi.org/project/urun-cli/)
|
|
@@ -13,6 +13,15 @@ uv tool install urun-cli
|
|
|
13
13
|
pip install urun-cli
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
For Rust (native SDK) deploys, install with the `[rust]` extra — see
|
|
17
|
+
[Deploy a Rust app (native SDK)](#deploy-a-rust-app-native-sdk):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv tool install "urun-cli[rust]"
|
|
21
|
+
# or
|
|
22
|
+
pip install "urun-cli[rust]"
|
|
23
|
+
```
|
|
24
|
+
|
|
16
25
|
The package installs the `urun` command:
|
|
17
26
|
|
|
18
27
|
```bash
|
|
@@ -132,6 +141,100 @@ Override the examples checkout with `--examples-root` or `URUN_EXAMPLES_ROOT`;
|
|
|
132
141
|
override the session API with `--session-url` or `URUN_SESSION_BASE_URL`; pin
|
|
133
142
|
the function with `--function` and the environment with `--environment`.
|
|
134
143
|
|
|
144
|
+
## Deploy a Rust app (native SDK)
|
|
145
|
+
|
|
146
|
+
Rust apps use the native SDK crate `urun` and are declared by an inline module
|
|
147
|
+
owning `#[urun::app(name = "...")]` with nested `#[urun::function]` handlers.
|
|
148
|
+
|
|
149
|
+
The executable calls `urun::main!(app)` from `fn main()`. Each handler crosses
|
|
150
|
+
the `ctx.self_test(...)` barrier before accessing session state. The runnable
|
|
151
|
+
`native-echo` example below includes the complete lifecycle.
|
|
152
|
+
|
|
153
|
+
Install the CLI with the Rust parser extra (a plain install stays
|
|
154
|
+
dependency-free; the extra pulls the pinned `tree-sitter`/`tree-sitter-rust`
|
|
155
|
+
wheels used for static declaration extraction — no Rust toolchain needed):
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
uv tool install "urun-cli[rust]"
|
|
159
|
+
# or
|
|
160
|
+
pip install "urun-cli[rust]"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Deploy by pointing at the project's `Cargo.toml` (a `src/main.rs` or the
|
|
164
|
+
project directory also work):
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
urun deploy path/to/project/Cargo.toml
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
- **Vendor the SDK in-root.** The `urun` SDK crate is not published to
|
|
171
|
+
crates.io and the build environment has no private git credentials, so the
|
|
172
|
+
crate must ship inside your deploy root: reference it by a relative path
|
|
173
|
+
dependency (`urun = { path = "vendor/urun" }`) or as a member of the Cargo
|
|
174
|
+
workspace rooted there. Credential-free registry/git dependencies are fine.
|
|
175
|
+
- **`Cargo.lock` is mandatory.** The builder builds with `--locked`; a missing
|
|
176
|
+
lock file fails the deploy.
|
|
177
|
+
- The bundle is the full in-root Cargo workspace / path-dependency / resource
|
|
178
|
+
closure, minus `target/`, `.git`, and `.cargo/credentials{,.toml}`.
|
|
179
|
+
|
|
180
|
+
A runnable starting point is the SDK checkout's `examples/native-echo` app,
|
|
181
|
+
which vendors the SDK via a path dependency and deploys as-is:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
git clone git@github.com:urun-sh/urun-rs.git
|
|
185
|
+
urun deploy urun-rs/examples/native-echo
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Deploy a mixed Python + Rust app
|
|
189
|
+
|
|
190
|
+
ONE app can hold functions implemented in BOTH languages: one identity, one
|
|
191
|
+
release, the full function set replaced atomically on every deploy. Declare
|
|
192
|
+
the sources explicitly in the project's `pyproject.toml` — nothing is scanned
|
|
193
|
+
or inferred, so vendored SDK examples are never pulled into your app:
|
|
194
|
+
|
|
195
|
+
```toml
|
|
196
|
+
# pyproject.toml
|
|
197
|
+
[tool.urun]
|
|
198
|
+
entrypoints = ["app.py", "native/src/main.rs"]
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Python functions register on one `App("mixed-app")`. Rust functions use
|
|
202
|
+
`#[urun::function(...)]` inside `#[urun::app(name = "mixed-app")]`.
|
|
203
|
+
The builder assembles both into one function registry: the language belongs
|
|
204
|
+
to the function, not to a separate app. See the runnable
|
|
205
|
+
[`examples/mixed-app`](https://github.com/urun-sh/urun-rs/tree/main/examples/mixed-app)
|
|
206
|
+
for complete Python and Rust implementations, including self-tests and calls
|
|
207
|
+
in both directions.
|
|
208
|
+
|
|
209
|
+
Deploy the whole app with any of these — each resolves the ENTIRE declared
|
|
210
|
+
app, so one component invocation never accidentally retires its sibling
|
|
211
|
+
functions:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
urun deploy . # the config directory
|
|
215
|
+
urun deploy pyproject.toml # the declaration itself
|
|
216
|
+
urun deploy app.py # a declared component
|
|
217
|
+
urun deploy native/src/main.rs
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
- **One identity across all sources.** Python helpers may import the same
|
|
221
|
+
`App` object; Rust declarations use the same app-name literal. A different
|
|
222
|
+
App object, mismatched name, or duplicate function name is a hard error.
|
|
223
|
+
- **Declared paths stay in the config directory** (root-relative, no `..`
|
|
224
|
+
escapes, every declared file present). The bundle root may widen through
|
|
225
|
+
ordinary Cargo workspace resolution — e.g. a crate that is a member of a
|
|
226
|
+
wider workspace ships that whole workspace closure — and every entrypoint
|
|
227
|
+
and `manifest_path` rebases onto it.
|
|
228
|
+
- **Python dependencies stay Python.** The manifest's `deps` block is
|
|
229
|
+
resolved from the Python source exactly like a pure Python deploy
|
|
230
|
+
(`Dependencies(python=...)`, `requirements.txt`, `python_version`); the
|
|
231
|
+
Rust sources' `apt`/`build_deps` function options fold into the same lists.
|
|
232
|
+
- **Resources ride with their code** in both halves: the Python import/data
|
|
233
|
+
closure plus each Rust crate's full workspace/path closure and every
|
|
234
|
+
referenced `Cargo.lock`.
|
|
235
|
+
|
|
236
|
+
A runnable starting point is the SDK checkout's `examples/mixed-app` app.
|
|
237
|
+
|
|
135
238
|
## Serve a model from the catalog
|
|
136
239
|
|
|
137
240
|
`urun serve` deploys a model straight from the urun model catalog — no app code
|
|
@@ -427,6 +530,8 @@ would fire kubectl at a cluster you are not looking at.
|
|
|
427
530
|
| Entrypoint | Included source |
|
|
428
531
|
| --- | --- |
|
|
429
532
|
| `urun deploy app.py` | `app.py` and local Python files it imports |
|
|
533
|
+
| `urun deploy Cargo.toml` | The in-root Cargo workspace / path-dependency closure (see [Deploy a Rust app (native SDK)](#deploy-a-rust-app-native-sdk)). |
|
|
534
|
+
| `urun deploy .` / `pyproject.toml` | The whole explicitly-declared `[tool.urun]` app — Python + Rust sources, both closures (see [Deploy a mixed Python + Rust app](#deploy-a-mixed-python--rust-app)). A declared component (`app.py`, `native/src/main.rs`) resolves the same entire app. |
|
|
430
535
|
|
|
431
536
|
Dependencies are declared in your urun app code. Project-level files such as
|
|
432
537
|
`pyproject.toml` and `requirements.txt` are not uploaded as dependency
|
|
@@ -10,7 +10,7 @@ build-backend = "hatchling.build"
|
|
|
10
10
|
|
|
11
11
|
[project]
|
|
12
12
|
name = "urun-cli"
|
|
13
|
-
# Cut the
|
|
13
|
+
# Cut the native Rust SDK deploy path (feature minor on 0.6.x).
|
|
14
14
|
#
|
|
15
15
|
# WHY THIS IS EDITED BY HAND. [tool.semantic_release] below is fully
|
|
16
16
|
# configured -- conventional commit parser, version_toml pointing here, tag
|
|
@@ -19,8 +19,8 @@ name = "urun-cli"
|
|
|
19
19
|
# READS this value and skips publishing when the matching tag already exists,
|
|
20
20
|
# so every merge remains on the already-published version until this value is
|
|
21
21
|
# advanced. Wiring semantic-release into CI remains separate work; this patch
|
|
22
|
-
# bump is the minimal release unblock
|
|
23
|
-
version = "0.
|
|
22
|
+
# bump is the minimal release unblock (post-v0.6.3 fixes + the Rust feature minor).
|
|
23
|
+
version = "0.7.0"
|
|
24
24
|
description = "End-user CLI for deploying apps to urun"
|
|
25
25
|
readme = "README.md"
|
|
26
26
|
requires-python = ">=3.11"
|
|
@@ -49,6 +49,16 @@ Issues = "https://github.com/urun-sh/urun-cli/issues"
|
|
|
49
49
|
urun = "urun.cli:main"
|
|
50
50
|
urun-cli = "urun.cli:main"
|
|
51
51
|
|
|
52
|
+
[project.optional-dependencies]
|
|
53
|
+
# Rust app discovery (urun/rust.py) parses with the pinned native
|
|
54
|
+
# tree-sitter wheels; lazy-imported only for Rust deploys, so the ordinary
|
|
55
|
+
# wheel stays dependency-free. Pin advance = contract native_crate_baselines
|
|
56
|
+
# (tree-sitter 0.25.2 / tree-sitter-rust 0.24.0).
|
|
57
|
+
rust = [
|
|
58
|
+
"tree-sitter==0.25.2",
|
|
59
|
+
"tree-sitter-rust==0.24.0",
|
|
60
|
+
]
|
|
61
|
+
|
|
52
62
|
[dependency-groups]
|
|
53
63
|
dev = [
|
|
54
64
|
# The build backend, present in the dev env so tests can import hatch_build.py
|
|
@@ -104,6 +104,7 @@ ENGINE_DEPS: dict[str, list[str]] = {
|
|
|
104
104
|
"echo-speak": [],
|
|
105
105
|
"echo-video": [],
|
|
106
106
|
"echo-video-diffusion": [],
|
|
107
|
+
"echo-t2av": [],
|
|
107
108
|
"vllm": [
|
|
108
109
|
"vllm==0.24.0",
|
|
109
110
|
"arctic-inference==0.1.1",
|
|
@@ -386,6 +387,65 @@ ENGINE_DEPS: dict[str, list[str]] = {
|
|
|
386
387
|
"huggingface_hub[hf_transfer]>=0.30.0,<1.0.0",
|
|
387
388
|
"sentencepiece",
|
|
388
389
|
],
|
|
390
|
+
"t2av": [
|
|
391
|
+
"remote_pdb>=1.4",
|
|
392
|
+
"scipy>=1.14.1",
|
|
393
|
+
"six>=1.16.0",
|
|
394
|
+
"h5py>=3.12.1",
|
|
395
|
+
"requests>=2.32.2",
|
|
396
|
+
"numpy>=1.26,<2",
|
|
397
|
+
"transformers>=5.0.0",
|
|
398
|
+
"tokenizers>=0.20.1,<0.23",
|
|
399
|
+
"sentencepiece>=0.2.0",
|
|
400
|
+
"timm>=1.0.11",
|
|
401
|
+
"peft>=0.15.0",
|
|
402
|
+
"diffusers>=0.38.0",
|
|
403
|
+
"torch==2.12.0",
|
|
404
|
+
"torchvision",
|
|
405
|
+
"torchaudio",
|
|
406
|
+
"accelerate==1.0.1",
|
|
407
|
+
"opencv-python>=4.10,<5",
|
|
408
|
+
"pillow>=10.3.0",
|
|
409
|
+
"imageio>=2.36.0",
|
|
410
|
+
"imageio-ffmpeg>=0.5.1",
|
|
411
|
+
"einops",
|
|
412
|
+
"loguru",
|
|
413
|
+
"tqdm",
|
|
414
|
+
"PyYAML>=6.0.1",
|
|
415
|
+
"protobuf>=5.28.3",
|
|
416
|
+
"aiohttp",
|
|
417
|
+
"huggingface_hub",
|
|
418
|
+
"cloudpickle",
|
|
419
|
+
"omegaconf",
|
|
420
|
+
"gpustat",
|
|
421
|
+
"ninja",
|
|
422
|
+
"fastvideo-kernel==0.3.2; sys_platform == 'linux'",
|
|
423
|
+
],
|
|
424
|
+
"ltx-realtime": [
|
|
425
|
+
"torch~=2.7",
|
|
426
|
+
"torchaudio",
|
|
427
|
+
"einops",
|
|
428
|
+
"numpy",
|
|
429
|
+
"av",
|
|
430
|
+
"transformers>=5.8.0,<5.15",
|
|
431
|
+
"safetensors",
|
|
432
|
+
"accelerate",
|
|
433
|
+
"scipy>=1.14",
|
|
434
|
+
"tqdm",
|
|
435
|
+
"pillow>=10.0.0",
|
|
436
|
+
"openimageio",
|
|
437
|
+
"cloudpickle>=3.1",
|
|
438
|
+
"huggingface_hub[hf_transfer]>=0.30.0,<1.0.0",
|
|
439
|
+
"sentencepiece",
|
|
440
|
+
"nvidia-cuda-nvcc==13.0.88",
|
|
441
|
+
"nvidia-cuda-crt==13.0.88",
|
|
442
|
+
"nvidia-cuda-cccl==13.0.85",
|
|
443
|
+
"nvidia-nvvm==13.0.88",
|
|
444
|
+
"nvidia-cuda-runtime==13.0.96",
|
|
445
|
+
"ninja",
|
|
446
|
+
"pip",
|
|
447
|
+
"nvidia-cutlass-dsl>=4.6.0.dev0,<5",
|
|
448
|
+
],
|
|
389
449
|
"media-transform": [
|
|
390
450
|
"torch==2.6.0",
|
|
391
451
|
"transformers==4.51.3",
|
|
@@ -50,10 +50,15 @@ from .config import (
|
|
|
50
50
|
secret_consent_scope,
|
|
51
51
|
)
|
|
52
52
|
from .deps import (
|
|
53
|
+
collect_python_sources_metadata,
|
|
53
54
|
confirm_local_env_capture,
|
|
54
55
|
extract_app_credentials_env,
|
|
55
56
|
extract_app_gpu_type,
|
|
56
57
|
extract_app_python_deps,
|
|
58
|
+
fold_rust_options,
|
|
59
|
+
mixed_gpu_type,
|
|
60
|
+
native_gpu_type,
|
|
61
|
+
native_runtime_credentials,
|
|
57
62
|
resolve_declared_credentials,
|
|
58
63
|
resolve_deps,
|
|
59
64
|
running_in_ci,
|
|
@@ -360,7 +365,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
360
365
|
)
|
|
361
366
|
add_login_args(login_parser)
|
|
362
367
|
|
|
363
|
-
deploy = sub.add_parser("deploy", help="Deploy a Python app to urun")
|
|
368
|
+
deploy = sub.add_parser("deploy", help="Deploy a Python or Rust (native SDK) app to urun")
|
|
364
369
|
add_deploy_args(deploy)
|
|
365
370
|
|
|
366
371
|
env_parser = sub.add_parser(
|
|
@@ -444,11 +449,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
444
449
|
"dev",
|
|
445
450
|
help="Watch a local app and hot-reload it on every save, or --attach to a deployed one",
|
|
446
451
|
description=(
|
|
447
|
-
"Deploy a Python app, then watch its directory
|
|
448
|
-
"redeploy on every save. Code-only edits
|
|
449
|
-
"hot-reload fast lane (the running pod
|
|
450
|
-
"
|
|
451
|
-
"
|
|
452
|
+
"Deploy a Python or Rust (native SDK) app, then watch its directory "
|
|
453
|
+
"and continuously redeploy on every save. Code-only Python edits "
|
|
454
|
+
"take the control plane's hot-reload fast lane (the running pod "
|
|
455
|
+
"adopts the new code in place, no dependency reinstall or image "
|
|
456
|
+
"rebuild), so iteration is fast; native Rust source changes always "
|
|
457
|
+
"rebuild through Cargo — they are never hot-patched. Changing "
|
|
458
|
+
"declared dependencies, adding/removing files, or moving the "
|
|
452
459
|
"entrypoint falls back to a full rebuild (and the CLI says so). "
|
|
453
460
|
"Dev deploys are ephemeral: while `urun dev` runs the app keeps one "
|
|
454
461
|
"replica warm with capacity capped at 1 (your app's own warm= / "
|
|
@@ -467,10 +474,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
467
474
|
|
|
468
475
|
run = sub.add_parser(
|
|
469
476
|
"run",
|
|
470
|
-
help="Run a Python app on urun",
|
|
477
|
+
help="Run a Python or Rust (native SDK) app on urun",
|
|
471
478
|
description=(
|
|
472
|
-
"Run a Python app on urun. In this release, run
|
|
473
|
-
"while the full deploy/run/monitor workflow
|
|
479
|
+
"Run a Python or Rust (native SDK) app on urun. In this release, run "
|
|
480
|
+
"uses the deploy pipeline while the full deploy/run/monitor workflow "
|
|
481
|
+
"is still being built."
|
|
474
482
|
),
|
|
475
483
|
)
|
|
476
484
|
add_deploy_args(run)
|
|
@@ -1728,7 +1736,14 @@ def make_retry_logger(verbose: bool) -> RetryReporter:
|
|
|
1728
1736
|
|
|
1729
1737
|
|
|
1730
1738
|
def add_deploy_args(parser: argparse.ArgumentParser) -> None:
|
|
1731
|
-
parser.add_argument(
|
|
1739
|
+
parser.add_argument(
|
|
1740
|
+
"entrypoint",
|
|
1741
|
+
nargs="?",
|
|
1742
|
+
help=(
|
|
1743
|
+
"App entrypoint: a Python file (app.py), or a Rust project's "
|
|
1744
|
+
"Cargo.toml / src/main.rs for a native SDK deploy"
|
|
1745
|
+
),
|
|
1746
|
+
)
|
|
1732
1747
|
parser.add_argument("--name", help="App name (defaults from entrypoint file)")
|
|
1733
1748
|
parser.add_argument(
|
|
1734
1749
|
"--environment",
|
|
@@ -2122,13 +2137,47 @@ def deploy_once(args: argparse.Namespace) -> DeployOutcome:
|
|
|
2122
2137
|
layout = discover_deploy_layout(args.entrypoint, invocation_root)
|
|
2123
2138
|
entrypoint, files = layout.entrypoint, layout.files
|
|
2124
2139
|
entrypoint_path = layout.entrypoint_path
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2140
|
+
runtime = layout.runtime
|
|
2141
|
+
if runtime is None:
|
|
2142
|
+
# Python deploy (unchanged behavior). Fail LOUD before registering
|
|
2143
|
+
# anything: an import that would silently not ship (or not import at
|
|
2144
|
+
# runtime) is a deploy-time error, never a pod crashloop (the dreamx
|
|
2145
|
+
# 2026-06-26 silent-drop hop).
|
|
2146
|
+
assert_collected_imports_resolve(
|
|
2147
|
+
layout, declared_deps=extract_app_python_deps(entrypoint_path).get("python", [])
|
|
2148
|
+
)
|
|
2149
|
+
deps, deps_blob, python_version = resolve_deps(layout.root, entrypoint_path)
|
|
2150
|
+
elif runtime.get("kind") == "mixed":
|
|
2151
|
+
# MIXED deploy: ONE app (Python + Rust sources), ONE release. EVERY
|
|
2152
|
+
# declared Python source's import closure is first-class metadata:
|
|
2153
|
+
# credentials, deps and GPU declarations are extracted across the
|
|
2154
|
+
# WHOLE union (deduped by file identity), imports are verified against
|
|
2155
|
+
# the collected closure, deps resolve through the normal Python path
|
|
2156
|
+
# (app/requirements kinds; NEVER the Rust-only supervisor spec) with
|
|
2157
|
+
# per-function declarations preserved for the builder, and the Rust
|
|
2158
|
+
# sources' apt/build_deps options fold into the SAME deps block.
|
|
2159
|
+
# Discovery already rejected identity mismatches and statically-visible
|
|
2160
|
+
# duplicate function names.
|
|
2161
|
+
python_entrypoint_path = layout.python_entrypoint_path
|
|
2162
|
+
# Computed ONCE here and reused by the credentials and GPU-class
|
|
2163
|
+
# blocks below — never re-parsed per metadata consumer.
|
|
2164
|
+
mixed_metadata = collect_python_sources_metadata(layout.collected)
|
|
2165
|
+
assert_collected_imports_resolve(
|
|
2166
|
+
layout._replace(entrypoint_path=python_entrypoint_path),
|
|
2167
|
+
declared_deps=mixed_metadata["python_deps"],
|
|
2168
|
+
)
|
|
2169
|
+
deps, deps_blob, python_version = resolve_deps(
|
|
2170
|
+
layout.root, python_entrypoint_path, python_sources=list(layout.collected)
|
|
2171
|
+
)
|
|
2172
|
+
deps = fold_rust_options(deps, runtime)
|
|
2173
|
+
else:
|
|
2174
|
+
# Native Rust deploy: there is no Python entrypoint to parse, so the
|
|
2175
|
+
# Python import/deps AST scans are skipped entirely. resolve_deps
|
|
2176
|
+
# consumes the discovery-produced runtime object directly — the Rust
|
|
2177
|
+
# source is never reparsed here.
|
|
2178
|
+
deps, deps_blob, python_version = resolve_deps(
|
|
2179
|
+
layout.root, entrypoint_path, runtime=runtime
|
|
2180
|
+
)
|
|
2132
2181
|
|
|
2133
2182
|
# PER-DEPLOY torch backend. `resolve_deps` already threads the app-declared
|
|
2134
2183
|
# ``Dependencies(torch_backend=...)`` into ``deps`` for a plain `urun deploy`.
|
|
@@ -2137,7 +2186,7 @@ def deploy_once(args: argparse.Namespace) -> DeployOutcome:
|
|
|
2137
2186
|
# build's app-deps install (urun-python deps_install) installs the engine's
|
|
2138
2187
|
# pinned torch from the GPU-correct CUDA index.
|
|
2139
2188
|
torch_backend = getattr(args, "torch_backend", None)
|
|
2140
|
-
if torch_backend:
|
|
2189
|
+
if torch_backend and (runtime is None or runtime.get("kind") == "mixed"):
|
|
2141
2190
|
deps = {**deps, "torch_backend": torch_backend}
|
|
2142
2191
|
|
|
2143
2192
|
# Derive the fallback name from the entrypoint's boundary-relative path —
|
|
@@ -2145,22 +2194,45 @@ def deploy_once(args: argparse.Namespace) -> DeployOutcome:
|
|
|
2145
2194
|
# app used to derive "app" from inside backend/ and "backend" from the
|
|
2146
2195
|
# repo root).
|
|
2147
2196
|
entrypoint_rel = entrypoint_path.relative_to(layout.boundary).as_posix()
|
|
2148
|
-
# The app's OWN declared
|
|
2149
|
-
#
|
|
2150
|
-
#
|
|
2151
|
-
#
|
|
2197
|
+
# The app's OWN declared identity is the SINGLE source of truth (OWNER
|
|
2198
|
+
# ruling: one identity per app, no divergence). Python reads a static
|
|
2199
|
+
# App("<literal>") name; a native deploy reads the #[urun::app(name =
|
|
2200
|
+
# "...")] literal carried on the layout. A divergent --name is a HARD
|
|
2201
|
+
# ERROR either way — that mismatch is exactly how one app
|
|
2152
2202
|
# (App("magic-mirror")) got deployed under a second control-plane identity
|
|
2153
2203
|
# (--name magic-mirror-sana) and forked into a duplicate row.
|
|
2154
|
-
declared = declared_app_name(entrypoint_path)
|
|
2204
|
+
declared = declared_app_name(entrypoint_path) if runtime is None else layout.app_name
|
|
2155
2205
|
if args.name is not None and declared is not None and args.name != declared:
|
|
2206
|
+
if runtime is None:
|
|
2207
|
+
declared_how = 'App("...")'
|
|
2208
|
+
elif runtime.get("kind") == "mixed":
|
|
2209
|
+
declared_how = (
|
|
2210
|
+
'the mixed app\'s declared identity (App("...") / #[urun::app(name = "...")])'
|
|
2211
|
+
)
|
|
2212
|
+
else:
|
|
2213
|
+
declared_how = '#[urun::app(name = "...")]'
|
|
2156
2214
|
raise UrunError(
|
|
2157
2215
|
f"--name {args.name!r} disagrees with the app's own declared identity "
|
|
2158
|
-
f'
|
|
2159
|
-
"single source of truth for an app's identity; drop --name (it is "
|
|
2160
|
-
|
|
2161
|
-
f'change the
|
|
2216
|
+
f'{declared_how} "{declared}" in {entrypoint_rel}. The declared name is '
|
|
2217
|
+
"the single source of truth for an app's identity; drop --name (it is "
|
|
2218
|
+
"inferred from the declaration) or make it match. To rename the app, "
|
|
2219
|
+
f'change the declared name to "{args.name}".'
|
|
2162
2220
|
)
|
|
2163
|
-
|
|
2221
|
+
if runtime is None:
|
|
2222
|
+
app_name = args.name or derive_app_name(entrypoint_rel, entrypoint_path)
|
|
2223
|
+
elif declared is None:
|
|
2224
|
+
# Fail-closed, and unreachable through real discovery (urun.rust
|
|
2225
|
+
# refuses any Rust app whose #[urun::app] lacks `name = "<literal>"`),
|
|
2226
|
+
# so --name can never substitute for a missing declared identity —
|
|
2227
|
+
# a --name-only identity is exactly the duplicate-app fork the
|
|
2228
|
+
# declared-identity ruling exists to prevent. No fallback is invented.
|
|
2229
|
+
raise UrunError(
|
|
2230
|
+
f'native app {entrypoint_rel} carries no declared #[urun::app(name = "...")] '
|
|
2231
|
+
"identity. The declared name is the single source of truth for a "
|
|
2232
|
+
"native deploy — declare it in the source; --name cannot substitute."
|
|
2233
|
+
)
|
|
2234
|
+
else:
|
|
2235
|
+
app_name = args.name or declared
|
|
2164
2236
|
|
|
2165
2237
|
# Segregated dev identity (OWNER ruling 2026-07-18): a dev deploy and a
|
|
2166
2238
|
# standard deploy of the same app share a release but NOT the app
|
|
@@ -2204,7 +2276,30 @@ def deploy_once(args: argparse.Namespace) -> DeployOutcome:
|
|
|
2204
2276
|
# AT PRIMITIVE USE — surfaced through the boot self-test's ``MissingCredentialError``
|
|
2205
2277
|
# (rendered by :func:`print_build_failure`), not by a blanket declaration check.
|
|
2206
2278
|
# Secret values are never printed.
|
|
2207
|
-
|
|
2279
|
+
if runtime is None:
|
|
2280
|
+
required_env, optional_env = extract_app_credentials_env(entrypoint_path)
|
|
2281
|
+
elif runtime.get("kind") == "mixed":
|
|
2282
|
+
# Mixed runtime: EVERY declared Python source's closure contributes
|
|
2283
|
+
# its Credentials declarations (deduped by file identity); the Rust
|
|
2284
|
+
# sources' declared function-credential NAMES union in on top.
|
|
2285
|
+
# Everything rides the same org-store resolution + local-env capture
|
|
2286
|
+
# consent as the pure Python extraction.
|
|
2287
|
+
rust_required, rust_optional = native_runtime_credentials(runtime)
|
|
2288
|
+
required_env = mixed_metadata["required_env"] + [
|
|
2289
|
+
n for n in rust_required if n not in mixed_metadata["required_env"]
|
|
2290
|
+
]
|
|
2291
|
+
# REQUIRED wins over OPTIONAL: a name any Python source or Rust
|
|
2292
|
+
# function REQUIRES never also rides the optional lane — the optional
|
|
2293
|
+
# consent path (a declined name just deploys without it) must never
|
|
2294
|
+
# weaken a required secret into a skippable one.
|
|
2295
|
+
optional_env = [n for n in mixed_metadata["optional_env"] if n not in required_env] + [
|
|
2296
|
+
n for n in rust_optional if n not in required_env
|
|
2297
|
+
]
|
|
2298
|
+
else:
|
|
2299
|
+
# Native runtime: credentials come from the declared function options
|
|
2300
|
+
# (env-var NAMES only) and ride the exact same org-store resolution +
|
|
2301
|
+
# local-env capture consent as the Python extraction.
|
|
2302
|
+
required_env, optional_env = native_runtime_credentials(runtime)
|
|
2208
2303
|
creds_to_push: dict[str, str] = {}
|
|
2209
2304
|
creds_from_store: list[str] = []
|
|
2210
2305
|
declared_credential_names: list[str] = []
|
|
@@ -2234,6 +2329,11 @@ def deploy_once(args: argparse.Namespace) -> DeployOutcome:
|
|
|
2234
2329
|
"client_version": f"urun-cli/{__version__}",
|
|
2235
2330
|
"environment": environment,
|
|
2236
2331
|
}
|
|
2332
|
+
if runtime is not None:
|
|
2333
|
+
# runtime IS content identity (contract): emitted VERBATIM from
|
|
2334
|
+
# discovery and folded into the canonical manifest hash — never
|
|
2335
|
+
# stripped. Python manifests carry no runtime key at all.
|
|
2336
|
+
manifest["runtime"] = runtime
|
|
2237
2337
|
# DEPLOY-DECLARED GPU CLASS (content-bearing — the class IS content: it
|
|
2238
2338
|
# decides the venv's torch backend). The builder's only other source of the
|
|
2239
2339
|
# class is importing the app, which legitimately fails pre-deps-install
|
|
@@ -2242,7 +2342,14 @@ def deploy_once(args: argparse.Namespace) -> DeployOutcome:
|
|
|
2242
2342
|
# an sm_120 (rtx6000) release (prod-use2 2026-07-17 rolling-sink poison).
|
|
2243
2343
|
# Stamped only when statically certain (see extract_app_gpu_type); absent =>
|
|
2244
2344
|
# byte-identical manifest and legacy builder behavior.
|
|
2245
|
-
|
|
2345
|
+
if runtime is None:
|
|
2346
|
+
gpu_type = extract_app_gpu_type(entrypoint_path)
|
|
2347
|
+
elif runtime.get("kind") == "mixed":
|
|
2348
|
+
# EVERY declared Python source's closure contributes its GPU class
|
|
2349
|
+
# (order-free conflict check against the Rust classes downstream).
|
|
2350
|
+
gpu_type = mixed_gpu_type(mixed_metadata["gpu_classes"], runtime)
|
|
2351
|
+
else:
|
|
2352
|
+
gpu_type = native_gpu_type(runtime)
|
|
2246
2353
|
if gpu_type:
|
|
2247
2354
|
manifest["gpu_type"] = gpu_type
|
|
2248
2355
|
# Declare the credential env-var NAMES (never values) on the manifest so the
|