pyfig 0.3.1 → 0.4.0
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.
- package/README.md +112 -121
- package/SECURITY.md +1 -1
- package/dist/cjs/index.js +468 -868
- package/dist/cjs/index.js.map +14 -15
- package/dist/cjs/sync-resolver.js +426 -657
- package/dist/cjs/sync-resolver.js.map +12 -12
- package/dist/cli.js +5 -8
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +3 -8
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +13 -11
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +4 -13
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -51
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +5 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/package-target.d.ts +7 -11
- package/dist/package-target.d.ts.map +1 -1
- package/dist/package-target.js +41 -57
- package/dist/package-target.js.map +1 -1
- package/dist/probes.d.ts +0 -18
- package/dist/probes.d.ts.map +1 -1
- package/dist/probes.js +0 -70
- package/dist/probes.js.map +1 -1
- package/dist/process.d.ts +1 -1
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +2 -1
- package/dist/process.js.map +1 -1
- package/dist/project-root.d.ts +1 -1
- package/dist/project-root.d.ts.map +1 -1
- package/dist/project-root.js +3 -64
- package/dist/project-root.js.map +1 -1
- package/dist/provision-lock.d.ts +10 -0
- package/dist/provision-lock.d.ts.map +1 -0
- package/dist/provision-lock.js +24 -0
- package/dist/provision-lock.js.map +1 -0
- package/dist/provision.d.ts +18 -6
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +273 -228
- package/dist/provision.js.map +1 -1
- package/dist/runners.d.ts +3 -7
- package/dist/runners.d.ts.map +1 -1
- package/dist/runners.js +35 -115
- package/dist/runners.js.map +1 -1
- package/dist/sync-resolver.js +4 -13
- package/dist/sync-resolver.js.map +1 -1
- package/dist/virtual-environment.d.ts +3 -1
- package/dist/virtual-environment.d.ts.map +1 -1
- package/dist/virtual-environment.js +8 -1
- package/dist/virtual-environment.js.map +1 -1
- package/package.json +6 -8
- package/dist/coordination.d.ts +0 -25
- package/dist/coordination.d.ts.map +0 -1
- package/dist/coordination.js +0 -187
- package/dist/coordination.js.map +0 -1
- package/dist/state.d.ts +0 -10
- package/dist/state.d.ts.map +0 -1
- package/dist/state.js +0 -62
- package/dist/state.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# pyfig
|
|
2
2
|
|
|
3
|
-
`pyfig`
|
|
3
|
+
`pyfig` prepares uv and a compatible Python virtual environment from Bun or Node.js. It prefers a valid uv already on the system, reuses a compatible venv, and searches system Python with downloads disabled before considering any managed Python download.
|
|
4
|
+
|
|
5
|
+
Pyfig never invokes standalone `pip`, never writes `pyproject.toml` or requirements files, and never creates a project-root `.venv`. When no compatible venv exists, its private environment is `<project-root>/node_modules/.pyfig/venv`.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
@@ -8,156 +10,144 @@
|
|
|
8
10
|
bun add pyfig
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
##
|
|
13
|
+
## Use
|
|
12
14
|
|
|
13
15
|
```ts
|
|
14
16
|
import { createPyfig } from 'pyfig';
|
|
15
17
|
|
|
16
18
|
const pyfig = createPyfig({
|
|
17
19
|
pythonRange: '>=3.11.0 <4.0.0',
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
requireGil: true, // reject free-threaded CPython ABIs
|
|
20
|
+
requireEmbedding: true,
|
|
21
|
+
requireGil: true,
|
|
21
22
|
});
|
|
22
|
-
const environment = await pyfig.prepare();
|
|
23
23
|
|
|
24
|
+
const environment = await pyfig.prepare(
|
|
25
|
+
['requests>=2.32'],
|
|
26
|
+
{ extras: ['database'] },
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
console.log(environment.uv);
|
|
24
30
|
console.log(environment.python);
|
|
25
|
-
console.log(environment.
|
|
31
|
+
console.log(environment.managed);
|
|
32
|
+
|
|
33
|
+
await pyfig.uv(
|
|
34
|
+
['run', '--', 'python', 'script.py'],
|
|
35
|
+
{ stdio: 'inherit' },
|
|
36
|
+
);
|
|
26
37
|
```
|
|
27
38
|
|
|
28
|
-
|
|
39
|
+
`prepare()` accepts runtime requirement strings plus optional `extras`, `groups`, or `allExtras`. If the project has a `pyproject.toml`, uv resolves that read-only input together with a temporary requirements file. Pyfig installs the resolved result with `uv pip install -r`, which preserves unrelated packages. Temporary inputs are created outside the repository and removed afterward.
|
|
29
40
|
|
|
30
|
-
|
|
31
|
-
import {
|
|
32
|
-
pipSpawn,
|
|
33
|
-
pipSpawnSync,
|
|
34
|
-
pythonSpawn,
|
|
35
|
-
pythonSpawnSync,
|
|
36
|
-
uvSpawn,
|
|
37
|
-
uvSpawnSync,
|
|
38
|
-
} from 'pyfig';
|
|
39
|
-
|
|
40
|
-
const python = await pythonSpawn(['script.py', '--verbose'], { stdio: 'inherit' });
|
|
41
|
-
const pythonVersion = pythonSpawnSync(['--version'], { encoding: 'utf8' });
|
|
42
|
-
|
|
43
|
-
const pip = await pipSpawn(['install', 'ruff'], { stdio: 'inherit' });
|
|
44
|
-
const installed = pipSpawnSync(['list', '--format', 'json'], { encoding: 'utf8' });
|
|
45
|
-
|
|
46
|
-
const uv = await uvSpawn(['python', 'list'], { stdio: 'inherit' });
|
|
47
|
-
const uvVersion = uvSpawnSync(['--version'], { encoding: 'utf8' });
|
|
48
|
-
```
|
|
41
|
+
The same uv-only surface handles direct commands:
|
|
49
42
|
|
|
50
|
-
|
|
43
|
+
```ts
|
|
44
|
+
import { uvSpawn, uvSpawnSync } from 'pyfig';
|
|
51
45
|
|
|
52
|
-
|
|
46
|
+
await uvSpawn(['pip', 'list'], { stdio: 'inherit' });
|
|
53
47
|
|
|
54
|
-
|
|
48
|
+
const result = uvSpawnSync(
|
|
49
|
+
['run', '--', 'python', '-c', 'import sys; print(sys.executable)'],
|
|
50
|
+
{ encoding: 'utf8' },
|
|
51
|
+
);
|
|
52
|
+
```
|
|
55
53
|
|
|
56
|
-
|
|
54
|
+
For `uv pip` and `uv run`, Pyfig selects the venv and injects its exact interpreter. Caller-supplied target overrides such as `--python`, `--system`, `--target`, `--prefix`, and `--user` are rejected. Inherited target-changing environment variables are removed. Other uv commands run with Pyfig's cache and system-Python preference but are otherwise unchanged.
|
|
57
55
|
|
|
58
|
-
|
|
56
|
+
The async spawn helper returns `Promise<ChildProcess>` because uv or environment setup may happen before spawning. The synchronous helper performs setup through `process.execPath`, so a Bun process remains Bun and does not assume a `node` command exists.
|
|
59
57
|
|
|
60
|
-
|
|
58
|
+
## Selection policy
|
|
61
59
|
|
|
62
|
-
|
|
60
|
+
Pyfig uses this order:
|
|
63
61
|
|
|
64
|
-
|
|
62
|
+
1. A compatible configured or `PATH` uv.
|
|
63
|
+
2. Pyfig's cached, checksum-verified uv.
|
|
64
|
+
3. A compatible explicitly configured venv, active `VIRTUAL_ENV`, root `.venv`, or Pyfig-owned venv.
|
|
65
|
+
4. Every installed system Python reported by uv, with downloads disabled, until one passes all capability checks.
|
|
66
|
+
5. An already-cached uv-managed Python that passes the same checks.
|
|
67
|
+
6. A managed Python download, only when installation policy allows it.
|
|
65
68
|
|
|
66
|
-
|
|
69
|
+
An explicit `python` option is strict: an unavailable or incompatible interpreter is an error. Incompatible external venvs are preserved and skipped. Only an incompatible environment under `node_modules/.pyfig/venv` may be rebuilt.
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+

|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
graph TD;
|
|
72
|
-
A[Ensure environment] --> V{Repository virtual environment usable};
|
|
73
|
-
V -->|Yes| Q{Compatible uv available};
|
|
74
|
-
V -->|No| B{Configured Python supplied};
|
|
75
|
-
B -->|Valid| P[Select base Python];
|
|
76
|
-
B -->|Invalid| X[Fail explicit configuration];
|
|
77
|
-
B -->|No| C{Managed uv Python compatible};
|
|
78
|
-
C -->|Yes| P;
|
|
79
|
-
C -->|No| D{System Python compatible};
|
|
80
|
-
D -->|Yes| P;
|
|
81
|
-
D -->|No| E{Python installation allowed};
|
|
82
|
-
E -->|No| X2[Fail without changes];
|
|
83
|
-
E -->|Yes| U[Install pinned uv];
|
|
84
|
-
U --> M[Install managed Python];
|
|
85
|
-
M --> P;
|
|
73
|
+
[Mermaid source](docs/diagrams/python-selection.mmd)
|
|
86
74
|
|
|
87
|
-
|
|
88
|
-
W -->|Yes| UVV[Create virtual environment with uv];
|
|
89
|
-
W -->|No| SV[Create virtual environment with Python];
|
|
90
|
-
UVV --> Q;
|
|
91
|
-
SV --> Q;
|
|
75
|
+
Native consumers can set `requireEmbedding: true` to require CPython, `Python.h`, a linkable embedding library, and architecture/pointer-width compatibility with the JavaScript runtime. Set `requireGil: true` when the native addon requires the standard GIL-enabled ABI. These checks apply to system, existing-venv, cached, and downloaded interpreters alike.
|
|
92
76
|
|
|
93
|
-
|
|
94
|
-
Q -->|No| S{Python module pip compatible};
|
|
95
|
-
S -->|Read only| T[Use Python module pip];
|
|
96
|
-
S -->|Mutation or missing| X3[Require uv or fail];
|
|
97
|
-
```
|
|
77
|
+
## Runtime dependency resolution
|
|
98
78
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
C -->|Yes| I[Download verified platform uv archive];
|
|
108
|
-
I --> J{Installed uv validates};
|
|
109
|
-
J -->|No| K[Fail installation];
|
|
110
|
-
J -->|Yes| H;
|
|
79
|
+
```ts
|
|
80
|
+
await pyfig.prepare(
|
|
81
|
+
['orjson>=3.10', 'httpx[http2]'],
|
|
82
|
+
{
|
|
83
|
+
extras: ['postgres'],
|
|
84
|
+
groups: ['runtime'],
|
|
85
|
+
},
|
|
86
|
+
);
|
|
111
87
|
```
|
|
112
88
|
|
|
113
|
-
|
|
89
|
+
Resolution follows the equivalent of:
|
|
114
90
|
|
|
115
91
|
```sh
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
92
|
+
uv pip compile pyproject.toml /temporary/runtime-requirements.txt \
|
|
93
|
+
--extra postgres --group runtime \
|
|
94
|
+
--python /selected/venv/python \
|
|
95
|
+
--output-file /temporary/resolved-requirements.txt
|
|
96
|
+
|
|
97
|
+
uv pip install \
|
|
98
|
+
--python /selected/venv/python \
|
|
99
|
+
-r /temporary/resolved-requirements.txt
|
|
122
100
|
```
|
|
123
101
|
|
|
124
|
-
|
|
102
|
+
When no `pyproject.toml` exists, only the temporary requirements input is compiled. Extras and dependency groups require an existing `pyproject.toml`. Pyfig does not use `uv add` or `uv pip sync`: the former would edit project metadata, while the latter could remove unrelated packages from the venv.
|
|
103
|
+
|
|
104
|
+

|
|
125
105
|
|
|
126
|
-
|
|
106
|
+
[Mermaid source](docs/diagrams/runtime-dependencies.mmd)
|
|
107
|
+
|
|
108
|
+
## Project root
|
|
109
|
+
|
|
110
|
+
Pyfig does not interpret `package.json`, workspace metadata, `pyproject.toml`, or lockfiles to choose ownership. Root precedence is:
|
|
111
|
+
|
|
112
|
+
1. `projectRoot`
|
|
113
|
+
2. `PYFIG_PROJECT_ROOT`
|
|
114
|
+
3. nearest Git worktree marker above `INIT_CWD`, when set, otherwise the current directory
|
|
115
|
+
4. that starting directory when no Git marker exists
|
|
116
|
+
|
|
117
|
+
Set `projectRoot` explicitly in reusable libraries and unusual workspace layouts.
|
|
127
118
|
|
|
128
119
|
## Public API
|
|
129
120
|
|
|
130
121
|
| Export | Responsibility |
|
|
131
122
|
| --- | --- |
|
|
132
|
-
| `createPyfig(options)` |
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
|
|
142
|
-
The
|
|
123
|
+
| `createPyfig(options)` | Create a scoped client with memoized base preparation. |
|
|
124
|
+
| `client.prepare(requirements?, options?)` | Select a venv and optionally resolve/install runtime dependencies. |
|
|
125
|
+
| `client.pythonInfo()` | Return the selected interpreter and embedding metadata. |
|
|
126
|
+
| `client.uvInfo()` | Return the selected uv executable and version. |
|
|
127
|
+
| `client.uv()` / `client.uvSync()` | Spawn uv and pin pip/run commands to the selected venv. |
|
|
128
|
+
| `ensureEnvironment(options?, requirements?, dependencyOptions?)` | Standalone preparation API. |
|
|
129
|
+
| `ensureUv(options?)` | Prefer existing uv, otherwise bootstrap verified uv when allowed. |
|
|
130
|
+
| `uvSpawn()` / `uvSpawnSync()` | Standalone uv spawn API. |
|
|
131
|
+
| `resolveProjectRoot(start?)` | Resolve the nearest Git root without reading manifests. |
|
|
132
|
+
|
|
133
|
+
The package exposes matching ESM and CommonJS entry points. Probe, download, lock, and command-policy internals are not exported.
|
|
143
134
|
|
|
144
135
|
## Configuration
|
|
145
136
|
|
|
146
|
-
Precedence is API
|
|
137
|
+
Precedence is API or CLI option, environment variable, then default.
|
|
147
138
|
|
|
148
139
|
| API option | Environment variable | Default |
|
|
149
140
|
| --- | --- | --- |
|
|
150
|
-
| `python` | `PYFIG_PYTHON` |
|
|
141
|
+
| `python` | `PYFIG_PYTHON` | uv discovery |
|
|
151
142
|
| `pythonRange` | `PYFIG_PYTHON_RANGE` | `>=3.10.0 <4.0.0` |
|
|
152
|
-
| `pipRange` | `PYFIG_PIP_RANGE` | `>=20.3.0` |
|
|
153
143
|
| `managedPython` | `PYFIG_MANAGED_PYTHON` | `3.12` |
|
|
154
144
|
| `uvVersion` | `PYFIG_UV_VERSION` | `0.13.6` |
|
|
155
|
-
| `uvRange` | `PYFIG_UV_RANGE` | `>=0.
|
|
145
|
+
| `uvRange` | `PYFIG_UV_RANGE` | `>=0.10.0 <1.0.0` |
|
|
156
146
|
| `cacheDir` | `PYFIG_HOME` | OS cache directory |
|
|
157
|
-
| `projectRoot` | `PYFIG_PROJECT_ROOT` |
|
|
147
|
+
| `projectRoot` | `PYFIG_PROJECT_ROOT` | `INIT_CWD`, Git root, or current directory |
|
|
158
148
|
| `requireEmbedding` | `PYFIG_REQUIRE_EMBEDDING` | `false` |
|
|
159
149
|
| `requireGil` | `PYFIG_REQUIRE_GIL` | `false` |
|
|
160
|
-
| `autoInstall` | `PYFIG_AUTO_INSTALL` | `true`
|
|
150
|
+
| `autoInstall` | `PYFIG_AUTO_INSTALL` | `true` |
|
|
161
151
|
| `installPython` | `PYFIG_INSTALL_PYTHON` | `autoInstall` |
|
|
162
152
|
| `installUv` | `PYFIG_INSTALL_UV` | `autoInstall` |
|
|
163
153
|
| `createVirtualEnvironment` | `PYFIG_CREATE_VIRTUAL_ENVIRONMENT` | `autoInstall` |
|
|
@@ -165,43 +155,44 @@ Precedence is API/CLI option, environment variable, then default.
|
|
|
165
155
|
| `installTimeoutMs` | `PYFIG_INSTALL_TIMEOUT_MS` | `600000` |
|
|
166
156
|
| `downloadTimeoutMs` | `PYFIG_DOWNLOAD_TIMEOUT_MS` | `300000` |
|
|
167
157
|
| `coordinationTimeoutMs` | `PYFIG_COORDINATION_TIMEOUT_MS` | install + download + 60 seconds |
|
|
168
|
-
| `cacheTtlMs` | `PYFIG_CACHE_TTL_MS` | `30000` (`0` disables completed-result caching) |
|
|
169
158
|
|
|
170
|
-
Set `PYFIG_UV` to
|
|
159
|
+
Set `PYFIG_UV` to choose an explicit uv executable. Set `PYFIG_AUTO_INSTALL=0` or use the CLI's `--check` flag on hosts where downloads and environment creation are prohibited.
|
|
171
160
|
|
|
172
|
-
##
|
|
161
|
+
## CLI
|
|
173
162
|
|
|
174
|
-
|
|
163
|
+
```sh
|
|
164
|
+
bunx --bun pyfig
|
|
165
|
+
bunx --bun pyfig --check
|
|
166
|
+
bunx --bun pyfig --python-range '>=3.12.0 <3.14.0'
|
|
167
|
+
bunx --bun pyfig --require-embedding --require-gil
|
|
168
|
+
bunx --bun pyfig --project-root /path/to/repository
|
|
169
|
+
bunx --bun pyfig --json
|
|
170
|
+
```
|
|
175
171
|
|
|
176
|
-
|
|
172
|
+
`bunx --bun` runs the CLI with Bun and does not require a separate Node.js executable.
|
|
177
173
|
|
|
178
|
-
##
|
|
174
|
+
## Concurrency and installation safety
|
|
179
175
|
|
|
180
|
-
Pyfig downloads
|
|
176
|
+
Pyfig downloads an exact uv release archive over HTTPS, verifies its adjacent SHA-256 checksum, validates the staged executable version, and atomically publishes it in Pyfig's cache. It does not execute installer scripts or modify shell profiles.
|
|
181
177
|
|
|
182
|
-
|
|
178
|
+
Provisioning mutations use Loqkit's layered in-memory, worker, native-file-lock-when-available, and portable-file coordination with capability checks before and after acquisition. uv supplies its own target-environment lock for installs. Base client preparation is memoized; dependency preparation is intentionally rerun so uv can cheaply verify current inputs through its shared cache.
|
|
183
179
|
|
|
184
|
-
|
|
185
|
-
2. Queue behind the current JavaScript realm's mutex.
|
|
186
|
-
3. Use Node's cross-worker `LockManager` when the runtime provides it.
|
|
187
|
-
4. Use the system `flock` command when it is available and functional.
|
|
188
|
-
5. Acquire the portable heartbeat-based filesystem lease used by every runtime.
|
|
189
|
-
6. Check again and install only when the resource is still absent.
|
|
180
|
+
## Platform support
|
|
190
181
|
|
|
191
|
-
|
|
182
|
+
Pyfig bootstraps uv on x64 and arm64 Windows, macOS, and Linux. Linux uses Astral's static musl artifact, which also runs on glibc. Node.js 22 through 26 and Bun 1.2+ are supported. Other JavaScript runtimes may work when they provide compatible Node built-ins and process semantics.
|
|
192
183
|
|
|
193
184
|
## Publishing
|
|
194
185
|
|
|
195
|
-
Pyfig publishes
|
|
186
|
+
Pyfig publishes from a `v<package-version>` Git tag through `.github/workflows/publish.yml`. The trusted-publishing workflow validates the tag, rejects an existing registry version, builds and audits one archive, and publishes that archive with provenance.
|
|
196
187
|
|
|
197
|
-
|
|
188
|
+
From a clean, synchronized `main` branch:
|
|
198
189
|
|
|
199
190
|
```sh
|
|
200
|
-
bun
|
|
201
|
-
bun run verify
|
|
202
|
-
bun run release:check -- v0.3.0
|
|
191
|
+
bun run release
|
|
203
192
|
```
|
|
204
193
|
|
|
194
|
+
The local gate derives the next version from remote GitHub tags, verifies locally, audits the package, commits, tags, and atomically pushes. Use `bun run release -- minor`, `bun run release -- major`, or `bun run release -- --dry-run` as needed. Tag publishing intentionally does not repeat the local test suite.
|
|
195
|
+
|
|
205
196
|
## Development
|
|
206
197
|
|
|
207
198
|
```sh
|
package/SECURITY.md
CHANGED
|
@@ -4,6 +4,6 @@ Pyfig can download Astral uv release archives and can ask uv to download Python
|
|
|
4
4
|
|
|
5
5
|
The uv release URL is pinned to an exact validated version and fetched over HTTPS with bounded redirects and timeouts. Pyfig verifies the adjacent SHA-256 checksum, validates the staged executable's reported version, and atomically publishes it into its cache. It never executes an installer script or modifies shell profiles or the host `PATH`. Proxy variables are honored through a per-request dispatcher without logging proxy credentials.
|
|
6
6
|
|
|
7
|
-
Provisioning uses
|
|
7
|
+
Provisioning uses in-process, worker, optional native advisory, and portable filesystem coordination followed by a post-lock capability check. Runtime dependency mutation uses uv's target-environment lock. Pyfig pins uv commands to the selected venv and removes configuration values that could redirect them. It reads an existing `pyproject.toml` only as a resolver input and never writes project manifests or requirements files.
|
|
8
8
|
|
|
9
9
|
Executable paths, project roots, version ranges, package specifications, indexes, and proxy settings remain trusted configuration. Do not let an untrusted user choose `python`, `PYFIG_UV`, `cacheDir`, package arguments, or related environment variables in a privileged process. A checksum fetched from the same release protects transfer integrity, not a compromised upstream publisher.
|