Skip to content

Switch to uv, update the tooling and bundled UIs, and honour the root_path - #12

Merged
fbjorn merged 15 commits into
ioxiocom:mainfrom
joakimnordling:feature/updates
Oct 16, 2026
Merged

fbjorn merged 15 commits into
ioxiocom:mainfrom
joakimnordling:feature/updates

Conversation

@joakimnordling

@joakimnordling joakimnordling commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Switches the project from Poetry to uv, brings the tooling and the bundled docs UIs up
to date, and fixes the asset URLs of an app that is served under a prefix.

The commits are each self-contained and worth reading on their own.

Packaging

  • Poetry -> uv. pyproject.toml is now PEP 621 with the uv_build backend,
    poetry.lock becomes uv.lock, and .python-version pins 3.11, the lowest supported
    version, for local development. skjold could only read poetry.lock, so uv audit
    took over the vulnerability scan in invoke test.
  • black + isort + flake8 -> ruff. setup.cfg is gone, with the pytest and mypy
    configuration moved into pyproject.toml. The reformatting was almost a no-op; the
    only code changes were Optional[str] -> str | None and List -> list.
  • The per-version Python classifiers are listed explicitly (3.11 through 3.15), as
    uv_build publishes only what pyproject.toml lists.
  • src/ layout, tests outside the package. The tests can now only import the
    installed package, never the source tree via the current directory, so they exercise
    what actually ships: a static file missing from the wheel fails the suite instead of
    passing locally. The wheel no longer contains the tests, which the Poetry build did
    ship (they are in the 0.2.0 wheel on PyPI); the sdist keeps them.

Verified that the built wheel still carries all eight static files, and that the test
suite passes against the wheel installed in an isolated environment, from outside the
repository.

Dependencies and tooling

  • The tests use httpx2. fastapi.testclient.TestClient is what needs httpx here,
    and starlette 1.7 deprecates plain httpx in its test client, which warned on every
    test run. The test client only understands httpx2 from starlette 1.2, and FastAPI
    capped starlette below that until 0.133.0, so the lowest supported FastAPI moves
    from 0.109.1 to 0.133.0
    . That is also the lowest FastAPI that allows a starlette
    with the CVE-2026-54283 fix (1.3.1); every earlier one pins starlette below 1.0.
  • Python 3.10 is no longer supported. It reached its end of life on 2026-10-01, so
    the floor is now 3.11, in requires-python, the classifiers, the CI matrix and
    .python-version. The lock no longer needs the exceptiongroup and tomli
    backports.
  • Python 3.15 is supported. It has its classifier and a job in the CI matrix.
  • uv 0.13 is required, the first release that knows Python 3.15.0:
    required-version is >=0.13, and [build-system] allows uv_build 0.13 (no
    configuration changes).
  • GitHub Actions updated to their latest releases, still SHA-pinned. The checkouts
    also stop persisting the GitHub token for the later steps of the job, which neither
    workflow needs.
  • pre-commit hooks updated: pre-commit-hooks v6.0.0, ruff-pre-commit v0.17.0,
    uv-pre-commit 0.13.0. Prettier stays on mirrors-prettier v2.7.1 deliberately:
    pre-commit autoupdate wants to move it to a prettier 4 alpha.
  • The dev dependency floors now match what the lock resolves to, rather than the Poetry
    constraints they were, most of which were a major version behind.
  • Swagger UI 5.19.0 -> 5.33.1 and ReDoc 2.4.0 -> 2.5.4, fetched with
    uv run update-fastapi-standalone-docs. FastAPI's own docs.py points at
    swagger-ui-dist@5 and redoc@2, so these are the versions it serves from the CDNs.
    Checked the files byte-for-byte against those pinned versions, and confirmed the ReDoc
    logo patch still applies: the bundle has no reference left to cdn.redoc.ly.

Fixes #11

An app mounted inside another app is served under a prefix it has no way of knowing:
mount() doesn't touch its root_path, and the prefix only arrives with each request,
in the ASGI scope. The asset URLs were built without it, so the docs of a mounted app
asked the outer app for swagger-ui.css and got a 404.

The functions this library patches don't get the request. FastAPI does build the
openapi_url it passes them out of the request's root_path and the app's own
openapi_url, though, so removing the latter leaves the prefix to build the URLs with.
Being per request, this also covers a proxy that varies the root_path.

Note that the fix suggested in the issue, self.app.root_path, does not cover the case
the issue describes: mount() never sets that attribute, so it stays empty for a mounted
app. It does cover an app given an explicit root_path, which was broken too and is
fixed by the same change here.

Five new tests: the mounted app for both UIs, an explicit root_path, a caller-supplied
favicon URL (used as-is, as it may point anywhere), and StandaloneDocs on both a main app
and an app mounted inside it, which now works as long as they agree on their settings.
The README's disclaimer says what "agree" means, which it did not spell out before:
docs_url, redoc_url and openapi_url are FastAPI settings, so the old wording could
be satisfied and still break.

Version

Bumped to 0.3.0 rather than a patch release: raising the FastAPI floor and dropping
Python 3.10 both leave users behind.

Checked

uv run invoke test is green on all counts: every pre-commit hook, mypy, 15 tests, and
uv audit. The tests and mypy also pass on Python 3.15. Both UIs were also opened in a
browser, with and without with_google_fonts, and with the app mounted under a prefix:
every request goes to localhost, and the only external reference anywhere is the Google
Fonts stylesheet that with_google_fonts=True is meant to add.

🤖 Generated with Claude Code

@joakimnordling
joakimnordling force-pushed the feature/updates branch 2 times, most recently from 5373560 to 651c220 Compare September 27, 2026 02:46
joakimnordling and others added 10 commits September 27, 2026 06:00
Packaging is now PEP 621 with the uv_build backend. The package stays in the
repository root (module-root = ""), and the built wheel contains the same files
as before, including the bundled static files.

- poetry.lock -> uv.lock, and .python-version pins the lowest supported Python
  for local development
- ruff replaces black, isort and flake8; setup.cfg is gone, with the pytest and
  mypy configuration moved into pyproject.toml
- skjold could only read poetry.lock, so `uv audit` took over the vulnerability
  scan in `invoke test`
- the workflows use astral-sh/setup-uv; pre-commit is a dev dependency now, so
  CI no longer installs it separately
- `invoke release` reads the version with `uv version` instead of a regex
- tests/conftests.py -> tests/conftest.py, which is the name pytest looks for

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Poetry build included them (they're in the 0.2.0 wheel on PyPI), and so did
the uv_build default, since they live inside the package directory. There's no
reason to install them into a user's environment. They stay in the sdist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
With a src layout the tests can only import the installed package, never the
source tree via the current directory, so they exercise what actually ships: a
missing static file in the wheel now fails the suite instead of passing locally.

The build backend needs no layout configuration any more; excluding the tests
from the wheel happens by itself, and the sdist keeps them via source-include.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
actions/checkout v4.2.2 -> v7.0.1, astral-sh/setup-uv v10.1.0 -> v10.2.0,
actions/upload-artifact v5.0.0 -> v7.0.1, actions/download-artifact v6.0.0 ->
v8.0.1 and pypa/gh-action-pypi-publish v1.13.0 -> v1.14.2.

The major bumps are mostly the move to the Node 24 runtime, which the hosted
runners have. Two behaviour changes that don't affect these workflows: checkout
now refuses to check out the PR head on pull_request_target without
allow-unsafe-pr-checkout (neither workflow uses that trigger), and
download-artifact fails on a digest mismatch instead of warning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pre-commit-hooks v5.0.0 -> v6.0.0, ruff-pre-commit v0.16.8 -> v0.16.9 (with the
ruff pin in pyproject.toml) and uv-pre-commit 0.12.16 -> 0.12.19.

Prettier stays on mirrors-prettier v2.7.1: `pre-commit autoupdate` wants to move
it to a prettier 4 alpha, which isn't worth taking.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
starlette 1.7 deprecates plain httpx in its test client, which is what
fastapi.testclient.TestClient is built on, and the tests warned about it on every
run. Nothing in the library itself uses httpx.

The test client only understands httpx2 from starlette 1.2 onwards, and FastAPI
capped starlette below that until 0.133.0, the first release without an upper
bound on it. That is also the lowest FastAPI that allows a starlette with the
CVE-2026-54283 fix (1.3.1); every earlier one pins starlette below 1.0.

Verified against fastapi 0.133.0: the tests pass with the starlette that a fresh
install resolves to, and the library itself works with starlette 0.40.0, the
lowest FastAPI still allows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The floors were the constraints Poetry had, most of them a major version behind
what the lock resolves to, so they documented versions that were never tested.
They now match what `uv sync` installs. The ruff pin stays exact, as it tracks
the ruff-pre-commit rev rather than the lock.

The upgrade moves three of pre-commit's dependencies: filelock 4.0.3 -> 4.0.4,
nodeenv 1.10.0 -> 1.11.0 and platformdirs 4.11.15 -> 4.12.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
swagger-ui-dist 5.19.0 -> 5.33.0 and redoc 2.4.0 -> 2.5.4, fetched with
`uv run update-fastapi-standalone-docs`. FastAPI's own docs.py points at
swagger-ui-dist@5 and redoc@2, so these are the versions it serves from the CDNs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An app mounted inside another app is served under a prefix it has no way of
knowing: mount() doesn't touch its root_path, and the prefix only arrives with
each request, in the ASGI scope. The asset URLs were built without it, so the docs
of a mounted app asked the outer app for swagger-ui.css and got a 404.

The functions this library patches don't get the request. FastAPI does build the
openapi_url it passes them out of the request's root_path and the app's own
openapi_url, though, so removing the latter leaves the prefix to build the URLs
with. Being per request, this also covers a proxy that varies the root_path.

An app given an explicit root_path was affected as well and is fixed by the same
change. Using StandaloneDocs on both a main app and an app mounted inside it now
works too, as long as they agree on the settings the README lists. A favicon URL
passed by the caller is still used as-is, as it may point anywhere.

Fixes ioxiocom#11

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A minor bump rather than a patch: the lowest supported FastAPI moved from 0.109.1
to 0.133.0, so this drops users of the versions in between.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The default is to persist the token for the later steps of the job to use, in a
file under $RUNNER_TEMP as of checkout v6. Neither workflow runs git operations
that need it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@joakimnordling
joakimnordling marked this pull request as ready for review September 27, 2026 03:25
joakimnordling and others added 4 commits October 4, 2026 06:33
ruff-pre-commit v0.16.9 -> v0.16.10, with the ruff pin that tracks it, and
uv-pre-commit 0.12.19 -> 0.12.23. pre-commit-hooks and all the pinned GitHub
Actions are already at their newest releases.

The lock moves fastapi 0.141.1 -> 0.142.2 and mypy 2.3.1 -> 2.4.0, with mypy's
floor following it, plus transitive updates. pydantic-core stays at 2.46.5, which
is what pydantic pins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
It reached its end of life on 2026-10-01. The floor moves to 3.11 in
requires-python, in the classifiers, in the CI matrix and in .python-version,
which pins the lowest supported version for local development.

Dropping it also lets the lock go without the exceptiongroup and tomli
backports.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A fix for the schema and operation virtualization that 5.33.0 introduced, with
path item references. Only the bundle changed; the stylesheet and the license are
the same as in 5.33.0, and ReDoc is still on 2.5.4, its newest 2.x.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Python 3.15: the trove classifier, and a 3.15 job in the test matrix. The
  tests and mypy pass on it.
- uv 0.13.0 knows Python 3.15.0. required-version is >=0.13, and
  [build-system] allows its build backend, uv_build 0.13 (no configuration
  changes). The uv-lock pre-commit hook is on 0.13.0.
- ruff-pre-commit v0.16.10 -> v0.17.0, with the ruff pin that tracks it. No new
  findings. pre-commit-hooks is already at its newest release, and prettier
  stays on mirrors-prettier v2.7.1.
- setup-uv 10.2.0 -> 10.3.0 (checksums for uv up to 0.13.0), upload-artifact
  7.0.1 -> 7.0.2 and download-artifact 8.0.1 -> 8.0.2 (retries when
  rate-limited). checkout and gh-action-pypi-publish are already the newest.
- The lock moves fastapi 0.142.2 -> 0.143.0, pydantic 2.13.5 -> 2.14.0 (with
  pydantic-core 2.50.0) and ruff, plus transitive updates. The other dev
  dependency floors already match the locked versions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fbjorn
fbjorn merged commit 9b69b49 into ioxiocom:main Oct 16, 2026
10 checks passed
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.

Include root_path when looking up assets

2 participants