Skip to content

Empty OpenAPI example: {} emitted for filter query params after deepcopying FastAPI metadata #670

Description

@pkarczmarczyk94

Package versions

  • fastapi-filter: 3.0.0
  • fastapi: 0.141.1
  • pydantic: 2.13.4
  • starlette: 1.3.1
  • Python: 3.10

Summary

FilterDepends can emit example: {} in the generated OpenAPI schema for optional query parameters declared with FastAPI Query(...) metadata, e.g. Field(Query(default=None)).

Swagger UI treats example: {} as an actual example value. For optional boolean filters, this can make Swagger send or validate {} instead of leaving the parameter unset, resulting in errors like Value must be a boolean.

Minimal reproduction

from typing import Annotated

from fastapi import FastAPI, Query
from fastapi_filter import FilterDepends
from fastapi_filter.contrib.sqlalchemy import Filter
from pydantic import Field
from pydantic.json_schema import SkipJsonSchema


class User:
    is_discharged = None


class UserFilter(Filter):
    is_discharged: bool | SkipJsonSchema[None] = Field(Query(default=None))

    class Constants(Filter.Constants):
        model = User


app = FastAPI()


@app.get("/users")
def list_users(filters: Annotated[UserFilter, FilterDepends(UserFilter)]):
    return {}


schema = app.openapi()
params = schema["paths"]["/users"]["get"]["parameters"]
print(params)

Actual behavior

The generated OpenAPI parameter contains an empty example:

  {
    "name": "is_discharged",
    "in": "query",
    "required": false,
    "schema": {
      "anyOf": [
        {"type": "boolean"},
        {"type": "null"}
      ],
      "title": "Is Discharged"
    },
    "example": {}
  }

Expected behavior

No example key should be emitted when the original FastAPI Query example was unset.

Explicit examples should still be preserved:

Field(Query(default=None, example=True))

should still emit:

"example": true

Suspected cause

fastapi-filter deep-copies Pydantic field metadata in _list_to_str_fields:

field_info = deepcopy(f)

FastAPI represents an unset example with the _Unset DefaultPlaceholder sentinel. In FastAPI 0.141.1, deep-copying that sentinel produces a new DefaultPlaceholder object:

  from copy import deepcopy
  from fastapi.params import _Unset

  copied = deepcopy(_Unset)

  print(copied is _Unset)  # False
  print(copied == _Unset)  # True

FastAPI's OpenAPI generation checks the sentinel by identity, so the copied placeholder is no longer recognized as unset and gets serialized as {}.

Why this matters

This appears to be a schema/documentation bug rather than a filter parsing bug. Runtime filtering can still work, but the generated OpenAPI schema causes Swagger UI to present/send invalid examples for optional filter params.

Possible fix

When creating the generated filter model, normalize copied FastAPI Param/Query metadata so copied unset example placeholders are restored to FastAPI's real _Unset sentinel, or avoid deep-copying that specific sentinel.

The fix should preserve:

  • explicit non-empty example
  • examples
  • aliases
  • descriptions
  • defaults
  • validators
  • current list-to-string behavior

It should only remove/reset unset or empty-object examples caused by the metadata copy.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions