Skip to content

Stop relying on app.openapi_schema assignment, which FastAPI 0.137.0 ignores #408

Description

@gaurav

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Backlog

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions