NodeNorm is not broken today — https://nodenormalization-exp.apps.renci.org/openapi.json
(v2.5.1) still serves info.x-translator, info.x-trapi, info.contact and
info.termsOfService. This is about the trip-wire that is keeping it that way.
node_normalizer/server.py ends with
app.openapi_schema = construct_open_api_schema(app)
FastAPI stopped honouring assignment to app.openapi_schema in 0.137.0. The attribute is still
settable — /status reading app.openapi_schema["info"]["version"] keeps working — but the
/openapi.json route no longer returns it, so FastAPI serves the default document built from
FastAPI(**get_app_info()): title, description and version, and nothing else.
Bisected against a ten-line reproduction:
| fastapi |
info keys served |
| 0.136.0 |
['title', 'version', 'x-translator'] |
| 0.137.0 |
['title', 'description', 'version'] |
The only thing standing between NodeNorm and that outcome is fastapi~=0.108.0 in
requirements.txt, which caps at <0.109. Whenever that pin moves — a security bump, a Python
upgrade, a routine dependency refresh — NodeNorm silently stops advertising the infores that
SmartAPI registration keys off, and x-trapi along with it. It fails open: the service starts,
answers queries, and serves a valid-but-wrong spec.
This is exactly what happened to NameRes, whose fastapi requirement carries no version specifier
at all: NCATSTranslator/NameResolution#294.
Suggested fix
Override the method rather than the attribute, so the pin is no longer load-bearing:
app.openapi = lambda: construct_open_api_schema(app)
Verified against fastapi 0.141.1: info.x-translator survives.
Note that construct_open_api_schema() opens with if app.openapi_schema: return app.openapi_schema() — calling a dict. It is unreachable today because the function is called
exactly once at import, but under the app.openapi = ... form it becomes reachable on the second
request. Drop it, or cache properly.
Suggested test
Nothing under tests/frontend/ covers the served spec, so this class of regression ships silently.
One in the style of tests/frontend/test_status.py:
# tests/frontend/test_openapi.py
from fastapi.testclient import TestClient
from node_normalizer.server import app
def test_openapi_json_carries_translator_metadata():
"""The served spec must carry what openapi.yml declares, not FastAPI's default document."""
openapi = TestClient(app).get("/openapi.json").json()
info = openapi["info"]
assert info["x-translator"]["infores"] == "infores:sri-node-normalizer"
assert info["x-translator"]["component"] == "Utility"
assert info["x-trapi"]
assert info["termsOfService"]
assert info["contact"]
assert openapi["servers"]
It has to go through TestClient: asserting on construct_open_api_schema(app) directly would pass
throughout the bug, since the function is fine — it is the wiring that stops being read.
NodeNorm is not broken today —
https://nodenormalization-exp.apps.renci.org/openapi.json(v2.5.1) still serves
info.x-translator,info.x-trapi,info.contactandinfo.termsOfService. This is about the trip-wire that is keeping it that way.node_normalizer/server.pyends withFastAPI stopped honouring assignment to
app.openapi_schemain 0.137.0. The attribute is stillsettable —
/statusreadingapp.openapi_schema["info"]["version"]keeps working — but the/openapi.jsonroute no longer returns it, so FastAPI serves the default document built fromFastAPI(**get_app_info()):title,descriptionandversion, and nothing else.Bisected against a ten-line reproduction:
infokeys served['title', 'version', 'x-translator']['title', 'description', 'version']The only thing standing between NodeNorm and that outcome is
fastapi~=0.108.0inrequirements.txt, which caps at <0.109. Whenever that pin moves — a security bump, a Pythonupgrade, a routine dependency refresh — NodeNorm silently stops advertising the
inforesthatSmartAPI registration keys off, and
x-trapialong with it. It fails open: the service starts,answers queries, and serves a valid-but-wrong spec.
This is exactly what happened to NameRes, whose
fastapirequirement carries no version specifierat all: NCATSTranslator/NameResolution#294.
Suggested fix
Override the method rather than the attribute, so the pin is no longer load-bearing:
Verified against fastapi 0.141.1:
info.x-translatorsurvives.Note that
construct_open_api_schema()opens withif app.openapi_schema: return app.openapi_schema()— calling adict. It is unreachable today because the function is calledexactly once at import, but under the
app.openapi = ...form it becomes reachable on the secondrequest. Drop it, or cache properly.
Suggested test
Nothing under
tests/frontend/covers the served spec, so this class of regression ships silently.One in the style of
tests/frontend/test_status.py:It has to go through
TestClient: asserting onconstruct_open_api_schema(app)directly would passthroughout the bug, since the function is fine — it is the wiring that stops being read.