Skip to content

"Circular reference detected" when requesting the swagger.json file #2095

@RobertBlackhart

Description

@RobertBlackhart

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)).raw

And 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"}}}}}}}}
$

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions