Skip to content

Incremented Python versions to 3.12.4 - #252

Draft
gaurav wants to merge 1 commit into
mainfrom
increment-python-version-to-3.12.4
Draft

Incremented Python versions to 3.12.4#252
gaurav wants to merge 1 commit into
mainfrom
increment-python-version-to-3.12.4

Conversation

@gaurav

@gaurav gaurav commented Apr 7, 2026

Copy link
Copy Markdown
Collaborator

Also added testing for Python v3.12-3.14.

WIP

Also added testing for Python v3.12-3.14.
@gaurav gaurav moved this from Backlog to In progress in NameRes sprints Apr 7, 2026
@gaurav gaurav added this to the NameRes v1.8.0 milestone Sep 1, 2026
gaurav added a commit that referenced this pull request Sep 1, 2026
…ve the contact and license blocks (#300)<

`https://name-resolution-exp.apps.renci.org/openapi.json` (v1.7.0)
served an `info` block of only `title`, `description` and `version`. The
`x-translator` block SmartAPI registration keys off, `termsOfService`,
and the top-level `tags` and `servers` (including the `x-maturity` /
`x-location` values ITRB sets) were all gone, with nothing changed in
our source. This restores them, adds the `contact` and `license` blocks
that `openapi.yml` has declared all along without ever serving, and puts
the served document under test.

Fixes #294.

## Why it broke

Since **FastAPI 0.137.0**, `FastAPI.openapi()` invalidates its own
cache:

```python
routes_version = self.router._get_routes_version()
if not self.openapi_schema or self._openapi_routes_version != routes_version:
    self.openapi_schema = get_openapi(...)   # overwrites ours
```

A schema assigned straight to `app.openapi_schema` — as `api/server.py`
did — never carries a matching `_openapi_routes_version` stamp, so the
*first* request to `/openapi.json` regenerates the default document and
overwrites the custom one. `requirements.txt` left `fastapi` unpinned,
so the image built for v1.7.0 picked this up silently. It fails open:
the service starts, answers queries, and serves a valid-but-wrong spec.
The attribute is no longer a supported extension point at any version;
only the method is.

## What's here

* `api/server.py` overrides `app.openapi` with a `custom_openapi()` that
caches into `app.openapi_schema` on first use.
* `api/apidocs.py` drops `if app.openapi_schema: return
app.openapi_schema()` — it called a `dict`, and the override would have
made it reachable on the second request. Caching now lives in the
wrapper, which keeps the builder pure and callable from tests.
* `fastapi` is pinned to `~=0.141.1`, so an unrelated rebuild cannot
move the stack again.
* `openapi.yml` carried loose `email` / `name` / `x-id` / `x-role` keys
in `info` — a `contact` block that was never nested — so **`contact` has
never been served**, not even by the working v1.5.2 deployment. Now
nested, matching what [NodeNorm
serves](https://nodenormalization-sri.renci.org/openapi.json).
* `construct_open_api_schema()` copied every declared `info` key except
`license`, so the MIT block never reached the spec either. Now copied.
* `openapi.yml` is parsed once behind an `lru_cache` instead of once per
caller — `get_app_info()` was re-reading the file on every `/status`
request. Callers get a `deepcopy`, because `construct_open_api_schema()`
rewrites the `servers` block in place from the environment and would
otherwise edit a document already served to somebody.
* `CLAUDE.md` records both traps: install the custom document by
overriding the method, and remember that a key declared in `openapi.yml`
is only served if the builder's allowlist copies it.

## Testing

`tests/test_openapi.py` (new, no Solr required) goes through
`TestClient` deliberately: asserting on `construct_open_api_schema(app)`
directly passes throughout the bug, because the builder kept returning
the right document while FastAPI served its own. The
environment-override tests call the builder instead, since the served
schema is cached after the first request and cannot observe an
environment changed later.

* 53/53 pass under both fastapi 0.141.1 and the older 0.115.2, so the
fix does not depend on the pin landing.
* Reverting only the `server.py` change makes
`test_openapi_json_carries_translator_metadata` fail with `KeyError:
'x-translator'` — the test catches the regression that actually shipped.
* Reverting only the `deepcopy` makes
`test_building_the_schema_twice_leaves_the_first_alone` fail.
* With `OTEL_ENABLED=true`, the FastAPI and httpx instrumentation still
works under the starlette 1.6.0 that fastapi 0.141.1 pulls in — that was
the one deployment risk in the pin.

`tests/nameres/test_nameres_api.py::test_openapi_json` in
[babel-validation](https://github.com/TranslatorSRI/babel-validation)
should go green against `exp` once this deploys.

## What it deliberately does not do

* **No Python upgrade.** `fastapi~=0.141.1` resolves cleanly on Python
3.11 alongside the existing opentelemetry pins, so this is independent
of #252 and can deploy without it.
* **No wider pinning.** The rest of `requirements.txt` is still unpinned
and can shift the same way; that needs a decision on approach rather
than a quick edit, so it is #303.
* **No change to `x-id` / `x-role`.** They are not OpenAPI 3.0.2 Contact
fields, but NodeNorm serves them the same way, so this matches the
existing convention rather than guessing at a better one — #301 tracks
confirming it against the SmartAPI validator.

## Before merging

Nothing blocking. The remaining loose ends are all filed:

* #301 — confirm `x-id` and `x-role` belong inside `info.contact`
against the SmartAPI validator.
* #302 — starlette 1.6 deprecates `httpx` in `TestClient`, which every
test uses.
* #303 — pin the rest of `requirements.txt`.


[NodeNormalization](https://github.com/NCATSTranslator/NodeNormalization)
has the identical `app.openapi_schema = ...` line but pins
`fastapi~=0.108.0`, so it is not affected yet; it will break the same
way whenever that pin moves past 0.137.0. Filed separately there.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

2 participants