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 +14 -12
- package/SECURITY.md +1 -1
- package/dist/cjs/index.js +82 -45
- package/dist/cjs/index.js.map +8 -7
- package/dist/cjs/sync-resolver.js +80 -43
- package/dist/cjs/sync-resolver.js.map +7 -6
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +2 -2
- package/dist/config.d.ts +5 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +15 -8
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/probes.d.ts +1 -1
- package/dist/probes.d.ts.map +1 -1
- package/dist/probes.js +13 -4
- package/dist/probes.js.map +1 -1
- package/dist/provision.d.ts +2 -2
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +50 -33
- package/dist/provision.js.map +1 -1
- package/dist/python-version.d.ts +10 -0
- package/dist/python-version.d.ts.map +1 -0
- package/dist/python-version.js +17 -0
- package/dist/python-version.js.map +1 -0
- package/package.json +5 -4
|
@@ -74,7 +74,7 @@ var import_node_os = __toESM(require("node:os"), 1);
|
|
|
74
74
|
var import_node_path5 = __toESM(require("node:path"), 1);
|
|
75
75
|
var import_node_stream = require("node:stream");
|
|
76
76
|
var import_promises2 = require("node:stream/promises");
|
|
77
|
-
var
|
|
77
|
+
var import_fflate = require("fflate");
|
|
78
78
|
var tar = __toESM(require("tar"), 1);
|
|
79
79
|
var import_undici = require("undici");
|
|
80
80
|
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
192
|
-
throw new TypeError(`Managed Python ${request} cannot satisfy
|
|
208
|
+
if (!pythonVersionSatisfies(normalized, requiresPython)) {
|
|
209
|
+
throw new TypeError(`Managed Python ${request} cannot satisfy requires-python ${requiresPython}.`);
|
|
193
210
|
}
|
|
194
211
|
}
|
|
195
212
|
}
|
|
@@ -497,7 +514,15 @@ if os.path.isfile(header_path) and library_path:
|
|
|
497
514
|
}
|
|
498
515
|
|
|
499
516
|
release_level = sys.version_info.releaselevel
|
|
500
|
-
|
|
517
|
+
release_suffixes = {
|
|
518
|
+
"alpha": "a",
|
|
519
|
+
"beta": "b",
|
|
520
|
+
"candidate": "rc",
|
|
521
|
+
"final": "",
|
|
522
|
+
}
|
|
523
|
+
release_suffix = release_suffixes[release_level]
|
|
524
|
+
if release_suffix:
|
|
525
|
+
release_suffix += str(sys.version_info.serial)
|
|
501
526
|
|
|
502
527
|
print(json.dumps({
|
|
503
528
|
"executable": sys.executable,
|
|
@@ -517,8 +542,8 @@ print(json.dumps({
|
|
|
517
542
|
"embedding": embedding,
|
|
518
543
|
}))
|
|
519
544
|
`;
|
|
520
|
-
function
|
|
521
|
-
return
|
|
545
|
+
function pythonSatisfiesPolicy(python, options) {
|
|
546
|
+
return pythonVersionSatisfies(python.version, options.requiresPython) && (!options.requireEmbedding || python.implementation === "cpython" && python.embedding !== null && pythonArchitectureMatchesRuntime(python)) && (!options.requireGil || !python.gilDisabled);
|
|
522
547
|
}
|
|
523
548
|
async function probePython(candidate, options) {
|
|
524
549
|
const executable = await resolveExecutable(candidate.executable, options.env);
|
|
@@ -527,7 +552,7 @@ async function probePython(candidate, options) {
|
|
|
527
552
|
try {
|
|
528
553
|
const { stdout } = run(executable, [...candidate.args, "-c", PYTHON_PROBE], options.env, options.probeTimeoutMs);
|
|
529
554
|
const info = JSON.parse(stdout.trim());
|
|
530
|
-
return
|
|
555
|
+
return isValidPythonVersion(info.version) ? info : null;
|
|
531
556
|
} catch {
|
|
532
557
|
return null;
|
|
533
558
|
}
|
|
@@ -570,14 +595,14 @@ function pythonArchitectureMatchesRuntime(python) {
|
|
|
570
595
|
async function ensureUv(options = {}) {
|
|
571
596
|
return ensureUvResolved(resolveOptions(options));
|
|
572
597
|
}
|
|
573
|
-
async function ensureEnvironment(options = {},
|
|
598
|
+
async function ensureEnvironment(options = {}, dependencies = [], dependencyOptions = {}) {
|
|
574
599
|
const resolved = resolveOptions(options);
|
|
575
|
-
const normalized =
|
|
600
|
+
const normalized = normalizeDependencies(dependencies);
|
|
576
601
|
const selections = normalizeDependencyOptions(dependencyOptions);
|
|
577
602
|
const uv = await ensureUvResolved(resolved);
|
|
578
603
|
const python = await ensureVirtualEnvironmentResolved(resolved, uv);
|
|
579
604
|
if (normalized.length > 0 || selections.extras.length > 0 || selections.groups.length > 0 || selections.allExtras) {
|
|
580
|
-
await
|
|
605
|
+
await installRuntimeDependencies(uv, python, normalized, selections, resolved);
|
|
581
606
|
}
|
|
582
607
|
return {
|
|
583
608
|
python,
|
|
@@ -592,7 +617,7 @@ async function ensureVirtualEnvironmentResolved(options, uv) {
|
|
|
592
617
|
const directory = virtualEnvironmentDirectory(options.projectRoot);
|
|
593
618
|
const check = async () => {
|
|
594
619
|
const candidate = await inspectVirtualEnvironment(directory, options);
|
|
595
|
-
return candidate &&
|
|
620
|
+
return candidate && pythonSatisfiesPolicy(candidate, options) && await virtualEnvironmentMatchesConfiguredPython(candidate, options) ? candidate : null;
|
|
596
621
|
};
|
|
597
622
|
if (!options.createVirtualEnvironment) {
|
|
598
623
|
throw new Error(`No compatible environment is available and creation is disabled. ` + `Pyfig would create ${directory}.`);
|
|
@@ -625,7 +650,7 @@ async function ensureVirtualEnvironmentResolved(options, uv) {
|
|
|
625
650
|
async function findReusableEnvironment(options) {
|
|
626
651
|
if (options.python) {
|
|
627
652
|
const configured = await probePython({ executable: options.python, args: [] }, options);
|
|
628
|
-
if (!configured || !
|
|
653
|
+
if (!configured || !pythonSatisfiesPolicy(configured, options)) {
|
|
629
654
|
throw incompatibleConfiguredPython(options);
|
|
630
655
|
}
|
|
631
656
|
if (configured.virtualEnvironment)
|
|
@@ -636,7 +661,7 @@ async function findReusableEnvironment(options) {
|
|
|
636
661
|
virtualEnvironmentDirectory(options.projectRoot)
|
|
637
662
|
]) {
|
|
638
663
|
const info = await inspectVirtualEnvironment(directory, options);
|
|
639
|
-
if (info &&
|
|
664
|
+
if (info && pythonSatisfiesPolicy(info, options) && await virtualEnvironmentMatchesConfiguredPython(info, options)) {
|
|
640
665
|
return info;
|
|
641
666
|
}
|
|
642
667
|
}
|
|
@@ -645,13 +670,13 @@ async function findReusableEnvironment(options) {
|
|
|
645
670
|
async function ensureBasePythonResolved(uv, options) {
|
|
646
671
|
if (options.python) {
|
|
647
672
|
const configured = await probePython({ executable: options.python, args: [] }, options);
|
|
648
|
-
if (!configured || !
|
|
673
|
+
if (!configured || !pythonSatisfiesPolicy(configured, options)) {
|
|
649
674
|
throw incompatibleConfiguredPython(options);
|
|
650
675
|
}
|
|
651
676
|
if (!configured.virtualEnvironment)
|
|
652
677
|
return configured;
|
|
653
678
|
const base = await probePython({ executable: configured.baseExecutable, args: [] }, options);
|
|
654
|
-
if (base &&
|
|
679
|
+
if (base && pythonSatisfiesPolicy(base, options))
|
|
655
680
|
return base;
|
|
656
681
|
throw incompatibleConfiguredPython(options);
|
|
657
682
|
}
|
|
@@ -693,7 +718,7 @@ async function findPythonWithUv(uv, options, source) {
|
|
|
693
718
|
try {
|
|
694
719
|
const { stdout } = await runAsync(uv.executable, args, explicitPythonSourceEnvironment(options), options.probeTimeoutMs);
|
|
695
720
|
const info = await probePython({ executable: stdout.trim(), args: [] }, options);
|
|
696
|
-
if (info &&
|
|
721
|
+
if (info && pythonSatisfiesPolicy(info, options))
|
|
697
722
|
return info;
|
|
698
723
|
} catch {}
|
|
699
724
|
}
|
|
@@ -711,7 +736,7 @@ async function findPythonWithUv(uv, options, source) {
|
|
|
711
736
|
const candidates = installedPythonPaths(stdout);
|
|
712
737
|
for (const executable of candidates) {
|
|
713
738
|
const info = await probePython({ executable, args: [] }, options);
|
|
714
|
-
if (info && !info.virtualEnvironment &&
|
|
739
|
+
if (info && !info.virtualEnvironment && pythonSatisfiesPolicy(info, options)) {
|
|
715
740
|
return info;
|
|
716
741
|
}
|
|
717
742
|
}
|
|
@@ -735,7 +760,7 @@ async function inspectVirtualEnvironment(directory, options) {
|
|
|
735
760
|
const info = await probePython({ executable, args: [] }, options);
|
|
736
761
|
return info?.virtualEnvironment && pathsEqual(info.virtualEnvironment, directory) ? info : null;
|
|
737
762
|
}
|
|
738
|
-
async function
|
|
763
|
+
async function installRuntimeDependencies(uv, python, dependencies, dependencyOptions, options) {
|
|
739
764
|
if (!python.virtualEnvironment) {
|
|
740
765
|
throw new Error("Runtime dependency installation requires a virtual environment.");
|
|
741
766
|
}
|
|
@@ -744,18 +769,18 @@ async function installRuntimeRequirements(uv, python, requirements, dependencyOp
|
|
|
744
769
|
timeoutMs: options.coordinationTimeoutMs,
|
|
745
770
|
check: async () => null,
|
|
746
771
|
mutate: async () => {
|
|
747
|
-
const temporary = await import_promises.mkdtemp(import_node_path5.default.join(import_node_os.default.tmpdir(), "pyfig-
|
|
772
|
+
const temporary = await import_promises.mkdtemp(import_node_path5.default.join(import_node_os.default.tmpdir(), "pyfig-dependencies-"));
|
|
748
773
|
try {
|
|
749
|
-
const additions = import_node_path5.default.join(temporary, "runtime-
|
|
750
|
-
const resolved = import_node_path5.default.join(temporary, "resolved-
|
|
774
|
+
const additions = import_node_path5.default.join(temporary, "runtime-dependencies.txt");
|
|
775
|
+
const resolved = import_node_path5.default.join(temporary, "resolved-dependencies.txt");
|
|
751
776
|
const pyproject = import_node_path5.default.join(options.projectRoot, "pyproject.toml");
|
|
752
777
|
const hasPyproject = import_node_fs4.default.existsSync(pyproject);
|
|
753
778
|
if (!hasPyproject && (dependencyOptions.extras.length > 0 || dependencyOptions.groups.length > 0 || dependencyOptions.allExtras)) {
|
|
754
779
|
throw new Error("Runtime extras and groups require an existing pyproject.toml.");
|
|
755
780
|
}
|
|
756
781
|
const sources = hasPyproject ? [pyproject] : [];
|
|
757
|
-
if (
|
|
758
|
-
await import_promises.writeFile(additions, `${
|
|
782
|
+
if (dependencies.length > 0) {
|
|
783
|
+
await import_promises.writeFile(additions, `${dependencies.join(`
|
|
759
784
|
`)}
|
|
760
785
|
`, "utf8");
|
|
761
786
|
sources.push(additions);
|
|
@@ -814,11 +839,11 @@ async function findAvailableUv(options) {
|
|
|
814
839
|
const managed = probeUv(uvExecutable(options), options);
|
|
815
840
|
return managed?.version === options.uvVersion ? managed : null;
|
|
816
841
|
}
|
|
817
|
-
function
|
|
818
|
-
const normalized =
|
|
819
|
-
for (const
|
|
820
|
-
if (!
|
|
821
|
-
throw new TypeError(`Invalid runtime
|
|
842
|
+
function normalizeDependencies(dependencies) {
|
|
843
|
+
const normalized = dependencies.map((dependency) => dependency.trim());
|
|
844
|
+
for (const dependency of normalized) {
|
|
845
|
+
if (!dependency || /[\0\r\n]/u.test(dependency)) {
|
|
846
|
+
throw new TypeError(`Invalid runtime dependency: ${JSON.stringify(dependency)}`);
|
|
822
847
|
}
|
|
823
848
|
}
|
|
824
849
|
return [...new Set(normalized)];
|
|
@@ -855,17 +880,17 @@ function targetUvEnvironment(options, python) {
|
|
|
855
880
|
return uvCommandEnvironment(uvEnvironment(options), python);
|
|
856
881
|
}
|
|
857
882
|
function incompatibleConfiguredPython(options) {
|
|
858
|
-
return new Error(`Configured Python ${options.python} is unavailable or does not satisfy ` +
|
|
883
|
+
return new Error(`Configured Python ${options.python} is unavailable or does not satisfy ` + `requires-python ${options.requiresPython}` + `${options.requireEmbedding ? " with CPython embedding files" : ""}.`);
|
|
859
884
|
}
|
|
860
885
|
function missingPythonMessage(options, reason) {
|
|
861
886
|
const embedding = options.requireEmbedding ? " with compatible CPython headers, library, architecture, and GIL ABI" : "";
|
|
862
|
-
return `No Python satisfies ${options.
|
|
887
|
+
return `No Python satisfies requires-python ${options.requiresPython}${embedding}; ${reason}.`;
|
|
863
888
|
}
|
|
864
889
|
async function virtualEnvironmentMatchesConfiguredPython(virtualEnvironment, options) {
|
|
865
890
|
if (!options.python)
|
|
866
891
|
return true;
|
|
867
892
|
const configured = await probePython({ executable: options.python, args: [] }, options);
|
|
868
|
-
if (!configured || !
|
|
893
|
+
if (!configured || !pythonSatisfiesPolicy(configured, options))
|
|
869
894
|
return false;
|
|
870
895
|
if (configured.virtualEnvironment && pathsEqual(configured.executable, virtualEnvironment.executable)) {
|
|
871
896
|
return true;
|
|
@@ -894,11 +919,7 @@ async function installUv(installDir, options) {
|
|
|
894
919
|
}
|
|
895
920
|
const extracted = import_node_path5.default.join(temporary, "extracted");
|
|
896
921
|
await import_promises.mkdir(extracted, { recursive: true });
|
|
897
|
-
|
|
898
|
-
await import_extract_zip.default(archive, { dir: extracted });
|
|
899
|
-
else
|
|
900
|
-
await tar.x({ file: archive, cwd: extracted, strict: true });
|
|
901
|
-
const source = await findFile(extracted, process.platform === "win32" ? "uv.exe" : "uv");
|
|
922
|
+
const source = await extractUvExecutable(archive, asset, extracted);
|
|
902
923
|
if (!source)
|
|
903
924
|
throw new Error(`uv archive did not contain the expected executable: ${asset}`);
|
|
904
925
|
await publishUvAtomically(source, installDir, options);
|
|
@@ -907,6 +928,22 @@ async function installUv(installDir, options) {
|
|
|
907
928
|
await import_promises.rm(temporary, { recursive: true, force: true });
|
|
908
929
|
}
|
|
909
930
|
}
|
|
931
|
+
async function extractUvExecutable(archive, asset, destination) {
|
|
932
|
+
const executableName = process.platform === "win32" ? "uv.exe" : "uv";
|
|
933
|
+
if (!asset.endsWith(".zip")) {
|
|
934
|
+
await tar.x({ file: archive, cwd: destination, strict: true });
|
|
935
|
+
return findFile(destination, executableName);
|
|
936
|
+
}
|
|
937
|
+
const entries = import_fflate.unzipSync(await import_promises.readFile(archive), {
|
|
938
|
+
filter: ({ name }) => import_node_path5.default.posix.basename(name.replaceAll("\\", "/")) === executableName
|
|
939
|
+
});
|
|
940
|
+
const matches = Object.entries(entries).filter(([name]) => import_node_path5.default.posix.basename(name.replaceAll("\\", "/")) === executableName);
|
|
941
|
+
if (matches.length !== 1 || !matches[0])
|
|
942
|
+
return null;
|
|
943
|
+
const source = import_node_path5.default.join(destination, executableName);
|
|
944
|
+
await import_promises.writeFile(source, matches[0][1], { flag: "wx" });
|
|
945
|
+
return source;
|
|
946
|
+
}
|
|
910
947
|
async function publishUvAtomically(source, installDir, options) {
|
|
911
948
|
await import_promises.mkdir(installDir, { recursive: true });
|
|
912
949
|
const executable = uvExecutable(options);
|
|
@@ -1008,4 +1045,4 @@ main().catch((error) => {
|
|
|
1008
1045
|
process.exitCode = 1;
|
|
1009
1046
|
});
|
|
1010
1047
|
|
|
1011
|
-
//# debugId=
|
|
1048
|
+
//# debugId=8D6E1E9C1BFA227064756E2164756E21
|