Skip to content

Python 3.11-3.15 foundation: refreshed dependencies, modern toolchain, CI and trusted publishing (0.7.0) - #53

Merged
jaykv merged 8 commits into
mainfrom
chore/python-3.11-3.15
Oct 12, 2026
Merged

jaykv merged 8 commits into
mainfrom
chore/python-3.11-3.15

Conversation

@jaykv

@jaykv jaykv commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

Stack 2 of 6 (more to follow). Base: soul. Merge in order; each PR shows only its own changes.

This branch is the foundation commits plus a merge of soul, so the stack stays linear.

Summary

Brings cliffy up to date after ~20 months without changes. It now supports Python 3.11-3.15, and lint, mypy --strict and all 188 tests pass on each of those versions against the refreshed lockfile. Every other modernization MR is meant to stack on this branch.

What changed

Packaging (pyproject.toml, uv.lock)

  • requires-python >=3.11, classifiers for 3.11-3.15, keywords, license-files, extra project URLs, version 0.7.0.
  • Dependency floors raised to releases that are current in Oct 2026: click>=8.2.1, deepmerge>=2.0, jinja2>=3.1.6, packaging>=24.2, pybash>=0.3.5, pydantic>=2.12.0, pyyaml>=6.0.2, shiv>=1.0.8, typer>=0.24.0, watchdog>=6.0.0, rich-click>=1.9.0, pydantic-ai>=2.0.0. The suite also passes on 3.11 with --resolution lowest-direct.
  • typer-slim is replaced by typer: typer-slim 0.24 is an empty shim that depends on typer.
  • The sdist is limited to the package, tests and examples (2.0 MB -> 66 kB).
  • uv.lock is regenerated with uv 0.13.
  • Project URLs stay on jaykv/cliffy and jaykv.github.io/cliffy. github.com/cliffy-sh/cliffy 301-redirects to jaykv/cliffy, and cliffy-sh.github.io/cliffy returns 404.

Compatibility fixes

  • cli dev --run-cli: removed type=str from the flag. With Click >= 8.2 the handler received the truthy string 'False', so the CLI re-ran on every reload even without --run-cli.
  • cli build: prints result.output on failure, because Click >= 8.2 no longer mixes stderr into Result.stdout.
  • rich_click.rich_group import (deprecated) replaced by from rich_click import RichGroup.
  • Thread.setDaemon() replaced by daemon=True.
  • pydantic-ai 2.x: result.data -> result.output; result.usage().request_tokens/response_tokens -> result.usage.input_tokens/output_tokens.
  • ManifestOrCLI.convert no longer uses click's private _is_file_like, whose typing differs between click releases and made mypy --strict depend on the installed click.
  • Tests: CliRunner.isolated_filesystem() (deprecated in Click 8.5) replaced by a tmp_path-based isolated_fs fixture; Result.stdout -> Result.output in one assertion; test_command imported under an alias so pytest stops trying to collect it.

Modernization (no behavior change intended)

  • ruff: target-version = "py311", rules E, W, F, I, UP, B, C4, SIM, PIE, RUF; generated code (cliffy/clis, examples/generated) is excluded.
  • X | Y unions, builtin generics, collections.abc, typing.Self (no more typing_extensions import), sorted imports.
  • Manifest file handles are now closed in cli update and Transformer.load_manifest.
  • mypy --strict uses types-pyyaml instead of ignoring the yaml module.
  • pytest turns DeprecationWarning and PendingDeprecationWarning into errors. Three narrow ignores cover third-party code: pybash's positional re.sub count, shiv's utcfromtimestamp, and pydantic-ai's asyncio.get_event_loop() on 3.12/3.13.

CI (.github/workflows)

  • python-app.yaml: matrix 3.11-3.15 on actions/checkout@v7, astral-sh/setup-uv@v10.3.0 (setup-uv publishes no floating major tag) and codecov/codecov-action@v7.
  • It now runs on pull_request and on changes to pyproject.toml, uv.lock, Makefile and the workflow itself. Pushes only build main. It installs with uv sync --locked, so a stale lockfile fails CI.
  • Coverage upload moves from the 3.13 job to the 3.15 job.
  • The extras: [true, false] matrix axis is removed. The old default dependency groups installed the extras in both variants, so it never tested a no-extras install.
  • python-publish.yaml (twine + PYPI_API_TOKEN) is replaced by publish.yaml: uv build, a tag == version check, a wheel smoke test, then uv publish --trusted-publishing always with id-token: write in a pypi environment.
  • docs.yaml: installs from the lockfile with uv, and also rebuilds on mkdocs.yaml changes.

Repo hygiene

  • Makefile: everything goes through uv (no bare pip, python or cli), all targets are declared phony, and clean no longer walks into .venv.
  • .python-version added (3.13) and no longer gitignored.
  • .gitignore now covers cliffy/clis/, cliffy/metadata/, the test build directories and .ruff_cache/.
  • examples/cliffy_schema.json regenerated. The committed file was already stale on main; the models on this branch produce the same schema as main's models.
  • examples/generated/* not regenerated: the only difference is import order, which varies from run to run.
  • README: added a supported-Python-versions badge.

Breaking changes

  1. Python 3.9 and 3.10 are no longer supported.
  2. typer-slim -> typer. rich and shellingham are now always installed.
  3. cliffy[dev] extra removed. It duplicated the dev dependency group; use uv sync. The extras dependency group is removed too; use uv sync --all-extras.
  4. cliffy[ai] now needs pydantic-ai >= 2.0. Model names must carry a provider prefix, so the default changed from gemini-2.0-flash to google:gemini-2.0-flash. Scripts passing -m gemini-... must add the prefix.
  5. cli dev --run-cli passes a real bool to Reloader.watch instead of the strings 'True' / 'False'.
  6. Publishing no longer uses PYPI_API_TOKEN.

Maintainer action required

  • On PyPI (project cliffy -> Publishing), add a GitHub trusted publisher: owner jaykv, repository cliffy, workflow publish.yaml, environment pypi. Releases will fail until this exists. The PYPI_API_TOKEN secret can then be deleted.
  • Release tags must equal the package version (0.7.0 or v0.7.0); the workflow checks this.

How verified

Run locally on macOS arm64 with uv 0.13.0, each Python in its own environment synced with uv sync --locked --all-extras:

Python ruff format --check ruff check mypy cliffy --strict mypy tests pytest
3.11.11 pass pass pass pass 188 passed
3.12.9 pass pass pass pass 188 passed
3.13.5 pass pass pass pass 188 passed
3.14.8 pass pass pass pass 188 passed
3.15.0 pass pass pass pass 188 passed
  • Baseline on origin/main with the old lockfile on 3.11: 188 passed.
  • Lower bounds on 3.11 (--resolution lowest-direct): ruff, mypy --strict and 188 tests pass.
  • uv build and the wheel smoke test (cli --version -> cli, version 0.7.0) pass; mkdocs build with only the docs group passes; uv lock --check passes.
  • The GitHub workflows were only parsed as YAML and their action inputs checked against each action's action.yml. They have not run on GitHub.

🤖 Generated with Claude Code

Summary by Sourcery

Modernize cliffy for Python 3.11–3.15 and establish a current uv-based toolchain, CI pipeline, and trusted release process.

New Features:

  • Support Python 3.11 through 3.15 with the 0.7.0 release and refreshed dependency requirements.
  • Publish releases to PyPI through GitHub Actions trusted publishing with release-tag validation and wheel smoke testing.

Bug Fixes:

  • Update CLI and AI integrations for current Click and pydantic-ai behavior, including correct reload flags, command output, and token usage reporting.
  • Close manifest resources and replace deprecated APIs across runtime and test code for compatibility with newer dependencies.

Enhancements:

  • Modernize typing, linting, strict mypy checks, warning handling, and packaging metadata.
  • Reduce source distribution contents and standardize project commands around uv.

Build:

  • Regenerate the uv lockfile and refresh runtime, development, and documentation dependencies.

CI:

  • Run locked dependency installation, linting, and tests across Python 3.11–3.15 on pull requests and main branch changes.
  • Refresh GitHub Actions versions, caching, concurrency, coverage reporting, and documentation deployment triggers.

Deployment:

  • Replace token-based PyPI publishing with OIDC trusted publishing in the pypi environment.

Documentation:

  • Add a supported Python versions badge to the README.

Tests:

  • Replace deprecated Click isolated filesystems with a temporary-path fixture and update tests for current Click APIs.

Chores:

  • Remove obsolete dependency groups and ignored/generated artifacts while tightening Makefile and repository hygiene.

jaykv and others added 8 commits October 12, 2026 01:00
- requires-python >=3.11, trove classifiers for 3.11-3.15, keywords, extra project URLs
- bump version to 0.7.0
- raise dependency floors to releases that are current in Oct 2026
- typer-slim -> typer (typer-slim is now an empty shim that depends on typer)
- drop the shellingham pin from the rich extra (typer depends on it)
- pydantic-ai floor moves to 2.x (result.output / usage property API)
- remove the dev extra and the extras dependency group, which duplicated the
  dev dependency group and the rich/ai extras
- drop unused mkdocs-material-extensions, add types-pyyaml for mypy
- regenerate uv.lock

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ntic-ai

- cli dev --run-cli: drop type=str from the flag. Click >= 8.2 converts the
  flag default through the type, so the handler received the truthy string
  'False' and re-ran the CLI on every reload even without --run-cli.
- cli build: print result.output on failure; Click >= 8.2 no longer mixes
  stderr into Result.stdout.
- rich: import RichGroup from rich_click instead of the deprecated
  rich_click.rich_group module.
- reloader: Thread.setDaemon() is deprecated, pass daemon=True.
- ai: pydantic-ai 2.x renamed result.data -> result.output, made usage a
  property with input_tokens/output_tokens, and requires provider-prefixed
  model names (gemini-2.0-flash -> google:gemini-2.0-flash).
- tests: replace CliRunner.isolated_filesystem() (deprecated in Click 8.5,
  removed in 9.0) with a tmp_path based fixture, and stop importing the
  test command under a name pytest tries to collect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ruff: target py311, select E/W/F/I/UP/B/C4/SIM/PIE/RUF, exclude generated
  code (cliffy/clis, examples/generated)
- apply pyupgrade fixes: X | Y unions, builtin generics, collections.abc,
  typing.Self instead of typing_extensions / bound TypeVar, redundant open modes
- sort imports, __all__ and __slots__
- close manifest file handles in 'cli update' and Transformer.load_manifest
- chain the ImportError raised by import_module_from_path
- drop a cast made redundant by click's TypeIs-typed _is_file_like
- pytest: turn Deprecation/PendingDeprecation warnings into errors, with
  narrow ignores for the two known third-party offenders (pybash, shiv)
- mypy: use types-pyyaml instead of ignoring the yaml module

No behavior change intended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…publishing

- test matrix 3.11-3.15 on actions/checkout v7, setup-uv v10.3.0, codecov v7
- trigger on pull_request and on pyproject.toml / uv.lock / Makefile / workflow
  changes; pushes only build main (PRs cover branches), stale runs are cancelled
- install with uv sync --locked so a stale lockfile fails CI
- drop the extras matrix axis: the old default dependency groups installed the
  extras in both variants, so it never tested a no-extras install
- replace the twine/API-token publish workflow with uv build + uv publish using
  PyPI trusted publishing (OIDC); verifies tag == version and smoke-tests the wheel
- docs workflow: install with uv from the lockfile, rebuild on mkdocs.yaml changes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h JSON schema

- Makefile: no bare pip/python/cli calls, everything goes through uv; declare
  all phony targets; clean no longer walks into .venv; drop the no-op shell target
- add .python-version (3.13, the newest interpreter every dependency ships
  wheels for on all platforms) and stop ignoring it
- gitignore loaded CLIs, their metadata, test build outputs and ruff cache
- limit the sdist to the package, tests and examples (2.0 MB -> 66 kB)
- regenerate examples/cliffy_schema.json, which had drifted from the models
- README: add supported Python versions badge

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent loop warning

- click>=8.2.1 and pydantic>=2.12 are what typer / pydantic-ai 2.0 already
  require; the suite passes on 3.11 with --resolution lowest-direct
- mypy>=2.4 (1.18 segfaults on this codebase under Python 3.11)
- pytest: ignore the asyncio.get_event_loop() DeprecationWarning raised from
  inside pydantic-ai's run_sync() on Python 3.12/3.13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
click types the helper as TypeGuard in 8.2 and TypeIs in later releases, so
mypy --strict passed or failed depending on the installed click. Branch on
str / PathLike explicitly instead; runtime behavior is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

This foundation release updates cliffy to Python 3.11–3.15 with refreshed locked dependencies and a modern strict-lint/type-check toolchain, applies compatibility fixes for current libraries, streamlines uv-based development and packaging, expands deterministic CI coverage, and introduces tag-validated PyPI trusted publishing plus updated docs deployment.

Sequence diagram for trusted PyPI publishing

sequenceDiagram
    participant Maintainer
    participant GitHub as GitHub Actions
    participant Build as Build job
    participant Publish as Publish job
    participant PyPI

    Maintainer->>GitHub: Publish release
    GitHub->>Build: Checkout and uv build
    Build->>Build: uv version --short
    Build->>Build: uv run cli --version
    Build-->>Publish: dist artifacts
    Publish->>PyPI: Request OIDC trusted publishing
    PyPI-->>Publish: Grant publisher identity
    Publish->>PyPI: uv publish --trusted-publishing always
Loading

Flow diagram for locked multi-version CI

flowchart TD
    Trigger[PR or main changes] --> Matrix[Python 3.11-3.15 matrix]
    Matrix --> Sync[uv sync --locked --all-extras]
    Sync --> Quality[make lint]
    Quality --> Tests[pytest]
    Tests --> Coverage{main on Python 3.15?}
    Coverage -->|yes| Upload[Upload coverage]
    Coverage -->|no| Done[Job complete]
    Upload --> Done
Loading

File-Level Changes

Change Details Files
Raise the supported Python baseline and refresh packaging metadata and dependencies for the 0.7.0 release.
  • Require Python 3.11+ and advertise support through 3.15.
  • Raise runtime and development dependency floors, replace typer-slim with typer, and regenerate the lockfile.
  • Reduce sdist contents and add project metadata, URLs, classifiers, and a Python-version badge.
  • Remove the dev/extras dependency extras in favor of uv dependency groups.
pyproject.toml
uv.lock
README.md
.python-version
.gitignore
examples/cliffy_schema.json
Adapt the application and tests to current Click, pydantic-ai, rich-click, watchdog, and Python typing APIs while enforcing stricter quality checks.
  • Fix Click boolean flag and result-output handling, update pydantic-ai result and token usage APIs, and update rich-click imports.
  • Replace deprecated APIs, private Click typing dependencies, and unclosed manifest file handles.
  • Modernize annotations and imports for Python 3.11, configure Ruff and strict mypy, and promote deprecation warnings to test failures with targeted third-party exclusions.
  • Replace deprecated Click isolated filesystems with a tmp_path fixture and update affected tests and collection behavior.
cliffy/ai.py
cliffy/blueprint.py
cliffy/builder.py
cliffy/cli.py
cliffy/commander.py
cliffy/commanders/click.py
cliffy/commanders/typer.py
cliffy/doc.py
cliffy/helper.py
cliffy/homer.py
cliffy/manifest.py
cliffy/parser.py
cliffy/reloader.py
cliffy/rich.py
cliffy/tester.py
cliffy/transformer.py
tests/conftest.py
tests/test_ai.py
tests/test_cli.py
tests/test_click_commander.py
tests/test_commander.py
tests/test_doc.py
tests/test_examples.py
tests/test_helper.py
tests/test_homer.py
tests/test_manifest.py
tests/test_reloader.py
tests/test_rich.py
tests/test_transformer.py
Rework local development commands around uv and tighten generated-artifact cleanup.
  • Route installation, formatting, testing, documentation, generation, packaging, and publishing through uv.
  • Use explicit generation dependencies and prevent implicit environment synchronization during generation.
  • Declare all Makefile targets phony and avoid traversing the virtual environment during cleanup.
Makefile
Expand CI across Python 3.11–3.15 and make dependency and workflow changes deterministic.
  • Run locked, all-extras CI on pull requests and main across the five supported Python versions.
  • Remove the ineffective extras matrix, add concurrency controls, and move coverage reporting to Python 3.15.
  • Upgrade checkout, uv setup, and Codecov actions and include lockfile/tooling changes in CI triggers.
.github/workflows/python-app.yaml
Replace token-based PyPI publishing with validated, trusted OIDC publishing and modernize documentation deployment.
  • Build and smoke-test distributions, validate release tags against the package version, and publish with uv trusted publishing in the pypi environment.
  • Update docs deployment to use uv and locked docs dependencies, add cache/concurrency settings, and rebuild on MkDocs configuration changes.
  • Remove the legacy twine/API-token publishing workflow.
.github/workflows/publish.yaml
.github/workflows/python-publish.yaml
.github/workflows/docs.yaml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d75b492e-4f48-4b26-a05c-414af234c857

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Fixed security issues:

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path=".github/workflows/publish.yaml" line_range="57" />
<code_context>
+      - name: Install uv
+        uses: astral-sh/setup-uv@v10.3.0
+      - name: Publish
+        run: uv publish --trusted-publishing always dist/*
</code_context>
<issue_to_address>
**Partial uploads block workflow retries**

When a publish run fails after PyPI has accepted at least one distribution, if PyPI accepts one distribution from `dist/*` and a later upload fails, the release is left with only part of the build. Retrying the workflow attempts to upload the already accepted file again, which PyPI rejects as a duplicate, so the retry does not complete the release.

Make publishing resumable by detecting already-published files and retrying only missing distributions.
</issue_to_address>

### Comment 2
<location path="pyproject.toml" line_range="31" />
<code_context>
+    "Topic :: Software Development :: Code Generators",
+    "Topic :: Software Development :: Libraries :: Python Modules",
+    "Topic :: System :: Shells",
+    "Typing :: Typed",
+]
 dependencies = [
</code_context>
<issue_to_address>
**Installed package loses type hints**

When a consumer type-checks code against an installed `cliffy` distribution, downstream type checkers treat `cliffy` as an untyped package, so callers lose its API annotations and strict mypy can reject imports despite the package advertising typed support.

Add a `cliffy/py.typed` marker and ensure it is included in the built wheel and sdist.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 2 findings to address first, and every published GitHub release can trigger an OIDC-authenticated upload to PyPI, so an overly broad publisher trust scope could make unintended releases available to users without a failing check. Reverting the workflow stops future uploads but cannot fully undo a package version already published or downstream installs of it.

Blocking findings: .github/workflows/publish.yaml:57, pyproject.toml:31


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread .github/workflows/publish.yaml Resolved
Comment thread pyproject.toml Resolved
@jaykv
jaykv changed the base branch from soul to main October 12, 2026 21:22
@jaykv
jaykv merged commit 1d0b536 into main Oct 12, 2026
7 checks passed
@jaykv
jaykv deleted the chore/python-3.11-3.15 branch October 12, 2026 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant