Your team publishes a Django REST Framework API's OpenAPI schema to external partners; how do you keep that schema honest as the code changes?
answer
- a committed artifact, not a live view
- who is asking changes the output
- regenerate and diff in CI
- warnings are defects
basics
~20 sPublish a generated, versioned schema file rather than a live, permission-filtered schema view; regenerate it in CI and fail on any diff or generator warning; exclude internal views, validate the document, and add error responses and auth the generator cannot infer.
solid answer
~40 sI publish a committed, versioned file generated by a command, not a live schema view: with `get_schema_view()`'s default `public=False` the document is filtered by the requesting user's permissions, and a live view changes on every deploy without review. I scope it with `schema = None` or `@schema(None)` on internal views and `urlconf`/`patterns` for the partner routes. CI regenerates the schema and fails when it differs from the committed file, runs generation with warnings as errors — duplicate operation ids, overridden components, serializers that raised during inspection — and validates it against the OpenAPI spec. Reviewers check the diff for breaking changes, which go to a new version. Finally I declare what inference misses: error responses, authentication, separate request and response bodies, and a few contract tests against real responses.
code
bash · 2 linespython -W error::UserWarning manage.py generateschema --urlconf partners.urls --api_version 1.4.0 --file openapi/partners-v1.yml
git diff --exit-code openapi/partners-v1.ymlgo deeper
Understand that partners generate code from the schema, so a schema that is wrong or out of date breaks their clients.
Explain public=False filtering, how schema = None and urlconf scope the document, and why generation from a command gives a stable file.
Design the CI gate: regenerate and diff, warnings as errors, spec validation, breaking-change review, plus declared errors and auth the generator cannot infer.
Own the versioning policy for the partner contract: what counts as breaking, how long old versions live, and who approves a schema change.
## The scenario An expense-claims API built with DRF is opening to external partners. The partners will generate client code from an OpenAPI document you publish, and a breaking change you did not notice becomes their production outage. The interview question is not "which package do you install" — it is how you make sure the published document **matches the code today and after every change**. ## Decide what you publish: a file, not a live view A schema view (`get_schema_view()` for the built-in generator, or a third-party generator's schema view) renders the schema **on request**. That is convenient during development and wrong as a partner contract, for three reasons: - **It changes silently** whenever code is deployed; nothing reviews the difference. - **It depends on who asks.** `get_schema_view()` defaults to `public=False`, and then the generator checks each view's permissions against the requesting user and drops the endpoints that user cannot use. Two partners, or a partner and your CI, can see different documents. - **It is served with your default permission classes**, which in DRF default to `AllowAny`, so an unprotected schema view exposes every endpoint it lists. The robust contract is a **generated file committed to the repository**, one per published API version, produced by a command (`./manage.py generateschema --file ...` for the built-in generator, or the third-party package's equivalent). Offline generation runs with no request and as public, so the file lists every included endpoint regardless of permissions — which is why scoping matters. ## Scope the document on purpose - **Exclude internal views** with `schema = None` on the class, or `@schema(None)` for a function-based view; the generator skips them. - **Restrict to the partner URLconf** with `urlconf=` or `patterns=` (both accepted by `get_schema_view()`; `--urlconf` on `generateschema`) instead of documenting the whole project. - **Version the document** (`version=` or `--api_version`) so partners can tell which contract they generated from. ## Make drift impossible to merge 1. **Regenerate in CI** on every change and **fail the build when the output differs** from the committed file. The developer then commits the new schema in the same pull request, and reviewers see the contract change next to the code change. 2. **Fail on generator warnings.** DRF's generator reports problems as Python warnings — a duplicated `operationId`, a schema component overridden with a different value, a `get_serializer()` that raised during inspection. These are plain `UserWarning`s, so running generation with Python's `-W error::UserWarning` turns them into failures instead of scrolling past them. 3. **Validate the output** against the OpenAPI specification with a validator in the same CI step; a document that parses but is invalid breaks partners' generators. 4. **Review the diff for breaking changes**: a removed field, a field that became required, a narrowed enum, a changed status code. Anything breaking goes to a new API version rather than into the published one. ## Close the gaps inference leaves A generated schema is only as honest as what the generator can infer: - Declare **error responses** partners must handle; DRF's built-in inspector documents only the success status. - Declare **separate request and response serializers** where a view uses two. - Declare **authentication**: the built-in generator emits no security schemes, so partners would not learn how to authenticate from the document. - Add a few **contract tests** that validate real responses from the API's test suite against the committed schema, so a serializer change that the schema did not capture fails a test. ## Serving the published file - Serve the committed file itself — as a static asset or through a partner portal — so what partners download is exactly what was reviewed. - Keep a live schema view for internal use if it helps, but protect it: `get_schema_view()` accepts `permission_classes` and `authentication_classes`, so restrict it to staff rather than inheriting a permissive default. - Render human-readable reference pages from the same committed file, so the pages and the generated clients never disagree. ## What "honest" means at the end | Check | Catches | |---|---| | CI regenerate-and-diff | Code changed, schema not republished | | Warnings as errors | Operations the generator could not describe | | Spec validation | Documents partners' tools will reject | | Breaking-change review | Silent contract breaks in a published version | | Contract tests | Serializers or views that the schema misdescribes |
- Why can two partners see different schemas from the same schema URL?Because `get_schema_view()` defaults to `public=False`. The generator then sets the requesting user on each view and calls its permission checks, dropping endpoints that raise a permission error. A partner without access to an endpoint simply does not see it. Offline generation with `generateschema` has no request and runs as public, so it lists every included endpoint.
- Is hand-editing the generated YAML acceptable for details the generator misses?Not as a routine: the next regeneration overwrites the edits, or CI's diff check fails forever. Put the missing details into the code — an `AutoSchema` subclass with overridden hooks, or the third-party generator's decorators — so the file stays a pure output of the code and the diff check keeps its meaning.
saying these in an interview costs you the question
- Pointing partners at the live schema view as the contract
- Assuming everyone sees the same document from a non-public schema view
- Hiding an internal endpoint from the schema by tightening its permissions
- Letting generator warnings scroll past in CI logs
- Shipping breaking field changes inside an already published version