From 242b664d5ce5393d72ccf804085245828c9a3cd0 Mon Sep 17 00:00:00 2001 From: Alex Luck Date: Mon, 12 Oct 2026 11:14:17 -0700 Subject: [PATCH 1/9] Python(chore): derive version from git tags and automate the changelog The Python version now comes from `python/v*` tags through setuptools-scm, so nothing is bumped in `pyproject.toml` or `uv.lock`. Changelog entries are per-PR fragments in `python/changelog.d`, rendered into `CHANGELOG.md` at release time. A release prep workflow opens the changelog PR, and merging it creates the tag and starts the PyPI publish workflow. The changelog also appears on the docs site and in the GitHub Release notes. --- .github/workflows/python_build.yaml | 27 ++++++- .github/workflows/python_changelog_check.yaml | 37 ++++++++++ .github/workflows/python_release.yaml | 14 +++- .github/workflows/python_release_prep.yaml | 70 +++++++++++++++++++ .github/workflows/python_release_tag.yaml | 52 ++++++++++++++ .gitignore | 1 + python/CHANGELOG.md | 2 + .../changelog.d/+release-automation.chore.md | 0 python/changelog.d/README.md | 43 ++++++++++++ python/changelog.d/template.md.jinja | 14 ++++ python/docs/changelog.md | 1 + python/mkdocs.yml | 1 + python/pyproject.toml | 42 ++++++++++- python/scripts/release_version.py | 63 +++++++++++++++++ python/uv.lock | 1 - 15 files changed, 360 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/python_changelog_check.yaml create mode 100644 .github/workflows/python_release_prep.yaml create mode 100644 .github/workflows/python_release_tag.yaml create mode 100644 python/changelog.d/+release-automation.chore.md create mode 100644 python/changelog.d/README.md create mode 100644 python/changelog.d/template.md.jinja create mode 100644 python/docs/changelog.md create mode 100644 python/scripts/release_version.py diff --git a/.github/workflows/python_build.yaml b/.github/workflows/python_build.yaml index dc6b8982ea..af0959799d 100644 --- a/.github/workflows/python_build.yaml +++ b/.github/workflows/python_build.yaml @@ -19,7 +19,15 @@ jobs: platforms: ${{ steps.set-matrix.outputs.matrix }} sift_package_version: ${{ steps.get-sift-version.outputs.version }} steps: + # The package version comes from git tags, so the full history is needed. - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" - name: Get supported Python versions from pyproject.toml id: get-python-versions @@ -27,10 +35,12 @@ jobs: versions=$(grep "Programming Language :: Python :: " python/pyproject.toml | sed 's/.*Python :: \([0-9.]*\).*/\1/' | jq -R -s -c 'split("\n")[:-1]') echo "versions=$versions" >> $GITHUB_OUTPUT - - name: Get sift-stack-py package version from pyproject.toml + - name: Get sift-stack-py package version from git tags id: get-sift-version + working-directory: python run: | - version=$(grep '^version = ' python/pyproject.toml | sed 's/version = "\(.*\)"/\1/') + pip install "setuptools-scm>=8,<11" + version=$(python -m setuptools_scm) echo "version=$version" >> $GITHUB_OUTPUT # We define the platforms here so we can reuse the matrix in multiple jobs @@ -54,6 +64,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 @@ -74,6 +86,17 @@ jobs: working-directory: python shell: bash run: | + # The version must come from a tag. The 0.0.0 fallback means no tag was found. + WHEEL_VERSION=$(ls dist/sift_stack_py*.whl | sed -E 's/.*sift_stack_py-([^-]+)-.*/\1/') + if [[ "$WHEEL_VERSION" == "0.0.0" ]]; then + echo "Error: derived the 0.0.0 fallback version. No python/v* tag was found." + exit 1 + fi + if [[ "${{ github.ref_type }}" == "tag" && "$WHEEL_VERSION" != "${GITHUB_REF_NAME#python/v}" ]]; then + echo "Error: wheel version $WHEEL_VERSION does not match tag $GITHUB_REF_NAME" + exit 1 + fi + # Check all distributions with twine twine check dist/* diff --git a/.github/workflows/python_changelog_check.yaml b/.github/workflows/python_changelog_check.yaml new file mode 100644 index 0000000000..d886edc786 --- /dev/null +++ b/.github/workflows/python_changelog_check.yaml @@ -0,0 +1,37 @@ +name: python-changelog-check + +# Every pull request that changes python/ must add a changelog fragment in python/changelog.d. +# An empty .chore.md fragment marks a change with no user-visible effect. +# See python/changelog.d/README.md. + +on: + pull_request: + paths: + - 'python/**' + +permissions: + contents: read + +jobs: + fragment: + name: Changelog fragment + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install towncrier + run: pip install "towncrier==26.9.0" + + - name: Check for a fragment + run: towncrier check --dir python --config python/pyproject.toml --compare-with "origin/${{ github.base_ref }}" + + - name: Check that the fragments render + run: towncrier build --dir python --config python/pyproject.toml --draft --version 0.0.0 --date "Draft" > /dev/null diff --git a/.github/workflows/python_release.yaml b/.github/workflows/python_release.yaml index 6fd73a864e..f8c66b64df 100644 --- a/.github/workflows/python_release.yaml +++ b/.github/workflows/python_release.yaml @@ -79,9 +79,17 @@ jobs: RELEASE_FLAGS=(--latest) fi - # Create release notes - cat > release_notes.md << 'EOL' - See [CHANGELOG.md](https://github.com/sift-stack/sift/blob/main/python/CHANGELOG.md) for details. + # Create release notes. Start with this version's section of the changelog, if any. + VERSION="${TAG_NAME#python/v}" + awk -v header="## [v${VERSION}]" ' + index($0, header) == 1 { found = 1; next } + found && /^## \[/ { exit } + found { print } + ' python/CHANGELOG.md > release_notes.md + + cat >> release_notes.md << 'EOL' + + See the [changelog](https://sift-stack.github.io/sift/python/latest/changelog/) for all releases. Offline archives are available for download for multiple platforms. Offline archives include wheels for all dependencies including build extras, e.g. openssl, development, and build. diff --git a/.github/workflows/python_release_prep.yaml b/.github/workflows/python_release_prep.yaml new file mode 100644 index 0000000000..f9b450f8c3 --- /dev/null +++ b/.github/workflows/python_release_prep.yaml @@ -0,0 +1,70 @@ +name: Python Release Prep + +# Renders the changelog fragments in python/changelog.d into python/CHANGELOG.md and opens a +# pull request. Merging that pull request starts the Python Release Tag workflow. + +on: + workflow_dispatch: + inputs: + version: + description: Optional explicit X.Y.Z. Skips the automatic bump from the fragment types. + type: string + required: false + +permissions: + contents: write + pull-requests: write + +jobs: + prep: + name: Open release prep PR + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + # The next version comes from the latest tag, so the full history is needed. + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Compute next version + id: version + env: + VERSION_OVERRIDE: ${{ inputs.version }} + run: | + version=$(python python/scripts/release_version.py --version "$VERSION_OVERRIDE") + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "Next version: $version" + + - name: Render changelog + env: + VERSION: ${{ steps.version.outputs.version }} + run: | + pip install "towncrier==26.9.0" + cd python + towncrier build --yes --version "$VERSION" --date "$(date '+%B %-d, %Y')" + + - name: Open pull request + env: + GH_TOKEN: ${{ github.token }} + VERSION: ${{ steps.version.outputs.version }} + BASE_BRANCH: ${{ github.event.repository.default_branch }} + run: | + branch="release/python-v${VERSION}" + title="Python(chore): v${VERSION} release prep" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -b "$branch" + git add -A python/CHANGELOG.md python/changelog.d + git commit -m "$title" + git push origin "$branch" + gh pr create \ + --base "$BASE_BRANCH" \ + --head "$branch" \ + --title "$title" \ + --body "Renders the changelog fragments for v${VERSION}. Review the entries before merging. Merging this pull request creates the \`python/v${VERSION}\` tag and starts the publish workflow." diff --git a/.github/workflows/python_release_tag.yaml b/.github/workflows/python_release_tag.yaml new file mode 100644 index 0000000000..0dbe062651 --- /dev/null +++ b/.github/workflows/python_release_tag.yaml @@ -0,0 +1,52 @@ +name: Python Release Tag + +# Tags a Python release when a new version section reaches python/CHANGELOG.md on the default +# branch, then starts the PyPI Publish workflow on that tag. The Python Release Prep workflow +# opens the pull request that adds the section. + +on: + push: + branches: + - main + paths: + - 'python/CHANGELOG.md' + +permissions: + contents: write + actions: write + +concurrency: + group: python-release-tag + cancel-in-progress: false + +jobs: + tag: + name: Create tag and start publish + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Create tag + id: tag + run: | + version=$(grep -m1 -oP '^## \[v\K[^\]]+' python/CHANGELOG.md) + tag="python/v${version}" + echo "tag=$tag" >> "$GITHUB_OUTPUT" + if [[ -n "$(git ls-remote --tags origin "refs/tags/${tag}")" ]]; then + echo "Tag ${tag} already exists. Nothing to release." + echo "created=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + git tag "$tag" "$GITHUB_SHA" + git push origin "refs/tags/${tag}" + echo "created=true" >> "$GITHUB_OUTPUT" + + # A tag pushed with the default token does not start other workflows, so start the + # publish workflow explicitly. It requires a tag ref. + - name: Start PyPI Publish + if: steps.tag.outputs.created == 'true' + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ steps.tag.outputs.tag }} + run: gh workflow run python_release.yaml --ref "$TAG" diff --git a/.gitignore b/.gitignore index b1b5f3756a..1fd06e7fbf 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ python/docs/sift_py ### C++ template # Prerequisites *.d +!changelog.d # Compiled Object files *.slo diff --git a/python/CHANGELOG.md b/python/CHANGELOG.md index 09f0529e38..14f810a193 100644 --- a/python/CHANGELOG.md +++ b/python/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). + + ## [v0.23.1] - October 8, 2026 - Test-results steps updated out of order now have their statuses corrected before the report is finalized, with a `SiftReportLifecycleWarning`. ([#821](https://github.com/sift-stack/sift/pull/821)) diff --git a/python/changelog.d/+release-automation.chore.md b/python/changelog.d/+release-automation.chore.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/python/changelog.d/README.md b/python/changelog.d/README.md new file mode 100644 index 0000000000..ba413207ea --- /dev/null +++ b/python/changelog.d/README.md @@ -0,0 +1,43 @@ +# Changelog fragments + +Each pull request that changes `python/` adds one fragment file to this directory. A release +renders the fragments into `CHANGELOG.md` and deletes them. Do not edit `CHANGELOG.md` by hand. + +## Add a fragment + +Name the file `..md`. Run these commands from `python/`: + +```bash +uvx towncrier create 830.feat.md --content "Added \`client.webhooks\`." +``` + +| Type | Use for | Version bump | +| ---------- | -------------------------------- | ------------ | +| `breaking` | A change that breaks callers | minor | +| `feat` | New user-facing behavior | minor | +| `fix` | A bug fix users can see | patch | +| `chore` | No user-visible change | none | + +The version bump follows the highest type in the release. While the major version is 0, a +`breaking` change bumps the minor version. To release a different version, pass `version` to the +release workflow. + +Write the entry for a user of the library. Start with what changed, in one sentence. The +renderer adds the PR link. A fragment can have more paragraphs and code blocks after the first +sentence. They are indented under the bullet. + +## PRs with no user-visible change + +Add an empty file named `.chore.md`. It is never rendered. A release made only of +`chore` fragments is rejected. + +## No PR number yet + +Name the file `+..md`. The entry has no PR link. + +## Release + +Run the Python Release Prep workflow. It computes the next version from the latest +`python/v*` tag and the fragment types, renders `CHANGELOG.md`, and opens a pull request. After +that PR merges, the Python Release Tag workflow creates the `python/vX.Y.Z` tag and starts +the publish workflow. diff --git a/python/changelog.d/template.md.jinja b/python/changelog.d/template.md.jinja new file mode 100644 index 0000000000..0286408769 --- /dev/null +++ b/python/changelog.d/template.md.jinja @@ -0,0 +1,14 @@ +{% if render_title %} +## [v{{ versiondata.version }}] - {{ versiondata.date }} + +{% endif %} +{% for category in ["breaking", "feat", "fix"] %} +{% for text, values in sections[""].get(category, {}).items() %} +{% set lines = text.strip().split("\n", 1) %} +- {{ lines[0] }}{% if values %} ({{ values|join(", ") }}){% endif %}{{ "" }} +{% if lines|length > 1 %} +{{ lines[1] }} +{% endif %} +{% endfor %} +{% endfor %} +{{ "\n" }} diff --git a/python/docs/changelog.md b/python/docs/changelog.md new file mode 100644 index 0000000000..786b75d5a3 --- /dev/null +++ b/python/docs/changelog.md @@ -0,0 +1 @@ +--8<-- "CHANGELOG.md" diff --git a/python/mkdocs.yml b/python/mkdocs.yml index 589c7d58da..5ab46e45a7 100644 --- a/python/mkdocs.yml +++ b/python/mkdocs.yml @@ -73,6 +73,7 @@ nav: - Running Modes: guides/pytest_plugin/running_modes.md - Report Structure: guides/pytest_plugin/report_structure.md - Pass/Fail Behavior: guides/pytest_plugin/pass_fail_behavior.md + - Changelog: changelog.md # - Guides: # - Logging # - Error Handling diff --git a/python/pyproject.toml b/python/pyproject.toml index 8b8f011704..02249b9f0a 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,10 +1,10 @@ [build-system] -requires = ["setuptools"] +requires = ["setuptools", "setuptools-scm>=8,<11"] build-backend = "setuptools.build_meta" [project] name = "sift_stack_py" -version = "0.23.1" +dynamic = ["version"] description = "Python client library for the Sift API" requires-python = ">=3.8" readme = { file = "README.md", content-type = "text/markdown" } @@ -414,6 +414,44 @@ ignore_errors = true module = "mcap.writer" follow_imports = "skip" +[tool.setuptools_scm] +# The version comes from the latest ``python/v*`` git tag. ``.git`` lives one level above this +# package, so ``root`` points there. A build with no matching tag fails to describe, so +# ``fallback_version`` keeps untagged CI builds working. Release builds check for it. +root = ".." +tag_regex = "^python/v(?P.+)$" +git_describe_command = ["git", "describe", "--dirty", "--tags", "--long", "--match", "python/v*"] +fallback_version = "0.0.0" + +[tool.towncrier] +# Changelog entries are fragments in ``changelog.d``. See ``changelog.d/README.md``. +directory = "changelog.d" +filename = "CHANGELOG.md" +start_string = "\n" +template = "changelog.d/template.md.jinja" +issue_format = "[#{issue}](https://github.com/sift-stack/sift/pull/{issue})" + +[[tool.towncrier.type]] +directory = "breaking" +name = "Breaking" +showcontent = true + +[[tool.towncrier.type]] +directory = "feat" +name = "Features" +showcontent = true + +[[tool.towncrier.type]] +directory = "fix" +name = "Fixes" +showcontent = true + +# An empty ``chore`` fragment marks a PR with no user-visible change. It is never rendered. +[[tool.towncrier.type]] +directory = "chore" +name = "Chores" +showcontent = false + [tool.setuptools.packages.find] where = ["lib"] exclude = ["sift_client._tests", "sift_client._tests.*"] diff --git a/python/scripts/release_version.py b/python/scripts/release_version.py new file mode 100644 index 0000000000..c899628a04 --- /dev/null +++ b/python/scripts/release_version.py @@ -0,0 +1,63 @@ +"""Print the next Python release version, from the latest `python/v*` tag and the fragment +types in `python/changelog.d`. Run from the repository root. Uses only the standard library.""" + +from __future__ import annotations + +import argparse +import re +import subprocess +import sys +from pathlib import Path + +FRAGMENTS_DIR = Path("python/changelog.d") +TAG_PREFIX = "python/v" +FRAGMENT_TYPES = {"breaking", "feat", "fix", "chore"} +STABLE = re.compile(r"^(\d+)\.(\d+)\.(\d+)$") + + +def latest_version() -> tuple[int, int, int]: + tag = subprocess.run( + ["git", "describe", "--tags", "--abbrev=0", "--match", f"{TAG_PREFIX}[0-9]*"], + check=True, + capture_output=True, + text=True, + ).stdout.strip() + match = STABLE.match(tag[len(TAG_PREFIX) :]) + if not match: + sys.exit(f"Latest tag {tag} is not a stable X.Y.Z version. Pass --version.") + major, minor, patch = (int(part) for part in match.groups()) + return major, minor, patch + + +def fragment_types() -> set[str]: + types = set() + for path in FRAGMENTS_DIR.glob("*.md"): + if path.name.lower() == "readme.md": + continue + parts = path.name.split(".") + if len(parts) < 3 or parts[-2] not in FRAGMENT_TYPES: + sys.exit(f"Bad fragment name {path.name}. Use ..md with a known type.") + types.add(parts[-2]) + return types + + +def next_version(explicit: str) -> str: + types = fragment_types() - {"chore"} + if not types: + sys.exit("No changelog fragments to release. Only `chore` fragments or none found.") + if explicit: + if not STABLE.match(explicit): + sys.exit(f"--version {explicit} is not X.Y.Z.") + return explicit + major, minor, patch = latest_version() + if "breaking" in types and major >= 1: + return f"{major + 1}.0.0" + if types & {"breaking", "feat"}: + return f"{major}.{minor + 1}.0" + return f"{major}.{minor}.{patch + 1}" + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--version", default="", help="Explicit X.Y.Z. Skips the automatic bump.") + print(next_version(parser.parse_args().version)) diff --git a/python/uv.lock b/python/uv.lock index 5b0d6c82f6..d2e46e6061 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -4460,7 +4460,6 @@ wheels = [ [[package]] name = "sift-stack-py" -version = "0.23.1" source = { editable = "." } dependencies = [ { name = "alive-progress", version = "3.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, From 6222aa6b6160c5a9dd241091e6db2ac69203330a Mon Sep 17 00:00:00 2001 From: Alex Luck Date: Mon, 12 Oct 2026 13:21:40 -0700 Subject: [PATCH 2/9] Python(chore): fix docs build and tighten release packaging Bump mkdocstrings to 1.0.7, which works with pymdown-extensions 12.2, and raise the docs extras to Python 3.11. Keep tests out of the wheel, drop the unreachable 0.0.0 guard, and document merging the release PR first. --- .github/workflows/python_build.yaml | 6 +- .github/workflows/python_release_prep.yaml | 2 +- python/changelog.d/README.md | 7 +- python/pyproject.toml | 61 ++-- python/uv.lock | 381 +++++++++------------ 5 files changed, 212 insertions(+), 245 deletions(-) diff --git a/.github/workflows/python_build.yaml b/.github/workflows/python_build.yaml index af0959799d..d104aeb05d 100644 --- a/.github/workflows/python_build.yaml +++ b/.github/workflows/python_build.yaml @@ -86,12 +86,8 @@ jobs: working-directory: python shell: bash run: | - # The version must come from a tag. The 0.0.0 fallback means no tag was found. + # A release build must derive exactly the version in its tag. WHEEL_VERSION=$(ls dist/sift_stack_py*.whl | sed -E 's/.*sift_stack_py-([^-]+)-.*/\1/') - if [[ "$WHEEL_VERSION" == "0.0.0" ]]; then - echo "Error: derived the 0.0.0 fallback version. No python/v* tag was found." - exit 1 - fi if [[ "${{ github.ref_type }}" == "tag" && "$WHEEL_VERSION" != "${GITHUB_REF_NAME#python/v}" ]]; then echo "Error: wheel version $WHEEL_VERSION does not match tag $GITHUB_REF_NAME" exit 1 diff --git a/.github/workflows/python_release_prep.yaml b/.github/workflows/python_release_prep.yaml index f9b450f8c3..a96b27e48b 100644 --- a/.github/workflows/python_release_prep.yaml +++ b/.github/workflows/python_release_prep.yaml @@ -67,4 +67,4 @@ jobs: --base "$BASE_BRANCH" \ --head "$branch" \ --title "$title" \ - --body "Renders the changelog fragments for v${VERSION}. Review the entries before merging. Merging this pull request creates the \`python/v${VERSION}\` tag and starts the publish workflow." + --body "Renders the changelog fragments for v${VERSION}. Review the entries before merging. Merging this pull request creates the \`python/v${VERSION}\` tag and starts the publish workflow. Merge it before other changes to \`python/\`, or those changes ship in this release without an entry." diff --git a/python/changelog.d/README.md b/python/changelog.d/README.md index ba413207ea..69c44cfd72 100644 --- a/python/changelog.d/README.md +++ b/python/changelog.d/README.md @@ -5,7 +5,7 @@ renders the fragments into `CHANGELOG.md` and deletes them. Do not edit `CHANGEL ## Add a fragment -Name the file `..md`. Run these commands from `python/`: +Name the file `..md`. From `python/`, run: ```bash uvx towncrier create 830.feat.md --content "Added \`client.webhooks\`." @@ -13,7 +13,7 @@ uvx towncrier create 830.feat.md --content "Added \`client.webhooks\`." | Type | Use for | Version bump | | ---------- | -------------------------------- | ------------ | -| `breaking` | A change that breaks callers | minor | +| `breaking` | A change that breaks callers | major | | `feat` | New user-facing behavior | minor | | `fix` | A bug fix users can see | patch | | `chore` | No user-visible change | none | @@ -41,3 +41,6 @@ Run the Python Release Prep workflow. It computes the next version from the late `python/v*` tag and the fragment types, renders `CHANGELOG.md`, and opens a pull request. After that PR merges, the Python Release Tag workflow creates the `python/vX.Y.Z` tag and starts the publish workflow. + +Merge the release pull request before other changes to `python/`. A change merged first ships in +that release, but its entry appears under the next version. diff --git a/python/pyproject.toml b/python/pyproject.toml index 02249b9f0a..27df334ce7 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -138,30 +138,30 @@ development = [ 'tomlkit~=0.13.3', ] docs = [ - "griffe-pydantic==1.3.1 ; python_version >= '3.10'", - "mike==2.1.3 ; python_version >= '3.10'", - "mkdocs-api-autonav==0.4.0 ; python_version >= '3.10'", - "mkdocs-include-markdown-plugin==7.1.6 ; python_version >= '3.10'", - "mkdocs-jupyter==0.25.1 ; python_version >= '3.10'", - "mkdocs-material==9.7.5 ; python_version >= '3.10'", - "mkdocs==1.6.1 ; python_version >= '3.10'", - "mkdocstrings[python]==1.0.3 ; python_version >= '3.10'", + "griffe-pydantic==1.3.1 ; python_version >= '3.11'", + "mike==2.1.3 ; python_version >= '3.11'", + "mkdocs-api-autonav==0.4.0 ; python_version >= '3.11'", + "mkdocs-include-markdown-plugin==7.1.6 ; python_version >= '3.11'", + "mkdocs-jupyter==0.25.1 ; python_version >= '3.11'", + "mkdocs-material==9.7.5 ; python_version >= '3.11'", + "mkdocs==1.6.1 ; python_version >= '3.11'", + "mkdocstrings[python]==1.0.7 ; python_version >= '3.11'", ] docs-build = [ 'build==1.2.1', 'cffi~=1.14', - "griffe-pydantic==1.3.1 ; python_version >= '3.10'", + "griffe-pydantic==1.3.1 ; python_version >= '3.11'", 'grpcio-testing~=1.13', 'h5py~=3.11', 'mcap-ros2-support~=0.5.7', 'mcap~=1.4', - "mike==2.1.3 ; python_version >= '3.10'", - "mkdocs-api-autonav==0.4.0 ; python_version >= '3.10'", - "mkdocs-include-markdown-plugin==7.1.6 ; python_version >= '3.10'", - "mkdocs-jupyter==0.25.1 ; python_version >= '3.10'", - "mkdocs-material==9.7.5 ; python_version >= '3.10'", - "mkdocs==1.6.1 ; python_version >= '3.10'", - "mkdocstrings[python]==1.0.3 ; python_version >= '3.10'", + "mike==2.1.3 ; python_version >= '3.11'", + "mkdocs-api-autonav==0.4.0 ; python_version >= '3.11'", + "mkdocs-include-markdown-plugin==7.1.6 ; python_version >= '3.11'", + "mkdocs-jupyter==0.25.1 ; python_version >= '3.11'", + "mkdocs-material==9.7.5 ; python_version >= '3.11'", + "mkdocs==1.6.1 ; python_version >= '3.11'", + "mkdocstrings[python]==1.0.7 ; python_version >= '3.11'", 'mypy==1.10.0', 'npTDMS~=1.9', 'pdoc==14.5.0', @@ -245,15 +245,16 @@ development = [ "pre-commit~=4.0 ; python_version >= '3.9'" ] build = ["pdoc==14.5.0", "build==1.2.1"] +# mkdocstrings 1.0.7 needs Python 3.11+. docs = [ - "mkdocs==1.6.1 ; python_version >= '3.10'", - "mkdocs-material==9.7.5 ; python_version >= '3.10'", - "mkdocstrings[python]==1.0.3 ; python_version >= '3.10'", - "mkdocs-include-markdown-plugin==7.1.6 ; python_version >= '3.10'", - "mkdocs-api-autonav==0.4.0 ; python_version >= '3.10'", - "mike==2.1.3 ; python_version >= '3.10'", - "griffe-pydantic==1.3.1 ; python_version >= '3.10'", - "mkdocs-jupyter==0.25.1 ; python_version >= '3.10'", + "mkdocs==1.6.1 ; python_version >= '3.11'", + "mkdocs-material==9.7.5 ; python_version >= '3.11'", + "mkdocstrings[python]==1.0.7 ; python_version >= '3.11'", + "mkdocs-include-markdown-plugin==7.1.6 ; python_version >= '3.11'", + "mkdocs-api-autonav==0.4.0 ; python_version >= '3.11'", + "mike==2.1.3 ; python_version >= '3.11'", + "griffe-pydantic==1.3.1 ; python_version >= '3.11'", + "mkdocs-jupyter==0.25.1 ; python_version >= '3.11'", ] # May be required for certain library functionality @@ -278,7 +279,7 @@ file-imports = ["tdms", "rosbags", "hdf5", "ulog", "mcap"] all = ["openssl", "sift-stream", "file-imports", "data-review"] dev-all = ["development", "all", "build"] -docs-build = ["dev-all", "docs"] # Note python 3.9+ +docs-build = ["dev-all", "docs"] # Note python 3.11+ [tool.uv] # Pin uv to a version that writes lockfile revision 3 (introduced in uv 0.8.4 @@ -416,8 +417,9 @@ follow_imports = "skip" [tool.setuptools_scm] # The version comes from the latest ``python/v*`` git tag. ``.git`` lives one level above this -# package, so ``root`` points there. A build with no matching tag fails to describe, so -# ``fallback_version`` keeps untagged CI builds working. Release builds check for it. +# package, so ``root`` points there. A checkout with no matching tag, such as a shallow CI +# clone, derives ``0.0.1.devN``. ``fallback_version`` applies only to a source tree with no git +# metadata at all. root = ".." tag_regex = "^python/v(?P.+)$" git_describe_command = ["git", "describe", "--dirty", "--tags", "--long", "--match", "python/v*"] @@ -452,6 +454,11 @@ directory = "chore" name = "Chores" showcontent = false +[tool.setuptools] +# setuptools-scm reports every git-tracked file to setuptools. Ship only the package data listed +# below, so tests and docs under the packages stay out of the wheel. +include-package-data = false + [tool.setuptools.packages.find] where = ["lib"] exclude = ["sift_client._tests", "sift_client._tests.*"] diff --git a/python/uv.lock b/python/uv.lock index d2e46e6061..a60819a2f9 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -233,8 +233,8 @@ name = "beautifulsoup4" version = "4.14.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "soupsieve", marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "soupsieve", marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } wheels = [ @@ -246,7 +246,7 @@ name = "bleach" version = "6.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "webencodings", marker = "python_full_version >= '3.10'" }, + { name = "webencodings", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } wheels = [ @@ -255,7 +255,7 @@ wheels = [ [package.optional-dependencies] css = [ - { name = "tinycss2", marker = "python_full_version >= '3.10'" }, + { name = "tinycss2", marker = "python_full_version >= '3.11'" }, ] [[package]] @@ -522,7 +522,7 @@ name = "click" version = "8.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/23/e4/796662cd90cf80e3a363c99db2b88e0e394b988a575f60a17e16440cd011/click-8.4.0.tar.gz", hash = "sha256:638f1338fe1235c8f4e008e4a8a254fb5c5fbdcbb40ece3c9142ebb78e792973", size = 350843, upload-time = "2026-05-17T00:47:58.425Z" } wheels = [ @@ -753,7 +753,7 @@ name = "ghp-import" version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } wheels = [ @@ -818,7 +818,7 @@ name = "griffe-pydantic" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "griffelib", marker = "python_full_version >= '3.10'" }, + { name = "griffelib", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5a/bd/d2eaeaf3f9910c9cd72793af0de18ee3d3a3a27bb30ab01cfd7659c08dc4/griffe_pydantic-1.3.1.tar.gz", hash = "sha256:f7caedfa0effedb22893bf01cc411fd567614f7b4de7ce0c1f4293eb7acb5c44", size = 38176, upload-time = "2026-02-21T09:38:49.674Z" } wheels = [ @@ -1279,63 +1279,29 @@ name = "ipykernel" version = "6.31.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "appnope", marker = "python_full_version >= '3.10' and sys_platform == 'darwin'" }, - { name = "comm", marker = "python_full_version >= '3.10'" }, - { name = "debugpy", marker = "python_full_version >= '3.10'" }, - { name = "ipython", version = "8.39.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "ipython", version = "9.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "jupyter-client", marker = "python_full_version >= '3.10'" }, - { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, - { name = "matplotlib-inline", marker = "python_full_version >= '3.10'" }, - { name = "nest-asyncio", marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "psutil", marker = "python_full_version >= '3.10'" }, - { name = "pyzmq", marker = "python_full_version >= '3.10'" }, - { name = "tornado", marker = "python_full_version >= '3.10'" }, - { name = "traitlets", marker = "python_full_version >= '3.10'" }, + { name = "appnope", marker = "python_full_version >= '3.11' and sys_platform == 'darwin'" }, + { name = "comm", marker = "python_full_version >= '3.11'" }, + { name = "debugpy", marker = "python_full_version >= '3.11'" }, + { name = "ipython", marker = "python_full_version >= '3.11'" }, + { name = "jupyter-client", marker = "python_full_version >= '3.11'" }, + { name = "jupyter-core", marker = "python_full_version >= '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, + { name = "nest-asyncio", marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "psutil", marker = "python_full_version >= '3.11'" }, + { name = "pyzmq", marker = "python_full_version >= '3.11'" }, + { name = "tornado", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a5/1d/d5ba6edbfe6fae4c3105bca3a9c889563cc752c7f2de45e333164c7f4846/ipykernel-6.31.0.tar.gz", hash = "sha256:2372ce8bc1ff4f34e58cafed3a0feb2194b91fc7cad0fc72e79e47b45ee9e8f6", size = 167493, upload-time = "2025-10-20T11:42:39.948Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl", hash = "sha256:abe5386f6ced727a70e0eb0cf1da801fa7c5fa6ff82147747d5a0406cd8c94af", size = 117003, upload-time = "2025-10-20T11:42:37.502Z" }, ] -[[package]] -name = "ipython" -version = "8.39.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.10.*'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version == '3.10.*' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version == '3.10.*'" }, - { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, - { name = "jedi", marker = "python_full_version == '3.10.*'" }, - { name = "matplotlib-inline", marker = "python_full_version == '3.10.*'" }, - { name = "pexpect", marker = "python_full_version == '3.10.*' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version == '3.10.*'" }, - { name = "pygments", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "stack-data", marker = "python_full_version == '3.10.*'" }, - { name = "traitlets", marker = "python_full_version == '3.10.*'" }, - { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971, upload-time = "2026-03-27T10:02:13.94Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/56/4cc7fc9e9e3f38fd324f24f8afe0ad8bb5fa41283f37f1aaf9de0612c968/ipython-8.39.0-py3-none-any.whl", hash = "sha256:bb3c51c4fa8148ab1dea07a79584d1c854e234ea44aa1283bcb37bc75054651f", size = 831849, upload-time = "2026-03-27T10:02:07.846Z" }, -] - [[package]] name = "ipython" version = "9.13.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", -] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, { name = "decorator", marker = "python_full_version >= '3.11'" }, @@ -1372,7 +1338,7 @@ name = "jedi" version = "0.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "parso", marker = "python_full_version >= '3.10'" }, + { name = "parso", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } wheels = [ @@ -1397,10 +1363,10 @@ name = "jsonschema" version = "4.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "attrs", marker = "python_full_version >= '3.10'" }, - { name = "jsonschema-specifications", marker = "python_full_version >= '3.10'" }, - { name = "referencing", marker = "python_full_version >= '3.10'" }, - { name = "rpds-py", marker = "python_full_version >= '3.10'" }, + { name = "attrs", marker = "python_full_version >= '3.11'" }, + { name = "jsonschema-specifications", marker = "python_full_version >= '3.11'" }, + { name = "referencing", marker = "python_full_version >= '3.11'" }, + { name = "rpds-py", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } wheels = [ @@ -1412,7 +1378,7 @@ name = "jsonschema-specifications" version = "2025.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "referencing", marker = "python_full_version >= '3.10'" }, + { name = "referencing", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } wheels = [ @@ -1424,11 +1390,11 @@ name = "jupyter-client" version = "8.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, - { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, - { name = "pyzmq", marker = "python_full_version >= '3.10'" }, - { name = "tornado", marker = "python_full_version >= '3.10'" }, - { name = "traitlets", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", marker = "python_full_version >= '3.11'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, + { name = "pyzmq", marker = "python_full_version >= '3.11'" }, + { name = "tornado", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" } wheels = [ @@ -1440,8 +1406,8 @@ name = "jupyter-core" version = "5.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs", version = "4.9.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "traitlets", marker = "python_full_version >= '3.10'" }, + { name = "platformdirs", version = "4.9.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } wheels = [ @@ -1462,12 +1428,11 @@ name = "jupytext" version = "1.19.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown-it-py", marker = "python_full_version >= '3.10'" }, - { name = "mdit-py-plugins", marker = "python_full_version >= '3.10'" }, - { name = "nbformat", marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pyyaml", marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "markdown-it-py", marker = "python_full_version >= '3.11'" }, + { name = "mdit-py-plugins", marker = "python_full_version >= '3.11'" }, + { name = "nbformat", marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pyyaml", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ef/2d/15624c3d9440d85a280ff13d2d23afd989802f25470ac59932f4fef6f0c6/jupytext-1.19.3.tar.gz", hash = "sha256:713c3ed4441afe0f31474d28ea2e6b61a268c04c40fd78e5ccfd7f7ac9e9f766", size = 4305350, upload-time = "2026-05-17T09:09:29.294Z" } wheels = [ @@ -1609,7 +1574,7 @@ name = "markdown-it-py" version = "4.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mdurl", marker = "python_full_version >= '3.10'" }, + { name = "mdurl", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } wheels = [ @@ -1789,7 +1754,7 @@ name = "matplotlib-inline" version = "0.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "traitlets", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" } wheels = [ @@ -1828,7 +1793,7 @@ name = "mdit-py-plugins" version = "0.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown-it-py", marker = "python_full_version >= '3.10'" }, + { name = "markdown-it-py", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/59/fc/f8d0863f8862f25602c0404d75568e89fb6b4109804645e5cdfb1be5cf56/mdit_py_plugins-0.6.1.tar.gz", hash = "sha256:a2bca0f039f39dbd35fb74ae1b5f998608c437463371f0ff7f49a19a17a114d0", size = 56114, upload-time = "2026-05-13T09:03:38.91Z" } wheels = [ @@ -1858,14 +1823,14 @@ name = "mike" version = "2.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "importlib-resources", marker = "python_full_version >= '3.10'" }, - { name = "jinja2", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs", marker = "python_full_version >= '3.10'" }, - { name = "pyparsing", marker = "python_full_version >= '3.10'" }, - { name = "pyyaml", marker = "python_full_version >= '3.10'" }, - { name = "pyyaml-env-tag", marker = "python_full_version >= '3.10'" }, - { name = "verspec", marker = "python_full_version >= '3.10'" }, + { name = "importlib-metadata", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "importlib-resources", marker = "python_full_version >= '3.11'" }, + { name = "jinja2", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs", marker = "python_full_version >= '3.11'" }, + { name = "pyparsing", marker = "python_full_version >= '3.11'" }, + { name = "pyyaml", marker = "python_full_version >= '3.11'" }, + { name = "pyyaml-env-tag", marker = "python_full_version >= '3.11'" }, + { name = "verspec", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ab/f7/2933f1a1fb0e0f077d5d6a92c6c7f8a54e6128241f116dff4df8b6050bbf/mike-2.1.3.tar.gz", hash = "sha256:abd79b8ea483fb0275b7972825d3082e5ae67a41820f8d8a0dc7a3f49944e810", size = 38119, upload-time = "2024-08-13T05:02:14.167Z" } wheels = [ @@ -1876,9 +1841,6 @@ wheels = [ name = "mistune" version = "3.2.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, -] sdist = { url = "https://files.pythonhosted.org/packages/ca/84/620cc3f7e3adf6f5067e10f4dbae71295d8f9e16d5d3f9ef97c40f2f592c/mistune-3.2.1.tar.gz", hash = "sha256:7c8e5501d38bac1582e067e46c8343f17d57ea1aaa735823f3aba1fd59c88a28", size = 98003, upload-time = "2026-05-03T14:33:22.312Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2a/7f/a946aa4f8752b37102b41e64dca18a1976ac705c3a0d1dfe74d820a02552/mistune-3.2.1-py3-none-any.whl", hash = "sha256:78cdb0ba5e938053ccf63651b352508d2efa9411dc8810bfb05f2dc5140c0048", size = 53749, upload-time = "2026-05-03T14:33:20.551Z" }, @@ -1889,19 +1851,19 @@ name = "mkdocs" version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click", marker = "python_full_version >= '3.10'" }, - { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, - { name = "ghp-import", marker = "python_full_version >= '3.10'" }, - { name = "jinja2", marker = "python_full_version >= '3.10'" }, - { name = "markdown", marker = "python_full_version >= '3.10'" }, - { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "mergedeep", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs-get-deps", marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pathspec", marker = "python_full_version >= '3.10'" }, - { name = "pyyaml", marker = "python_full_version >= '3.10'" }, - { name = "pyyaml-env-tag", marker = "python_full_version >= '3.10'" }, - { name = "watchdog", marker = "python_full_version >= '3.10'" }, + { name = "click", marker = "python_full_version >= '3.11'" }, + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "ghp-import", marker = "python_full_version >= '3.11'" }, + { name = "jinja2", marker = "python_full_version >= '3.11'" }, + { name = "markdown", marker = "python_full_version >= '3.11'" }, + { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "mergedeep", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs-get-deps", marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pathspec", marker = "python_full_version >= '3.11'" }, + { name = "pyyaml", marker = "python_full_version >= '3.11'" }, + { name = "pyyaml-env-tag", marker = "python_full_version >= '3.11'" }, + { name = "watchdog", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } wheels = [ @@ -1913,9 +1875,9 @@ name = "mkdocs-api-autonav" version = "0.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mkdocs", marker = "python_full_version >= '3.10'" }, - { name = "mkdocstrings-python", marker = "python_full_version >= '3.10'" }, - { name = "pyyaml", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs", marker = "python_full_version >= '3.11'" }, + { name = "mkdocstrings-python", marker = "python_full_version >= '3.11'" }, + { name = "pyyaml", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6d/b0/20960ee733a419a349877d09712d02e8ec2bda031672e5f0d0a15fc020b3/mkdocs_api_autonav-0.4.0.tar.gz", hash = "sha256:3527b0e5cf1b682bd374a3ce699ac12d6288f5fcaf93877f34a6b14c79740637", size = 17987, upload-time = "2025-09-09T12:42:02.216Z" } wheels = [ @@ -1927,9 +1889,9 @@ name = "mkdocs-autorefs" version = "1.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown", marker = "python_full_version >= '3.10'" }, - { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "mkdocs", marker = "python_full_version >= '3.10'" }, + { name = "markdown", marker = "python_full_version >= '3.11'" }, + { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "mkdocs", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/52/c0/f641843de3f612a6b48253f39244165acff36657a91cc903633d456ae1ac/mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197", size = 56588, upload-time = "2026-02-10T15:23:55.105Z" } wheels = [ @@ -1941,9 +1903,9 @@ name = "mkdocs-get-deps" version = "0.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mergedeep", marker = "python_full_version >= '3.10'" }, - { name = "platformdirs", version = "4.9.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "pyyaml", marker = "python_full_version >= '3.10'" }, + { name = "mergedeep", marker = "python_full_version >= '3.11'" }, + { name = "platformdirs", version = "4.9.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pyyaml", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047, upload-time = "2026-03-10T02:46:33.632Z" } wheels = [ @@ -1955,8 +1917,8 @@ name = "mkdocs-include-markdown-plugin" version = "7.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mkdocs", marker = "python_full_version >= '3.10'" }, - { name = "wcmatch", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs", marker = "python_full_version >= '3.11'" }, + { name = "wcmatch", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2c/17/988d97ac6849b196f54d45ca9c60ca894880c160a512785f03834704b3d9/mkdocs_include_markdown_plugin-7.1.6.tar.gz", hash = "sha256:a0753cb82704c10a287f1e789fc9848f82b6beb8749814b24b03dd9f67816677", size = 23391, upload-time = "2025-06-13T18:25:51.193Z" } wheels = [ @@ -1968,12 +1930,12 @@ name = "mkdocs-jupyter" version = "0.25.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ipykernel", marker = "python_full_version >= '3.10'" }, - { name = "jupytext", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs-material", marker = "python_full_version >= '3.10'" }, - { name = "nbconvert", marker = "python_full_version >= '3.10'" }, - { name = "pygments", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "ipykernel", marker = "python_full_version >= '3.11'" }, + { name = "jupytext", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs-material", marker = "python_full_version >= '3.11'" }, + { name = "nbconvert", marker = "python_full_version >= '3.11'" }, + { name = "pygments", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6c/23/6ffb8d2fd2117aa860a04c6fe2510b21bc3c3c085907ffdd851caba53152/mkdocs_jupyter-0.25.1.tar.gz", hash = "sha256:0e9272ff4947e0ec683c92423a4bfb42a26477c103ab1a6ab8277e2dcc8f7afe", size = 1626747, upload-time = "2024-10-15T14:56:32.373Z" } wheels = [ @@ -1985,17 +1947,17 @@ name = "mkdocs-material" version = "9.7.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "babel", marker = "python_full_version >= '3.10'" }, - { name = "backrefs", marker = "python_full_version >= '3.10'" }, - { name = "colorama", marker = "python_full_version >= '3.10'" }, - { name = "jinja2", marker = "python_full_version >= '3.10'" }, - { name = "markdown", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs-material-extensions", marker = "python_full_version >= '3.10'" }, - { name = "paginate", marker = "python_full_version >= '3.10'" }, - { name = "pygments", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "pymdown-extensions", marker = "python_full_version >= '3.10'" }, - { name = "requests", version = "2.34.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "babel", marker = "python_full_version >= '3.11'" }, + { name = "backrefs", marker = "python_full_version >= '3.11'" }, + { name = "colorama", marker = "python_full_version >= '3.11'" }, + { name = "jinja2", marker = "python_full_version >= '3.11'" }, + { name = "markdown", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs-material-extensions", marker = "python_full_version >= '3.11'" }, + { name = "paginate", marker = "python_full_version >= '3.11'" }, + { name = "pygments", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pymdown-extensions", marker = "python_full_version >= '3.11'" }, + { name = "requests", version = "2.34.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/74/76/5c202fecdc45d53e83e03a85bae70c48b6c81e9f87f0bc19a9e9c723bdc0/mkdocs_material-9.7.5.tar.gz", hash = "sha256:f76bdab532bad1d9c57ca7187b37eccf64dd12e1586909307f8856db3be384ea", size = 4097749, upload-time = "2026-03-10T15:43:22.809Z" } wheels = [ @@ -2013,24 +1975,24 @@ wheels = [ [[package]] name = "mkdocstrings" -version = "1.0.3" +version = "1.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jinja2", marker = "python_full_version >= '3.10'" }, - { name = "markdown", marker = "python_full_version >= '3.10'" }, - { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "mkdocs", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs-autorefs", marker = "python_full_version >= '3.10'" }, - { name = "pymdown-extensions", marker = "python_full_version >= '3.10'" }, + { name = "jinja2", marker = "python_full_version >= '3.11'" }, + { name = "markdown", marker = "python_full_version >= '3.11'" }, + { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "mkdocs", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs-autorefs", marker = "python_full_version >= '3.11'" }, + { name = "pymdown-extensions", marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/62/0dfc5719514115bf1781f44b1d7f2a0923fcc01e9c5d7990e48a05c9ae5d/mkdocstrings-1.0.3.tar.gz", hash = "sha256:ab670f55040722b49bb45865b2e93b824450fb4aef638b00d7acb493a9020434", size = 100946, upload-time = "2026-02-07T14:31:40.973Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/e8/445b19834bf9559ebbbaa76897e965665b83d2861461eed8d8de4d122057/mkdocstrings-1.0.7.tar.gz", hash = "sha256:0951b147f352118a45a3db85f0f3b660833d34cb5120a2751315b8d08dafd159", size = 103739, upload-time = "2026-10-10T13:50:59.957Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl", hash = "sha256:0d66d18430c2201dc7fe85134277382baaa15e6b30979f3f3bdbabd6dbdb6046", size = 35523, upload-time = "2026-02-07T14:31:39.27Z" }, + { url = "https://files.pythonhosted.org/packages/22/c3/9062531735ff1fd27f3a79c353c4cf4d822eeffd062fd90b2c553a96f4bb/mkdocstrings-1.0.7-py3-none-any.whl", hash = "sha256:9563828667eeb35256f87a6b61fe857676ac628ec1abbce9aea9839a502c9cb1", size = 41597, upload-time = "2026-10-10T13:50:58.43Z" }, ] [package.optional-dependencies] python = [ - { name = "mkdocstrings-python", marker = "python_full_version >= '3.10'" }, + { name = "mkdocstrings-python", marker = "python_full_version >= '3.11'" }, ] [[package]] @@ -2038,10 +2000,9 @@ name = "mkdocstrings-python" version = "2.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "griffelib", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs-autorefs", marker = "python_full_version >= '3.10'" }, - { name = "mkdocstrings", marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "griffelib", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs-autorefs", marker = "python_full_version >= '3.11'" }, + { name = "mkdocstrings", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/29/33/c225eaf898634bdda489a6766fc35d1683c640bffe0e0acd10646b13536d/mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8", size = 199083, upload-time = "2026-02-20T10:38:36.368Z" } wheels = [ @@ -2102,10 +2063,10 @@ name = "nbclient" version = "0.10.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jupyter-client", marker = "python_full_version >= '3.10'" }, - { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, - { name = "nbformat", marker = "python_full_version >= '3.10'" }, - { name = "traitlets", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-client", marker = "python_full_version >= '3.11'" }, + { name = "jupyter-core", marker = "python_full_version >= '3.11'" }, + { name = "nbformat", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554, upload-time = "2025-12-23T07:45:46.369Z" } wheels = [ @@ -2117,20 +2078,20 @@ name = "nbconvert" version = "7.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "beautifulsoup4", marker = "python_full_version >= '3.10'" }, - { name = "bleach", extra = ["css"], marker = "python_full_version >= '3.10'" }, - { name = "defusedxml", marker = "python_full_version >= '3.10'" }, - { name = "jinja2", marker = "python_full_version >= '3.10'" }, - { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, - { name = "jupyterlab-pygments", marker = "python_full_version >= '3.10'" }, - { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "mistune", marker = "python_full_version >= '3.10'" }, - { name = "nbclient", marker = "python_full_version >= '3.10'" }, - { name = "nbformat", marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pandocfilters", marker = "python_full_version >= '3.10'" }, - { name = "pygments", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "traitlets", marker = "python_full_version >= '3.10'" }, + { name = "beautifulsoup4", marker = "python_full_version >= '3.11'" }, + { name = "bleach", extra = ["css"], marker = "python_full_version >= '3.11'" }, + { name = "defusedxml", marker = "python_full_version >= '3.11'" }, + { name = "jinja2", marker = "python_full_version >= '3.11'" }, + { name = "jupyter-core", marker = "python_full_version >= '3.11'" }, + { name = "jupyterlab-pygments", marker = "python_full_version >= '3.11'" }, + { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "mistune", marker = "python_full_version >= '3.11'" }, + { name = "nbclient", marker = "python_full_version >= '3.11'" }, + { name = "nbformat", marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pandocfilters", marker = "python_full_version >= '3.11'" }, + { name = "pygments", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/01/b1/708e53fe2e429c103c6e6e159106bcf0357ac41aa4c28772bd8402339051/nbconvert-7.17.1.tar.gz", hash = "sha256:34d0d0a7e73ce3cbab6c5aae8f4f468797280b01fd8bd2ca746da8569eddd7d2", size = 865311, upload-time = "2026-04-08T00:44:14.914Z" } wheels = [ @@ -2142,10 +2103,10 @@ name = "nbformat" version = "5.10.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "fastjsonschema", marker = "python_full_version >= '3.10'" }, - { name = "jsonschema", marker = "python_full_version >= '3.10'" }, - { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, - { name = "traitlets", marker = "python_full_version >= '3.10'" }, + { name = "fastjsonschema", marker = "python_full_version >= '3.11'" }, + { name = "jsonschema", marker = "python_full_version >= '3.11'" }, + { name = "jupyter-core", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" } wheels = [ @@ -2747,7 +2708,7 @@ name = "pexpect" version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ptyprocess", marker = "(python_full_version == '3.10.*' and sys_platform == 'emscripten') or (python_full_version == '3.10.*' and sys_platform == 'win32') or (python_full_version >= '3.10' and sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "ptyprocess", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } wheels = [ @@ -2932,7 +2893,7 @@ name = "prompt-toolkit" version = "3.0.52" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "wcwidth", marker = "python_full_version >= '3.10'" }, + { name = "wcwidth", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } wheels = [ @@ -3593,8 +3554,8 @@ name = "pymdown-extensions" version = "10.21.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown", marker = "python_full_version >= '3.10'" }, - { name = "pyyaml", marker = "python_full_version >= '3.10'" }, + { name = "markdown", marker = "python_full_version >= '3.11'" }, + { name = "pyyaml", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9e/26/d1015444da4d952a1ca487a236b522eb979766f0295a0bd0c5fc089989a9/pymdown_extensions-10.21.3.tar.gz", hash = "sha256:72cfcf55f07aea0d4af2c4f11dd4e52466ddfb1bb819673146398e0bd3a77354", size = 854140, upload-time = "2026-05-13T12:57:32.267Z" } wheels = [ @@ -3909,7 +3870,7 @@ name = "pyyaml-env-tag" version = "1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyyaml", marker = "python_full_version >= '3.10'" }, + { name = "pyyaml", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } wheels = [ @@ -3921,7 +3882,7 @@ name = "pyzmq" version = "27.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi", marker = "python_full_version >= '3.10' and implementation_name == 'pypy'" }, + { name = "cffi", marker = "python_full_version >= '3.11' and implementation_name == 'pypy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } wheels = [ @@ -4094,9 +4055,9 @@ name = "referencing" version = "0.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "attrs", marker = "python_full_version >= '3.10'" }, - { name = "rpds-py", marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and python_full_version < '3.13'" }, + { name = "attrs", marker = "python_full_version >= '3.11'" }, + { name = "rpds-py", marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } wheels = [ @@ -4605,19 +4566,19 @@ development = [ { name = "tomlkit" }, ] docs = [ - { name = "griffe-pydantic", marker = "python_full_version >= '3.10'" }, - { name = "mike", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs-api-autonav", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs-include-markdown-plugin", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs-jupyter", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs-material", marker = "python_full_version >= '3.10'" }, - { name = "mkdocstrings", extra = ["python"], marker = "python_full_version >= '3.10'" }, + { name = "griffe-pydantic", marker = "python_full_version >= '3.11'" }, + { name = "mike", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs-api-autonav", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs-include-markdown-plugin", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs-jupyter", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs-material", marker = "python_full_version >= '3.11'" }, + { name = "mkdocstrings", extra = ["python"], marker = "python_full_version >= '3.11'" }, ] docs-build = [ { name = "build" }, { name = "cffi" }, - { name = "griffe-pydantic", marker = "python_full_version >= '3.10'" }, + { name = "griffe-pydantic", marker = "python_full_version >= '3.11'" }, { name = "grpcio-testing", version = "1.70.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "grpcio-testing", version = "1.80.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "h5py", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, @@ -4625,13 +4586,13 @@ docs-build = [ { name = "h5py", version = "3.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "mcap" }, { name = "mcap-ros2-support" }, - { name = "mike", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs-api-autonav", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs-include-markdown-plugin", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs-jupyter", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs-material", marker = "python_full_version >= '3.10'" }, - { name = "mkdocstrings", extra = ["python"], marker = "python_full_version >= '3.10'" }, + { name = "mike", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs-api-autonav", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs-include-markdown-plugin", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs-jupyter", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs-material", marker = "python_full_version >= '3.11'" }, + { name = "mkdocstrings", extra = ["python"], marker = "python_full_version >= '3.11'" }, { name = "mypy" }, { name = "nptdms" }, { name = "pdoc" }, @@ -4720,8 +4681,8 @@ requires-dist = [ { name = "eval-type-backport", specifier = "~=0.2" }, { name = "filelock", specifier = "~=3.15" }, { name = "googleapis-common-protos", specifier = ">=1.60" }, - { name = "griffe-pydantic", marker = "python_full_version >= '3.10' and extra == 'docs'", specifier = "==1.3.1" }, - { name = "griffe-pydantic", marker = "python_full_version >= '3.10' and extra == 'docs-build'", specifier = "==1.3.1" }, + { name = "griffe-pydantic", marker = "python_full_version >= '3.11' and extra == 'docs'", specifier = "==1.3.1" }, + { name = "griffe-pydantic", marker = "python_full_version >= '3.11' and extra == 'docs-build'", specifier = "==1.3.1" }, { name = "grpcio", specifier = "~=1.13" }, { name = "grpcio-testing", marker = "extra == 'dev'", specifier = "~=1.13" }, { name = "grpcio-testing", marker = "extra == 'dev-all'", specifier = "~=1.13" }, @@ -4742,20 +4703,20 @@ requires-dist = [ { name = "mcap-ros2-support", marker = "extra == 'docs-build'", specifier = "~=0.5.7" }, { name = "mcap-ros2-support", marker = "extra == 'file-imports'", specifier = "~=0.5.7" }, { name = "mcap-ros2-support", marker = "extra == 'mcap'", specifier = "~=0.5.7" }, - { name = "mike", marker = "python_full_version >= '3.10' and extra == 'docs'", specifier = "==2.1.3" }, - { name = "mike", marker = "python_full_version >= '3.10' and extra == 'docs-build'", specifier = "==2.1.3" }, - { name = "mkdocs", marker = "python_full_version >= '3.10' and extra == 'docs'", specifier = "==1.6.1" }, - { name = "mkdocs", marker = "python_full_version >= '3.10' and extra == 'docs-build'", specifier = "==1.6.1" }, - { name = "mkdocs-api-autonav", marker = "python_full_version >= '3.10' and extra == 'docs'", specifier = "==0.4.0" }, - { name = "mkdocs-api-autonav", marker = "python_full_version >= '3.10' and extra == 'docs-build'", specifier = "==0.4.0" }, - { name = "mkdocs-include-markdown-plugin", marker = "python_full_version >= '3.10' and extra == 'docs'", specifier = "==7.1.6" }, - { name = "mkdocs-include-markdown-plugin", marker = "python_full_version >= '3.10' and extra == 'docs-build'", specifier = "==7.1.6" }, - { name = "mkdocs-jupyter", marker = "python_full_version >= '3.10' and extra == 'docs'", specifier = "==0.25.1" }, - { name = "mkdocs-jupyter", marker = "python_full_version >= '3.10' and extra == 'docs-build'", specifier = "==0.25.1" }, - { name = "mkdocs-material", marker = "python_full_version >= '3.10' and extra == 'docs'", specifier = "==9.7.5" }, - { name = "mkdocs-material", marker = "python_full_version >= '3.10' and extra == 'docs-build'", specifier = "==9.7.5" }, - { name = "mkdocstrings", extras = ["python"], marker = "python_full_version >= '3.10' and extra == 'docs'", specifier = "==1.0.3" }, - { name = "mkdocstrings", extras = ["python"], marker = "python_full_version >= '3.10' and extra == 'docs-build'", specifier = "==1.0.3" }, + { name = "mike", marker = "python_full_version >= '3.11' and extra == 'docs'", specifier = "==2.1.3" }, + { name = "mike", marker = "python_full_version >= '3.11' and extra == 'docs-build'", specifier = "==2.1.3" }, + { name = "mkdocs", marker = "python_full_version >= '3.11' and extra == 'docs'", specifier = "==1.6.1" }, + { name = "mkdocs", marker = "python_full_version >= '3.11' and extra == 'docs-build'", specifier = "==1.6.1" }, + { name = "mkdocs-api-autonav", marker = "python_full_version >= '3.11' and extra == 'docs'", specifier = "==0.4.0" }, + { name = "mkdocs-api-autonav", marker = "python_full_version >= '3.11' and extra == 'docs-build'", specifier = "==0.4.0" }, + { name = "mkdocs-include-markdown-plugin", marker = "python_full_version >= '3.11' and extra == 'docs'", specifier = "==7.1.6" }, + { name = "mkdocs-include-markdown-plugin", marker = "python_full_version >= '3.11' and extra == 'docs-build'", specifier = "==7.1.6" }, + { name = "mkdocs-jupyter", marker = "python_full_version >= '3.11' and extra == 'docs'", specifier = "==0.25.1" }, + { name = "mkdocs-jupyter", marker = "python_full_version >= '3.11' and extra == 'docs-build'", specifier = "==0.25.1" }, + { name = "mkdocs-material", marker = "python_full_version >= '3.11' and extra == 'docs'", specifier = "==9.7.5" }, + { name = "mkdocs-material", marker = "python_full_version >= '3.11' and extra == 'docs-build'", specifier = "==9.7.5" }, + { name = "mkdocstrings", extras = ["python"], marker = "python_full_version >= '3.11' and extra == 'docs'", specifier = "==1.0.7" }, + { name = "mkdocstrings", extras = ["python"], marker = "python_full_version >= '3.11' and extra == 'docs-build'", specifier = "==1.0.7" }, { name = "mypy", marker = "extra == 'dev'", specifier = "==1.10.0" }, { name = "mypy", marker = "extra == 'dev-all'", specifier = "==1.10.0" }, { name = "mypy", marker = "extra == 'development'", specifier = "==1.10.0" }, @@ -4938,9 +4899,9 @@ name = "stack-data" version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "asttokens", marker = "python_full_version >= '3.10'" }, - { name = "executing", marker = "python_full_version >= '3.10'" }, - { name = "pure-eval", marker = "python_full_version >= '3.10'" }, + { name = "asttokens", marker = "python_full_version >= '3.11'" }, + { name = "executing", marker = "python_full_version >= '3.11'" }, + { name = "pure-eval", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } wheels = [ @@ -4952,7 +4913,7 @@ name = "tinycss2" version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "webencodings", marker = "python_full_version >= '3.10'" }, + { name = "webencodings", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } wheels = [ @@ -5400,7 +5361,7 @@ name = "wcmatch" version = "10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "bracex", marker = "python_full_version >= '3.10'" }, + { name = "bracex", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/79/3e/c0bdc27cf06f4e47680bd5803a07cb3dfd17de84cde92dd217dcb9e05253/wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af", size = 117421, upload-time = "2025-06-22T19:14:02.49Z" } wheels = [ From f7c29e66eb7e04af615c084f4a0a791a7cecbb73 Mon Sep 17 00:00:00 2001 From: Alex Luck Date: Mon, 12 Oct 2026 13:50:21 -0700 Subject: [PATCH 3/9] Python(chore): bump a static version in the release PR Drop setuptools-scm and keep the version in pyproject.toml. Release Prep sets it with uv version alongside the rendered changelog, and Release Tag tags the version in pyproject.toml once it merges. --- .github/workflows/python_build.yaml | 18 +++-------------- .github/workflows/python_release_prep.yaml | 17 +++++++++------- .github/workflows/python_release_tag.yaml | 10 +++++----- python/changelog.d/README.md | 8 ++++---- python/pyproject.toml | 19 ++---------------- python/scripts/release_version.py | 23 +++++++++------------- python/uv.lock | 1 + 7 files changed, 34 insertions(+), 62 deletions(-) diff --git a/.github/workflows/python_build.yaml b/.github/workflows/python_build.yaml index d104aeb05d..73219e23a2 100644 --- a/.github/workflows/python_build.yaml +++ b/.github/workflows/python_build.yaml @@ -19,15 +19,7 @@ jobs: platforms: ${{ steps.set-matrix.outputs.matrix }} sift_package_version: ${{ steps.get-sift-version.outputs.version }} steps: - # The package version comes from git tags, so the full history is needed. - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - name: Get supported Python versions from pyproject.toml id: get-python-versions @@ -35,12 +27,10 @@ jobs: versions=$(grep "Programming Language :: Python :: " python/pyproject.toml | sed 's/.*Python :: \([0-9.]*\).*/\1/' | jq -R -s -c 'split("\n")[:-1]') echo "versions=$versions" >> $GITHUB_OUTPUT - - name: Get sift-stack-py package version from git tags + - name: Get sift-stack-py package version from pyproject.toml id: get-sift-version - working-directory: python run: | - pip install "setuptools-scm>=8,<11" - version=$(python -m setuptools_scm) + version=$(grep '^version = ' python/pyproject.toml | sed 's/version = "\(.*\)"/\1/') echo "version=$version" >> $GITHUB_OUTPUT # We define the platforms here so we can reuse the matrix in multiple jobs @@ -64,8 +54,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 @@ -86,7 +74,7 @@ jobs: working-directory: python shell: bash run: | - # A release build must derive exactly the version in its tag. + # A release build must match the version in its tag. WHEEL_VERSION=$(ls dist/sift_stack_py*.whl | sed -E 's/.*sift_stack_py-([^-]+)-.*/\1/') if [[ "${{ github.ref_type }}" == "tag" && "$WHEEL_VERSION" != "${GITHUB_REF_NAME#python/v}" ]]; then echo "Error: wheel version $WHEEL_VERSION does not match tag $GITHUB_REF_NAME" diff --git a/.github/workflows/python_release_prep.yaml b/.github/workflows/python_release_prep.yaml index a96b27e48b..8e375e5401 100644 --- a/.github/workflows/python_release_prep.yaml +++ b/.github/workflows/python_release_prep.yaml @@ -1,7 +1,8 @@ name: Python Release Prep -# Renders the changelog fragments in python/changelog.d into python/CHANGELOG.md and opens a -# pull request. Merging that pull request starts the Python Release Tag workflow. +# Sets the next version in python/pyproject.toml, renders the changelog fragments in +# python/changelog.d into python/CHANGELOG.md and opens a pull request. Merging that pull request +# starts the Python Release Tag workflow. on: workflow_dispatch: @@ -24,14 +25,15 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ github.event.repository.default_branch }} - # The next version comes from the latest tag, so the full history is needed. - fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" + - name: Set up uv + uses: astral-sh/setup-uv@v6 + - name: Compute next version id: version env: @@ -41,12 +43,13 @@ jobs: echo "version=$version" >> "$GITHUB_OUTPUT" echo "Next version: $version" - - name: Render changelog + - name: Set version and render changelog env: VERSION: ${{ steps.version.outputs.version }} run: | pip install "towncrier==26.9.0" cd python + uv version --no-sync "$VERSION" towncrier build --yes --version "$VERSION" --date "$(date '+%B %-d, %Y')" - name: Open pull request @@ -60,11 +63,11 @@ jobs: git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git checkout -b "$branch" - git add -A python/CHANGELOG.md python/changelog.d + git add -A python/pyproject.toml python/uv.lock python/CHANGELOG.md python/changelog.d git commit -m "$title" git push origin "$branch" gh pr create \ --base "$BASE_BRANCH" \ --head "$branch" \ --title "$title" \ - --body "Renders the changelog fragments for v${VERSION}. Review the entries before merging. Merging this pull request creates the \`python/v${VERSION}\` tag and starts the publish workflow. Merge it before other changes to \`python/\`, or those changes ship in this release without an entry." + --body "Sets the version to ${VERSION} and renders its changelog fragments. Review the version and entries before merging. Merging this pull request creates the \`python/v${VERSION}\` tag and starts the publish workflow. Merge it before other changes to \`python/\`, or those changes ship in this release without an entry." diff --git a/.github/workflows/python_release_tag.yaml b/.github/workflows/python_release_tag.yaml index 0dbe062651..fc0cd17515 100644 --- a/.github/workflows/python_release_tag.yaml +++ b/.github/workflows/python_release_tag.yaml @@ -1,15 +1,15 @@ name: Python Release Tag -# Tags a Python release when a new version section reaches python/CHANGELOG.md on the default -# branch, then starts the PyPI Publish workflow on that tag. The Python Release Prep workflow -# opens the pull request that adds the section. +# Tags a Python release when python/pyproject.toml on the default branch has a version with no tag +# yet, then starts the PyPI Publish workflow on that tag. The Python Release Prep workflow opens the +# pull request that sets the version. on: push: branches: - main paths: - - 'python/CHANGELOG.md' + - 'python/pyproject.toml' permissions: contents: write @@ -30,7 +30,7 @@ jobs: - name: Create tag id: tag run: | - version=$(grep -m1 -oP '^## \[v\K[^\]]+' python/CHANGELOG.md) + version=$(grep '^version = ' python/pyproject.toml | sed 's/version = "\(.*\)"/\1/') tag="python/v${version}" echo "tag=$tag" >> "$GITHUB_OUTPUT" if [[ -n "$(git ls-remote --tags origin "refs/tags/${tag}")" ]]; then diff --git a/python/changelog.d/README.md b/python/changelog.d/README.md index 69c44cfd72..c6d4217eec 100644 --- a/python/changelog.d/README.md +++ b/python/changelog.d/README.md @@ -37,10 +37,10 @@ Name the file `+..md`. The entry has no PR link. ## Release -Run the Python Release Prep workflow. It computes the next version from the latest -`python/v*` tag and the fragment types, renders `CHANGELOG.md`, and opens a pull request. After -that PR merges, the Python Release Tag workflow creates the `python/vX.Y.Z` tag and starts -the publish workflow. +Run the Python Release Prep workflow. It computes the next version from the current version in +`pyproject.toml` and the fragment types, sets it in `pyproject.toml` and `uv.lock`, renders +`CHANGELOG.md`, and opens a pull request. After that PR merges, the Python Release Tag workflow +creates the `python/vX.Y.Z` tag and starts the publish workflow. Merge the release pull request before other changes to `python/`. A change merged first ships in that release, but its entry appears under the next version. diff --git a/python/pyproject.toml b/python/pyproject.toml index 27df334ce7..655894d509 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,10 +1,10 @@ [build-system] -requires = ["setuptools", "setuptools-scm>=8,<11"] +requires = ["setuptools"] build-backend = "setuptools.build_meta" [project] name = "sift_stack_py" -dynamic = ["version"] +version = "0.23.1" description = "Python client library for the Sift API" requires-python = ">=3.8" readme = { file = "README.md", content-type = "text/markdown" } @@ -415,16 +415,6 @@ ignore_errors = true module = "mcap.writer" follow_imports = "skip" -[tool.setuptools_scm] -# The version comes from the latest ``python/v*`` git tag. ``.git`` lives one level above this -# package, so ``root`` points there. A checkout with no matching tag, such as a shallow CI -# clone, derives ``0.0.1.devN``. ``fallback_version`` applies only to a source tree with no git -# metadata at all. -root = ".." -tag_regex = "^python/v(?P.+)$" -git_describe_command = ["git", "describe", "--dirty", "--tags", "--long", "--match", "python/v*"] -fallback_version = "0.0.0" - [tool.towncrier] # Changelog entries are fragments in ``changelog.d``. See ``changelog.d/README.md``. directory = "changelog.d" @@ -454,11 +444,6 @@ directory = "chore" name = "Chores" showcontent = false -[tool.setuptools] -# setuptools-scm reports every git-tracked file to setuptools. Ship only the package data listed -# below, so tests and docs under the packages stay out of the wheel. -include-package-data = false - [tool.setuptools.packages.find] where = ["lib"] exclude = ["sift_client._tests", "sift_client._tests.*"] diff --git a/python/scripts/release_version.py b/python/scripts/release_version.py index c899628a04..fa52581137 100644 --- a/python/scripts/release_version.py +++ b/python/scripts/release_version.py @@ -1,30 +1,25 @@ -"""Print the next Python release version, from the latest `python/v*` tag and the fragment -types in `python/changelog.d`. Run from the repository root. Uses only the standard library.""" +"""Print the next Python release version, from the version in `python/pyproject.toml` and the +fragment types in `python/changelog.d`. Run from the repository root. Uses only the standard +library.""" from __future__ import annotations import argparse import re -import subprocess import sys from pathlib import Path +PYPROJECT = Path("python/pyproject.toml") FRAGMENTS_DIR = Path("python/changelog.d") -TAG_PREFIX = "python/v" FRAGMENT_TYPES = {"breaking", "feat", "fix", "chore"} STABLE = re.compile(r"^(\d+)\.(\d+)\.(\d+)$") -def latest_version() -> tuple[int, int, int]: - tag = subprocess.run( - ["git", "describe", "--tags", "--abbrev=0", "--match", f"{TAG_PREFIX}[0-9]*"], - check=True, - capture_output=True, - text=True, - ).stdout.strip() - match = STABLE.match(tag[len(TAG_PREFIX) :]) +def current_version() -> tuple[int, int, int]: + version = re.search(r'^version = "(.+)"$', PYPROJECT.read_text(), re.MULTILINE).group(1) + match = STABLE.match(version) if not match: - sys.exit(f"Latest tag {tag} is not a stable X.Y.Z version. Pass --version.") + sys.exit(f"Current version {version} is not a stable X.Y.Z version. Pass --version.") major, minor, patch = (int(part) for part in match.groups()) return major, minor, patch @@ -49,7 +44,7 @@ def next_version(explicit: str) -> str: if not STABLE.match(explicit): sys.exit(f"--version {explicit} is not X.Y.Z.") return explicit - major, minor, patch = latest_version() + major, minor, patch = current_version() if "breaking" in types and major >= 1: return f"{major + 1}.0.0" if types & {"breaking", "feat"}: diff --git a/python/uv.lock b/python/uv.lock index a60819a2f9..90b93a116a 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -4421,6 +4421,7 @@ wheels = [ [[package]] name = "sift-stack-py" +version = "0.23.1" source = { editable = "." } dependencies = [ { name = "alive-progress", version = "3.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, From 77a1e13e85ab7a81b39068cdc5e330fac1cd88bd Mon Sep 17 00:00:00 2001 From: Alex Luck Date: Mon, 12 Oct 2026 14:12:44 -0700 Subject: [PATCH 4/9] Python(chore): stop releases that miss changelog fragments Fail a release PR whose merge result still has fragments, and fail a tag build with leftover fragments before it reaches PyPI. --- .github/workflows/python_build.yaml | 11 +++++++++++ .github/workflows/python_changelog_check.yaml | 14 ++++++++++++++ .github/workflows/python_release_prep.yaml | 2 +- python/changelog.d/README.md | 6 ++++-- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python_build.yaml b/.github/workflows/python_build.yaml index 73219e23a2..d6f4036cfd 100644 --- a/.github/workflows/python_build.yaml +++ b/.github/workflows/python_build.yaml @@ -81,6 +81,17 @@ jobs: exit 1 fi + # A release must include every changelog fragment. A leftover fragment means a change + # merged after the release PR was opened and would ship without an entry. + if [[ "${{ github.ref_type }}" == "tag" ]]; then + FRAGMENTS=$(find changelog.d -type f ! -name README.md ! -name template.md.jinja) + if [[ -n "$FRAGMENTS" ]]; then + echo "Error: unreleased changelog fragments at $GITHUB_REF_NAME:" + echo "$FRAGMENTS" + exit 1 + fi + fi + # Check all distributions with twine twine check dist/* diff --git a/.github/workflows/python_changelog_check.yaml b/.github/workflows/python_changelog_check.yaml index d886edc786..519c62e2fc 100644 --- a/.github/workflows/python_changelog_check.yaml +++ b/.github/workflows/python_changelog_check.yaml @@ -33,5 +33,19 @@ jobs: - name: Check for a fragment run: towncrier check --dir python --config python/pyproject.toml --compare-with "origin/${{ github.base_ref }}" + # A release PR renders and deletes every fragment. A fragment in the merge result means + # another change merged after the release PR was opened and would ship without an entry. + - name: Check that a release PR includes every fragment + run: | + if git diff "origin/${{ github.base_ref }}...HEAD" -- python/CHANGELOG.md | grep -q '^+## \[v'; then + fragments=$(find python/changelog.d -type f ! -name README.md ! -name template.md.jinja) + if [[ -n "$fragments" ]]; then + echo "Error: fragments merged after this release PR was opened:" + echo "$fragments" + echo "Close this PR and run Python Release Prep again." + exit 1 + fi + fi + - name: Check that the fragments render run: towncrier build --dir python --config python/pyproject.toml --draft --version 0.0.0 --date "Draft" > /dev/null diff --git a/.github/workflows/python_release_prep.yaml b/.github/workflows/python_release_prep.yaml index 8e375e5401..2880dc5b23 100644 --- a/.github/workflows/python_release_prep.yaml +++ b/.github/workflows/python_release_prep.yaml @@ -70,4 +70,4 @@ jobs: --base "$BASE_BRANCH" \ --head "$branch" \ --title "$title" \ - --body "Sets the version to ${VERSION} and renders its changelog fragments. Review the version and entries before merging. Merging this pull request creates the \`python/v${VERSION}\` tag and starts the publish workflow. Merge it before other changes to \`python/\`, or those changes ship in this release without an entry." + --body "Sets the version to ${VERSION} and renders its changelog fragments. Review the version and entries before merging. Merging this pull request creates the \`python/v${VERSION}\` tag and starts the publish workflow. If another change to \`python/\` merges first, the changelog check fails. Close this pull request and run Python Release Prep again." diff --git a/python/changelog.d/README.md b/python/changelog.d/README.md index c6d4217eec..98dc548690 100644 --- a/python/changelog.d/README.md +++ b/python/changelog.d/README.md @@ -42,5 +42,7 @@ Run the Python Release Prep workflow. It computes the next version from the curr `CHANGELOG.md`, and opens a pull request. After that PR merges, the Python Release Tag workflow creates the `python/vX.Y.Z` tag and starts the publish workflow. -Merge the release pull request before other changes to `python/`. A change merged first ships in -that release, but its entry appears under the next version. +If another change to `python/` merges while the release pull request is open, that change would +ship without its entry. The changelog check fails the release pull request in that case, and the +publish workflow refuses a tag with leftover fragments. Close the release pull request and run +Python Release Prep again. From 25b84e7eed8ba94fec4531306c3ffdfc430ffd9a Mon Sep 17 00:00:00 2001 From: Alex Luck Date: Mon, 12 Oct 2026 14:31:12 -0700 Subject: [PATCH 5/9] Python(chore): consolidate release checks and fragment rules release_version.py now reads the version and fragment rules from pyproject.toml and adds --current and --leftover. Release Tag refuses to tag with leftover fragments, PyPI Publish checks stable tags before CI, and python_build.yaml matches main again. The changelog check fetches two commits and runs towncrier through uvx. --- .github/workflows/python_build.yaml | 18 ----- .github/workflows/python_changelog_check.yaml | 27 +++---- .github/workflows/python_release.yaml | 30 +++++++- .github/workflows/python_release_prep.yaml | 12 +--- .github/workflows/python_release_tag.yaml | 29 ++++---- python/changelog.d/README.md | 7 +- python/scripts/release_version.py | 72 ++++++++++++------- 7 files changed, 104 insertions(+), 91 deletions(-) diff --git a/.github/workflows/python_build.yaml b/.github/workflows/python_build.yaml index d6f4036cfd..dc6b8982ea 100644 --- a/.github/workflows/python_build.yaml +++ b/.github/workflows/python_build.yaml @@ -74,24 +74,6 @@ jobs: working-directory: python shell: bash run: | - # A release build must match the version in its tag. - WHEEL_VERSION=$(ls dist/sift_stack_py*.whl | sed -E 's/.*sift_stack_py-([^-]+)-.*/\1/') - if [[ "${{ github.ref_type }}" == "tag" && "$WHEEL_VERSION" != "${GITHUB_REF_NAME#python/v}" ]]; then - echo "Error: wheel version $WHEEL_VERSION does not match tag $GITHUB_REF_NAME" - exit 1 - fi - - # A release must include every changelog fragment. A leftover fragment means a change - # merged after the release PR was opened and would ship without an entry. - if [[ "${{ github.ref_type }}" == "tag" ]]; then - FRAGMENTS=$(find changelog.d -type f ! -name README.md ! -name template.md.jinja) - if [[ -n "$FRAGMENTS" ]]; then - echo "Error: unreleased changelog fragments at $GITHUB_REF_NAME:" - echo "$FRAGMENTS" - exit 1 - fi - fi - # Check all distributions with twine twine check dist/* diff --git a/.github/workflows/python_changelog_check.yaml b/.github/workflows/python_changelog_check.yaml index 519c62e2fc..d77d13bf55 100644 --- a/.github/workflows/python_changelog_check.yaml +++ b/.github/workflows/python_changelog_check.yaml @@ -17,35 +17,28 @@ jobs: name: Changelog fragment runs-on: ubuntu-latest steps: + # HEAD is the merge of the PR into the base branch, and HEAD^1 is the base branch tip. - name: Checkout code uses: actions/checkout@v4 with: - fetch-depth: 0 + fetch-depth: 2 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Install towncrier - run: pip install "towncrier==26.9.0" + - name: Set up uv + uses: astral-sh/setup-uv@v6 - name: Check for a fragment - run: towncrier check --dir python --config python/pyproject.toml --compare-with "origin/${{ github.base_ref }}" + run: uvx towncrier@26.9.0 check --dir python --config python/pyproject.toml --compare-with HEAD^1 - # A release PR renders and deletes every fragment. A fragment in the merge result means - # another change merged after the release PR was opened and would ship without an entry. + # A release PR adds a version heading to the changelog. See python/changelog.d/README.md. - name: Check that a release PR includes every fragment run: | - if git diff "origin/${{ github.base_ref }}...HEAD" -- python/CHANGELOG.md | grep -q '^+## \[v'; then - fragments=$(find python/changelog.d -type f ! -name README.md ! -name template.md.jinja) - if [[ -n "$fragments" ]]; then - echo "Error: fragments merged after this release PR was opened:" - echo "$fragments" + if git diff HEAD^1 HEAD -- python/CHANGELOG.md | grep -q '^+## \[v'; then + if ! python3 python/scripts/release_version.py --leftover; then + echo "Error: the fragments above merged after this release PR was opened." echo "Close this PR and run Python Release Prep again." exit 1 fi fi - name: Check that the fragments render - run: towncrier build --dir python --config python/pyproject.toml --draft --version 0.0.0 --date "Draft" > /dev/null + run: uvx towncrier@26.9.0 build --dir python --config python/pyproject.toml --draft --version 0.0.0 --date "Draft" > /dev/null diff --git a/.github/workflows/python_release.yaml b/.github/workflows/python_release.yaml index f8c66b64df..99c706e8d3 100644 --- a/.github/workflows/python_release.yaml +++ b/.github/workflows/python_release.yaml @@ -4,8 +4,34 @@ on: workflow_dispatch: jobs: + # A stable release tag must match the version in pyproject.toml and leave no unreleased + # changelog fragments. See python/changelog.d/README.md. Pre-release tags skip these checks. + preflight: + if: github.event_name == 'workflow_dispatch' && startsWith(github.ref, 'refs/tags') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check release tag + run: | + tag_version="${GITHUB_REF_NAME#python/v}" + if [[ ! "$tag_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Pre-release tag $GITHUB_REF_NAME. Skipping checks." + exit 0 + fi + version=$(python3 python/scripts/release_version.py --current) + if [[ "$version" != "$tag_version" ]]; then + echo "Error: tag $GITHUB_REF_NAME does not match version $version in pyproject.toml" + exit 1 + fi + if ! python3 python/scripts/release_version.py --leftover; then + echo "Error: the fragments above are not in the changelog for $GITHUB_REF_NAME" + exit 1 + fi + python-ci: if: github.event_name == 'workflow_dispatch' && startsWith(github.ref, 'refs/tags') + needs: preflight uses: ./.github/workflows/python_ci.yaml secrets: inherit @@ -69,6 +95,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG_NAME: ${{ github.ref_name }} + FULL_VERSION: ${{ steps.version.outputs.full_version }} IS_PRERELEASE: ${{ steps.version.outputs.is_prerelease }} run: | if [[ "$IS_PRERELEASE" == "true" ]]; then @@ -80,8 +107,7 @@ jobs: fi # Create release notes. Start with this version's section of the changelog, if any. - VERSION="${TAG_NAME#python/v}" - awk -v header="## [v${VERSION}]" ' + awk -v header="## [${FULL_VERSION}]" ' index($0, header) == 1 { found = 1; next } found && /^## \[/ { exit } found { print } diff --git a/.github/workflows/python_release_prep.yaml b/.github/workflows/python_release_prep.yaml index 2880dc5b23..b0dfbacc1e 100644 --- a/.github/workflows/python_release_prep.yaml +++ b/.github/workflows/python_release_prep.yaml @@ -26,11 +26,6 @@ jobs: with: ref: ${{ github.event.repository.default_branch }} - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - name: Set up uv uses: astral-sh/setup-uv@v6 @@ -39,7 +34,7 @@ jobs: env: VERSION_OVERRIDE: ${{ inputs.version }} run: | - version=$(python python/scripts/release_version.py --version "$VERSION_OVERRIDE") + version=$(python3 python/scripts/release_version.py --version "$VERSION_OVERRIDE") echo "version=$version" >> "$GITHUB_OUTPUT" echo "Next version: $version" @@ -47,10 +42,9 @@ jobs: env: VERSION: ${{ steps.version.outputs.version }} run: | - pip install "towncrier==26.9.0" cd python uv version --no-sync "$VERSION" - towncrier build --yes --version "$VERSION" --date "$(date '+%B %-d, %Y')" + uvx towncrier@26.9.0 build --yes --version "$VERSION" --date "$(date '+%B %-d, %Y')" - name: Open pull request env: @@ -70,4 +64,4 @@ jobs: --base "$BASE_BRANCH" \ --head "$branch" \ --title "$title" \ - --body "Sets the version to ${VERSION} and renders its changelog fragments. Review the version and entries before merging. Merging this pull request creates the \`python/v${VERSION}\` tag and starts the publish workflow. If another change to \`python/\` merges first, the changelog check fails. Close this pull request and run Python Release Prep again." + --body "Sets the version to ${VERSION} and renders its changelog fragments. Review the version and entries before merging. Merging this pull request tags \`python/v${VERSION}\` and publishes it. See \`python/changelog.d/README.md\`." diff --git a/.github/workflows/python_release_tag.yaml b/.github/workflows/python_release_tag.yaml index fc0cd17515..8004c14927 100644 --- a/.github/workflows/python_release_tag.yaml +++ b/.github/workflows/python_release_tag.yaml @@ -10,6 +10,7 @@ on: - main paths: - 'python/pyproject.toml' + - 'python/CHANGELOG.md' permissions: contents: write @@ -27,26 +28,22 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Create tag - id: tag + # A tag pushed with the default token does not start other workflows, so this starts the + # publish workflow explicitly. It requires a tag ref. + - name: Create tag and start publish + env: + GH_TOKEN: ${{ github.token }} run: | - version=$(grep '^version = ' python/pyproject.toml | sed 's/version = "\(.*\)"/\1/') - tag="python/v${version}" - echo "tag=$tag" >> "$GITHUB_OUTPUT" + tag="python/v$(python3 python/scripts/release_version.py --current)" if [[ -n "$(git ls-remote --tags origin "refs/tags/${tag}")" ]]; then echo "Tag ${tag} already exists. Nothing to release." - echo "created=false" >> "$GITHUB_OUTPUT" exit 0 fi + # See python/changelog.d/README.md. + if ! python3 python/scripts/release_version.py --leftover; then + echo "Error: the fragments above are not in the changelog for ${tag}. Not tagging." + exit 1 + fi git tag "$tag" "$GITHUB_SHA" git push origin "refs/tags/${tag}" - echo "created=true" >> "$GITHUB_OUTPUT" - - # A tag pushed with the default token does not start other workflows, so start the - # publish workflow explicitly. It requires a tag ref. - - name: Start PyPI Publish - if: steps.tag.outputs.created == 'true' - env: - GH_TOKEN: ${{ github.token }} - TAG: ${{ steps.tag.outputs.tag }} - run: gh workflow run python_release.yaml --ref "$TAG" + gh workflow run python_release.yaml --ref "$tag" diff --git a/python/changelog.d/README.md b/python/changelog.d/README.md index 98dc548690..794c973c71 100644 --- a/python/changelog.d/README.md +++ b/python/changelog.d/README.md @@ -43,6 +43,7 @@ Run the Python Release Prep workflow. It computes the next version from the curr creates the `python/vX.Y.Z` tag and starts the publish workflow. If another change to `python/` merges while the release pull request is open, that change would -ship without its entry. The changelog check fails the release pull request in that case, and the -publish workflow refuses a tag with leftover fragments. Close the release pull request and run -Python Release Prep again. +ship without its entry. The changelog check fails the release pull request in that case. Close it +and run Python Release Prep again. If the release pull request merges anyway, Python Release Tag +refuses to tag. Move the leftover entries into the new version's section of `CHANGELOG.md`, delete +their fragments, and merge that change to release. diff --git a/python/scripts/release_version.py b/python/scripts/release_version.py index fa52581137..1ccad5fc2c 100644 --- a/python/scripts/release_version.py +++ b/python/scripts/release_version.py @@ -1,6 +1,6 @@ """Print the next Python release version, from the version in `python/pyproject.toml` and the -fragment types in `python/changelog.d`. Run from the repository root. Uses only the standard -library.""" +fragment types in `python/changelog.d`. Run from the repository root with Python 3.11+. Uses only +the standard library.""" from __future__ import annotations @@ -9,42 +9,46 @@ import sys from pathlib import Path -PYPROJECT = Path("python/pyproject.toml") -FRAGMENTS_DIR = Path("python/changelog.d") -FRAGMENT_TYPES = {"breaking", "feat", "fix", "chore"} +import tomllib + +PYPROJECT = tomllib.loads(Path("python/pyproject.toml").read_text()) +TOWNCRIER = PYPROJECT["tool"]["towncrier"] +FRAGMENTS_DIR = Path("python") / TOWNCRIER["directory"] +FRAGMENT_TYPES = {t["directory"] for t in TOWNCRIER["type"]} +NOT_FRAGMENTS = {"README.md", Path(TOWNCRIER["template"]).name} STABLE = re.compile(r"^(\d+)\.(\d+)\.(\d+)$") -def current_version() -> tuple[int, int, int]: - version = re.search(r'^version = "(.+)"$', PYPROJECT.read_text(), re.MULTILINE).group(1) - match = STABLE.match(version) - if not match: - sys.exit(f"Current version {version} is not a stable X.Y.Z version. Pass --version.") - major, minor, patch = (int(part) for part in match.groups()) - return major, minor, patch +def current_version() -> str: + return PYPROJECT["project"]["version"] + +def fragments() -> list[Path]: + return sorted(p for p in FRAGMENTS_DIR.iterdir() if p.is_file() and p.name not in NOT_FRAGMENTS) -def fragment_types() -> set[str]: - types = set() - for path in FRAGMENTS_DIR.glob("*.md"): - if path.name.lower() == "readme.md": - continue - parts = path.name.split(".") - if len(parts) < 3 or parts[-2] not in FRAGMENT_TYPES: - sys.exit(f"Bad fragment name {path.name}. Use ..md with a known type.") - types.add(parts[-2]) - return types + +def fragment_type(path: Path) -> str: + # ..md, or ...md when towncrier deduplicates a name. + parts = path.name.removesuffix(".md").split(".") + if len(parts) > 2 and parts[-1].isdigit(): + parts = parts[:-1] + if len(parts) < 2 or parts[-1] not in FRAGMENT_TYPES: + sys.exit(f"Bad fragment name {path.name}. Use ..md with a known type.") + return parts[-1] def next_version(explicit: str) -> str: - types = fragment_types() - {"chore"} + types = {fragment_type(path) for path in fragments()} - {"chore"} if not types: sys.exit("No changelog fragments to release. Only `chore` fragments or none found.") if explicit: if not STABLE.match(explicit): sys.exit(f"--version {explicit} is not X.Y.Z.") return explicit - major, minor, patch = current_version() + match = STABLE.match(current_version()) + if not match: + sys.exit(f"Current version {current_version()} is not X.Y.Z. Pass --version.") + major, minor, patch = (int(part) for part in match.groups()) if "breaking" in types and major >= 1: return f"{major + 1}.0.0" if types & {"breaking", "feat"}: @@ -54,5 +58,21 @@ def next_version(explicit: str) -> str: if __name__ == "__main__": parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) - parser.add_argument("--version", default="", help="Explicit X.Y.Z. Skips the automatic bump.") - print(next_version(parser.parse_args().version)) + mode = parser.add_mutually_exclusive_group() + mode.add_argument("--version", default="", help="Explicit X.Y.Z. Skips the automatic bump.") + mode.add_argument("--current", action="store_true", help="Print the current version.") + mode.add_argument( + "--leftover", + action="store_true", + help="List unreleased fragments and exit 1 if there are any.", + ) + args = parser.parse_args() + if args.current: + print(current_version()) + elif args.leftover: + leftover = fragments() + for path in leftover: + print(path) + sys.exit(1 if leftover else 0) + else: + print(next_version(args.version)) From b3018fa04560093e80decd0c67cec75451ca1909 Mon Sep 17 00:00:00 2001 From: Alex Luck Date: Mon, 12 Oct 2026 14:35:10 -0700 Subject: [PATCH 6/9] Python(chore): limit token permissions in the publish workflow --- .github/workflows/python_release.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/python_release.yaml b/.github/workflows/python_release.yaml index 99c706e8d3..987b734c96 100644 --- a/.github/workflows/python_release.yaml +++ b/.github/workflows/python_release.yaml @@ -9,6 +9,8 @@ jobs: preflight: if: github.event_name == 'workflow_dispatch' && startsWith(github.ref, 'refs/tags') runs-on: ubuntu-latest + permissions: + contents: read steps: - uses: actions/checkout@v4 @@ -32,12 +34,17 @@ jobs: python-ci: if: github.event_name == 'workflow_dispatch' && startsWith(github.ref, 'refs/tags') needs: preflight + permissions: + contents: read + pull-requests: read uses: ./.github/workflows/python_ci.yaml secrets: inherit build-offline-archives: if: github.event_name == 'workflow_dispatch' && startsWith(github.ref, 'refs/tags') needs: python-ci + permissions: + contents: read uses: ./.github/workflows/python_build.yaml secrets: inherit From 4e141d4d3ead8d633bbebefacb6a790e6d43b074 Mon Sep 17 00:00:00 2001 From: Alex Luck Date: Mon, 12 Oct 2026 14:36:02 -0700 Subject: [PATCH 7/9] Python(chore): drop the docs dependency bump Docs CI now installs from uv.lock, so mkdocstrings 1.0.3 and the Python 3.10 floor for the docs extras stay as they are on main. --- python/pyproject.toml | 51 +++--- python/uv.lock | 381 +++++++++++++++++++++++------------------- 2 files changed, 235 insertions(+), 197 deletions(-) diff --git a/python/pyproject.toml b/python/pyproject.toml index 655894d509..58ea9a274b 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -138,30 +138,30 @@ development = [ 'tomlkit~=0.13.3', ] docs = [ - "griffe-pydantic==1.3.1 ; python_version >= '3.11'", - "mike==2.1.3 ; python_version >= '3.11'", - "mkdocs-api-autonav==0.4.0 ; python_version >= '3.11'", - "mkdocs-include-markdown-plugin==7.1.6 ; python_version >= '3.11'", - "mkdocs-jupyter==0.25.1 ; python_version >= '3.11'", - "mkdocs-material==9.7.5 ; python_version >= '3.11'", - "mkdocs==1.6.1 ; python_version >= '3.11'", - "mkdocstrings[python]==1.0.7 ; python_version >= '3.11'", + "griffe-pydantic==1.3.1 ; python_version >= '3.10'", + "mike==2.1.3 ; python_version >= '3.10'", + "mkdocs-api-autonav==0.4.0 ; python_version >= '3.10'", + "mkdocs-include-markdown-plugin==7.1.6 ; python_version >= '3.10'", + "mkdocs-jupyter==0.25.1 ; python_version >= '3.10'", + "mkdocs-material==9.7.5 ; python_version >= '3.10'", + "mkdocs==1.6.1 ; python_version >= '3.10'", + "mkdocstrings[python]==1.0.3 ; python_version >= '3.10'", ] docs-build = [ 'build==1.2.1', 'cffi~=1.14', - "griffe-pydantic==1.3.1 ; python_version >= '3.11'", + "griffe-pydantic==1.3.1 ; python_version >= '3.10'", 'grpcio-testing~=1.13', 'h5py~=3.11', 'mcap-ros2-support~=0.5.7', 'mcap~=1.4', - "mike==2.1.3 ; python_version >= '3.11'", - "mkdocs-api-autonav==0.4.0 ; python_version >= '3.11'", - "mkdocs-include-markdown-plugin==7.1.6 ; python_version >= '3.11'", - "mkdocs-jupyter==0.25.1 ; python_version >= '3.11'", - "mkdocs-material==9.7.5 ; python_version >= '3.11'", - "mkdocs==1.6.1 ; python_version >= '3.11'", - "mkdocstrings[python]==1.0.7 ; python_version >= '3.11'", + "mike==2.1.3 ; python_version >= '3.10'", + "mkdocs-api-autonav==0.4.0 ; python_version >= '3.10'", + "mkdocs-include-markdown-plugin==7.1.6 ; python_version >= '3.10'", + "mkdocs-jupyter==0.25.1 ; python_version >= '3.10'", + "mkdocs-material==9.7.5 ; python_version >= '3.10'", + "mkdocs==1.6.1 ; python_version >= '3.10'", + "mkdocstrings[python]==1.0.3 ; python_version >= '3.10'", 'mypy==1.10.0', 'npTDMS~=1.9', 'pdoc==14.5.0', @@ -245,16 +245,15 @@ development = [ "pre-commit~=4.0 ; python_version >= '3.9'" ] build = ["pdoc==14.5.0", "build==1.2.1"] -# mkdocstrings 1.0.7 needs Python 3.11+. docs = [ - "mkdocs==1.6.1 ; python_version >= '3.11'", - "mkdocs-material==9.7.5 ; python_version >= '3.11'", - "mkdocstrings[python]==1.0.7 ; python_version >= '3.11'", - "mkdocs-include-markdown-plugin==7.1.6 ; python_version >= '3.11'", - "mkdocs-api-autonav==0.4.0 ; python_version >= '3.11'", - "mike==2.1.3 ; python_version >= '3.11'", - "griffe-pydantic==1.3.1 ; python_version >= '3.11'", - "mkdocs-jupyter==0.25.1 ; python_version >= '3.11'", + "mkdocs==1.6.1 ; python_version >= '3.10'", + "mkdocs-material==9.7.5 ; python_version >= '3.10'", + "mkdocstrings[python]==1.0.3 ; python_version >= '3.10'", + "mkdocs-include-markdown-plugin==7.1.6 ; python_version >= '3.10'", + "mkdocs-api-autonav==0.4.0 ; python_version >= '3.10'", + "mike==2.1.3 ; python_version >= '3.10'", + "griffe-pydantic==1.3.1 ; python_version >= '3.10'", + "mkdocs-jupyter==0.25.1 ; python_version >= '3.10'", ] # May be required for certain library functionality @@ -279,7 +278,7 @@ file-imports = ["tdms", "rosbags", "hdf5", "ulog", "mcap"] all = ["openssl", "sift-stream", "file-imports", "data-review"] dev-all = ["development", "all", "build"] -docs-build = ["dev-all", "docs"] # Note python 3.11+ +docs-build = ["dev-all", "docs"] # Note python 3.9+ [tool.uv] # Pin uv to a version that writes lockfile revision 3 (introduced in uv 0.8.4 diff --git a/python/uv.lock b/python/uv.lock index 90b93a116a..5b0d6c82f6 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -233,8 +233,8 @@ name = "beautifulsoup4" version = "4.14.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "soupsieve", marker = "python_full_version >= '3.11'" }, - { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "soupsieve", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } wheels = [ @@ -246,7 +246,7 @@ name = "bleach" version = "6.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "webencodings", marker = "python_full_version >= '3.11'" }, + { name = "webencodings", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } wheels = [ @@ -255,7 +255,7 @@ wheels = [ [package.optional-dependencies] css = [ - { name = "tinycss2", marker = "python_full_version >= '3.11'" }, + { name = "tinycss2", marker = "python_full_version >= '3.10'" }, ] [[package]] @@ -522,7 +522,7 @@ name = "click" version = "8.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/23/e4/796662cd90cf80e3a363c99db2b88e0e394b988a575f60a17e16440cd011/click-8.4.0.tar.gz", hash = "sha256:638f1338fe1235c8f4e008e4a8a254fb5c5fbdcbb40ece3c9142ebb78e792973", size = 350843, upload-time = "2026-05-17T00:47:58.425Z" } wheels = [ @@ -753,7 +753,7 @@ name = "ghp-import" version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } wheels = [ @@ -818,7 +818,7 @@ name = "griffe-pydantic" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "griffelib", marker = "python_full_version >= '3.11'" }, + { name = "griffelib", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5a/bd/d2eaeaf3f9910c9cd72793af0de18ee3d3a3a27bb30ab01cfd7659c08dc4/griffe_pydantic-1.3.1.tar.gz", hash = "sha256:f7caedfa0effedb22893bf01cc411fd567614f7b4de7ce0c1f4293eb7acb5c44", size = 38176, upload-time = "2026-02-21T09:38:49.674Z" } wheels = [ @@ -1279,29 +1279,63 @@ name = "ipykernel" version = "6.31.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "appnope", marker = "python_full_version >= '3.11' and sys_platform == 'darwin'" }, - { name = "comm", marker = "python_full_version >= '3.11'" }, - { name = "debugpy", marker = "python_full_version >= '3.11'" }, - { name = "ipython", marker = "python_full_version >= '3.11'" }, - { name = "jupyter-client", marker = "python_full_version >= '3.11'" }, - { name = "jupyter-core", marker = "python_full_version >= '3.11'" }, - { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, - { name = "nest-asyncio", marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "psutil", marker = "python_full_version >= '3.11'" }, - { name = "pyzmq", marker = "python_full_version >= '3.11'" }, - { name = "tornado", marker = "python_full_version >= '3.11'" }, - { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "appnope", marker = "python_full_version >= '3.10' and sys_platform == 'darwin'" }, + { name = "comm", marker = "python_full_version >= '3.10'" }, + { name = "debugpy", marker = "python_full_version >= '3.10'" }, + { name = "ipython", version = "8.39.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "ipython", version = "9.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "jupyter-client", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.10'" }, + { name = "nest-asyncio", marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "psutil", marker = "python_full_version >= '3.10'" }, + { name = "pyzmq", marker = "python_full_version >= '3.10'" }, + { name = "tornado", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a5/1d/d5ba6edbfe6fae4c3105bca3a9c889563cc752c7f2de45e333164c7f4846/ipykernel-6.31.0.tar.gz", hash = "sha256:2372ce8bc1ff4f34e58cafed3a0feb2194b91fc7cad0fc72e79e47b45ee9e8f6", size = 167493, upload-time = "2025-10-20T11:42:39.948Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl", hash = "sha256:abe5386f6ced727a70e0eb0cf1da801fa7c5fa6ff82147747d5a0406cd8c94af", size = 117003, upload-time = "2025-10-20T11:42:37.502Z" }, ] +[[package]] +name = "ipython" +version = "8.39.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version == '3.10.*' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version == '3.10.*'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "jedi", marker = "python_full_version == '3.10.*'" }, + { name = "matplotlib-inline", marker = "python_full_version == '3.10.*'" }, + { name = "pexpect", marker = "python_full_version == '3.10.*' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version == '3.10.*'" }, + { name = "pygments", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "stack-data", marker = "python_full_version == '3.10.*'" }, + { name = "traitlets", marker = "python_full_version == '3.10.*'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971, upload-time = "2026-03-27T10:02:13.94Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/56/4cc7fc9e9e3f38fd324f24f8afe0ad8bb5fa41283f37f1aaf9de0612c968/ipython-8.39.0-py3-none-any.whl", hash = "sha256:bb3c51c4fa8148ab1dea07a79584d1c854e234ea44aa1283bcb37bc75054651f", size = 831849, upload-time = "2026-03-27T10:02:07.846Z" }, +] + [[package]] name = "ipython" version = "9.13.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, { name = "decorator", marker = "python_full_version >= '3.11'" }, @@ -1338,7 +1372,7 @@ name = "jedi" version = "0.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "parso", marker = "python_full_version >= '3.11'" }, + { name = "parso", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } wheels = [ @@ -1363,10 +1397,10 @@ name = "jsonschema" version = "4.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "attrs", marker = "python_full_version >= '3.11'" }, - { name = "jsonschema-specifications", marker = "python_full_version >= '3.11'" }, - { name = "referencing", marker = "python_full_version >= '3.11'" }, - { name = "rpds-py", marker = "python_full_version >= '3.11'" }, + { name = "attrs", marker = "python_full_version >= '3.10'" }, + { name = "jsonschema-specifications", marker = "python_full_version >= '3.10'" }, + { name = "referencing", marker = "python_full_version >= '3.10'" }, + { name = "rpds-py", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } wheels = [ @@ -1378,7 +1412,7 @@ name = "jsonschema-specifications" version = "2025.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "referencing", marker = "python_full_version >= '3.11'" }, + { name = "referencing", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } wheels = [ @@ -1390,11 +1424,11 @@ name = "jupyter-client" version = "8.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jupyter-core", marker = "python_full_version >= '3.11'" }, - { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, - { name = "pyzmq", marker = "python_full_version >= '3.11'" }, - { name = "tornado", marker = "python_full_version >= '3.11'" }, - { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, + { name = "pyzmq", marker = "python_full_version >= '3.10'" }, + { name = "tornado", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" } wheels = [ @@ -1406,8 +1440,8 @@ name = "jupyter-core" version = "5.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs", version = "4.9.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "platformdirs", version = "4.9.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } wheels = [ @@ -1428,11 +1462,12 @@ name = "jupytext" version = "1.19.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown-it-py", marker = "python_full_version >= '3.11'" }, - { name = "mdit-py-plugins", marker = "python_full_version >= '3.11'" }, - { name = "nbformat", marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "pyyaml", marker = "python_full_version >= '3.11'" }, + { name = "markdown-it-py", marker = "python_full_version >= '3.10'" }, + { name = "mdit-py-plugins", marker = "python_full_version >= '3.10'" }, + { name = "nbformat", marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pyyaml", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ef/2d/15624c3d9440d85a280ff13d2d23afd989802f25470ac59932f4fef6f0c6/jupytext-1.19.3.tar.gz", hash = "sha256:713c3ed4441afe0f31474d28ea2e6b61a268c04c40fd78e5ccfd7f7ac9e9f766", size = 4305350, upload-time = "2026-05-17T09:09:29.294Z" } wheels = [ @@ -1574,7 +1609,7 @@ name = "markdown-it-py" version = "4.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mdurl", marker = "python_full_version >= '3.11'" }, + { name = "mdurl", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } wheels = [ @@ -1754,7 +1789,7 @@ name = "matplotlib-inline" version = "0.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" } wheels = [ @@ -1793,7 +1828,7 @@ name = "mdit-py-plugins" version = "0.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown-it-py", marker = "python_full_version >= '3.11'" }, + { name = "markdown-it-py", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/59/fc/f8d0863f8862f25602c0404d75568e89fb6b4109804645e5cdfb1be5cf56/mdit_py_plugins-0.6.1.tar.gz", hash = "sha256:a2bca0f039f39dbd35fb74ae1b5f998608c437463371f0ff7f49a19a17a114d0", size = 56114, upload-time = "2026-05-13T09:03:38.91Z" } wheels = [ @@ -1823,14 +1858,14 @@ name = "mike" version = "2.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "importlib-resources", marker = "python_full_version >= '3.11'" }, - { name = "jinja2", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs", marker = "python_full_version >= '3.11'" }, - { name = "pyparsing", marker = "python_full_version >= '3.11'" }, - { name = "pyyaml", marker = "python_full_version >= '3.11'" }, - { name = "pyyaml-env-tag", marker = "python_full_version >= '3.11'" }, - { name = "verspec", marker = "python_full_version >= '3.11'" }, + { name = "importlib-metadata", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "importlib-resources", marker = "python_full_version >= '3.10'" }, + { name = "jinja2", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs", marker = "python_full_version >= '3.10'" }, + { name = "pyparsing", marker = "python_full_version >= '3.10'" }, + { name = "pyyaml", marker = "python_full_version >= '3.10'" }, + { name = "pyyaml-env-tag", marker = "python_full_version >= '3.10'" }, + { name = "verspec", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ab/f7/2933f1a1fb0e0f077d5d6a92c6c7f8a54e6128241f116dff4df8b6050bbf/mike-2.1.3.tar.gz", hash = "sha256:abd79b8ea483fb0275b7972825d3082e5ae67a41820f8d8a0dc7a3f49944e810", size = 38119, upload-time = "2024-08-13T05:02:14.167Z" } wheels = [ @@ -1841,6 +1876,9 @@ wheels = [ name = "mistune" version = "3.2.1" source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, +] sdist = { url = "https://files.pythonhosted.org/packages/ca/84/620cc3f7e3adf6f5067e10f4dbae71295d8f9e16d5d3f9ef97c40f2f592c/mistune-3.2.1.tar.gz", hash = "sha256:7c8e5501d38bac1582e067e46c8343f17d57ea1aaa735823f3aba1fd59c88a28", size = 98003, upload-time = "2026-05-03T14:33:22.312Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2a/7f/a946aa4f8752b37102b41e64dca18a1976ac705c3a0d1dfe74d820a02552/mistune-3.2.1-py3-none-any.whl", hash = "sha256:78cdb0ba5e938053ccf63651b352508d2efa9411dc8810bfb05f2dc5140c0048", size = 53749, upload-time = "2026-05-03T14:33:20.551Z" }, @@ -1851,19 +1889,19 @@ name = "mkdocs" version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click", marker = "python_full_version >= '3.11'" }, - { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, - { name = "ghp-import", marker = "python_full_version >= '3.11'" }, - { name = "jinja2", marker = "python_full_version >= '3.11'" }, - { name = "markdown", marker = "python_full_version >= '3.11'" }, - { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "mergedeep", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs-get-deps", marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "pathspec", marker = "python_full_version >= '3.11'" }, - { name = "pyyaml", marker = "python_full_version >= '3.11'" }, - { name = "pyyaml-env-tag", marker = "python_full_version >= '3.11'" }, - { name = "watchdog", marker = "python_full_version >= '3.11'" }, + { name = "click", marker = "python_full_version >= '3.10'" }, + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "ghp-import", marker = "python_full_version >= '3.10'" }, + { name = "jinja2", marker = "python_full_version >= '3.10'" }, + { name = "markdown", marker = "python_full_version >= '3.10'" }, + { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mergedeep", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs-get-deps", marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pathspec", marker = "python_full_version >= '3.10'" }, + { name = "pyyaml", marker = "python_full_version >= '3.10'" }, + { name = "pyyaml-env-tag", marker = "python_full_version >= '3.10'" }, + { name = "watchdog", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } wheels = [ @@ -1875,9 +1913,9 @@ name = "mkdocs-api-autonav" version = "0.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mkdocs", marker = "python_full_version >= '3.11'" }, - { name = "mkdocstrings-python", marker = "python_full_version >= '3.11'" }, - { name = "pyyaml", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs", marker = "python_full_version >= '3.10'" }, + { name = "mkdocstrings-python", marker = "python_full_version >= '3.10'" }, + { name = "pyyaml", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6d/b0/20960ee733a419a349877d09712d02e8ec2bda031672e5f0d0a15fc020b3/mkdocs_api_autonav-0.4.0.tar.gz", hash = "sha256:3527b0e5cf1b682bd374a3ce699ac12d6288f5fcaf93877f34a6b14c79740637", size = 17987, upload-time = "2025-09-09T12:42:02.216Z" } wheels = [ @@ -1889,9 +1927,9 @@ name = "mkdocs-autorefs" version = "1.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown", marker = "python_full_version >= '3.11'" }, - { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "mkdocs", marker = "python_full_version >= '3.11'" }, + { name = "markdown", marker = "python_full_version >= '3.10'" }, + { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mkdocs", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/52/c0/f641843de3f612a6b48253f39244165acff36657a91cc903633d456ae1ac/mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197", size = 56588, upload-time = "2026-02-10T15:23:55.105Z" } wheels = [ @@ -1903,9 +1941,9 @@ name = "mkdocs-get-deps" version = "0.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mergedeep", marker = "python_full_version >= '3.11'" }, - { name = "platformdirs", version = "4.9.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "pyyaml", marker = "python_full_version >= '3.11'" }, + { name = "mergedeep", marker = "python_full_version >= '3.10'" }, + { name = "platformdirs", version = "4.9.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyyaml", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047, upload-time = "2026-03-10T02:46:33.632Z" } wheels = [ @@ -1917,8 +1955,8 @@ name = "mkdocs-include-markdown-plugin" version = "7.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mkdocs", marker = "python_full_version >= '3.11'" }, - { name = "wcmatch", marker = "python_full_version >= '3.11'" }, + { name = "mkdocs", marker = "python_full_version >= '3.10'" }, + { name = "wcmatch", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2c/17/988d97ac6849b196f54d45ca9c60ca894880c160a512785f03834704b3d9/mkdocs_include_markdown_plugin-7.1.6.tar.gz", hash = "sha256:a0753cb82704c10a287f1e789fc9848f82b6beb8749814b24b03dd9f67816677", size = 23391, upload-time = "2025-06-13T18:25:51.193Z" } wheels = [ @@ -1930,12 +1968,12 @@ name = "mkdocs-jupyter" version = "0.25.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ipykernel", marker = "python_full_version >= '3.11'" }, - { name = "jupytext", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs-material", marker = "python_full_version >= '3.11'" }, - { name = "nbconvert", marker = "python_full_version >= '3.11'" }, - { name = "pygments", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "ipykernel", marker = "python_full_version >= '3.10'" }, + { name = "jupytext", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs-material", marker = "python_full_version >= '3.10'" }, + { name = "nbconvert", marker = "python_full_version >= '3.10'" }, + { name = "pygments", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6c/23/6ffb8d2fd2117aa860a04c6fe2510b21bc3c3c085907ffdd851caba53152/mkdocs_jupyter-0.25.1.tar.gz", hash = "sha256:0e9272ff4947e0ec683c92423a4bfb42a26477c103ab1a6ab8277e2dcc8f7afe", size = 1626747, upload-time = "2024-10-15T14:56:32.373Z" } wheels = [ @@ -1947,17 +1985,17 @@ name = "mkdocs-material" version = "9.7.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "babel", marker = "python_full_version >= '3.11'" }, - { name = "backrefs", marker = "python_full_version >= '3.11'" }, - { name = "colorama", marker = "python_full_version >= '3.11'" }, - { name = "jinja2", marker = "python_full_version >= '3.11'" }, - { name = "markdown", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs-material-extensions", marker = "python_full_version >= '3.11'" }, - { name = "paginate", marker = "python_full_version >= '3.11'" }, - { name = "pygments", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "pymdown-extensions", marker = "python_full_version >= '3.11'" }, - { name = "requests", version = "2.34.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "babel", marker = "python_full_version >= '3.10'" }, + { name = "backrefs", marker = "python_full_version >= '3.10'" }, + { name = "colorama", marker = "python_full_version >= '3.10'" }, + { name = "jinja2", marker = "python_full_version >= '3.10'" }, + { name = "markdown", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs-material-extensions", marker = "python_full_version >= '3.10'" }, + { name = "paginate", marker = "python_full_version >= '3.10'" }, + { name = "pygments", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pymdown-extensions", marker = "python_full_version >= '3.10'" }, + { name = "requests", version = "2.34.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/74/76/5c202fecdc45d53e83e03a85bae70c48b6c81e9f87f0bc19a9e9c723bdc0/mkdocs_material-9.7.5.tar.gz", hash = "sha256:f76bdab532bad1d9c57ca7187b37eccf64dd12e1586909307f8856db3be384ea", size = 4097749, upload-time = "2026-03-10T15:43:22.809Z" } wheels = [ @@ -1975,24 +2013,24 @@ wheels = [ [[package]] name = "mkdocstrings" -version = "1.0.7" +version = "1.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jinja2", marker = "python_full_version >= '3.11'" }, - { name = "markdown", marker = "python_full_version >= '3.11'" }, - { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "mkdocs", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs-autorefs", marker = "python_full_version >= '3.11'" }, - { name = "pymdown-extensions", marker = "python_full_version >= '3.11'" }, + { name = "jinja2", marker = "python_full_version >= '3.10'" }, + { name = "markdown", marker = "python_full_version >= '3.10'" }, + { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mkdocs", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs-autorefs", marker = "python_full_version >= '3.10'" }, + { name = "pymdown-extensions", marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f5/e8/445b19834bf9559ebbbaa76897e965665b83d2861461eed8d8de4d122057/mkdocstrings-1.0.7.tar.gz", hash = "sha256:0951b147f352118a45a3db85f0f3b660833d34cb5120a2751315b8d08dafd159", size = 103739, upload-time = "2026-10-10T13:50:59.957Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/62/0dfc5719514115bf1781f44b1d7f2a0923fcc01e9c5d7990e48a05c9ae5d/mkdocstrings-1.0.3.tar.gz", hash = "sha256:ab670f55040722b49bb45865b2e93b824450fb4aef638b00d7acb493a9020434", size = 100946, upload-time = "2026-02-07T14:31:40.973Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/c3/9062531735ff1fd27f3a79c353c4cf4d822eeffd062fd90b2c553a96f4bb/mkdocstrings-1.0.7-py3-none-any.whl", hash = "sha256:9563828667eeb35256f87a6b61fe857676ac628ec1abbce9aea9839a502c9cb1", size = 41597, upload-time = "2026-10-10T13:50:58.43Z" }, + { url = "https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl", hash = "sha256:0d66d18430c2201dc7fe85134277382baaa15e6b30979f3f3bdbabd6dbdb6046", size = 35523, upload-time = "2026-02-07T14:31:39.27Z" }, ] [package.optional-dependencies] python = [ - { name = "mkdocstrings-python", marker = "python_full_version >= '3.11'" }, + { name = "mkdocstrings-python", marker = "python_full_version >= '3.10'" }, ] [[package]] @@ -2000,9 +2038,10 @@ name = "mkdocstrings-python" version = "2.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "griffelib", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs-autorefs", marker = "python_full_version >= '3.11'" }, - { name = "mkdocstrings", marker = "python_full_version >= '3.11'" }, + { name = "griffelib", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs-autorefs", marker = "python_full_version >= '3.10'" }, + { name = "mkdocstrings", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/29/33/c225eaf898634bdda489a6766fc35d1683c640bffe0e0acd10646b13536d/mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8", size = 199083, upload-time = "2026-02-20T10:38:36.368Z" } wheels = [ @@ -2063,10 +2102,10 @@ name = "nbclient" version = "0.10.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jupyter-client", marker = "python_full_version >= '3.11'" }, - { name = "jupyter-core", marker = "python_full_version >= '3.11'" }, - { name = "nbformat", marker = "python_full_version >= '3.11'" }, - { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "jupyter-client", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, + { name = "nbformat", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554, upload-time = "2025-12-23T07:45:46.369Z" } wheels = [ @@ -2078,20 +2117,20 @@ name = "nbconvert" version = "7.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "beautifulsoup4", marker = "python_full_version >= '3.11'" }, - { name = "bleach", extra = ["css"], marker = "python_full_version >= '3.11'" }, - { name = "defusedxml", marker = "python_full_version >= '3.11'" }, - { name = "jinja2", marker = "python_full_version >= '3.11'" }, - { name = "jupyter-core", marker = "python_full_version >= '3.11'" }, - { name = "jupyterlab-pygments", marker = "python_full_version >= '3.11'" }, - { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "mistune", marker = "python_full_version >= '3.11'" }, - { name = "nbclient", marker = "python_full_version >= '3.11'" }, - { name = "nbformat", marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "pandocfilters", marker = "python_full_version >= '3.11'" }, - { name = "pygments", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "beautifulsoup4", marker = "python_full_version >= '3.10'" }, + { name = "bleach", extra = ["css"], marker = "python_full_version >= '3.10'" }, + { name = "defusedxml", marker = "python_full_version >= '3.10'" }, + { name = "jinja2", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, + { name = "jupyterlab-pygments", marker = "python_full_version >= '3.10'" }, + { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mistune", marker = "python_full_version >= '3.10'" }, + { name = "nbclient", marker = "python_full_version >= '3.10'" }, + { name = "nbformat", marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pandocfilters", marker = "python_full_version >= '3.10'" }, + { name = "pygments", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/01/b1/708e53fe2e429c103c6e6e159106bcf0357ac41aa4c28772bd8402339051/nbconvert-7.17.1.tar.gz", hash = "sha256:34d0d0a7e73ce3cbab6c5aae8f4f468797280b01fd8bd2ca746da8569eddd7d2", size = 865311, upload-time = "2026-04-08T00:44:14.914Z" } wheels = [ @@ -2103,10 +2142,10 @@ name = "nbformat" version = "5.10.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "fastjsonschema", marker = "python_full_version >= '3.11'" }, - { name = "jsonschema", marker = "python_full_version >= '3.11'" }, - { name = "jupyter-core", marker = "python_full_version >= '3.11'" }, - { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "fastjsonschema", marker = "python_full_version >= '3.10'" }, + { name = "jsonschema", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" } wheels = [ @@ -2708,7 +2747,7 @@ name = "pexpect" version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ptyprocess", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "ptyprocess", marker = "(python_full_version == '3.10.*' and sys_platform == 'emscripten') or (python_full_version == '3.10.*' and sys_platform == 'win32') or (python_full_version >= '3.10' and sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } wheels = [ @@ -2893,7 +2932,7 @@ name = "prompt-toolkit" version = "3.0.52" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "wcwidth", marker = "python_full_version >= '3.11'" }, + { name = "wcwidth", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } wheels = [ @@ -3554,8 +3593,8 @@ name = "pymdown-extensions" version = "10.21.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown", marker = "python_full_version >= '3.11'" }, - { name = "pyyaml", marker = "python_full_version >= '3.11'" }, + { name = "markdown", marker = "python_full_version >= '3.10'" }, + { name = "pyyaml", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9e/26/d1015444da4d952a1ca487a236b522eb979766f0295a0bd0c5fc089989a9/pymdown_extensions-10.21.3.tar.gz", hash = "sha256:72cfcf55f07aea0d4af2c4f11dd4e52466ddfb1bb819673146398e0bd3a77354", size = 854140, upload-time = "2026-05-13T12:57:32.267Z" } wheels = [ @@ -3870,7 +3909,7 @@ name = "pyyaml-env-tag" version = "1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyyaml", marker = "python_full_version >= '3.11'" }, + { name = "pyyaml", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } wheels = [ @@ -3882,7 +3921,7 @@ name = "pyzmq" version = "27.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi", marker = "python_full_version >= '3.11' and implementation_name == 'pypy'" }, + { name = "cffi", marker = "python_full_version >= '3.10' and implementation_name == 'pypy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } wheels = [ @@ -4055,9 +4094,9 @@ name = "referencing" version = "0.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "attrs", marker = "python_full_version >= '3.11'" }, - { name = "rpds-py", marker = "python_full_version >= '3.11'" }, - { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, + { name = "attrs", marker = "python_full_version >= '3.10'" }, + { name = "rpds-py", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } wheels = [ @@ -4567,19 +4606,19 @@ development = [ { name = "tomlkit" }, ] docs = [ - { name = "griffe-pydantic", marker = "python_full_version >= '3.11'" }, - { name = "mike", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs-api-autonav", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs-include-markdown-plugin", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs-jupyter", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs-material", marker = "python_full_version >= '3.11'" }, - { name = "mkdocstrings", extra = ["python"], marker = "python_full_version >= '3.11'" }, + { name = "griffe-pydantic", marker = "python_full_version >= '3.10'" }, + { name = "mike", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs-api-autonav", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs-include-markdown-plugin", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs-jupyter", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs-material", marker = "python_full_version >= '3.10'" }, + { name = "mkdocstrings", extra = ["python"], marker = "python_full_version >= '3.10'" }, ] docs-build = [ { name = "build" }, { name = "cffi" }, - { name = "griffe-pydantic", marker = "python_full_version >= '3.11'" }, + { name = "griffe-pydantic", marker = "python_full_version >= '3.10'" }, { name = "grpcio-testing", version = "1.70.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "grpcio-testing", version = "1.80.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "h5py", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, @@ -4587,13 +4626,13 @@ docs-build = [ { name = "h5py", version = "3.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "mcap" }, { name = "mcap-ros2-support" }, - { name = "mike", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs-api-autonav", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs-include-markdown-plugin", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs-jupyter", marker = "python_full_version >= '3.11'" }, - { name = "mkdocs-material", marker = "python_full_version >= '3.11'" }, - { name = "mkdocstrings", extra = ["python"], marker = "python_full_version >= '3.11'" }, + { name = "mike", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs-api-autonav", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs-include-markdown-plugin", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs-jupyter", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs-material", marker = "python_full_version >= '3.10'" }, + { name = "mkdocstrings", extra = ["python"], marker = "python_full_version >= '3.10'" }, { name = "mypy" }, { name = "nptdms" }, { name = "pdoc" }, @@ -4682,8 +4721,8 @@ requires-dist = [ { name = "eval-type-backport", specifier = "~=0.2" }, { name = "filelock", specifier = "~=3.15" }, { name = "googleapis-common-protos", specifier = ">=1.60" }, - { name = "griffe-pydantic", marker = "python_full_version >= '3.11' and extra == 'docs'", specifier = "==1.3.1" }, - { name = "griffe-pydantic", marker = "python_full_version >= '3.11' and extra == 'docs-build'", specifier = "==1.3.1" }, + { name = "griffe-pydantic", marker = "python_full_version >= '3.10' and extra == 'docs'", specifier = "==1.3.1" }, + { name = "griffe-pydantic", marker = "python_full_version >= '3.10' and extra == 'docs-build'", specifier = "==1.3.1" }, { name = "grpcio", specifier = "~=1.13" }, { name = "grpcio-testing", marker = "extra == 'dev'", specifier = "~=1.13" }, { name = "grpcio-testing", marker = "extra == 'dev-all'", specifier = "~=1.13" }, @@ -4704,20 +4743,20 @@ requires-dist = [ { name = "mcap-ros2-support", marker = "extra == 'docs-build'", specifier = "~=0.5.7" }, { name = "mcap-ros2-support", marker = "extra == 'file-imports'", specifier = "~=0.5.7" }, { name = "mcap-ros2-support", marker = "extra == 'mcap'", specifier = "~=0.5.7" }, - { name = "mike", marker = "python_full_version >= '3.11' and extra == 'docs'", specifier = "==2.1.3" }, - { name = "mike", marker = "python_full_version >= '3.11' and extra == 'docs-build'", specifier = "==2.1.3" }, - { name = "mkdocs", marker = "python_full_version >= '3.11' and extra == 'docs'", specifier = "==1.6.1" }, - { name = "mkdocs", marker = "python_full_version >= '3.11' and extra == 'docs-build'", specifier = "==1.6.1" }, - { name = "mkdocs-api-autonav", marker = "python_full_version >= '3.11' and extra == 'docs'", specifier = "==0.4.0" }, - { name = "mkdocs-api-autonav", marker = "python_full_version >= '3.11' and extra == 'docs-build'", specifier = "==0.4.0" }, - { name = "mkdocs-include-markdown-plugin", marker = "python_full_version >= '3.11' and extra == 'docs'", specifier = "==7.1.6" }, - { name = "mkdocs-include-markdown-plugin", marker = "python_full_version >= '3.11' and extra == 'docs-build'", specifier = "==7.1.6" }, - { name = "mkdocs-jupyter", marker = "python_full_version >= '3.11' and extra == 'docs'", specifier = "==0.25.1" }, - { name = "mkdocs-jupyter", marker = "python_full_version >= '3.11' and extra == 'docs-build'", specifier = "==0.25.1" }, - { name = "mkdocs-material", marker = "python_full_version >= '3.11' and extra == 'docs'", specifier = "==9.7.5" }, - { name = "mkdocs-material", marker = "python_full_version >= '3.11' and extra == 'docs-build'", specifier = "==9.7.5" }, - { name = "mkdocstrings", extras = ["python"], marker = "python_full_version >= '3.11' and extra == 'docs'", specifier = "==1.0.7" }, - { name = "mkdocstrings", extras = ["python"], marker = "python_full_version >= '3.11' and extra == 'docs-build'", specifier = "==1.0.7" }, + { name = "mike", marker = "python_full_version >= '3.10' and extra == 'docs'", specifier = "==2.1.3" }, + { name = "mike", marker = "python_full_version >= '3.10' and extra == 'docs-build'", specifier = "==2.1.3" }, + { name = "mkdocs", marker = "python_full_version >= '3.10' and extra == 'docs'", specifier = "==1.6.1" }, + { name = "mkdocs", marker = "python_full_version >= '3.10' and extra == 'docs-build'", specifier = "==1.6.1" }, + { name = "mkdocs-api-autonav", marker = "python_full_version >= '3.10' and extra == 'docs'", specifier = "==0.4.0" }, + { name = "mkdocs-api-autonav", marker = "python_full_version >= '3.10' and extra == 'docs-build'", specifier = "==0.4.0" }, + { name = "mkdocs-include-markdown-plugin", marker = "python_full_version >= '3.10' and extra == 'docs'", specifier = "==7.1.6" }, + { name = "mkdocs-include-markdown-plugin", marker = "python_full_version >= '3.10' and extra == 'docs-build'", specifier = "==7.1.6" }, + { name = "mkdocs-jupyter", marker = "python_full_version >= '3.10' and extra == 'docs'", specifier = "==0.25.1" }, + { name = "mkdocs-jupyter", marker = "python_full_version >= '3.10' and extra == 'docs-build'", specifier = "==0.25.1" }, + { name = "mkdocs-material", marker = "python_full_version >= '3.10' and extra == 'docs'", specifier = "==9.7.5" }, + { name = "mkdocs-material", marker = "python_full_version >= '3.10' and extra == 'docs-build'", specifier = "==9.7.5" }, + { name = "mkdocstrings", extras = ["python"], marker = "python_full_version >= '3.10' and extra == 'docs'", specifier = "==1.0.3" }, + { name = "mkdocstrings", extras = ["python"], marker = "python_full_version >= '3.10' and extra == 'docs-build'", specifier = "==1.0.3" }, { name = "mypy", marker = "extra == 'dev'", specifier = "==1.10.0" }, { name = "mypy", marker = "extra == 'dev-all'", specifier = "==1.10.0" }, { name = "mypy", marker = "extra == 'development'", specifier = "==1.10.0" }, @@ -4900,9 +4939,9 @@ name = "stack-data" version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "asttokens", marker = "python_full_version >= '3.11'" }, - { name = "executing", marker = "python_full_version >= '3.11'" }, - { name = "pure-eval", marker = "python_full_version >= '3.11'" }, + { name = "asttokens", marker = "python_full_version >= '3.10'" }, + { name = "executing", marker = "python_full_version >= '3.10'" }, + { name = "pure-eval", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } wheels = [ @@ -4914,7 +4953,7 @@ name = "tinycss2" version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "webencodings", marker = "python_full_version >= '3.11'" }, + { name = "webencodings", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } wheels = [ @@ -5362,7 +5401,7 @@ name = "wcmatch" version = "10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "bracex", marker = "python_full_version >= '3.11'" }, + { name = "bracex", marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/79/3e/c0bdc27cf06f4e47680bd5803a07cb3dfd17de84cde92dd217dcb9e05253/wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af", size = 117421, upload-time = "2025-06-22T19:14:02.49Z" } wheels = [ From a53d4079484b81bd1d834c7522a348cd72c05311 Mon Sep 17 00:00:00 2001 From: Alex Luck Date: Tue, 13 Oct 2026 15:52:54 -0700 Subject: [PATCH 8/9] Python(chore): make release prep rerunnable and fix nested CI permissions --- .github/workflows/python_changelog_check.yaml | 2 +- .github/workflows/python_release.yaml | 2 ++ .github/workflows/python_release_prep.yaml | 7 ++++++- python/changelog.d/README.md | 6 ++++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python_changelog_check.yaml b/.github/workflows/python_changelog_check.yaml index d77d13bf55..aa53185ea8 100644 --- a/.github/workflows/python_changelog_check.yaml +++ b/.github/workflows/python_changelog_check.yaml @@ -35,7 +35,7 @@ jobs: if git diff HEAD^1 HEAD -- python/CHANGELOG.md | grep -q '^+## \[v'; then if ! python3 python/scripts/release_version.py --leftover; then echo "Error: the fragments above merged after this release PR was opened." - echo "Close this PR and run Python Release Prep again." + echo "Run Python Release Prep again to update this PR." exit 1 fi fi diff --git a/.github/workflows/python_release.yaml b/.github/workflows/python_release.yaml index 987b734c96..99f6abe85c 100644 --- a/.github/workflows/python_release.yaml +++ b/.github/workflows/python_release.yaml @@ -43,8 +43,10 @@ jobs: build-offline-archives: if: github.event_name == 'workflow_dispatch' && startsWith(github.ref, 'refs/tags') needs: python-ci + # python_build.yaml nests python_ci.yaml, which needs pull-requests: read even when skipped. permissions: contents: read + pull-requests: read uses: ./.github/workflows/python_build.yaml secrets: inherit diff --git a/.github/workflows/python_release_prep.yaml b/.github/workflows/python_release_prep.yaml index b0dfbacc1e..c49d78ba75 100644 --- a/.github/workflows/python_release_prep.yaml +++ b/.github/workflows/python_release_prep.yaml @@ -59,7 +59,12 @@ jobs: git checkout -b "$branch" git add -A python/pyproject.toml python/uv.lock python/CHANGELOG.md python/changelog.d git commit -m "$title" - git push origin "$branch" + # A rerun for the same version replaces the branch and updates its open pull request. + git push --force origin "$branch" + if [[ "$(gh pr view "$branch" --json state -q .state 2>/dev/null)" == "OPEN" ]]; then + echo "Updated the open pull request for ${branch}." + exit 0 + fi gh pr create \ --base "$BASE_BRANCH" \ --head "$branch" \ diff --git a/python/changelog.d/README.md b/python/changelog.d/README.md index 794c973c71..28cc9311ba 100644 --- a/python/changelog.d/README.md +++ b/python/changelog.d/README.md @@ -43,7 +43,9 @@ Run the Python Release Prep workflow. It computes the next version from the curr creates the `python/vX.Y.Z` tag and starts the publish workflow. If another change to `python/` merges while the release pull request is open, that change would -ship without its entry. The changelog check fails the release pull request in that case. Close it -and run Python Release Prep again. If the release pull request merges anyway, Python Release Tag +ship without its entry. The changelog check fails the release pull request in that case. Run +Python Release Prep again. For the same version, it replaces the release branch and updates the +open pull request. If the new changes raise the version, it opens a new pull request, so close the +old one. If the release pull request merges anyway, Python Release Tag refuses to tag. Move the leftover entries into the new version's section of `CHANGELOG.md`, delete their fragments, and merge that change to release. From 9c825b762cfd98d7a977dd8fb3a5ccb225cc9042 Mon Sep 17 00:00:00 2001 From: Alex Luck Date: Tue, 13 Oct 2026 16:22:09 -0700 Subject: [PATCH 9/9] Python(chore): retry publish for an existing unpublished tag --- .github/workflows/python_release_tag.yaml | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/python_release_tag.yaml b/.github/workflows/python_release_tag.yaml index 8004c14927..0ac8119aa8 100644 --- a/.github/workflows/python_release_tag.yaml +++ b/.github/workflows/python_release_tag.yaml @@ -36,14 +36,19 @@ jobs: run: | tag="python/v$(python3 python/scripts/release_version.py --current)" if [[ -n "$(git ls-remote --tags origin "refs/tags/${tag}")" ]]; then - echo "Tag ${tag} already exists. Nothing to release." - exit 0 + # A rerun after a failed publish request starts publish for the existing tag. + if [[ -n "$(gh run list --workflow python_release.yaml --branch "$tag" --limit 1 --json databaseId -q '.[].databaseId')" ]]; then + echo "Tag ${tag} already exists and its publish has started. Nothing to release." + exit 0 + fi + echo "Tag ${tag} already exists but was never published. Starting publish." + else + # See python/changelog.d/README.md. + if ! python3 python/scripts/release_version.py --leftover; then + echo "Error: the fragments above are not in the changelog for ${tag}. Not tagging." + exit 1 + fi + git tag "$tag" "$GITHUB_SHA" + git push origin "refs/tags/${tag}" fi - # See python/changelog.d/README.md. - if ! python3 python/scripts/release_version.py --leftover; then - echo "Error: the fragments above are not in the changelog for ${tag}. Not tagging." - exit 1 - fi - git tag "$tag" "$GITHUB_SHA" - git push origin "refs/tags/${tag}" gh workflow run python_release.yaml --ref "$tag"