@styx-api/core 0.5.0 → 0.5.2
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/dist/index.cjs +42 -22
- package/dist/index.mjs +42 -22
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/backend/python/packaging.ts +38 -12
- package/src/backend/python/python.ts +26 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@styx-api/core",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Styx compiler core: parses CLI tool descriptors (e.g. Boutiques), optimizes an IR, solves typed parameter bindings, and generates type-safe wrappers (TypeScript, Python, JSON Schema). Part of the Styx/NiWrap ecosystem.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"styx",
|
|
@@ -72,17 +72,23 @@ function licenseField(proj: ProjectMeta): string {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* Per-suite `pyproject.toml`. The
|
|
76
|
-
*
|
|
77
|
-
* import
|
|
75
|
+
* Per-suite `pyproject.toml`. The suite source stays in a flat directory
|
|
76
|
+
* (`python/<pkg>/bet.py`), but setuptools' `package-dir` maps that directory onto
|
|
77
|
+
* the dotted import package `<project>.<pkg>`, so every suite nests under the
|
|
78
|
+
* metapackage's `<project>/` namespace. That restores `from <project> import
|
|
79
|
+
* <pkg>` while keeping each suite a separately-installable distribution (and
|
|
80
|
+
* leaves no top-level `<pkg>` polluting the global namespace). With no project
|
|
81
|
+
* name the import name is the bare `<pkg>` (top-level fallback). The styxdefs
|
|
78
82
|
* floor is the only runtime dependency.
|
|
79
83
|
*
|
|
80
|
-
* Precondition: `
|
|
81
|
-
*
|
|
82
|
-
* it verbatim as the directory name, so this stays consistent with that.
|
|
84
|
+
* Precondition: `importName` is a valid (possibly dotted) Python package path -
|
|
85
|
+
* the caller scrubs the project + package names into identifiers accordingly.
|
|
83
86
|
*/
|
|
84
|
-
export function generateSubPyproject(
|
|
85
|
-
|
|
87
|
+
export function generateSubPyproject(
|
|
88
|
+
proj: ProjectMeta,
|
|
89
|
+
pkg: PackageMeta,
|
|
90
|
+
importName: string,
|
|
91
|
+
): string {
|
|
86
92
|
const cb = new CodeBuilder(" ");
|
|
87
93
|
cb.line("[project]");
|
|
88
94
|
cb.line(`name = "${tomlStr(pyDistName(proj, pkg))}"`);
|
|
@@ -133,14 +139,28 @@ export function generateRootInitPy(): string {
|
|
|
133
139
|
|
|
134
140
|
/**
|
|
135
141
|
* Root `pyproject.toml`: a metapackage depending on `styxkit[all]` (the runner
|
|
136
|
-
* stack it re-exports) plus each per-suite distribution
|
|
137
|
-
*
|
|
138
|
-
*
|
|
142
|
+
* stack it re-exports) plus each per-suite distribution, pinned to this exact
|
|
143
|
+
* project version (`niwrap_fsl==1.2.3`). The suites all ride the project version
|
|
144
|
+
* and ship in lockstep, so an exact pin keeps `pip install niwrap==X` consistent:
|
|
145
|
+
* during the post-release index-propagation window (or any momentary registry
|
|
146
|
+
* inconsistency) pip errors cleanly instead of silently grafting an older suite
|
|
147
|
+
* whose layout no longer matches the metapackage. Mirrors the way the CLI pins
|
|
148
|
+
* `@styx-api/core` exactly.
|
|
149
|
+
*
|
|
150
|
+
* `packages` lists only the metapackage's own module so setuptools ships the
|
|
151
|
+
* styxkit re-export without sweeping the sibling suite directories into this
|
|
152
|
+
* distribution. The module is the `<project>/` namespace package the suites nest
|
|
153
|
+
* into, so installing the metapackage makes `<project>.use_docker()` reachable
|
|
154
|
+
* alongside the suites' `from <project> import <pkg>`. `moduleDir` is the on-disk
|
|
155
|
+
* source directory; it differs from the import `moduleName` only when a suite is
|
|
156
|
+
* named after the project, in which case a `package-dir` remap keeps the import
|
|
157
|
+
* name intact.
|
|
139
158
|
*/
|
|
140
159
|
export function generateRootPyproject(
|
|
141
160
|
proj: ProjectMeta,
|
|
142
161
|
distNames: string[],
|
|
143
162
|
moduleName: string,
|
|
163
|
+
moduleDir: string,
|
|
144
164
|
): string {
|
|
145
165
|
const cb = new CodeBuilder(" ");
|
|
146
166
|
cb.line("[project]");
|
|
@@ -153,11 +173,17 @@ export function generateRootPyproject(
|
|
|
153
173
|
cb.line(`requires-python = "${REQUIRES_PYTHON}"`);
|
|
154
174
|
cb.line("dependencies = [");
|
|
155
175
|
for (const dep of PYTHON_RUNNER_DEPS) cb.line(` "${dep}",`);
|
|
156
|
-
|
|
176
|
+
// Pin each suite to the exact project version (they ship in lockstep) so a
|
|
177
|
+
// mid-propagation install can't mix a new metapackage with an old suite.
|
|
178
|
+
const suitePin = tomlStr(proj.version ?? "0.0.0");
|
|
179
|
+
for (const dist of distNames) cb.line(` "${tomlStr(dist)}==${suitePin}",`);
|
|
157
180
|
cb.line("]");
|
|
158
181
|
cb.blank();
|
|
159
182
|
cb.line("[tool.setuptools]");
|
|
160
183
|
cb.line(`packages = ["${moduleName}"]`);
|
|
184
|
+
if (moduleDir !== moduleName) {
|
|
185
|
+
cb.line(`package-dir = { "${moduleName}" = "${moduleDir}" }`);
|
|
186
|
+
}
|
|
161
187
|
cb.blank();
|
|
162
188
|
cb.line("[tool.setuptools.package-data]");
|
|
163
189
|
cb.line(`"${moduleName}" = ["py.typed"]`);
|
|
@@ -547,6 +547,14 @@ export class PythonBackend implements Backend {
|
|
|
547
547
|
const pkgDirs: string[] = [];
|
|
548
548
|
const warnings: EmitWarning[] = [];
|
|
549
549
|
|
|
550
|
+
// The metapackage's importable module doubles as the namespace every suite
|
|
551
|
+
// nests under (`<project>.<suite>`), so `from <project> import <suite>` and
|
|
552
|
+
// `<project>.use_docker()` both resolve from one shared `<project>/` package.
|
|
553
|
+
// Scrub the project name to a valid, non-keyword identifier; with no project
|
|
554
|
+
// name there is no namespace and suites stay top-level (matching the bare
|
|
555
|
+
// distribution-name fallback in `pyDistName`).
|
|
556
|
+
const nsName = proj.name && proj.name.trim() ? pyScrubIdent(proj.name, PY_RESERVED) : undefined;
|
|
557
|
+
|
|
550
558
|
for (const p of packages) {
|
|
551
559
|
const pkg = p.meta ?? {};
|
|
552
560
|
// Mirror the CLI's `pkgDir` fallback so a nameless package's source dir
|
|
@@ -554,27 +562,31 @@ export class PythonBackend implements Backend {
|
|
|
554
562
|
const dir = pkg.name ?? "package";
|
|
555
563
|
pkgDirs.push(dir);
|
|
556
564
|
distNames.push(pyDistName(proj, pkg));
|
|
557
|
-
|
|
565
|
+
// Import package: `<project>.<suite>` so it shares the metapackage's
|
|
566
|
+
// namespace; setuptools maps the flat suite dir onto this dotted path.
|
|
567
|
+
const importPkg = nsName ? `${nsName}.${dir}` : dir;
|
|
568
|
+
files.set(`${dir}/pyproject.toml`, generateSubPyproject(proj, pkg, importPkg));
|
|
558
569
|
files.set(`${dir}/README.md`, generateSubReadme(proj, pkg));
|
|
559
570
|
}
|
|
560
571
|
|
|
561
|
-
// The metapackage ships
|
|
562
|
-
//
|
|
563
|
-
// the
|
|
564
|
-
//
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
572
|
+
// The metapackage ships `<project>/__init__.py` (a thin styxkit re-export so
|
|
573
|
+
// `<project>.use_docker()` works, PEP 561 typed) into the same `<project>/`
|
|
574
|
+
// package the suites nest under. Its on-disk source dir must dodge a suite
|
|
575
|
+
// named after the project (both would write `<project>/`); the import name
|
|
576
|
+
// stays `<project>` via a `package-dir` remap so the namespace is preserved.
|
|
577
|
+
const metaImport = nsName ?? "project";
|
|
578
|
+
let metaDir = metaImport;
|
|
579
|
+
if (pkgDirs.includes(metaDir)) {
|
|
580
|
+
const collided = metaDir;
|
|
581
|
+
while (pkgDirs.includes(metaDir)) metaDir += "_";
|
|
570
582
|
warnings.push({
|
|
571
|
-
message: `metapackage
|
|
583
|
+
message: `metapackage directory "${collided}" collides with a suite directory; emitting its sources in "${metaDir}" instead (import name stays "${metaImport}")`,
|
|
572
584
|
});
|
|
573
585
|
}
|
|
574
|
-
files.set(`${
|
|
575
|
-
files.set(`${
|
|
586
|
+
files.set(`${metaDir}/__init__.py`, generateRootInitPy());
|
|
587
|
+
files.set(`${metaDir}/py.typed`, "");
|
|
576
588
|
|
|
577
|
-
files.set("pyproject.toml", generateRootPyproject(proj, distNames,
|
|
589
|
+
files.set("pyproject.toml", generateRootPyproject(proj, distNames, metaImport, metaDir));
|
|
578
590
|
files.set("README.md", generateRootReadme(proj, distNames));
|
|
579
591
|
files.set("requirements.txt", generateRequirementsTxt(pkgDirs));
|
|
580
592
|
|