Skip to content

Feat: replace drf yasg with drf spectacular - #39108

Open
Faraz32123 wants to merge 12 commits into
masterfrom
feat/replace_drf_yasg_with_drf_spectacular
Open

Faraz32123 wants to merge 12 commits into
masterfrom
feat/replace_drf_yasg_with_drf_spectacular

Conversation

@Faraz32123

@Faraz32123 Faraz32123 commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

This PR must be merged before the schema generation PR: #39025

Replace drf-yasg with drf-spectacular

Follow-up to the FC-0118 API standardization work. Migrates all platform API
documentation off drf-yasg and edx-api-doc-tools onto drf-spectacular,
including the /api-docs site itself.

Slack Discussion thread, the goal was to drop both dependencies as part
of the drf-spectacular conversion, replacing the edx-api-doc-tools /api-docs
endpoint with the drf-spectacular equivalent.

What changed

~400 decorator call sites across 43 files, following the
drf-yasg migration guide:

  • @swagger_auto_schema / @apidocs.schema → @extend_schema
  • @apidocs.schema_for → @extend_schema_view
  • apidocs.string_parameter / query_parameter / path_parameter and
    openapi.Parameter → OpenApiParameter
  • Bare string responses (401: "Not authenticated.") → OpenApiResponse
  • openapi.Schema objects → inline_serializer where they had structure,
    OpenApiTypes where they didn't
  • /api-docs now served by SpectacularAPIView / SpectacularSwaggerView /
    SpectacularRedocView

Commits

Reviewable one at a time, each independently handled:

  1. Direct drf_yasg users (5 files)
  2. openedx/core (10 files)
  3. lms, excluding instructor (4 files)
  4. instructor (2 files, ~120 sites)
  5. cms (22 files)
  6. drf-spectacular extensions for BaseSerializer subclasses
  7. /api-docs swap and dependency removal
  8. Review feedback

Three things worth knowing

The dependencies don't actually disappear. edx-api-doc-tools is still
required by openedx-authz, and drf-yasg by django-user-tasks. Both remain
in base.txt as transitive dependencies. What this PR removes is the platform's
own dependency on them — pyproject.toml, INSTALLED_APPS, and every import.
Fully dropping them needs those upstream packages to migrate first.

Commit 6 exists because /api-docs is unfiltered. Six serializers subclass
BaseSerializer, which has no fields attribute, so drf-spectacular raised
AttributeError when generating a schema covering the whole surface. This never
surfaced before because the existing drf-spectacular schemas
(/authoring-api/, /lms-api/) are filtered down to a handful of endpoints.
Each serializer now has an OpenApiSerializerExtension declaring the type it
actually produces.

/api-docs documents more than it used to. edx-api-doc-tools'
ApiSchemaGenerator kept only paths under /api/; drf-spectacular documents
every DRF endpoint in the service — 633 LMS paths versus the 293 in the
committed docs/lms-openapi.yaml. That file also becomes an OpenAPI 3 document
rather than Swagger 2.0, with untrimmed paths. Flagging it so the widening is a
recorded decision rather than a side effect.

Verification

Tested against a running Tutor instance:

Before After
LMS /api-docs drf-yasg 633 paths, 304 components
CMS /api-docs drf-yasg 236 paths, 185 components
/authoring-api/schema/ 57 paths 57 paths (unchanged)
/lms-api/schema/ 16 paths 16 paths (unchanged)

The last two matter most: /api-docs uses custom_settings rather than the
global SPECTACULAR_SETTINGS, so the narrow SDK-facing schemas are untouched —
same paths, same prefix trimming. The SDK's generated client is unaffected.

Swagger UI and ReDoc confirmed rendering on both services. swagger.json,
swagger.yaml, api-docs/ and swagger/ keep their existing paths and URL
names; api-docs/schema/ is new, because the Swagger and ReDoc views reverse
their schema URL without arguments.

Schema caching is preserved: with OPENAPI_CACHE_TIMEOUT at its 1-hour default,
the first request to /api-docs/schema/ took 5.69s and the second 0.08s.

Notes for reviewers

  • Commit 4 was partly machine-generated. instructor/views/api_v2.py had 91
    string responses and 46 parameters, so the repetitive conversions were
    scripted. Spot-checking won't catch a systematic miss — I verified by
    extracting every response and parameter from both versions and diffing the
    sets. Same method used on commits 1–3 and 5 to confirm nothing was dropped.
  • docs/docs_settings.py security definitions moved from Swagger 2.0
    (type: basic) to OpenAPI 3 (type: http, scheme: basic). This is the one
    change I couldn't exercise locally
    — it only takes effect in the Sphinx docs
    build.
  • Swagger UI and ReDoc assets are served by drf-spectacular-sidecar, so they
    stay self-hosted and version-pinned as the drf-yasg bundles were, rather than
    drf-spectacular's default unpinned @latest CDN URLs. This is a new
    dependency — a uv sync or container rebuild is needed on this branch.
  • Some pre-existing schema warnings remain (views without serializer_class,
    unresolvable authenticators, two CourseEnrollment components with clashing
    names). They don't block generation and are out of scope here.

@Faraz32123 Faraz32123 self-assigned this Sep 16, 2026
@Faraz32123 Faraz32123 changed the title Feat/replace drf yasg with drf spectacular Feat: replace drf yasg with drf spectacular Sep 16, 2026
@Faraz32123
Faraz32123 force-pushed the feat/replace_drf_yasg_with_drf_spectacular branch 3 times, most recently from c232307 to b9f6467 Compare September 16, 2026 08:45
@Faraz32123
Faraz32123 marked this pull request as ready for review September 16, 2026 10:32
@Faraz32123
Faraz32123 requested review from a team as code owners September 16, 2026 10:32
Comment thread lms/urls.py Outdated
Comment thread lms/urls.py
Comment thread lms/urls.py
Comment thread openedx/core/apidocs.py
Comment thread openedx/core/apidocs.py Outdated
Comment thread openedx/core/lib/api/schema_extensions.py Outdated
Comment thread openedx/core/djangoapps/bookmarks/serializers.py Outdated
Comment thread lms/envs/common.py Outdated
@Faraz32123
Faraz32123 marked this pull request as draft September 17, 2026 14:33
@Faraz32123
Faraz32123 force-pushed the feat/replace_drf_yasg_with_drf_spectacular branch 2 times, most recently from dedc96a to 4bf390f Compare September 17, 2026 14:54
@Faraz32123
Faraz32123 marked this pull request as ready for review September 17, 2026 15:27
@Faraz32123
Faraz32123 requested a review from feanil September 17, 2026 15:27
Comment thread lms/urls.py Outdated
Converts @swagger_auto_schema to @extend_schema in the five modules that
import drf_yasg directly, following the drf-spectacular migration guide:
https://drf-spectacular.readthedocs.io/en/latest/drf_yasg.html

Structured openapi.Schema objects become inline_serializer so they get
named components in the generated schema; untyped ones become
OpenApiTypes.OBJECT. openapi.Parameter becomes OpenApiParameter.

The other four drf_yasg users go through edx-api-doc-tools and will be
migrated with it.
Converts @apidocs.schema to @extend_schema across openedx/core, replacing
the parameter helpers with OpenApiParameter and string response
descriptions with OpenApiResponse.

bookmarks/serializers.py inlines is_schema_request, which has no
drf-spectacular equivalent, and extends it to recognise drf-spectacular's
swagger_fake_view alongside drf-yasg's format=openapi.
Converts @apidocs.schema and @Schema to @extend_schema across the lms
app, excluding instructor. Parameter helpers become OpenApiParameter and
string response descriptions become OpenApiResponse.

discussion/rest_api/views.py also drops its remaining direct drf_yasg
import, which was interleaved with the apidocs decorators.
Converts the 26 @apidocs.schema decorators in the instructor v1 and v2
APIs to @extend_schema.

The course_id, problem, and exam_id path parameters were repeated
verbatim across 29 decorators; those are now module-level constants.
Converts the remaining @apidocs.schema decorators across contentstore
and modulestore_migrator to @extend_schema. The three class-level
@apidocs.schema_for decorators become @extend_schema_view, splitting
each docstring into summary and description as schema_for did.

Files that already imported drf-spectacular for the FC-0118 work have
their import lines merged rather than duplicated.
Six serializers subclass BaseSerializer, which has no `fields` attribute,
so drf-spectacular raises AttributeError when generating a schema that
covers them. Each extension declares the type its serializer produces.

Registered from CommonInitializationConfig.ready() so they load in both
services regardless of which schema is being generated.
Replaces make_docs_urls with SpectacularAPIView, SpectacularSwaggerView
and SpectacularRedocView, preserving the swagger.json, swagger.yaml,
api-docs/ and swagger/ routes and their URL names. The UI views reverse
their schema URL without arguments, so api-docs/schema/ is registered
alongside the format-suffixed routes.

/api-docs serves the full API surface via custom_settings, leaving
SPECTACULAR_SETTINGS to the narrower Authoring and Enrollment schemas
the SDK consumes.

Also removes drf_yasg from INSTALLED_APPS, drops SWAGGER_SETTINGS, and
converts the docs security definitions to OpenAPI 3 form. `make swagger`
now runs `manage.py lms spectacular`, since generate_swagger came from
drf_yasg; docs_settings applies the same unfiltered configuration as
/api-docs so the generated file still covers the whole surface.

edx-api-doc-tools and drf-yasg remain installed as transitive
dependencies of openedx-authz and django-user-tasks respectively.
- restore server-side caching on the OpenAPI schema endpoints, which
  edx-api-doc-tools provided via SchemaView.as_cached_view
- point the api-docs test at /api-docs/schema/ so it exercises schema
  generation again, and add the CMS equivalent
- serve Swagger UI and ReDoc assets from drf-spectacular-sidecar instead
  of drf-spectacular's unpinned jsdelivr CDN defaults
- correct the /api-docs comment: edx-api-doc-tools was /api/-only, so
  this widens the documented surface rather than being "the opposite"
- drop the LMS-only claim about API_ACCESS_MANAGER_EMAIL, which lives in
  openedx/envs/common.py and is shared by both services
- point the schema_extensions docstring at CommonInitializationConfig,
  the actual registration site
- remove the dead format=openapi branch from is_schema_request, since
  nothing in the platform serves drf-yasg any more
- delete the now-empty Django Rest Framework banner in lms/envs/common.py
@Faraz32123
Faraz32123 force-pushed the feat/replace_drf_yasg_with_drf_spectacular branch from 4bf390f to ed0c4e9 Compare September 22, 2026 12:40
cache_page pickles the whole Response, and this schema is over memcached's
1MB item limit. CACHES['default'] has ignore_exc set, so the oversized set
failed silently and Django dropped the key — the endpoint looked cached while
regenerating on every request.

cached_schema_view() stores the zlib-compressed body instead, keyed on the
path and the negotiated representation, which brings it to about a tenth of
the limit. add_never_cache_headers restores the no-store that drf-yasg sent,
and a falsy OPENAPI_CACHE_TIMEOUT still goes straight to the view so devstack
is unchanged.
@Faraz32123
Faraz32123 requested a review from feanil September 22, 2026 16:34
Comment thread openedx/core/apidocs.py Outdated
Comment thread openedx/core/apidocs.py
Comment thread openedx/core/apidocs.py
Comment thread lms/tests.py
The cached branch stored anything that came back 200, so DRF's OPTIONS
metadata document landed under the GET key and was served to the next GET.
cache_page only ever stored GET and HEAD; do the same.

drf-spectacular renders the document under whatever language is active, and
LocaleMiddleware sets that from the request before the view runs, so one
locale's document could be served to another's. Add the active language to
the key.

Adds a test to both suites for the two cases. The suite runs with
OPENAPI_CACHE_TIMEOUT = 0 and a DummyCache, so it overrides both to reach the
cached branch at all.
@Faraz32123
Faraz32123 force-pushed the feat/replace_drf_yasg_with_drf_spectacular branch from 7b27cd3 to f7499b5 Compare September 23, 2026 10:25

@feanil feanil left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one last thing and then we're good to land this I think

Comment thread openedx/core/apidocs.py
The LMS and the CMS serve this view at the same path, under the same Accept
and language, and share one cache by default, so both landed on the same key
and whichever generated first served its document at the other's URL.
cache_page avoided this by hashing build_absolute_uri(), host included.

Add ROOT_URLCONF to the key.

@feanil feanil left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more test to add and then I think this is good to merge.

Comment thread lms/tests.py
LMS and CMS serve /api-docs/schema/ at the same path and share one cache, so
the key must include ROOT_URLCONF. The existing cache test runs under a single
URLconf and could not detect that line being dropped; this test serves the view
from a second URLconf and fails without it.
@Faraz32123
Faraz32123 requested a review from feanil October 2, 2026 17:22

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants