pyfig 0.4.0 → 0.5.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 CHANGED
@@ -16,7 +16,7 @@ bun add pyfig
16
16
  import { createPyfig } from 'pyfig';
17
17
 
18
18
  const pyfig = createPyfig({
19
- pythonRange: '>=3.11.0 <4.0.0',
19
+ requiresPython: '>=3.11,<4',
20
20
  requireEmbedding: true,
21
21
  requireGil: true,
22
22
  });
@@ -36,7 +36,7 @@ await pyfig.uv(
36
36
  );
37
37
  ```
38
38
 
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.
39
+ `prepare()` accepts PEP 508 dependency specifiers plus optional `extras`, `groups`, or `allExtras`. If the project has a `pyproject.toml`, uv resolves that read-only input together with a temporary dependency 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.
40
40
 
41
41
  The same uv-only surface handles direct commands:
42
42
 
@@ -89,19 +89,19 @@ await pyfig.prepare(
89
89
  Resolution follows the equivalent of:
90
90
 
91
91
  ```sh
92
- uv pip compile pyproject.toml /temporary/runtime-requirements.txt \
92
+ uv pip compile pyproject.toml /temporary/runtime-dependencies.txt \
93
93
  --extra postgres --group runtime \
94
94
  --python /selected/venv/python \
95
- --output-file /temporary/resolved-requirements.txt
95
+ --output-file /temporary/resolved-dependencies.txt
96
96
 
97
97
  uv pip install \
98
98
  --python /selected/venv/python \
99
- -r /temporary/resolved-requirements.txt
99
+ -r /temporary/resolved-dependencies.txt
100
100
  ```
101
101
 
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.
102
+ When no `pyproject.toml` exists, only the temporary dependency 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
103
 
104
- ![Runtime requirements are resolved in temporary files before installation](docs/diagrams/runtime-dependencies.png)
104
+ ![Runtime dependencies are resolved in temporary files before installation](docs/diagrams/runtime-dependencies.png)
105
105
 
106
106
  [Mermaid source](docs/diagrams/runtime-dependencies.mmd)
107
107
 
@@ -121,11 +121,11 @@ Set `projectRoot` explicitly in reusable libraries and unusual workspace layouts
121
121
  | Export | Responsibility |
122
122
  | --- | --- |
123
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. |
124
+ | `client.prepare(dependencies?, options?)` | Select a venv and optionally resolve/install runtime dependencies. |
125
125
  | `client.pythonInfo()` | Return the selected interpreter and embedding metadata. |
126
126
  | `client.uvInfo()` | Return the selected uv executable and version. |
127
127
  | `client.uv()` / `client.uvSync()` | Spawn uv and pin pip/run commands to the selected venv. |
128
- | `ensureEnvironment(options?, requirements?, dependencyOptions?)` | Standalone preparation API. |
128
+ | `ensureEnvironment(options?, dependencies?, dependencyOptions?)` | Standalone preparation API. |
129
129
  | `ensureUv(options?)` | Prefer existing uv, otherwise bootstrap verified uv when allowed. |
130
130
  | `uvSpawn()` / `uvSpawnSync()` | Standalone uv spawn API. |
131
131
  | `resolveProjectRoot(start?)` | Resolve the nearest Git root without reading manifests. |
@@ -136,10 +136,12 @@ The package exposes matching ESM and CommonJS entry points. Probe, download, loc
136
136
 
137
137
  Precedence is API or CLI option, environment variable, then default.
138
138
 
139
+ `requiresPython` uses the same PEP 440 specifier syntax as `[project].requires-python` in `pyproject.toml`. Comma-separated clauses, compatible releases (`~=`), exclusions (`!=`), wildcard equality (`==3.12.*`), arbitrary equality (`===`), and pre-release versions are supported. For example: `>=3.10,!=3.12.*,<4`.
140
+
139
141
  | API option | Environment variable | Default |
140
142
  | --- | --- | --- |
141
143
  | `python` | `PYFIG_PYTHON` | uv discovery |
142
- | `pythonRange` | `PYFIG_PYTHON_RANGE` | `>=3.10.0 <4.0.0` |
144
+ | `requiresPython` | `PYFIG_REQUIRES_PYTHON` | `>=3.10,<4` |
143
145
  | `managedPython` | `PYFIG_MANAGED_PYTHON` | `3.12` |
144
146
  | `uvVersion` | `PYFIG_UV_VERSION` | `0.13.6` |
145
147
  | `uvRange` | `PYFIG_UV_RANGE` | `>=0.10.0 <1.0.0` |
@@ -163,7 +165,7 @@ Set `PYFIG_UV` to choose an explicit uv executable. Set `PYFIG_AUTO_INSTALL=0` o
163
165
  ```sh
164
166
  bunx --bun pyfig
165
167
  bunx --bun pyfig --check
166
- bunx --bun pyfig --python-range '>=3.12.0 <3.14.0'
168
+ bunx --bun pyfig --requires-python '>=3.12,<3.14'
167
169
  bunx --bun pyfig --require-embedding --require-gil
168
170
  bunx --bun pyfig --project-root /path/to/repository
169
171
  bunx --bun pyfig --json
@@ -179,7 +181,7 @@ Provisioning mutations use Loqkit's layered in-memory, worker, native-file-lock-
179
181
 
180
182
  ## Platform support
181
183
 
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.
184
+ 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.11+ on the 22.x line, Node.js 24.10 through 26.x, and Bun 1.2+ are supported. Other JavaScript runtimes may work when they provide compatible Node built-ins and process semantics.
183
185
 
184
186
  ## Publishing
185
187
 
package/SECURITY.md CHANGED
@@ -6,4 +6,4 @@ The uv release URL is pinned to an exact validated version and fetched over HTTP
6
6
 
7
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
- 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.
9
+ Executable paths, project roots, version specifiers, 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.
package/dist/cjs/index.js CHANGED
@@ -66,7 +66,7 @@ var __export = (target, all) => {
66
66
  var exports_src = {};
67
67
  __export(exports_src, {
68
68
  DEFAULT_MANAGED_PYTHON: () => DEFAULT_MANAGED_PYTHON,
69
- DEFAULT_PYTHON_RANGE: () => DEFAULT_PYTHON_RANGE,
69
+ DEFAULT_REQUIRES_PYTHON: () => DEFAULT_REQUIRES_PYTHON,
70
70
  DEFAULT_UV_RANGE: () => DEFAULT_UV_RANGE,
71
71
  DEFAULT_UV_VERSION: () => DEFAULT_UV_VERSION,
72
72
  createPyfig: () => createPyfig,
@@ -105,8 +105,20 @@ function* ancestors(startDirectory) {
105
105
  }
106
106
  }
107
107
 
108
+ // src/python-version.ts
109
+ var import_pep440 = require("@renovatebot/pep440");
110
+ function isValidPythonSpecifier(specifier) {
111
+ return import_pep440.validRange(specifier);
112
+ }
113
+ function isValidPythonVersion(version) {
114
+ return import_pep440.valid(version) !== null;
115
+ }
116
+ function pythonVersionSatisfies(version, specifier) {
117
+ return import_pep440.filter([version], specifier).length === 1;
118
+ }
119
+
108
120
  // src/config.ts
109
- var DEFAULT_PYTHON_RANGE = ">=3.10.0 <4.0.0";
121
+ var DEFAULT_REQUIRES_PYTHON = ">=3.10,<4";
110
122
  var DEFAULT_MANAGED_PYTHON = "3.12";
111
123
  var DEFAULT_UV_VERSION = "0.13.6";
112
124
  var DEFAULT_UV_RANGE = ">=0.10.0 <1.0.0";
@@ -122,13 +134,13 @@ var envPathsFunction = (() => {
122
134
  })();
123
135
  function resolveOptions(options = {}) {
124
136
  const env = { ...options.env ?? process.env };
125
- const pythonRange = options.pythonRange ?? env["PYFIG_PYTHON_RANGE"] ?? DEFAULT_PYTHON_RANGE;
137
+ const requiresPython = options.requiresPython ?? env["PYFIG_REQUIRES_PYTHON"] ?? DEFAULT_REQUIRES_PYTHON;
126
138
  const uvRange = options.uvRange ?? env["PYFIG_UV_RANGE"] ?? DEFAULT_UV_RANGE;
127
- assertRange("Python", pythonRange);
139
+ assertPythonSpecifier(requiresPython);
128
140
  assertRange("uv", uvRange);
129
141
  const managedPython = options.managedPython ?? env["PYFIG_MANAGED_PYTHON"] ?? DEFAULT_MANAGED_PYTHON;
130
142
  const uvVersion = options.uvVersion ?? env["PYFIG_UV_VERSION"] ?? DEFAULT_UV_VERSION;
131
- assertManagedPythonRequest(managedPython, pythonRange);
143
+ assertManagedPythonRequest(managedPython, requiresPython);
132
144
  assertUvVersion(uvVersion, uvRange);
133
145
  const probeTimeoutMs = resolveDuration("probeTimeoutMs", options.probeTimeoutMs, env["PYFIG_PROBE_TIMEOUT_MS"], DEFAULT_PROBE_TIMEOUT_MS);
134
146
  const installTimeoutMs = resolveDuration("installTimeoutMs", options.installTimeoutMs, env["PYFIG_INSTALL_TIMEOUT_MS"], DEFAULT_INSTALL_TIMEOUT_MS);
@@ -141,7 +153,7 @@ function resolveOptions(options = {}) {
141
153
  const autoInstall = options.autoInstall ?? parseBoolean(env["PYFIG_AUTO_INSTALL"], true);
142
154
  return {
143
155
  python: options.python ?? env["PYFIG_PYTHON"] ?? "",
144
- pythonRange,
156
+ requiresPython,
145
157
  managedPython,
146
158
  uvVersion,
147
159
  uvRange,
@@ -182,14 +194,19 @@ function assertUvVersion(version, range) {
182
194
  throw new TypeError(`Pinned uv ${version} does not satisfy supported range ${range}.`);
183
195
  }
184
196
  }
185
- function assertManagedPythonRequest(request, pythonRange) {
197
+ function assertPythonSpecifier(specifier) {
198
+ if (!isValidPythonSpecifier(specifier)) {
199
+ throw new TypeError(`Invalid requires-python PEP 440 specifier: ${specifier}`);
200
+ }
201
+ }
202
+ function assertManagedPythonRequest(request, requiresPython) {
186
203
  if (!request || /[\\/\0\r\n]/u.test(request) || request.includes("..")) {
187
204
  throw new TypeError(`Invalid managed Python request: ${request}`);
188
205
  }
189
206
  if (/^\d+\.\d+(?:\.\d+)?$/u.test(request)) {
190
207
  const normalized = request.split(".").length === 2 ? `${request}.0` : request;
191
- if (!import_semver.default.satisfies(normalized, pythonRange)) {
192
- throw new TypeError(`Managed Python ${request} cannot satisfy configured range ${pythonRange}.`);
208
+ if (!pythonVersionSatisfies(normalized, requiresPython)) {
209
+ throw new TypeError(`Managed Python ${request} cannot satisfy requires-python ${requiresPython}.`);
193
210
  }
194
211
  }
195
212
  }
@@ -222,7 +239,7 @@ var import_node_os = __toESM(require("node:os"), 1);
222
239
  var import_node_path5 = __toESM(require("node:path"), 1);
223
240
  var import_node_stream = require("node:stream");
224
241
  var import_promises2 = require("node:stream/promises");
225
- var import_extract_zip = __toESM(require("extract-zip"), 1);
242
+ var import_fflate = require("fflate");
226
243
  var tar = __toESM(require("tar"), 1);
227
244
  var import_undici = require("undici");
228
245
 
@@ -512,7 +529,15 @@ if os.path.isfile(header_path) and library_path:
512
529
  }
513
530
 
514
531
  release_level = sys.version_info.releaselevel
515
- release_suffix = "" if release_level == "final" else f"-{release_level}.{sys.version_info.serial}"
532
+ release_suffixes = {
533
+ "alpha": "a",
534
+ "beta": "b",
535
+ "candidate": "rc",
536
+ "final": "",
537
+ }
538
+ release_suffix = release_suffixes[release_level]
539
+ if release_suffix:
540
+ release_suffix += str(sys.version_info.serial)
516
541
 
517
542
  print(json.dumps({
518
543
  "executable": sys.executable,
@@ -532,8 +557,8 @@ print(json.dumps({
532
557
  "embedding": embedding,
533
558
  }))
534
559
  `;
535
- function pythonSatisfiesRequirements(python, options) {
536
- return import_semver2.default.satisfies(python.version, options.pythonRange) && (!options.requireEmbedding || python.implementation === "cpython" && python.embedding !== null && pythonArchitectureMatchesRuntime(python)) && (!options.requireGil || !python.gilDisabled);
560
+ function pythonSatisfiesPolicy(python, options) {
561
+ return pythonVersionSatisfies(python.version, options.requiresPython) && (!options.requireEmbedding || python.implementation === "cpython" && python.embedding !== null && pythonArchitectureMatchesRuntime(python)) && (!options.requireGil || !python.gilDisabled);
537
562
  }
538
563
  async function probePython(candidate, options) {
539
564
  const executable = await resolveExecutable(candidate.executable, options.env);
@@ -542,7 +567,7 @@ async function probePython(candidate, options) {
542
567
  try {
543
568
  const { stdout } = run(executable, [...candidate.args, "-c", PYTHON_PROBE], options.env, options.probeTimeoutMs);
544
569
  const info = JSON.parse(stdout.trim());
545
- return import_semver2.default.valid(info.version) ? info : null;
570
+ return isValidPythonVersion(info.version) ? info : null;
546
571
  } catch {
547
572
  return null;
548
573
  }
@@ -585,14 +610,14 @@ function pythonArchitectureMatchesRuntime(python) {
585
610
  async function ensureUv(options = {}) {
586
611
  return ensureUvResolved(resolveOptions(options));
587
612
  }
588
- async function ensureEnvironment(options = {}, requirements = [], dependencyOptions = {}) {
613
+ async function ensureEnvironment(options = {}, dependencies = [], dependencyOptions = {}) {
589
614
  const resolved = resolveOptions(options);
590
- const normalized = normalizeRequirements(requirements);
615
+ const normalized = normalizeDependencies(dependencies);
591
616
  const selections = normalizeDependencyOptions(dependencyOptions);
592
617
  const uv = await ensureUvResolved(resolved);
593
618
  const python = await ensureVirtualEnvironmentResolved(resolved, uv);
594
619
  if (normalized.length > 0 || selections.extras.length > 0 || selections.groups.length > 0 || selections.allExtras) {
595
- await installRuntimeRequirements(uv, python, normalized, selections, resolved);
620
+ await installRuntimeDependencies(uv, python, normalized, selections, resolved);
596
621
  }
597
622
  return {
598
623
  python,
@@ -607,7 +632,7 @@ async function ensureVirtualEnvironmentResolved(options, uv) {
607
632
  const directory = virtualEnvironmentDirectory(options.projectRoot);
608
633
  const check = async () => {
609
634
  const candidate = await inspectVirtualEnvironment(directory, options);
610
- return candidate && pythonSatisfiesRequirements(candidate, options) && await virtualEnvironmentMatchesConfiguredPython(candidate, options) ? candidate : null;
635
+ return candidate && pythonSatisfiesPolicy(candidate, options) && await virtualEnvironmentMatchesConfiguredPython(candidate, options) ? candidate : null;
611
636
  };
612
637
  if (!options.createVirtualEnvironment) {
613
638
  throw new Error(`No compatible environment is available and creation is disabled. ` + `Pyfig would create ${directory}.`);
@@ -640,7 +665,7 @@ async function ensureVirtualEnvironmentResolved(options, uv) {
640
665
  async function findReusableEnvironment(options) {
641
666
  if (options.python) {
642
667
  const configured = await probePython({ executable: options.python, args: [] }, options);
643
- if (!configured || !pythonSatisfiesRequirements(configured, options)) {
668
+ if (!configured || !pythonSatisfiesPolicy(configured, options)) {
644
669
  throw incompatibleConfiguredPython(options);
645
670
  }
646
671
  if (configured.virtualEnvironment)
@@ -651,7 +676,7 @@ async function findReusableEnvironment(options) {
651
676
  virtualEnvironmentDirectory(options.projectRoot)
652
677
  ]) {
653
678
  const info = await inspectVirtualEnvironment(directory, options);
654
- if (info && pythonSatisfiesRequirements(info, options) && await virtualEnvironmentMatchesConfiguredPython(info, options)) {
679
+ if (info && pythonSatisfiesPolicy(info, options) && await virtualEnvironmentMatchesConfiguredPython(info, options)) {
655
680
  return info;
656
681
  }
657
682
  }
@@ -660,13 +685,13 @@ async function findReusableEnvironment(options) {
660
685
  async function ensureBasePythonResolved(uv, options) {
661
686
  if (options.python) {
662
687
  const configured = await probePython({ executable: options.python, args: [] }, options);
663
- if (!configured || !pythonSatisfiesRequirements(configured, options)) {
688
+ if (!configured || !pythonSatisfiesPolicy(configured, options)) {
664
689
  throw incompatibleConfiguredPython(options);
665
690
  }
666
691
  if (!configured.virtualEnvironment)
667
692
  return configured;
668
693
  const base = await probePython({ executable: configured.baseExecutable, args: [] }, options);
669
- if (base && pythonSatisfiesRequirements(base, options))
694
+ if (base && pythonSatisfiesPolicy(base, options))
670
695
  return base;
671
696
  throw incompatibleConfiguredPython(options);
672
697
  }
@@ -708,7 +733,7 @@ async function findPythonWithUv(uv, options, source) {
708
733
  try {
709
734
  const { stdout } = await runAsync(uv.executable, args, explicitPythonSourceEnvironment(options), options.probeTimeoutMs);
710
735
  const info = await probePython({ executable: stdout.trim(), args: [] }, options);
711
- if (info && pythonSatisfiesRequirements(info, options))
736
+ if (info && pythonSatisfiesPolicy(info, options))
712
737
  return info;
713
738
  } catch {}
714
739
  }
@@ -726,7 +751,7 @@ async function findPythonWithUv(uv, options, source) {
726
751
  const candidates = installedPythonPaths(stdout);
727
752
  for (const executable of candidates) {
728
753
  const info = await probePython({ executable, args: [] }, options);
729
- if (info && !info.virtualEnvironment && pythonSatisfiesRequirements(info, options)) {
754
+ if (info && !info.virtualEnvironment && pythonSatisfiesPolicy(info, options)) {
730
755
  return info;
731
756
  }
732
757
  }
@@ -750,7 +775,7 @@ async function inspectVirtualEnvironment(directory, options) {
750
775
  const info = await probePython({ executable, args: [] }, options);
751
776
  return info?.virtualEnvironment && pathsEqual(info.virtualEnvironment, directory) ? info : null;
752
777
  }
753
- async function installRuntimeRequirements(uv, python, requirements, dependencyOptions, options) {
778
+ async function installRuntimeDependencies(uv, python, dependencies, dependencyOptions, options) {
754
779
  if (!python.virtualEnvironment) {
755
780
  throw new Error("Runtime dependency installation requires a virtual environment.");
756
781
  }
@@ -759,18 +784,18 @@ async function installRuntimeRequirements(uv, python, requirements, dependencyOp
759
784
  timeoutMs: options.coordinationTimeoutMs,
760
785
  check: async () => null,
761
786
  mutate: async () => {
762
- const temporary = await import_promises.mkdtemp(import_node_path5.default.join(import_node_os.default.tmpdir(), "pyfig-requirements-"));
787
+ const temporary = await import_promises.mkdtemp(import_node_path5.default.join(import_node_os.default.tmpdir(), "pyfig-dependencies-"));
763
788
  try {
764
- const additions = import_node_path5.default.join(temporary, "runtime-requirements.txt");
765
- const resolved = import_node_path5.default.join(temporary, "resolved-requirements.txt");
789
+ const additions = import_node_path5.default.join(temporary, "runtime-dependencies.txt");
790
+ const resolved = import_node_path5.default.join(temporary, "resolved-dependencies.txt");
766
791
  const pyproject = import_node_path5.default.join(options.projectRoot, "pyproject.toml");
767
792
  const hasPyproject = import_node_fs4.default.existsSync(pyproject);
768
793
  if (!hasPyproject && (dependencyOptions.extras.length > 0 || dependencyOptions.groups.length > 0 || dependencyOptions.allExtras)) {
769
794
  throw new Error("Runtime extras and groups require an existing pyproject.toml.");
770
795
  }
771
796
  const sources = hasPyproject ? [pyproject] : [];
772
- if (requirements.length > 0) {
773
- await import_promises.writeFile(additions, `${requirements.join(`
797
+ if (dependencies.length > 0) {
798
+ await import_promises.writeFile(additions, `${dependencies.join(`
774
799
  `)}
775
800
  `, "utf8");
776
801
  sources.push(additions);
@@ -829,11 +854,11 @@ async function findAvailableUv(options) {
829
854
  const managed = probeUv(uvExecutable(options), options);
830
855
  return managed?.version === options.uvVersion ? managed : null;
831
856
  }
832
- function normalizeRequirements(requirements) {
833
- const normalized = requirements.map((requirement) => requirement.trim());
834
- for (const requirement of normalized) {
835
- if (!requirement || /[\0\r\n]/u.test(requirement)) {
836
- throw new TypeError(`Invalid runtime requirement: ${JSON.stringify(requirement)}`);
857
+ function normalizeDependencies(dependencies) {
858
+ const normalized = dependencies.map((dependency) => dependency.trim());
859
+ for (const dependency of normalized) {
860
+ if (!dependency || /[\0\r\n]/u.test(dependency)) {
861
+ throw new TypeError(`Invalid runtime dependency: ${JSON.stringify(dependency)}`);
837
862
  }
838
863
  }
839
864
  return [...new Set(normalized)];
@@ -870,17 +895,17 @@ function targetUvEnvironment(options, python) {
870
895
  return uvCommandEnvironment(uvEnvironment(options), python);
871
896
  }
872
897
  function incompatibleConfiguredPython(options) {
873
- return new Error(`Configured Python ${options.python} is unavailable or does not satisfy ` + `${options.pythonRange}${options.requireEmbedding ? " with CPython embedding files" : ""}.`);
898
+ return new Error(`Configured Python ${options.python} is unavailable or does not satisfy ` + `requires-python ${options.requiresPython}` + `${options.requireEmbedding ? " with CPython embedding files" : ""}.`);
874
899
  }
875
900
  function missingPythonMessage(options, reason) {
876
901
  const embedding = options.requireEmbedding ? " with compatible CPython headers, library, architecture, and GIL ABI" : "";
877
- return `No Python satisfies ${options.pythonRange}${embedding}; ${reason}.`;
902
+ return `No Python satisfies requires-python ${options.requiresPython}${embedding}; ${reason}.`;
878
903
  }
879
904
  async function virtualEnvironmentMatchesConfiguredPython(virtualEnvironment, options) {
880
905
  if (!options.python)
881
906
  return true;
882
907
  const configured = await probePython({ executable: options.python, args: [] }, options);
883
- if (!configured || !pythonSatisfiesRequirements(configured, options))
908
+ if (!configured || !pythonSatisfiesPolicy(configured, options))
884
909
  return false;
885
910
  if (configured.virtualEnvironment && pathsEqual(configured.executable, virtualEnvironment.executable)) {
886
911
  return true;
@@ -909,11 +934,7 @@ async function installUv(installDir, options) {
909
934
  }
910
935
  const extracted = import_node_path5.default.join(temporary, "extracted");
911
936
  await import_promises.mkdir(extracted, { recursive: true });
912
- if (asset.endsWith(".zip"))
913
- await import_extract_zip.default(archive, { dir: extracted });
914
- else
915
- await tar.x({ file: archive, cwd: extracted, strict: true });
916
- const source = await findFile(extracted, process.platform === "win32" ? "uv.exe" : "uv");
937
+ const source = await extractUvExecutable(archive, asset, extracted);
917
938
  if (!source)
918
939
  throw new Error(`uv archive did not contain the expected executable: ${asset}`);
919
940
  await publishUvAtomically(source, installDir, options);
@@ -922,6 +943,22 @@ async function installUv(installDir, options) {
922
943
  await import_promises.rm(temporary, { recursive: true, force: true });
923
944
  }
924
945
  }
946
+ async function extractUvExecutable(archive, asset, destination) {
947
+ const executableName = process.platform === "win32" ? "uv.exe" : "uv";
948
+ if (!asset.endsWith(".zip")) {
949
+ await tar.x({ file: archive, cwd: destination, strict: true });
950
+ return findFile(destination, executableName);
951
+ }
952
+ const entries = import_fflate.unzipSync(await import_promises.readFile(archive), {
953
+ filter: ({ name }) => import_node_path5.default.posix.basename(name.replaceAll("\\", "/")) === executableName
954
+ });
955
+ const matches = Object.entries(entries).filter(([name]) => import_node_path5.default.posix.basename(name.replaceAll("\\", "/")) === executableName);
956
+ if (matches.length !== 1 || !matches[0])
957
+ return null;
958
+ const source = import_node_path5.default.join(destination, executableName);
959
+ await import_promises.writeFile(source, matches[0][1], { flag: "wx" });
960
+ return source;
961
+ }
925
962
  async function publishUvAtomically(source, installDir, options) {
926
963
  await import_promises.mkdir(installDir, { recursive: true });
927
964
  const executable = uvExecutable(options);
@@ -1091,7 +1128,7 @@ function createPyfig(options = {}) {
1091
1128
  const baseEnvironment = import_loqkit2.memoize(() => ensureEnvironment(scopedOptions));
1092
1129
  const uvInfo = import_loqkit2.memoize(() => ensureUv(scopedOptions));
1093
1130
  return {
1094
- prepare: (requirements = [], dependencyOptions = {}) => requirements.length === 0 && !dependencyOptions.allExtras && (dependencyOptions.extras?.length ?? 0) === 0 && (dependencyOptions.groups?.length ?? 0) === 0 ? baseEnvironment() : ensureEnvironment(scopedOptions, requirements, dependencyOptions),
1131
+ prepare: (dependencies = [], dependencyOptions = {}) => dependencies.length === 0 && !dependencyOptions.allExtras && (dependencyOptions.extras?.length ?? 0) === 0 && (dependencyOptions.groups?.length ?? 0) === 0 ? baseEnvironment() : ensureEnvironment(scopedOptions, dependencies, dependencyOptions),
1095
1132
  pythonInfo: async () => (await baseEnvironment()).python,
1096
1133
  uvInfo,
1097
1134
  uv: (args = [], spawnOptions = {}) => uvSpawn(args, spawnOptions, scopedOptions),
@@ -1099,4 +1136,4 @@ function createPyfig(options = {}) {
1099
1136
  };
1100
1137
  }
1101
1138
 
1102
- //# debugId=B84AA2037417535D64756E2164756E21
1139
+ //# debugId=5564427996CDDBA964756E2164756E21