OpenAPI

We support OpenAPI versions from 3.0 all the way up including 3.2.

By default, we use OpenAPI 3.1, since tooling such as Swagger, Scalar, and Redoc does not yet fully support the latest specification. You can track the current progress here.

Setting up OpenAPI views

We support:

Important

We always recommend installing 'django-modern-rest[openapi]' extra when working with OpenAPI.

Here’s how it works:

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"
      },
      "UserCreateModel": {
        "properties": {
          "email": {
            "type": "string"
          }
        },
        "required": [
          "email"
        ],
        "title": "UserCreateModel",
        "type": "object"
      },
      "UserModel": {
        "properties": {
          "email": {
            "type": "string"
          },
          "uid": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "email",
          "uid"
        ],
        "title": "UserModel",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/user/": {
      "post": {
        "deprecated": false,
        "operationId": "postUsercontrollerApiUser",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UserCreateModel"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserModel"
                }
              }
            },
            "description": "Created"
          },
          "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"
          }
        }
      }
    }
  }
}

And then visit https://localhost:8000/docs/swagger/ (or any other renderer) for the interactive docs.

Swagger view

What happens in the example above?

  1. We create / take an existing API dmr.routing.Router instance and create an OpenAPI schema from it using build_schema()

  2. Next, we define regular Django views that will serve you the API renderers

  3. You can modify these views to require auth / role / permissions / etc as all other regular Django views

Important

Make sure that 'dmr' is listed in the INSTALLED_APPS and that static files are enabled, so we can serve you the required static files.

Note

By default Swagger, Redoc, and Scalar use bundled static assets that are shipped with django-modern-rest and served by Django. To switch any renderer to a CDN, configure dmr.settings.Settings.openapi_static_cdn. Only renderers listed in that mapping will use CDN; all others keep using local static files. Exact bundled versions and license texts are documented in licenses/.

You can also modify the exact versions that we use for each tool this way.

Example:

settings.py
>>> from dmr.settings import Settings

>>> DMR_SETTINGS = {
...     Settings.openapi_static_cdn: {
...         # or `@5.32.1`, or whatever other version:
...         'swagger': 'https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.32.0',
...     },
... }

Customizing OpenAPI config

We support customizing dmr.openapi.OpenAPIConfig that will be used for the final schema in two ways:

  1. By defining dmr.settings.Settings.openapi_config setting inside DMR_SETTINGS in your settings.py

  2. By passing OpenAPIConfig instance into build_schema()

For example, this is how you can change some OpenAPI metadata, including the spec version:

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"
      },
      "UserCreateModel": {
        "properties": {
          "email": {
            "type": "string"
          }
        },
        "required": [
          "email"
        ],
        "title": "UserCreateModel",
        "type": "object"
      },
      "UserModel": {
        "properties": {
          "email": {
            "type": "string"
          },
          "uid": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "email",
          "uid"
        ],
        "title": "UserModel",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "My awesome API",
    "version": "1.0.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/user/": {
      "post": {
        "deprecated": false,
        "operationId": "postUsercontrollerApiUser",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UserCreateModel"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserModel"
                }
              }
            },
            "description": "Created"
          },
          "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"
          }
        }
      }
    }
  },
  "servers": [
    {
      "url": "https://prod.example.com"
    },
    {
      "url": "https://dev.example.com"
    }
  ]
}

Customizing OpenAPI generation

Customizing schema

We delegate all schema generation to the model’s library directly. To do so, we use BaseSchemaGenerator subclasses for different serializers.

To customize a schema, use the native methods.

Docs: https://jcristharif.com/msgspec/jsonschema.html

Note

By default docstring or __doc__ from the model is used as a description.

Customizing path items

Controller allows customizing some metadata for PathItem:

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"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/usercontroller/": {
      "description": "Create new users",
      "post": {
        "deprecated": false,
        "operationId": "postUsercontrollerApiUsercontroller",
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "Created"
          },
          "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"
          }
        }
      },
      "servers": [
        {
          "url": "https://example.com"
        },
        {
          "url": "https://dev.example.com"
        }
      ]
    }
  }
}

Note

By default docstring or __doc__ from the controller is used to generate summary and description for the PathItem.

Customizing operation

@modify and @validate can be used to customize the resulting Operation metadata.

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"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/usercontroller/": {
      "post": {
        "deprecated": false,
        "operationId": "postUsercontrollerApiUsercontroller",
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "Created"
          },
          "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"
          }
        },
        "servers": [
          {
            "url": "https://example.com"
          }
        ]
      },
      "put": {
        "deprecated": false,
        "description": "PUT operation description",
        "operationId": "putUsercontrollerApiUsercontroller",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "OK"
          },
          "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"
          }
        },
        "tags": [
          "Public"
        ]
      }
    }
  }
}

Note

By default docstring or __doc__ from endpoint’s function definition is used to generate summary and description for the Operation.

Customizing parameter

There are different styles and other features that Parameter supports in OpenAPI Parameters.

For example, if you want to change how Query parameter is documented with the help of dmr.openapi.objects.ParameterMetadata annotation:

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"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/usercontroller/": {
      "post": {
        "deprecated": false,
        "operationId": "postUsercontrollerApiUsercontroller",
        "parameters": [
          {
            "deprecated": true,
            "description": "Old way to search things",
            "in": "query",
            "name": "search",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "deprecated": true,
            "description": "Old way to search things",
            "in": "query",
            "name": "max_items",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "Created"
          },
          "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"
          }
        }
      }
    }
  }
}

Customizing media types

There are different metadata fields, like examples and encoding, that MediaType supports in OpenAPI MediaType.

For example, if you want to change how Body provides examples, you can use dmr.openapi.objects.MediaTypeMetadata annotation:

OpenAPI Schema

Preview openapi.json
{
  "components": {
    "schemas": {
      "ErrorDetail": {
        "description": "Base schema for error details description.",
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                }
              ]
            },
            "title": "Loc",
            "type": "array"
          },
          "msg": {
            "title": "Msg",
            "type": "string"
          },
          "type": {
            "title": "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"
            },
            "title": "Detail",
            "type": "array"
          }
        },
        "required": [
          "detail"
        ],
        "title": "ErrorModel",
        "type": "object"
      },
      "SearchModel": {
        "properties": {
          "max_items": {
            "title": "Max Items",
            "type": "integer"
          },
          "search": {
            "title": "Search",
            "type": "string"
          }
        },
        "required": [
          "search",
          "max_items"
        ],
        "title": "SearchModel",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/usercontroller/": {
      "post": {
        "deprecated": false,
        "operationId": "postUsercontrollerApiUsercontroller",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "max_items": 10,
                "search": "example"
              },
              "schema": {
                "$ref": "#/components/schemas/SearchModel"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "Created"
          },
          "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 also support the same way for conditional types:

OpenAPI Schema

Preview openapi.json
{
  "components": {
    "schemas": {
      "ErrorDetail": {
        "description": "Base schema for error details description.",
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                }
              ]
            },
            "title": "Loc",
            "type": "array"
          },
          "msg": {
            "title": "Msg",
            "type": "string"
          },
          "type": {
            "title": "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"
            },
            "title": "Detail",
            "type": "array"
          }
        },
        "required": [
          "detail"
        ],
        "title": "ErrorModel",
        "type": "object"
      },
      "XmlSearchModel": {
        "properties": {
          "search": {
            "title": "Search",
            "type": "string"
          }
        },
        "required": [
          "search"
        ],
        "title": "XmlSearchModel",
        "type": "object"
      },
      "_SearchModel": {
        "properties": {
          "max_items": {
            "title": "Max Items",
            "type": "integer"
          },
          "search": {
            "title": "Search",
            "type": "string"
          }
        },
        "required": [
          "search",
          "max_items"
        ],
        "title": "_SearchModel",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/usercontroller/": {
      "post": {
        "deprecated": false,
        "operationId": "postUsercontrollerApiUsercontroller",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "max_items": 10,
                "search": "example"
              },
              "schema": {
                "$ref": "#/components/schemas/_SearchModel"
              }
            },
            "application/xml": {
              "schema": {
                "$ref": "#/components/schemas/XmlSearchModel"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "Created"
          },
          "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"
          }
        }
      }
    }
  }
}

And for FileMetadata:

OpenAPI Schema

Preview openapi.json
{
  "components": {
    "schemas": {
      "ErrorDetail": {
        "description": "Base schema for error details description.",
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                }
              ]
            },
            "title": "Loc",
            "type": "array"
          },
          "msg": {
            "title": "Msg",
            "type": "string"
          },
          "type": {
            "title": "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"
            },
            "title": "Detail",
            "type": "array"
          }
        },
        "required": [
          "detail"
        ],
        "title": "ErrorModel",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/usercontroller/": {
      "post": {
        "deprecated": false,
        "operationId": "postUsercontrollerApiUsercontroller",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "encoding": {
                "avatar": {
                  "contentType": "image/png"
                }
              },
              "schema": {
                "properties": {
                  "avatar": {
                    "format": "binary",
                    "type": "string"
                  }
                },
                "required": [
                  "avatar"
                ],
                "title": "UserUpload",
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "Created"
          },
          "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"
          }
        }
      }
    }
  }
}

Customizing response

ResponseSpec supports all the metadata fields that Response has.

Providing an explicit Link for schemathesis stateful API testing would look like so:

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"
      },
      "UserModel": {
        "properties": {
          "uid": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "uid"
        ],
        "title": "UserModel",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/usercontroller/": {
      "post": {
        "deprecated": false,
        "operationId": "postUsercontrollerApiUsercontroller",
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserModel"
                }
              }
            },
            "description": "This is a description for your response",
            "links": {
              "GetUser": {
                "operationId": "getUser",
                "parameters": {
                  "userId": "$response.body#/uid"
                }
              }
            }
          },
          "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"
          }
        }
      },
      "put": {
        "deprecated": false,
        "operationId": "putUsercontrollerApiUsercontroller",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserModel"
                }
              }
            },
            "description": "OK",
            "links": {
              "GetUser": {
                "operationId": "getUser",
                "parameters": {
                  "userId": "$response.body#/uid"
                }
              }
            }
          },
          "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"
          }
        }
      }
    }
  }
}

Examples generation

If you installed 'django-modern-rest[openapi]' extra and enabled openapi_examples_seed setting, we will generate missing examples in your OpenAPI schemas using polyfactory.

They will not have the best data quality, since they are clearly autogenerated from fake data, but sometimes it is better than nothing.

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.",
        "example": {
          "detail": [
            {
              "loc": [
                "vbzQIBShDtFfOfiPXwvm"
              ],
              "msg": "gNZYFcagWptUqCwdERil"
            }
          ]
        },
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            },
            "type": "array"
          }
        },
        "required": [
          "detail"
        ],
        "title": "ErrorModel",
        "type": "object"
      },
      "UserModel": {
        "example": {
          "uid": "7b89296c-6dcb-4c50-8857-7eb1924770d3",
          "username": "EkQQHiBrmXZcSFtoJxJI"
        },
        "properties": {
          "uid": {
            "format": "uuid",
            "type": "string"
          },
          "username": {
            "type": "string"
          }
        },
        "required": [
          "uid",
          "username"
        ],
        "title": "UserModel",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/usercontroller/": {
      "post": {
        "deprecated": false,
        "operationId": "postUsercontrollerApiUsercontroller",
        "parameters": [
          {
            "deprecated": false,
            "in": "query",
            "name": "search",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "deprecated": false,
            "in": "query",
            "name": "max_items",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserModel"
                }
              }
            },
            "description": "Created"
          },
          "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"
          }
        }
      }
    }
  }
}

Important

However, we recommend adding semantic named examples by hand.

Top level API

This is how OpenAPI spec is generated, top level overview:

        ---
config:
  theme: forest

---
graph
    Start[build_schema] --> Router[Router];
    Router -->|for each controller| Controller[Controller.get_path_item];
    Router -->|for each defined auth| SecurityScheme[Auth.security_scheme];
    Controller -->|for each endpoint| Endpoint[Endpoint.get_schema];
    Endpoint -->|for each component| ComponentParser[ComponentParser.get_schema]
    Endpoint -->|for each response| ResponseSpec[ResponseSpec.get_schema];
    Endpoint -->|for each used auth| SecurityRequirement[Auth.security_requirement];
    ComponentParser -->|for each schema| Schema[serializer.schema_generator.get_schema];
    ResponseSpec -->|for each schema| Schema[serializer.schema_generator.get_schema];
    

OpenAPI spec generation

We have several major design principles that define our API:

  1. Regular user-facing objects must know how to build the OpenAPI schema. For example: Endpoint, Controller, and Router all know how to build the spec for themselves. Since they are user-facing, it is easy to modify how the schema generation works if needed

  2. All model schemas must be directly generated by their libraries. We don’t do anything with the JSON Schema that is generated by pydantic or msgspec. They can do a better job than we do. However, their schemas still can be customized. See PydanticSchemaGenerator and MsgspecSchemaGenerator

APIs for schema overrides

Useful APIs for users to override:

API Reference

This is the API every user needs:

dmr.openapi.build_schema(router: Router, *, context: OpenAPIContext) OpenAPI[source]
dmr.openapi.build_schema(router: Router, *, config: OpenAPIConfig | None = None) OpenAPI

Build OpenAPI schema.

Parameters:
  • router – Router that contains all API endpoints and all controllers.

  • context – OpenAPI context with all the builder tools.

  • config – Optional configuration of OpenAPI metadata. Can be None, in this case we fetch OpenAPI config from settings.

class dmr.openapi.OpenAPIConfig(*, title: str, version: str, openapi_version: str = '3.1.0', summary: str | None = None, description: str | None = None, terms_of_service: str | None = None, contact: Contact | None = None, external_docs: ExternalDocumentation | None = None, security: list[dict[str, list[str]]] | None = None, license: License | None = None, components: Components | list[Components] | None = None, servers: list[Server] | None = None, tags: list[Tag] | None = None, webhooks: dict[str, PathItem | Reference] | None = None)[source]

Configuration class for customizing OpenAPI specification metadata.

This class provides a way to configure various aspects of the OpenAPI specification that will be generated for your API documentation. It allows you to customize the API information, contact details, licensing, security requirements, and other metadata that appears in the generated OpenAPI spec.

class dmr.openapi.OpenAPIContext(config: OpenAPIConfig)[source]

Context for OpenAPI specification generation.

Maintains shared state and generators used across the OpenAPI generation process. Provides access to different generators.

register_schema(annotation: Any, schema: Schema | Reference | SchemaCallback, *, override: bool = False) None[source]

Register top-level annotation resolution into an OpenAPI schema.

You can pass either a schema object itself, a reference, or a callback that returns schema, reference, or None to fallback to the default schema resolution process.

Warning

This only works for the top-level annotations with direct matches. For example: when you register User to have a specific schema, it will take effect only in cases where User is used directly. list[User] will use the default serializer schema resolution strategy.

All other objects that are only used if you decide to customize the schema are listed in OpenAPI Reference.