-
-
Notifications
You must be signed in to change notification settings - Fork 780
Description
After upgrading to connexion 3.3.0, we found that we would get a "Circular reference detected" error when trying to perform a GET request on the swagger.json file/endpoint. Here's a minimal example of the kind of spec that leads to this issue:
import copy
from importlib import metadata
from connexion import AsyncApp
from connexion.spec import Specification
spec = {
"basePath": "/api/v1",
"swagger": "2.0",
"info": {
"version": "1.0.0",
"title": "Simple API"
},
"definitions": {
"widget": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"owners": {
"type": "array",
"items": {
"$ref": "#/definitions/owner"
}
}
}
},
"owner": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"widgets": {
"type": "array",
"items": {
"$ref": "#/definitions/widget"
}
}
}
}
},
"paths": {
"/owners": {
"get": {
"description": "Return a list of all registered owners",
"operationId": "simple_app.get_owners",
"responses": {
"200": {
"description": "A list of owners",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/owner"
}
}
}
}
}
}
}
}
def get_owners():
return [{"name": "Alice"}, {"name": "Bob"}]
print(f"Connexion version: {metadata.version('connexion')}")
if int(metadata.version("connexion").split(".")[1]) < 3:
print("Applying monkeypatch to work around https://github.com/spec-first/connexion/issues/2028")
Specification.clone = lambda self: type(self)(copy.deepcopy(self._raw_spec))
app = AsyncApp(__name__)
app.add_api(spec, validate_responses=False)Running this application under connexion 3.2.0 (with the included monkeypatch for issue #2028) results in a success. Here's the application logs:
$ python -m uvicorn simple_app:app
Connexion version: 3.2.0
Applying monkeypatch to work around https://github.com/spec-first/connexion/issues/2028
INFO: Started server process [3598565]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: 127.0.0.1:58120 - "GET /api/v1/owners HTTP/1.1" 200 OK
INFO: 127.0.0.1:50990 - "GET /api/v1/swagger.json HTTP/1.1" 200 OK
And here's the HTTP request/response logs:
$ curl -i http://localhost:8000/api/v1/owners
HTTP/1.1 200 OK
date: Mon, 27 Oct 2025 12:39:30 GMT
server: uvicorn
content-type: application/json
content-length: 37
[{"name": "Alice"}, {"name": "Bob"}]
$ curl -i http://localhost:8000/api/v1/swagger.json
HTTP/1.1 200 OK
date: Mon, 27 Oct 2025 12:39:31 GMT
server: uvicorn
content-length: 715
content-type: application/json
{"basePath": "/api/v1", "swagger": "2.0", "info": {"version": "1.0.0", "title": "Simple API"}, "definitions": {"widget": {"type": "object", "properties": {"id": {"type": "string"}, "name": {"type": "string"}, "owners": {"type": "array", "items": {"$ref": "#/definitions/owner"}}}}, "owner": {"type": "object", "properties": {"id": {"type": "string"}, "name": {"type": "string"}, "widgets": {"type": "array", "items": {"$ref": "#/definitions/widget"}}}}}, "paths": {"/owners": {"get": {"description": "Return a list of all registered owners", "operationId": "simple_app.get_owners", "responses": {"200": {"description": "A list of owners", "schema": {"type": "array", "items": {"$ref": "#/definitions/owner"}}}}}}}}
$
But upgrading to connexion 3.3.0 runs into this error for the same GET on /swagger.json:
$ python -m uvicorn simple_app:app
Connexion version: 3.3.0
INFO: Started server process [3598595]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
ValueError('Circular reference detected')
Traceback (most recent call last):
File "~/pyenv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
await app(scope, receive, sender)
File "~/pyenv/lib/python3.12/site-packages/starlette/routing.py", line 75, in app
response = await f(request)
^^^^^^^^^^^^^^^^
File "~/pyenv/lib/python3.12/site-packages/connexion/middleware/swagger_ui.py", line 110, in _get_openapi_json
content=jsonifier.dumps(self._spec_for_prefix(request)),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "~/pyenv/lib/python3.12/site-packages/connexion/jsonifier.py", line 84, in dumps
return self.json.dumps(data, **kwargs) + "\n"
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/json/__init__.py", line 238, in dumps
**kw).encode(obj)
^^^^^^^^^^^
File "/usr/local/lib/python3.12/json/encoder.py", line 200, in encode
chunks = self.iterencode(o, _one_shot=True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/json/encoder.py", line 258, in iterencode
return _iterencode(o, 0)
^^^^^^^^^^^^^^^^^
ValueError: Circular reference detected
INFO: 127.0.0.1:51944 - "GET /api/v1/swagger.json HTTP/1.1" 500 Internal Server Error
Looking through the changes between 3.2.0 and 3.3.0, I found that #2089 was the culprit, specifically the change in swagger_ui.py. I found that if I changed that back by changing my monkeypatches like this, then things were working again:
from connexion.spec import Specification
from connexion.middleware.swagger_ui import SwaggerUIAPI
print(f"Connexion version: {metadata.version('connexion')}")
if int(metadata.version("connexion").split(".")[1]) < 3:
print("Applying monkeypatch to work around https://github.com/spec-first/connexion/issues/2028")
Specification.clone = lambda self: type(self)(copy.deepcopy(self._raw_spec))
else:
print("Applying monkeypatch to work around https://github.com/spec-first/connexion/issues/new_issue")
SwaggerUIAPI._spec_for_prefix = lambda self, request: self.specification.with_base_path(self._base_path_for_prefix(request)).rawAnd here's the resulting application log:
$ python -m uvicorn simple_app:app
Connexion version: 3.3.0
Applying monkeypatch to work around https://github.com/spec-first/connexion/issues/new_issue
INFO: Started server process [3598725]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: 127.0.0.1:39704 - "GET /api/v1/swagger.json HTTP/1.1" 200 OK
and the curl response:
$ curl -i http://localhost:8000/api/v1/swagger.json
HTTP/1.1 200 OK
date: Mon, 27 Oct 2025 12:49:15 GMT
server: uvicorn
content-length: 715
content-type: application/json
{"basePath": "/api/v1", "swagger": "2.0", "info": {"version": "1.0.0", "title": "Simple API"}, "definitions": {"widget": {"type": "object", "properties": {"id": {"type": "string"}, "name": {"type": "string"}, "owners": {"type": "array", "items": {"$ref": "#/definitions/owner"}}}}, "owner": {"type": "object", "properties": {"id": {"type": "string"}, "name": {"type": "string"}, "widgets": {"type": "array", "items": {"$ref": "#/definitions/widget"}}}}}, "paths": {"/owners": {"get": {"description": "Return a list of all registered owners", "operationId": "simple_app.get_owners", "responses": {"200": {"description": "A list of owners", "schema": {"type": "array", "items": {"$ref": "#/definitions/owner"}}}}}}}}
$