uvlazy 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.
@@ -0,0 +1,7 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "uvlazy"
7
+ version = "0.1.0"
@@ -0,0 +1,25 @@
1
+ [package]
2
+ name = "uvlazy"
3
+ version = "0.1.0"
4
+ edition = "2024"
5
+ rust-version = "1.90"
6
+ description = "Run project-locked tools and install Python dependencies only when used"
7
+ license = "MIT"
8
+ repository = "https://github.com/rvben/uvlazy"
9
+ homepage = "https://github.com/rvben/uvlazy"
10
+ readme = "README.md"
11
+ keywords = ["uv", "python", "dependencies", "lazy", "cli"]
12
+ categories = ["command-line-utilities", "development-tools"]
13
+ include = [
14
+ "/Cargo.toml", "/Cargo.lock", "/build.rs", "/src/*.rs", "/src/uvlazy/*.py",
15
+ "/scripts/launch.py", "/pyproject.toml", "/README.md", "/LICENSE", "/tests/*.py",
16
+ ]
17
+
18
+ [[bin]]
19
+ name = "uvlazy"
20
+ path = "src/main.rs"
21
+
22
+ [profile.release]
23
+ strip = true
24
+ lto = true
25
+ codegen-units = 1
uvlazy-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ruben Jongejan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
uvlazy-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,288 @@
1
+ Metadata-Version: 2.4
2
+ Name: uvlazy
3
+ Version: 0.1.0
4
+ Classifier: Development Status :: 3 - Alpha
5
+ Classifier: Environment :: Console
6
+ Classifier: Intended Audience :: Developers
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: MacOS
9
+ Classifier: Operating System :: POSIX :: Linux
10
+ Classifier: Programming Language :: Rust
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Software Development :: Build Tools
13
+ License-File: LICENSE
14
+ Summary: Run project-locked tools and install Python dependencies only when used.
15
+ Home-Page: https://github.com/rvben/uvlazy
16
+ License: MIT
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
19
+ Project-URL: Homepage, https://github.com/rvben/uvlazy
20
+ Project-URL: Repository, https://github.com/rvben/uvlazy
21
+
22
+ # uvlazy
23
+
24
+ **Run the tool you need, at the project's locked version, without installing the
25
+ rest of the project.**
26
+
27
+ ```sh
28
+ uvlazy run rumdl check .
29
+ ```
30
+
31
+ uvlazy finds the declared package providing `rumdl`, reads the version constraints
32
+ from `uv.lock`, installs that package and its dependency tree into a dedicated
33
+ environment, and runs its executable. Application dependencies and unrelated
34
+ tools stay uninstalled. Python console scripts and native executables both work.
35
+
36
+ This is experimental software for macOS and Linux. It requires Python 3.11+ and
37
+ `uv` 0.6.6+ on PATH. Cargo and PyPI install the same Rust executable, which
38
+ bundles a Python engine with no third-party runtime dependencies.
39
+
40
+ ## Install
41
+
42
+ Install through either registry:
43
+
44
+ ```sh
45
+ cargo install uvlazy --locked
46
+ # or
47
+ uv tool install uvlazy
48
+ # or, in a Python environment
49
+ pip install uvlazy
50
+ ```
51
+
52
+ Or install from this checkout (both routes require Rust):
53
+
54
+ ```sh
55
+ cargo install --path . --locked
56
+ # or
57
+ uv tool install .
58
+ ```
59
+
60
+ Or run directly from source without installing uvlazy:
61
+
62
+ ```sh
63
+ PYTHONPATH=/path/to/uvlazy/src python3 -m uvlazy run rumdl check .
64
+ ```
65
+
66
+ The executable uses `uv python find --system --no-project` to locate an
67
+ installed Python 3.11+ without syncing a project. Set `UV_PYTHON` to choose a
68
+ specific interpreter, for example `UV_PYTHON=3.12 uvlazy run rumdl check .`.
69
+ If no compatible Python is installed, run `uv python install 3.11` first.
70
+
71
+ ## Use in CI
72
+
73
+ Declare the tool in your project's `pyproject.toml`, alongside your existing
74
+ application dependencies:
75
+
76
+ ```toml
77
+ [project]
78
+ name = "my-app"
79
+ version = "0.1.0"
80
+ requires-python = ">=3.11"
81
+ dependencies = ["rich>=13", "Pillow>=10", "rumdl"]
82
+ ```
83
+
84
+ No dependency groups or separate tool configuration are required when the
85
+ command and package names match.
86
+
87
+ Generate or update `uv.lock` during development with `uv lock`, and commit it
88
+ with your project. In CI, with uv and uvlazy available, run:
89
+
90
+ ```sh
91
+ uvlazy run rumdl check .
92
+ ```
93
+
94
+ There is no preceding `uv sync` step. Tool commands require an existing, current
95
+ lockfile; a missing or stale lock fails without updating it. The selected tool's
96
+ exit status, signals, stdin, stdout, stderr, arguments, and working directory
97
+ are preserved, so lint failures fail the job normally. Installation diagnostics
98
+ go to stderr; `--quiet` suppresses installation progress.
99
+
100
+ The [CI example](examples/ci/) includes a lockfile and declares Rich, Pillow, and
101
+ rumdl. Running the following installs only rumdl into the example's environment:
102
+
103
+ ```sh
104
+ uvlazy run --project examples/ci rumdl check examples/ci
105
+ ```
106
+
107
+ `--project` selects the dependency configuration without changing the current
108
+ working directory. Every option for uvlazy goes before the tool name; everything
109
+ after it is passed to the tool unchanged.
110
+
111
+ ## Package names and dependency groups
112
+
113
+ Matching command/package names such as `rumdl` need no configuration. For a
114
+ command whose package name differs, select its declared provider explicitly:
115
+
116
+ ```sh
117
+ uvlazy run --from httpie http --help
118
+ ```
119
+
120
+ Or save that mapping:
121
+
122
+ ```toml
123
+ [dependency-groups]
124
+ dev = ["httpie"]
125
+
126
+ [tool.uvlazy.commands]
127
+ http = "httpie"
128
+ ```
129
+
130
+ Then use `uvlazy run http --help`. Providers must be declared in
131
+ `[project].dependencies`, `[dependency-groups]`, or legacy
132
+ `[tool.uv].dev-dependencies`. Unknown commands fail without installing arbitrary
133
+ packages or falling back to executables from the system PATH. The executable must
134
+ belong to the selected package.
135
+
136
+ By default, a tool declared in project dependencies uses those declarations.
137
+ Otherwise, uvlazy automatically selects groups that directly declare the tool.
138
+ Only the requested package and its dependency tree are installed, including when
139
+ its group contains other tools. Nested `{include-group = "lint"}` declarations
140
+ are supported.
141
+
142
+ To select a particular group, use:
143
+
144
+ ```sh
145
+ uvlazy run --group lint rumdl check .
146
+ ```
147
+
148
+ `--group` can be repeated. This is also useful when the same tool has different
149
+ requirements in different groups: choose compatible groups explicitly. uv
150
+ validates the selected combination against the project's lockfile.
151
+
152
+ ## Lazy Python imports
153
+
154
+ Scripts and modules retain the original import-based behavior:
155
+
156
+ ```sh
157
+ uvlazy run app.py --your-argument
158
+ uvlazy run -m your_module
159
+ uvlazy run --group dev app.py
160
+ uvlazy run --locked app.py
161
+ ```
162
+
163
+ A script starts in a separate environment and installs declared packages when
164
+ execution first reaches their missing imports. Each imported package brings its
165
+ normal dependency tree. The script is never restarted, and exceptions inside
166
+ imported packages propagate normally. Existing modules and your local code take
167
+ precedence.
168
+
169
+ By default, scripts select `[project].dependencies`; `--group` adds groups.
170
+ Mappings handle differing import and distribution names:
171
+
172
+ ```toml
173
+ [project]
174
+ name = "my-app"
175
+ version = "0.1.0"
176
+ requires-python = ">=3.11"
177
+ dependencies = ["Pillow>=10"]
178
+
179
+ [tool.uvlazy.imports]
180
+ PIL = "Pillow"
181
+ ```
182
+
183
+ `from PIL import Image` triggers Pillow's installation. An undeclared missing
184
+ import raises `ModuleNotFoundError` normally. Top-level imports count as use,
185
+ even when no functions from the package are subsequently called. Import
186
+ probes such as `importlib.util.find_spec()` can also trigger installation.
187
+
188
+ The [conditional example](examples/conditional/) has `plain`, `pretty`, and
189
+ `image` branches. From this checkout:
190
+
191
+ ```sh
192
+ uvlazy run --project examples/conditional examples/conditional/demo.py plain
193
+ uvlazy run --project examples/conditional examples/conditional/demo.py pretty
194
+ uvlazy run --project examples/conditional examples/conditional/demo.py image
195
+ ```
196
+
197
+ Unlike tool commands, scripts may run without a lockfile. At the first missing
198
+ import, `uv pip compile` resolves all selected requirements and caches the pins.
199
+ That resolution can fetch metadata or build artifacts for unused dependencies,
200
+ and an unsatisfiable unused dependency can prevent it. With `uv.lock`, the pins
201
+ come from `uv export --locked`. Add `--locked` to require and validate the lock
202
+ before any application code runs, even on paths that import nothing.
203
+
204
+ ## Environments and caching
205
+
206
+ Environments and constraints live in `.uvlazy/<fingerprint>/`. Each tool package
207
+ and group selection has its own environment, separate from Python script runs.
208
+ Subsequent runs reuse installed packages. Concurrent runs serialize environment
209
+ creation and installation. A warm tool environment can run offline.
210
+
211
+ Changes to `pyproject.toml`, `uv.lock`, local `uv.toml`, the launcher interpreter,
212
+ selected groups, or checkout path select a fresh environment. This avoids
213
+ reusing virtualenv scripts whose absolute paths point to another checkout.
214
+ Existing `.venv` and `uv.lock` files are left alone.
215
+
216
+ For CI, cache uv's download cache to reuse package artifacts across jobs.
217
+ `.uvlazy` environments contain absolute paths and should only be reused at the
218
+ same checkout path with a compatible interpreter. Add `.uvlazy/` to the project's
219
+ `.gitignore`. Delete it while no uvlazy processes are running to start fresh.
220
+
221
+ The native executable also caches its bundled engine under
222
+ `$XDG_CACHE_HOME/uvlazy` (default: `~/Library/Caches/uvlazy` on macOS or
223
+ `~/.cache/uvlazy` on Linux). `UVLAZY_CACHE_DIR` overrides that location. The
224
+ archive is keyed by its contents and recreated locally when needed.
225
+
226
+ ## Current boundaries
227
+
228
+ - Tool runs install the tool's normal dependency tree. They do not inject a lazy
229
+ import hook into the executable or automatically install undeclared plugins.
230
+ Tools that need the application installed, such as many pytest workflows,
231
+ still need a complete project environment.
232
+ - Lock export supplies version/source constraints without enforcing lockfile
233
+ artifact hashes. This is not a complete replacement for uv's locked sync.
234
+ - The project itself is not installed. Editable installs and uv sources/workspaces
235
+ remain unsupported; uv sources/workspaces are rejected explicitly.
236
+ - Project optional-dependency groups cannot be selected yet. Extras within a
237
+ selected requirement and environment markers are passed through to uv.
238
+ - For Python scripts, metadata queries, plugin discovery, namespace-package
239
+ mappings, and newly installed `.pth` hooks have no special lazy handling.
240
+ New Python subprocesses do not inherit the import hook.
241
+ - Set `UV_PYTHON` to an installed interpreter compatible with the project and
242
+ selected groups. The default selects an installed Python 3.11+; it does not
243
+ infer the project's Python requirement. Windows is not supported yet.
244
+
245
+ ## Development
246
+
247
+ The integration suite generates local wheels and exercises real uv with network
248
+ access disabled, including command execution and selective installation.
249
+
250
+ ```sh
251
+ cargo build --locked
252
+ UVLAZY_TEST_BINARY=target/debug/uvlazy PYTHONPATH=src \
253
+ python3 -m unittest discover -s tests -v
254
+ PYTHONPATH=src python3 -m unittest discover -s tests -v
255
+ cargo fmt --check
256
+ cargo clippy --locked --all-targets -- -D warnings
257
+ ruff check src tests scripts examples
258
+ ruff format --check src tests scripts examples
259
+ ```
260
+
261
+ `Cargo.toml` is the version source for both registries. maturin uses
262
+ [`bindings = "bin"`](https://www.maturin.rs/bindings.html#bin) to put the native
263
+ executable into platform wheels. Cargo builds embed the Python sources without
264
+ requiring Python at build time; running the installed command needs no checkout
265
+ or separately installed `uvlazy` Python package.
266
+
267
+ Verify both source packages before a release:
268
+
269
+ ```sh
270
+ cargo package --locked
271
+ maturin build --release --sdist --locked --out dist
272
+ ```
273
+
274
+ CI installs and tests both the Cargo executable and a wheel rebuilt from the
275
+ source distribution, with offline local-wheel fixtures. It covers Linux and
276
+ macOS, Python 3.11 and 3.14, and the minimum and latest uv versions.
277
+
278
+ The release workflow builds Linux and macOS wheels for x86_64 and aarch64, plus
279
+ a source distribution. A matching `v<version>` tag publishes to crates.io and
280
+ PyPI in independent jobs using `CARGO_REGISTRY_TOKEN` and `PYPI_API_TOKEN`
281
+ repository secrets. Manual runs default to `dry_run: true`; publishing requires
282
+ a matching version tag. After both registries succeed, the workflow creates a
283
+ GitHub release with the packages and SHA-256 checksums.
284
+
285
+ The implementation uses uv's [lockfile export](https://docs.astral.sh/uv/concepts/projects/export/)
286
+ and [constraints](https://docs.astral.sh/uv/pip/compile/), and Python's
287
+ [import finder API](https://docs.python.org/3/library/importlib.html).
288
+
uvlazy-0.1.0/README.md ADDED
@@ -0,0 +1,266 @@
1
+ # uvlazy
2
+
3
+ **Run the tool you need, at the project's locked version, without installing the
4
+ rest of the project.**
5
+
6
+ ```sh
7
+ uvlazy run rumdl check .
8
+ ```
9
+
10
+ uvlazy finds the declared package providing `rumdl`, reads the version constraints
11
+ from `uv.lock`, installs that package and its dependency tree into a dedicated
12
+ environment, and runs its executable. Application dependencies and unrelated
13
+ tools stay uninstalled. Python console scripts and native executables both work.
14
+
15
+ This is experimental software for macOS and Linux. It requires Python 3.11+ and
16
+ `uv` 0.6.6+ on PATH. Cargo and PyPI install the same Rust executable, which
17
+ bundles a Python engine with no third-party runtime dependencies.
18
+
19
+ ## Install
20
+
21
+ Install through either registry:
22
+
23
+ ```sh
24
+ cargo install uvlazy --locked
25
+ # or
26
+ uv tool install uvlazy
27
+ # or, in a Python environment
28
+ pip install uvlazy
29
+ ```
30
+
31
+ Or install from this checkout (both routes require Rust):
32
+
33
+ ```sh
34
+ cargo install --path . --locked
35
+ # or
36
+ uv tool install .
37
+ ```
38
+
39
+ Or run directly from source without installing uvlazy:
40
+
41
+ ```sh
42
+ PYTHONPATH=/path/to/uvlazy/src python3 -m uvlazy run rumdl check .
43
+ ```
44
+
45
+ The executable uses `uv python find --system --no-project` to locate an
46
+ installed Python 3.11+ without syncing a project. Set `UV_PYTHON` to choose a
47
+ specific interpreter, for example `UV_PYTHON=3.12 uvlazy run rumdl check .`.
48
+ If no compatible Python is installed, run `uv python install 3.11` first.
49
+
50
+ ## Use in CI
51
+
52
+ Declare the tool in your project's `pyproject.toml`, alongside your existing
53
+ application dependencies:
54
+
55
+ ```toml
56
+ [project]
57
+ name = "my-app"
58
+ version = "0.1.0"
59
+ requires-python = ">=3.11"
60
+ dependencies = ["rich>=13", "Pillow>=10", "rumdl"]
61
+ ```
62
+
63
+ No dependency groups or separate tool configuration are required when the
64
+ command and package names match.
65
+
66
+ Generate or update `uv.lock` during development with `uv lock`, and commit it
67
+ with your project. In CI, with uv and uvlazy available, run:
68
+
69
+ ```sh
70
+ uvlazy run rumdl check .
71
+ ```
72
+
73
+ There is no preceding `uv sync` step. Tool commands require an existing, current
74
+ lockfile; a missing or stale lock fails without updating it. The selected tool's
75
+ exit status, signals, stdin, stdout, stderr, arguments, and working directory
76
+ are preserved, so lint failures fail the job normally. Installation diagnostics
77
+ go to stderr; `--quiet` suppresses installation progress.
78
+
79
+ The [CI example](examples/ci/) includes a lockfile and declares Rich, Pillow, and
80
+ rumdl. Running the following installs only rumdl into the example's environment:
81
+
82
+ ```sh
83
+ uvlazy run --project examples/ci rumdl check examples/ci
84
+ ```
85
+
86
+ `--project` selects the dependency configuration without changing the current
87
+ working directory. Every option for uvlazy goes before the tool name; everything
88
+ after it is passed to the tool unchanged.
89
+
90
+ ## Package names and dependency groups
91
+
92
+ Matching command/package names such as `rumdl` need no configuration. For a
93
+ command whose package name differs, select its declared provider explicitly:
94
+
95
+ ```sh
96
+ uvlazy run --from httpie http --help
97
+ ```
98
+
99
+ Or save that mapping:
100
+
101
+ ```toml
102
+ [dependency-groups]
103
+ dev = ["httpie"]
104
+
105
+ [tool.uvlazy.commands]
106
+ http = "httpie"
107
+ ```
108
+
109
+ Then use `uvlazy run http --help`. Providers must be declared in
110
+ `[project].dependencies`, `[dependency-groups]`, or legacy
111
+ `[tool.uv].dev-dependencies`. Unknown commands fail without installing arbitrary
112
+ packages or falling back to executables from the system PATH. The executable must
113
+ belong to the selected package.
114
+
115
+ By default, a tool declared in project dependencies uses those declarations.
116
+ Otherwise, uvlazy automatically selects groups that directly declare the tool.
117
+ Only the requested package and its dependency tree are installed, including when
118
+ its group contains other tools. Nested `{include-group = "lint"}` declarations
119
+ are supported.
120
+
121
+ To select a particular group, use:
122
+
123
+ ```sh
124
+ uvlazy run --group lint rumdl check .
125
+ ```
126
+
127
+ `--group` can be repeated. This is also useful when the same tool has different
128
+ requirements in different groups: choose compatible groups explicitly. uv
129
+ validates the selected combination against the project's lockfile.
130
+
131
+ ## Lazy Python imports
132
+
133
+ Scripts and modules retain the original import-based behavior:
134
+
135
+ ```sh
136
+ uvlazy run app.py --your-argument
137
+ uvlazy run -m your_module
138
+ uvlazy run --group dev app.py
139
+ uvlazy run --locked app.py
140
+ ```
141
+
142
+ A script starts in a separate environment and installs declared packages when
143
+ execution first reaches their missing imports. Each imported package brings its
144
+ normal dependency tree. The script is never restarted, and exceptions inside
145
+ imported packages propagate normally. Existing modules and your local code take
146
+ precedence.
147
+
148
+ By default, scripts select `[project].dependencies`; `--group` adds groups.
149
+ Mappings handle differing import and distribution names:
150
+
151
+ ```toml
152
+ [project]
153
+ name = "my-app"
154
+ version = "0.1.0"
155
+ requires-python = ">=3.11"
156
+ dependencies = ["Pillow>=10"]
157
+
158
+ [tool.uvlazy.imports]
159
+ PIL = "Pillow"
160
+ ```
161
+
162
+ `from PIL import Image` triggers Pillow's installation. An undeclared missing
163
+ import raises `ModuleNotFoundError` normally. Top-level imports count as use,
164
+ even when no functions from the package are subsequently called. Import
165
+ probes such as `importlib.util.find_spec()` can also trigger installation.
166
+
167
+ The [conditional example](examples/conditional/) has `plain`, `pretty`, and
168
+ `image` branches. From this checkout:
169
+
170
+ ```sh
171
+ uvlazy run --project examples/conditional examples/conditional/demo.py plain
172
+ uvlazy run --project examples/conditional examples/conditional/demo.py pretty
173
+ uvlazy run --project examples/conditional examples/conditional/demo.py image
174
+ ```
175
+
176
+ Unlike tool commands, scripts may run without a lockfile. At the first missing
177
+ import, `uv pip compile` resolves all selected requirements and caches the pins.
178
+ That resolution can fetch metadata or build artifacts for unused dependencies,
179
+ and an unsatisfiable unused dependency can prevent it. With `uv.lock`, the pins
180
+ come from `uv export --locked`. Add `--locked` to require and validate the lock
181
+ before any application code runs, even on paths that import nothing.
182
+
183
+ ## Environments and caching
184
+
185
+ Environments and constraints live in `.uvlazy/<fingerprint>/`. Each tool package
186
+ and group selection has its own environment, separate from Python script runs.
187
+ Subsequent runs reuse installed packages. Concurrent runs serialize environment
188
+ creation and installation. A warm tool environment can run offline.
189
+
190
+ Changes to `pyproject.toml`, `uv.lock`, local `uv.toml`, the launcher interpreter,
191
+ selected groups, or checkout path select a fresh environment. This avoids
192
+ reusing virtualenv scripts whose absolute paths point to another checkout.
193
+ Existing `.venv` and `uv.lock` files are left alone.
194
+
195
+ For CI, cache uv's download cache to reuse package artifacts across jobs.
196
+ `.uvlazy` environments contain absolute paths and should only be reused at the
197
+ same checkout path with a compatible interpreter. Add `.uvlazy/` to the project's
198
+ `.gitignore`. Delete it while no uvlazy processes are running to start fresh.
199
+
200
+ The native executable also caches its bundled engine under
201
+ `$XDG_CACHE_HOME/uvlazy` (default: `~/Library/Caches/uvlazy` on macOS or
202
+ `~/.cache/uvlazy` on Linux). `UVLAZY_CACHE_DIR` overrides that location. The
203
+ archive is keyed by its contents and recreated locally when needed.
204
+
205
+ ## Current boundaries
206
+
207
+ - Tool runs install the tool's normal dependency tree. They do not inject a lazy
208
+ import hook into the executable or automatically install undeclared plugins.
209
+ Tools that need the application installed, such as many pytest workflows,
210
+ still need a complete project environment.
211
+ - Lock export supplies version/source constraints without enforcing lockfile
212
+ artifact hashes. This is not a complete replacement for uv's locked sync.
213
+ - The project itself is not installed. Editable installs and uv sources/workspaces
214
+ remain unsupported; uv sources/workspaces are rejected explicitly.
215
+ - Project optional-dependency groups cannot be selected yet. Extras within a
216
+ selected requirement and environment markers are passed through to uv.
217
+ - For Python scripts, metadata queries, plugin discovery, namespace-package
218
+ mappings, and newly installed `.pth` hooks have no special lazy handling.
219
+ New Python subprocesses do not inherit the import hook.
220
+ - Set `UV_PYTHON` to an installed interpreter compatible with the project and
221
+ selected groups. The default selects an installed Python 3.11+; it does not
222
+ infer the project's Python requirement. Windows is not supported yet.
223
+
224
+ ## Development
225
+
226
+ The integration suite generates local wheels and exercises real uv with network
227
+ access disabled, including command execution and selective installation.
228
+
229
+ ```sh
230
+ cargo build --locked
231
+ UVLAZY_TEST_BINARY=target/debug/uvlazy PYTHONPATH=src \
232
+ python3 -m unittest discover -s tests -v
233
+ PYTHONPATH=src python3 -m unittest discover -s tests -v
234
+ cargo fmt --check
235
+ cargo clippy --locked --all-targets -- -D warnings
236
+ ruff check src tests scripts examples
237
+ ruff format --check src tests scripts examples
238
+ ```
239
+
240
+ `Cargo.toml` is the version source for both registries. maturin uses
241
+ [`bindings = "bin"`](https://www.maturin.rs/bindings.html#bin) to put the native
242
+ executable into platform wheels. Cargo builds embed the Python sources without
243
+ requiring Python at build time; running the installed command needs no checkout
244
+ or separately installed `uvlazy` Python package.
245
+
246
+ Verify both source packages before a release:
247
+
248
+ ```sh
249
+ cargo package --locked
250
+ maturin build --release --sdist --locked --out dist
251
+ ```
252
+
253
+ CI installs and tests both the Cargo executable and a wheel rebuilt from the
254
+ source distribution, with offline local-wheel fixtures. It covers Linux and
255
+ macOS, Python 3.11 and 3.14, and the minimum and latest uv versions.
256
+
257
+ The release workflow builds Linux and macOS wheels for x86_64 and aarch64, plus
258
+ a source distribution. A matching `v<version>` tag publishes to crates.io and
259
+ PyPI in independent jobs using `CARGO_REGISTRY_TOKEN` and `PYPI_API_TOKEN`
260
+ repository secrets. Manual runs default to `dry_run: true`; publishing requires
261
+ a matching version tag. After both registries succeed, the workflow creates a
262
+ GitHub release with the packages and SHA-256 checksums.
263
+
264
+ The implementation uses uv's [lockfile export](https://docs.astral.sh/uv/concepts/projects/export/)
265
+ and [constraints](https://docs.astral.sh/uv/pip/compile/), and Python's
266
+ [import finder API](https://docs.python.org/3/library/importlib.html).
uvlazy-0.1.0/build.rs ADDED
@@ -0,0 +1,53 @@
1
+ use std::{env, fmt::Write, fs, path::PathBuf};
2
+
3
+ // Embed the stdlib-only Python engine without requiring Python at build time.
4
+ // Python's own zipfile module materializes it when the executable first runs.
5
+ fn python_string(value: &str) -> String {
6
+ let mut result = String::from("'");
7
+ for character in value.chars() {
8
+ match character {
9
+ '\\' => result.push_str("\\\\"),
10
+ '\'' => result.push_str("\\'"),
11
+ '\n' => result.push_str("\\n"),
12
+ '\r' => result.push_str("\\r"),
13
+ '\t' => result.push_str("\\t"),
14
+ c if c.is_control() => write!(result, "\\U{:08x}", c as u32).unwrap(),
15
+ c => result.push(c),
16
+ }
17
+ }
18
+ result.push('\'');
19
+ result
20
+ }
21
+
22
+ fn main() {
23
+ println!("cargo:rerun-if-changed=src/uvlazy");
24
+ println!("cargo:rerun-if-changed=scripts/launch.py");
25
+ println!("cargo:rerun-if-changed=Cargo.toml");
26
+ let mut paths: Vec<_> = fs::read_dir("src/uvlazy")
27
+ .unwrap()
28
+ .map(|entry| entry.unwrap().path())
29
+ .filter(|path| path.extension().is_some_and(|extension| extension == "py"))
30
+ .collect();
31
+ paths.sort();
32
+ let mut bootstrap = String::from("files = {\n");
33
+ for path in paths {
34
+ let name = format!("uvlazy/{}", path.file_name().unwrap().to_str().unwrap());
35
+ let source = fs::read_to_string(&path).unwrap();
36
+ writeln!(
37
+ bootstrap,
38
+ " {}: {},",
39
+ python_string(&name),
40
+ python_string(&source)
41
+ )
42
+ .unwrap();
43
+ }
44
+ writeln!(
45
+ bootstrap,
46
+ " 'uvlazy/_version': {},\n}}",
47
+ python_string(&env::var("CARGO_PKG_VERSION").unwrap())
48
+ )
49
+ .unwrap();
50
+ bootstrap.push_str(&fs::read_to_string("scripts/launch.py").unwrap());
51
+ let output = PathBuf::from(env::var_os("OUT_DIR").unwrap());
52
+ fs::write(output.join("bootstrap.py"), bootstrap).unwrap();
53
+ }