Query parameters

You can define Query parameters the same way you define Headers, Path and Cookies parameters.

Note

Parsed Query parameter must be named parsed_query.

This is how you can parse Query parameters:

Run result

$ curl 'http://127.0.0.1:8000/api/users/?query=abc&count=10' -X GET
{"query":"abc","count":10}

$ curl 'http://127.0.0.1:8000/api/users/?query=abc' -D - -X GET
HTTP/1.1 400 Bad Request
date: Thu, 07 May 2026 12:57:41 GMT
server: uvicorn
Content-Type: application/json
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
Content-Length: 103
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin

{"detail":[{"msg":"Object missing required field `count` - at `$.parsed_query`","type":"value_error"}]}

OpenAPI Schema

Preview openapi.json
{
  "components": {
    "schemas": {
      "ErrorDetail": {
        "description": "Base schema for error details description.",
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                }
              ]
            },
            "type": "array"
          },
          "msg": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "msg"
        ],
        "title": "ErrorDetail",
        "type": "object"
      },
      "ErrorModel": {
        "description": "Default error response schema.\n\nCan be customized.\nSee :ref:`customizing-error-messages` for more details.",
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            },
            "type": "array"
          }
        },
        "required": [
          "detail"
        ],
        "title": "ErrorModel",
        "type": "object"
      },
      "_QueryModel": {
        "properties": {
          "count": {
            "type": "integer"
          },
          "query": {
            "type": "string"
          }
        },
        "required": [
          "query",
          "count"
        ],
        "title": "_QueryModel",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/apicontroller/": {
      "get": {
        "deprecated": false,
        "operationId": "getApicontrollerApiApicontroller",
        "parameters": [
          {
            "deprecated": false,
            "in": "query",
            "name": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "deprecated": false,
            "in": "query",
            "name": "count",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/_QueryModel"
                }
              }
            },
            "description": "OK"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when request components cannot be parsed"
          },
          "406": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when provided `Accept` header cannot be satisfied"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when returned response does not match the response schema"
          }
        }
      }
    }
  }
}

What happens in this example?

  1. We define a Query model using msgspec.Struct or pydantic.BaseModel. Other types are also supported: typing.TypedDict, dataclasses.dataclass(), etc

  2. Next, we use Query component, provide the model as a type parameter, and subclass it when defining Controller type

  3. Then we use self.parsed_query that will have the correct model type

Customizing OpenAPI metadata for Query

See Customizing parameter.

Forcing query params to be a list

Internally query parameters are represented as django.utils.datastructures.MultiValueDict in Django. It supports setting and getting several values for a single key.

Users can customize how they want their values: as single values or as lists of values. To do so, use __dmr_force_list__ optional attribute. Set it to frozenset of field aliases that need to be lists. All other values will be regular single values:

Run result

$ curl 'http://127.0.0.1:8000/api/users/?query=abc&query=xyz&regular=1&regular=2' -X GET
{"query":["abc","xyz"],"regular":2}

OpenAPI Schema

Preview openapi.json
{
  "components": {
    "schemas": {
      "ErrorDetail": {
        "description": "Base schema for error details description.",
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                }
              ]
            },
            "type": "array"
          },
          "msg": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "msg"
        ],
        "title": "ErrorDetail",
        "type": "object"
      },
      "ErrorModel": {
        "description": "Default error response schema.\n\nCan be customized.\nSee :ref:`customizing-error-messages` for more details.",
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            },
            "type": "array"
          }
        },
        "required": [
          "detail"
        ],
        "title": "ErrorModel",
        "type": "object"
      },
      "_QueryModel": {
        "properties": {
          "query": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "regular": {
            "type": "integer"
          }
        },
        "required": [
          "query",
          "regular"
        ],
        "title": "_QueryModel",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/apicontroller/": {
      "get": {
        "deprecated": false,
        "operationId": "getApicontrollerApiApicontroller",
        "parameters": [
          {
            "deprecated": false,
            "in": "query",
            "name": "query",
            "required": true,
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "deprecated": false,
            "in": "query",
            "name": "regular",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/_QueryModel"
                }
              }
            },
            "description": "OK"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when request components cannot be parsed"
          },
          "406": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when provided `Accept` header cannot be satisfied"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when returned response does not match the response schema"
          }
        }
      }
    }
  }
}

We don’t infer __dmr_force_list__ value in any way, it is up to users to set.

Casting nulls

Queries in Django cannot be None by default. So, when some tools send 'null' as a way to represent None, we need to handle that.

To do so, set the field aliases that should do that into __dmr_cast_null__:

Run result

$ curl 'http://127.0.0.1:8000/api/users/?query=abc&regular=null' -X GET
{"query":"abc","regular":"null"}

$ curl 'http://127.0.0.1:8000/api/users/?query=null&regular=null' -X GET
{"query":null,"regular":"null"}

OpenAPI Schema

Preview openapi.json
{
  "components": {
    "schemas": {
      "ErrorDetail": {
        "description": "Base schema for error details description.",
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                }
              ]
            },
            "type": "array"
          },
          "msg": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "msg"
        ],
        "title": "ErrorDetail",
        "type": "object"
      },
      "ErrorModel": {
        "description": "Default error response schema.\n\nCan be customized.\nSee :ref:`customizing-error-messages` for more details.",
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            },
            "type": "array"
          }
        },
        "required": [
          "detail"
        ],
        "title": "ErrorModel",
        "type": "object"
      },
      "_QueryModel": {
        "properties": {
          "query": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "regular": {
            "type": "string"
          }
        },
        "required": [
          "query",
          "regular"
        ],
        "title": "_QueryModel",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/apicontroller/": {
      "get": {
        "deprecated": false,
        "operationId": "getApicontrollerApiApicontroller",
        "parameters": [
          {
            "deprecated": false,
            "in": "query",
            "name": "query",
            "required": true,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ]
            }
          },
          {
            "deprecated": false,
            "in": "query",
            "name": "regular",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/_QueryModel"
                }
              }
            },
            "description": "OK"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when request components cannot be parsed"
          },
          "406": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when provided `Accept` header cannot be satisfied"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when returned response does not match the response schema"
          }
        }
      }
    }
  }
}

We don’t infer __dmr_cast_null__ value in any way, it is up to users to set.

API Reference

dmr.components.Query

Annotated alias for parsing query parameters.

alias of Annotated[_QueryT, <dmr.components.QueryComponent object at 0x70cd6431a880>]

class dmr.components.QueryComponent[source]

Bases: ComponentParser

Parses query params of the request.

For example:

>>> import pydantic
>>> from dmr import Query, Controller
>>> from dmr.plugins.pydantic import PydanticSerializer

>>> class ProductQuery(pydantic.BaseModel):
...     category: str
...     reversed: bool

>>> class ProductListController(Controller[PydanticSerializer]):
...     def get(self, parsed_query: Query[ProductQuery]) -> str:
...         return parsed_query.category

Will parse a request like ?category=cars&reversed=true into ProductQuery model.

Parameter for Query component must be named parsed_query.

context_name: ClassVar[str] = 'parsed_query'

All subtypes must provide a unique name that will be used to parse context.

We use a single context for all parsing, this component will live under a dict field with this name.

get_schema(model: Any, model_meta: tuple[Any, ...], metadata: EndpointMetadata, serializer: type[BaseSerializer], context: OpenAPIContext) list[Parameter | Reference] | RequestBody[source]

Generate OpenAPI spec for component.

provide_context_data(endpoint: Endpoint, controller: Controller[BaseSerializer], *, field_model: Any) dict[str, Any][source]

Return unstructured raw values for serializer.from_python().

It must return the same number of elements that has type vars. Basically, each type var is a model. Each element in a tuple is the corresponding data for that model.

When this method returns not a tuple and there’s only one type variable, it also works.