diff --git a/.agents/skills/runtime-release/SKILL.md b/.agents/skills/runtime-release/SKILL.md new file mode 100644 index 0000000..426f805 --- /dev/null +++ b/.agents/skills/runtime-release/SKILL.md @@ -0,0 +1,100 @@ +--- +name: runtime-release +description: Cut a gpustack-runtime package release end to end (select and push the tag, monitor the release workflow, curate the GitHub Release notes). +--- + +# Runtime release + +One maintainer duty: cut a package release. Push a version tag; `release.yml` builds and publishes +it. Then write the GitHub Release notes. + +## Cut a package release + +The git tag is the single source of truth: hatch-vcs derives the package version from it at build +time, and there is no version file to bump. Pushing a `v*.*.*` tag runs +`.github/workflows/release.yml`: build the distributions, create the GitHub Release with the +wheel/sdist assets, publish to PyPI, then verify an isolated install. Branch pushes to `main` or +`v*-dev` run the same job against TestPyPI and create no release. + +Two workflow facts shape the procedure: + +- The GitHub Release is a pre-release only when the tag contains `rc`. A final tag goes public and + becomes `latest` immediately. There is no promotion gate. +- The workflow generates no baseline notes. The release body starts empty, so curating the notes is + not polish; it is the entire text the reader gets. + +PyPI is the irreversible step: a version once published there cannot be re-uploaded or edited. + +### Hard rules + +- Tag shapes: `vX.Y.Z` final, `vX.Y.ZrcN` pre-release, `vX.Y.Z.postN` post-release. PEP 440 + canonical spellings only; history contains the non-dotted `v0.2.5postN` — do not repeat it. +- A tag whose PyPI publish succeeded is burned. NEVER delete or move it; fix forward with a new + version. Delete-and-recreate is limited to a tag that never reached PyPI and is interactive-only. +- An `rc` tag stays a pre-release; never mark it latest. +- Release from `origin/main` HEAD by default. A maintenance line (`v*-dev` branch) targets that + branch's HEAD; flag the choice in the summary. +- Interactive mode confirms every mutating step (tag push, note replacement). Auto mode runs + unattended through note curation and reports the release URL. + +### 1. Preflight (read-only) + +```sh +gh auth status +git fetch origin --tags --prune +git status --porcelain # must be empty +git tag -l 'v*' --sort=-v:refname | head -5 +``` + +### 2. Version and commit (confirm) + +- Pick the target ref: `origin/main` by default, or the `v*-dev` branch for a maintenance line. The + steps below call it `$TARGET`; `$SHA` is its HEAD (`git rev-parse "$TARGET"`). +- Read the range since the previous tag: `git log --no-merges --oneline ..$TARGET`. +- Propose the bump from the Conventional Commits in range: any `feat` → minor; fixes, docs, and + chores → patch. Use `.postN` only to re-issue the same version line (packaging metadata or a + docs-only rebuild of unchanged content). +- Confirm the (version, commit) pair before tagging. A final tag publishes immediately, so this + confirmation is the only gate. + +### 3. Draft notes + +Group the range by type: Features / Fixes / Docs / Other; omit empty sections. Keep highlights and +fold noisy `chore`/`ci`/`test` entries. Link PRs from `(#NNN)` subjects. Do not open the body with +a heading that repeats the tag. End with the Full Changelog compare URL +(`https://github.com/gpustack/runtime/compare/...`). Write the draft to `notes.md`; +step 5 publishes that file. + +### 4. Tag and monitor (confirm, then read-only) + +```sh +git tag -a "$VER" "$SHA" -m "Release $VER" +git push origin "$VER" +gh run list --branch "$VER" --limit 5 # the release.yml run for the tag +gh run watch --exit-status # background it; wait for the completion notification +``` + +The job's own verify step installs the package from PyPI in an isolated env, but it is unpinned and +only prints the installed version. A green run proves the upload succeeded, not that PyPI already +serves the new version; read the verify log's `version =` line and confirm it equals `$VER`. On +failure read `gh run view --log-failed`; retry the same run on transient errors. If the +failure is real and PyPI already holds the version, cut the next version instead — never re-tag. + +### 5. Curate notes (confirm) + +```sh +gh release view "$VER" --json body,isPrerelease # the body starts empty +gh release edit "$VER" --notes-file notes.md +``` + +A maintenance-line final keeps `--latest=false` when a newer stable tag exists. Verify the badge +with `gh api repos/gpustack/runtime/releases/latest --jq .tag_name`. An `rc` release stays a +pre-release and needs no further action. + +## Scope + +Package releases and their notes run here. No other repository document changes with a release: +the version comes from the tag, and no README or docs table tracks it. Container image builds are +`make package` (`pack/`) and deployment manifests live in `deploy/`; both are outside this skill. +Docs-only changes never trigger a release: the release workflow ignores docs paths on branch +pushes, so docs updates need no tag. diff --git a/.claude/skills/runtime-release b/.claude/skills/runtime-release new file mode 120000 index 0000000..d3072c2 --- /dev/null +++ b/.claude/skills/runtime-release @@ -0,0 +1 @@ +../../.agents/skills/runtime-release \ No newline at end of file diff --git a/.gitignore b/.gitignore index 787a6fe..06142e3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,10 @@ .vscode/ .DS_Store *.swp -.claude/ +**/.claude/* +!/.claude/skills +/.claude/skills/* +!/.claude/skills/runtime-release # Python-generated files _version.py diff --git a/AGENTS.md b/AGENTS.md index b7da1c4..e160862 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,6 +59,8 @@ Setup is `make install` (uv sync plus pre-commit hooks). Day-to-day targets: `gpustack_runtime/_version.py` is generated by hatch-vcs and `_version_appendix.py` by `make prepare`; never hand-edit either. +Cut package releases with the [runtime-release](.agents/skills/runtime-release/SKILL.md) skill. + ### Python conventions - Prefer clarity over cleverness to simplify long-term maintenance. diff --git a/pyproject.toml b/pyproject.toml index 6578d74..4c5fa0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ # NB(thxCode): Pin to this Podman version for a custom patch. See gpustack_runtime/deployer/__patches__.py. "podman==5.6.0", "pyyaml", - "gpustack-runner>=0.1.29", + "gpustack-runner>=0.1.31", "nvidia-ml-py>=13.580.65", "mthreads-ml-py>=2.2.10", "argcomplete>=3.6.3", diff --git a/uv.lock b/uv.lock index a4b1354..29eb8b9 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,10 @@ version = 1 revision = 3 requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version < '3.11'", +] [[package]] name = "amdsmi" @@ -170,11 +174,11 @@ wheels = [ [[package]] name = "distlib" -version = "0.4.0" +version = "0.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, + { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, ] [[package]] @@ -258,7 +262,7 @@ wheels = [ [[package]] name = "gpustack-runner" -version = "0.1.29" +version = "0.1.31" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argcomplete" }, @@ -266,9 +270,9 @@ dependencies = [ { name = "packaging" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7b/5b/fcd80ec9f3b74d36c3e5e1462af5d29604585a3050d9bb6d36d50f6d4397/gpustack_runner-0.1.29.tar.gz", hash = "sha256:22c4ec21c3aee2c9d48f3535397821c85172370654ad71549d09289ca00ec393", size = 13464730, upload-time = "2026-09-01T09:23:07.718Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/cc/58ba6b213bec7c703dfa1b02c4f423056d86d61465950b7aab563176acb2/gpustack_runner-0.1.31.tar.gz", hash = "sha256:87e68cb889410db503657231667f1748055869e10d1a9bffeabc27e13a06001d", size = 13746189, upload-time = "2026-10-12T09:12:28.784Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/41/baf89298ead365239a326583b7310500cb05eda6b0113aade845181995d3/gpustack_runner-0.1.29-py3-none-any.whl", hash = "sha256:a0330f9512a4ebd0261acba41819e19e89188b47c466ca31db8499fbf9843686", size = 30850, upload-time = "2026-09-01T09:23:05.162Z" }, + { url = "https://files.pythonhosted.org/packages/21/a4/c66496c6c0a413cc10fb94d2b5efcaa5dc4d2a62aa4378b6a96f29e01ae4/gpustack_runner-0.1.31-py3-none-any.whl", hash = "sha256:f7dc1ec7b42f605ee27d6f4b8a36308c5d6f392f42a5bc27fed604f4a68f314f", size = 32461, upload-time = "2026-10-12T09:12:27.031Z" }, ] [[package]] @@ -307,7 +311,7 @@ requires-dist = [ { name = "argcomplete", specifier = ">=3.6.3" }, { name = "cachetools", specifier = ">=5.5.2" }, { name = "docker", specifier = ">=7.1.0" }, - { name = "gpustack-runner", specifier = ">=0.1.29" }, + { name = "gpustack-runner", specifier = ">=0.1.31" }, { name = "kubernetes", specifier = ">=33.1.0" }, { name = "mthreads-ml-py", specifier = ">=2.2.10" }, { name = "nvidia-ml-py", specifier = ">=13.580.65" }, @@ -565,7 +569,8 @@ version = "0.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mergedeep" }, - { name = "platformdirs" }, + { name = "platformdirs", version = "4.12.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "platformdirs", version = "4.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pyyaml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } @@ -673,20 +678,20 @@ wheels = [ [[package]] name = "oauthlib" -version = "3.3.1" +version = "4.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/d8/a1bcc8ba112a627f8ffbdc212a78ce18d3ac07e91a5ca65d27918eee25a1/oauthlib-4.0.0.tar.gz", hash = "sha256:efb274799819440f95b4ab3b818869f1ce9ae26c5beacba0201d1a1b76b54f86", size = 187232, upload-time = "2026-09-28T06:01:18.77Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f4/78229a1066068ca14fc60fb26cf7381cabe4382261392b90e5f9552722d4/oauthlib-4.0.0-py3-none-any.whl", hash = "sha256:624c28c13a0a59cabf9747dfa52af63be3e512a7f2714df16e91b5b3a145e6cd", size = 159715, upload-time = "2026-09-28T06:01:17.008Z" }, ] [[package]] name = "packaging" -version = "25.0" +version = "26.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, ] [[package]] @@ -709,11 +714,26 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.3.8" +version = "4.12.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/90/a1/d5f9002a70298c64a789779077d8dd90c10aa1f47fe40c86802df874f2a6/platformdirs-4.12.4.tar.gz", hash = "sha256:63743c02414e755de4e31b8f68125c1407495b86c5a006e203c01ff8b9924250", size = 60010, upload-time = "2026-10-07T23:30:32.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ba/223e00b885e960edd5d4b4d178c88acce019bd5b83786093681b8c393492/platformdirs-4.12.4-py3-none-any.whl", hash = "sha256:78bfb9db2a8471ed7eebe3c3c932da413911042994e699b384fbb4493fa872d7", size = 32596, upload-time = "2026-10-07T23:30:30.825Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.13.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/80/a8/66d45abadff219e36e2a824181b8f6a67e7ed4572934d6252c71c29d5731/platformdirs-4.13.0.tar.gz", hash = "sha256:1aa0b0d3f224c1f07c295121e312a5a24a180d6ae5a8425ea1784b3e3863e9c0", size = 61094, upload-time = "2026-10-11T02:05:24.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/15/1633010b26e88e872c93b67c0b6c5e174fb74cb6fb5c1472b4d51d4a8f22/platformdirs-4.13.0-py3-none-any.whl", hash = "sha256:3dbcf4cd708f21cf876c4eaa90e58412bc4f033d87143f41b1493ff77c25b7e1", size = 32724, upload-time = "2026-10-11T02:05:22.776Z" }, ] [[package]] @@ -839,14 +859,14 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.6.0" +version = "1.6.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/91/96/0f93e27c9f60a650838f2118159aa115fd5732c0716247917b7ba7ede665/python_discovery-1.6.0.tar.gz", hash = "sha256:6393b4eae1be8b2182670635e7baff89ac21cb9f8e86fd1ff40c7b1144febb4c", size = 82849, upload-time = "2026-08-28T17:30:02.366Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/73/54993df8fc57e906dc9b7e01d1d9b0b2d3eb0ce3190639e33dccb12853f7/python_discovery-1.6.2.tar.gz", hash = "sha256:cd1738ca1d37c86ef9d0b654dd46fcee1e41c97c6add12575e8501ced39afdb4", size = 86921, upload-time = "2026-10-10T01:24:27.912Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/5e/21abf578182fb15006a57faf3711a1e659e29d600d19b6e557eae908c81d/python_discovery-1.6.0-py3-none-any.whl", hash = "sha256:d4e244cf17b8b29819ed78003d55fbacf86eda23425b075454fff9271b79377a", size = 38451, upload-time = "2026-08-28T17:30:01.236Z" }, + { url = "https://files.pythonhosted.org/packages/61/3b/50ed32583bfffa737df67d33b9c15d7b25df232c55ab606d059f610ef6ca/python_discovery-1.6.2-py3-none-any.whl", hash = "sha256:f0c697f95a3aaec4174a6e5e58f5885e25768684bafc76f1afde384709ebdcf2", size = 38912, upload-time = "2026-10-10T01:24:26.721Z" }, ] [[package]] @@ -1061,11 +1081,11 @@ wheels = [ [[package]] name = "typing-extensions" -version = "4.14.1" +version = "4.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] [[package]] @@ -1083,27 +1103,29 @@ wheels = [ [[package]] name = "urllib3" -version = "2.7.0" +version = "2.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, + { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, ] [[package]] name = "virtualenv" -version = "21.7.7" +version = "21.14.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, - { name = "platformdirs" }, + { name = "packaging" }, + { name = "platformdirs", version = "4.12.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "platformdirs", version = "4.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "python-discovery" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/41/c3f34799487924f2a6f43b8a8b7acd345a6c61aac2211d4bced8621ca4f1/virtualenv-21.7.7.tar.gz", hash = "sha256:6874376f99ba6b8d4e3ee8bde67f9285412400c7d5b29ba41ee6daa5e0221bdc", size = 5347022, upload-time = "2026-08-28T18:59:50.093Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/3f/935cf1757643381fb7f7cc98197b4bc119223d9c2cf37dc3b03ad2a4455f/virtualenv-21.14.7.tar.gz", hash = "sha256:5f427d56f39eb7e7447d641dd4b7ab1e9c92793f498f35f26da2e4972c5546ae", size = 5471741, upload-time = "2026-10-12T01:27:31.842Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/f8/dcec8dc767b812193316c7debed1e2b53e586e59c267bfe517688ff90275/virtualenv-21.7.7-py3-none-any.whl", hash = "sha256:67a6a68fef3ad8ca16b8b89f33fd8f97996cc0bf0db31629d07ecf8dec539a2c", size = 5324620, upload-time = "2026-08-28T18:59:48.056Z" }, + { url = "https://files.pythonhosted.org/packages/52/5b/799c3e5b8e04d5fa1c9b61a4c1f5079603e747205c7311b92abd30283df5/virtualenv-21.14.7-py3-none-any.whl", hash = "sha256:3769219a308c5d2f093e7729621ad12a6346b5767a7ec4338414e2a0fb0c526b", size = 5487587, upload-time = "2026-10-12T01:27:29.37Z" }, ] [[package]]