Skip to content

Commit 38f2012

Browse files
fix(packaging): read __version__ from installed metadata and pin documented install to Python 3.12 (#255)
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
1 parent ffca4e8 commit 38f2012

14 files changed

Lines changed: 48 additions & 130 deletions

.github/workflows/publish.yml

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,7 @@ jobs:
122122
- name: Stamp dev source distribution metadata
123123
if: ${{ github.event_name == 'workflow_dispatch' && inputs.build_dev_matrix }}
124124
run: |
125-
python scripts/release/set_dev_wheel_version.py \
126-
"${{ inputs.dev_version }}" \
127-
--package-name "${DEV_PACKAGE_NAME}"
125+
python scripts/release/set_dev_wheel_version.py "${{ inputs.dev_version }}"
128126
- name: Build sdist
129127
run: uv run maturin sdist --out dist
130128
- name: Verify sdist license files
@@ -197,9 +195,7 @@ jobs:
197195
if: ${{ github.event_name == 'workflow_dispatch' && inputs.build_dev_matrix }}
198196
shell: bash
199197
run: |
200-
python scripts/release/set_dev_wheel_version.py \
201-
"${{ inputs.dev_version }}" \
202-
--package-name "${DEV_PACKAGE_NAME}"
198+
python scripts/release/set_dev_wheel_version.py "${{ inputs.dev_version }}"
203199
- name: Setup QEMU for Linux cross builds
204200
if: runner.os == 'Linux' && runner.arch != 'ARM64' && matrix.manylinux_arch == 'aarch64'
205201
uses: docker/setup-qemu-action@v3
@@ -314,9 +310,7 @@ jobs:
314310
- name: Stamp dev wheel metadata
315311
shell: bash
316312
run: |
317-
python scripts/release/set_dev_wheel_version.py \
318-
"${{ inputs.dev_version }}" \
319-
--package-name "${DEV_PACKAGE_NAME}"
313+
python scripts/release/set_dev_wheel_version.py "${{ inputs.dev_version }}"
320314
- name: Build manylinux x86_64 wheel
321315
shell: bash
322316
run: |

INSTALLATION.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Switchyard supports modular installation based on your use case. Install only th
44

55
## System Requirements
66

7+
- Python 3.12 or newer. If the active interpreter is older, run
8+
`uv pip install --python 3.12 nemo-switchyard`.
79
- Linux x86_64 wheels require an x86-64-v3 / AVX2-class CPU (post 2013).
810
- Linux aarch64 wheels require a Neoverse N1-class CPU (post 2020).
911

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ not already available, then install the published Switchyard tool:
3535
```bash
3636
curl -LsSf https://astral.sh/uv/install.sh | sh
3737
source "$HOME/.local/bin/env"
38-
uv tool install "nemo-switchyard[cli,server]"
38+
uv tool install --python 3.12 "nemo-switchyard[cli,server]"
3939
```
4040

4141
The coding agent you launch must also be installed and on your `PATH`. This does

docs/cli_reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Switchyard has two command-line paths:
99

1010
## Launcher Path: `switchyard launch`
1111

12-
Install the launcher with `uv tool install "nemo-switchyard[cli,server]"`. The
12+
Install the launcher with `uv tool install --python 3.12 "nemo-switchyard[cli,server]"`. The
1313
selected coding agent must also be installed and available on `PATH`.
1414

1515
### Usage

docs/getting_started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ source "$HOME/.local/bin/env"
2525
Then install the published Switchyard tool:
2626

2727
```bash
28-
uv tool install "nemo-switchyard[cli,server]"
28+
uv tool install --python 3.12 "nemo-switchyard[cli,server]"
2929
```
3030

3131
This creates an isolated Python tool environment containing the `switchyard`

docs/internal/release_workflow.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ To preview the metadata stamp locally:
9191

9292
```bash
9393
python scripts/release/set_dev_wheel_version.py 0.0.1.dev0 --print-version
94-
python scripts/release/set_dev_wheel_version.py 0.0.1.dev0 --package-name nemo-switchyard
94+
python scripts/release/set_dev_wheel_version.py 0.0.1.dev0
9595
```
9696

9797
Do not commit the stamped package metadata unless the release process explicitly requires it.

scripts/release/set_dev_wheel_version.py

Lines changed: 12 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,32 @@
44

55
"""Stamp temporary Python package metadata for dev wheel artifact builds."""
66

7-
from __future__ import annotations
8-
97
import argparse
10-
import dataclasses
118
import re
129
import sys
1310
from pathlib import Path
1411

1512
DEV_VERSION_RE = re.compile(r"^(?P<release>\d+\.\d+\.\d+)\.dev(?P<number>\d*)$")
16-
PACKAGE_NAME_RE = re.compile(r'^(name\s*=\s*")([^"]+)(".*)$')
1713
PACKAGE_VERSION_RE = re.compile(r'^(version\s*=\s*")([^"]+)(".*)$')
18-
PYTHON_VERSION_RE = re.compile(r'^(__version__\s*=\s*")([^"]+)(".*)$', re.MULTILINE)
19-
20-
21-
@dataclasses.dataclass(frozen=True)
22-
class DevWheelVersion:
23-
"""Normalized metadata used for a short-lived dev wheel artifact build."""
2414

25-
version: str
2615

27-
28-
def parse_dev_wheel_version(version: str) -> DevWheelVersion:
16+
def parse_dev_wheel_version(version: str) -> str:
2917
"""Return the normalized PEP 440 `.dev` version or raise `ValueError`."""
3018

3119
match = DEV_VERSION_RE.fullmatch(version)
3220
if match is None:
3321
raise ValueError("dev wheel versions must look like 0.0.1.dev0")
3422

3523
number = match.group("number") or "0"
36-
return DevWheelVersion(version=f"{match.group('release')}.dev{number}")
24+
return f"{match.group('release')}.dev{number}"
3725

3826

39-
def update_pyproject(path: Path, *, package_name: str, version: str) -> bool:
40-
"""Set `[project]` name and version in `pyproject.toml`."""
27+
def update_pyproject(path: Path, version: str) -> bool:
28+
"""Set `[project].version` in `pyproject.toml`."""
4129

4230
lines = path.read_text().splitlines(keepends=True)
4331
in_project = False
4432
changed = False
45-
found_name = False
4633
found_version = False
4734
output: list[str] = []
4835

@@ -53,74 +40,37 @@ def update_pyproject(path: Path, *, package_name: str, version: str) -> bool:
5340

5441
updated = line
5542
if in_project:
56-
updated, count = PACKAGE_NAME_RE.subn(rf"\g<1>{package_name}\g<3>", updated, count=1)
57-
if count:
58-
found_name = True
5943
updated, count = PACKAGE_VERSION_RE.subn(rf"\g<1>{version}\g<3>", updated, count=1)
6044
if count:
6145
found_version = True
6246

6347
changed = changed or updated != line
6448
output.append(updated)
6549

66-
if not found_name:
67-
raise ValueError(f"{path}: missing [project] name")
6850
if not found_version:
6951
raise ValueError(f"{path}: missing [project] version")
7052
if changed:
7153
path.write_text("".join(output))
7254
return changed
7355

7456

75-
def update_python_init(path: Path, version: str) -> bool:
76-
"""Set `switchyard.__version__` for the dev wheel artifact."""
77-
78-
text = path.read_text()
79-
updated, count = PYTHON_VERSION_RE.subn(rf"\g<1>{version}\g<3>", text, count=1)
80-
if count != 1:
81-
raise ValueError(f"{path}: missing __version__")
82-
if updated != text:
83-
path.write_text(updated)
84-
return True
85-
return False
57+
def apply_version(version: str) -> None:
58+
"""Set the wheel version in `pyproject.toml`."""
8659

87-
88-
def apply_version(version: DevWheelVersion, *, package_name: str) -> None:
89-
"""Update package metadata files used by maturin wheel builds."""
90-
91-
changes = [
92-
(
93-
"pyproject.toml",
94-
update_pyproject(
95-
Path("pyproject.toml"),
96-
package_name=package_name,
97-
version=version.version,
98-
),
99-
),
100-
("switchyard/__init__.py", update_python_init(Path("switchyard/__init__.py"), version.version)),
101-
]
102-
103-
changed = [path for path, did_change in changes if did_change]
60+
changed = update_pyproject(Path("pyproject.toml"), version)
10461
if changed:
10562
print("Set dev wheel metadata:")
106-
print(f" Package: {package_name}")
107-
print(f" Version: {version.version}")
108-
for path in changed:
109-
print(f" updated {path}")
63+
print(f" Version: {version}")
64+
print(" updated pyproject.toml")
11065
else:
111-
print(f"dev wheel metadata already set for {package_name} {version.version}")
66+
print(f"dev wheel metadata already set to {version}")
11267

11368

11469
def main(argv: list[str] | None = None) -> int:
11570
"""CLI entry point."""
11671

11772
parser = argparse.ArgumentParser(description=__doc__)
11873
parser.add_argument("version", help="PEP 440 .dev version, such as 0.0.1.dev0")
119-
parser.add_argument(
120-
"--package-name",
121-
default="nemo-switchyard",
122-
help="Distribution name to stamp into wheel metadata",
123-
)
12474
parser.add_argument(
12575
"--print-version",
12676
action="store_true",
@@ -131,9 +81,9 @@ def main(argv: list[str] | None = None) -> int:
13181
try:
13282
version = parse_dev_wheel_version(args.version)
13383
if args.print_version:
134-
print(version.version)
84+
print(version)
13585
return 0
136-
apply_version(version, package_name=args.package_name)
86+
apply_version(version)
13787
except ValueError as exc:
13888
print(f"error: {exc}", file=sys.stderr)
13989
return 2

switchyard/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
format translation, and extensible middleware.
1010
"""
1111

12+
from importlib import metadata as _metadata
1213
from typing import TYPE_CHECKING, Any
1314

1415
from switchyard.lib.backends import (
@@ -190,4 +191,8 @@ def __getattr__(name: str) -> Any:
190191
"AnyResponseStream",
191192
]
192193

193-
__version__ = "0.1.0"
194+
try:
195+
__version__ = _metadata.version("nemo-switchyard")
196+
except _metadata.PackageNotFoundError:
197+
# A source checkout may not have installed distribution metadata.
198+
__version__ = "0.0.0+unknown"

switchyard/cli/switchyard_cli.py

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@
44

55
"""Switchyard command-line entry point."""
66

7-
from __future__ import annotations
8-
97
import argparse
108
import logging
119
import os
12-
from importlib.metadata import PackageNotFoundError, version
1310

11+
from switchyard import __version__
1412
from switchyard.cli.command_utils import (
1513
quiet_dependency_loggers as _quiet_dependency_loggers,
1614
)
@@ -71,19 +69,8 @@ def _cmd_serve(args: argparse.Namespace) -> None:
7169
)
7270

7371

74-
def _switchyard_version() -> str:
75-
"""Resolve the installed distribution version."""
76-
77-
try:
78-
return version("nemo-switchyard")
79-
except PackageNotFoundError:
80-
from switchyard import __version__
81-
82-
return __version__
83-
84-
8572
def _add_launch_parser(
86-
subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
73+
subparsers: "argparse._SubParsersAction[argparse.ArgumentParser]",
8774
) -> None:
8875
launch = subparsers.add_parser(
8976
"launch",
@@ -125,7 +112,7 @@ def _build_parser() -> argparse.ArgumentParser:
125112
parser.add_argument(
126113
"--version",
127114
action="version",
128-
version=f"%(prog)s {_switchyard_version()}",
115+
version=f"%(prog)s {__version__}",
129116
)
130117
subparsers = parser.add_subparsers(dest="command")
131118

tests/getting_started/test_getting_started.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def test_getting_started_documents_current_paths() -> None:
1212
).read_text()
1313

1414
assert guide.index("## Launcher Path") < guide.index("## Server Path")
15-
assert 'uv tool install "nemo-switchyard[cli,server]"' in guide
15+
assert 'uv tool install --python 3.12 "nemo-switchyard[cli,server]"' in guide
1616
assert "switchyard launch claude --model switchyard" in guide
1717
assert "cargo build --locked --release -p switchyard-server" in guide
1818
assert "./target/release/switchyard-server --config routes.toml --dry-run" in guide

0 commit comments

Comments
 (0)