Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
a7e307f
feat: add /api/user/v2/ users, user_preferences and time_zones
Abdul-Muqadim-Arbisoft Oct 14, 2026
149e8c8
feat: publish the user API in the LMS schema
Abdul-Muqadim-Arbisoft Oct 14, 2026
930d42f
feat: standardize the account, name-change and verification APIs as /…
Abdul-Muqadim-Arbisoft Oct 23, 2026
efcace5
feat: serve the profile image from /api/user/v2/accounts/{username}/p…
Abdul-Muqadim-Arbisoft Oct 23, 2026
73e7adf
fix: let the account tests be collected under the CMS settings
Abdul-Muqadim-Arbisoft Oct 26, 2026
98bc51a
feat: standardize the preferences and email opt-in APIs as /api/user/v2/
Abdul-Muqadim-Arbisoft Oct 26, 2026
9854bb5
fix: name the library JWT class on the views that replace a default a…
Abdul-Muqadim-Arbisoft Oct 26, 2026
431c87a
fix: align four published details across the three /api/user/v2/ phases
Abdul-Muqadim-Arbisoft Oct 26, 2026
38f013e
fix: publish a body no request may leave out as one the operation needs
Abdul-Muqadim-Arbisoft Oct 29, 2026
6c3501f
test: hold the four corrected success codes to the handlers that answ…
Abdul-Muqadim-Arbisoft Oct 29, 2026
305d883
fix: mark the superseded user addresses Studio serves as superseded t…
Abdul-Muqadim-Arbisoft Oct 29, 2026
2f0e946
fix: carry the service's appended components into the documentation s…
Abdul-Muqadim-Arbisoft Oct 29, 2026
b299ce1
fix: refuse a page size no collection of this version will give
Abdul-Muqadim-Arbisoft Oct 29, 2026
33d4d62
fix: say in words why a request was refused, not in printed data
Abdul-Muqadim-Arbisoft Oct 29, 2026
0c76a41
fix: answer a member account address from the record the lookup resol…
Abdul-Muqadim-Arbisoft Oct 31, 2026
68eba1d
fix: answer every account holding a selected email address
Abdul-Muqadim-Arbisoft Oct 31, 2026
f837684
fix: admit an operation on one account by the rules of the account re…
Abdul-Muqadim-Arbisoft Oct 31, 2026
20382b1
fix: describe each address by the body and the media types it reads
Abdul-Muqadim-Arbisoft Oct 31, 2026
7d839ab
docs: record where the retirement of a re-used address is read differ…
Abdul-Muqadim-Arbisoft Oct 31, 2026
c80f785
test: guard the record-visibility layer of the two queryset-backed us…
Abdul-Muqadim-Arbisoft Oct 31, 2026
d822e5f
fix: describe each address of this version to the caller that asks, n…
Abdul-Muqadim-Arbisoft Oct 31, 2026
a0304d1
fix: refuse a selected property name that reaches into a property
Abdul-Muqadim-Arbisoft Oct 31, 2026
225cefa
fix: test and describe the rate limit on changing a recovery address
Abdul-Muqadim-Arbisoft Oct 31, 2026
8fa3c51
fix: describe the body a search is asked for in, not the one it answe…
Abdul-Muqadim-Arbisoft Oct 31, 2026
291deb8
fix: say both things that get a clearing of a profile image refused
Abdul-Muqadim-Arbisoft Oct 31, 2026
2baf385
fix: publish the two refusals every address of this version could alr…
Abdul-Muqadim-Arbisoft Oct 31, 2026
7e865aa
fix: stop naming the site's sharing configuration in a refusal about …
Abdul-Muqadim-Arbisoft Nov 1, 2026
4686f14
fix: fetch rows carried inside another resource through the policy th…
Abdul-Muqadim-Arbisoft Nov 1, 2026
2163356
fix: describe the superseded user addresses the same way in both serv…
Abdul-Muqadim-Arbisoft Nov 1, 2026
9591e0f
fix: mount the prefix that answers unknown addresses after the applic…
Abdul-Muqadim-Arbisoft Nov 1, 2026
6014f4b
fix: serve the document published for client generation from the cach…
Abdul-Muqadim-Arbisoft Nov 1, 2026
2a0af32
test: hold the settings module the published reference is generated from
Abdul-Muqadim-Arbisoft Nov 1, 2026
a76673a
fix: carry inside a preference only the owner entries the caller may …
Abdul-Muqadim-Arbisoft Nov 1, 2026
8e7a19c
fix: say in the document that a change has to name a preference
Abdul-Muqadim-Arbisoft Nov 1, 2026
236e7d7
fix: name the refusal of an address more than one account answers to
Abdul-Muqadim-Arbisoft Nov 1, 2026
946aee9
fix: record a profile image on the account the request was admitted for
Abdul-Muqadim-Arbisoft Nov 1, 2026
00c48de
fix: read an address held and left more than once through the addresses
Abdul-Muqadim-Arbisoft Nov 1, 2026
2dd57bd
docs: record beside the name in an address what each address does wit…
Abdul-Muqadim-Arbisoft Nov 1, 2026
ce73cb8
test: say which refusal the recording of an image without a date is e…
Abdul-Muqadim-Arbisoft Nov 1, 2026
b063524
refactor: hold these addresses, their descriptions and the hook that …
Abdul-Muqadim-Arbisoft Nov 1, 2026
0612ddd
test: check that neither schema module reaches into the other service
Abdul-Muqadim-Arbisoft Nov 1, 2026
6a7ceb9
test: hold each version to the refusal it answers with
Abdul-Muqadim-Arbisoft Nov 1, 2026
cab976a
style: satisfy the repository's own lint on the lines this work adds
Abdul-Muqadim-Arbisoft Nov 1, 2026
f3e0053
feat: standardize the account retirement pipeline as /api/user/v2/
Abdul-Muqadim-Arbisoft Oct 26, 2026
be5fda6
fix: refuse an ambiguous account name, and answer the method a caller…
Abdul-Muqadim-Arbisoft Oct 27, 2026
0b01a80
fix: publish what a caller must send, and read every step it names
Abdul-Muqadim-Arbisoft Oct 27, 2026
d1e4781
test: pin the claims of /api/user/v2/ that no test could fail
Abdul-Muqadim-Arbisoft Oct 27, 2026
c5ef806
fix: resolve every retirement operation through its own resource
Abdul-Muqadim-Arbisoft Oct 27, 2026
ae21ab5
fix: publish the partner-report entry the claim can actually produce
Abdul-Muqadim-Arbisoft Oct 28, 2026
feb9230
fix: judge the record a request resolved, and let a document keep its…
Abdul-Muqadim-Arbisoft Oct 28, 2026
bdd03bb
fix: name the retirement component once, and cover its queue reads in…
Abdul-Muqadim-Arbisoft Nov 1, 2026
cdcf571
fix: keep the line a refused batch wrote about itself, and name the c…
Abdul-Muqadim-Arbisoft Nov 1, 2026
03b9f2a
fix: say that a selected name reaching into a property is refused, an…
Abdul-Muqadim-Arbisoft Nov 1, 2026
7697480
fix: publish and measure the refusal every retirement address already…
Abdul-Muqadim-Arbisoft Nov 1, 2026
4e56d40
docs: describe each retirement address to the caller that asks, not t…
Abdul-Muqadim-Arbisoft Nov 1, 2026
d3b9418
style: import the two modules of this version on a line each
Abdul-Muqadim-Arbisoft Nov 1, 2026
4b1bfae
test: hold the superseded names Studio publishes for the retirement a…
Abdul-Muqadim-Arbisoft Nov 1, 2026
f01a160
style: declare the attributes a helper fills before it fills them
Abdul-Muqadim-Arbisoft Nov 1, 2026
ed83bea
fix: append the component Studio's document had stopped carrying
Abdul-Muqadim-Arbisoft Nov 1, 2026
a92f81c
feat: serve registration, sign-in and password reset under the user A…
Abdul-Muqadim-Arbisoft Nov 1, 2026
d619b1e
feat: mark the older registration, sign-in and password reset address…
Abdul-Muqadim-Arbisoft Nov 1, 2026
c6d0bdc
docs: describe the order this version's mount is matched in as it now is
Abdul-Muqadim-Arbisoft Nov 1, 2026
4c360de
test: read what each sign-in, registration and reset view declares be…
Abdul-Muqadim-Arbisoft Nov 1, 2026
1e5cb05
fix: publish the label and the hint of a registration input as possib…
Abdul-Muqadim-Arbisoft Nov 1, 2026
1ca0e94
fix: publish the refusals of an unusable Accept header and an unreada…
Abdul-Muqadim-Arbisoft Nov 1, 2026
c1ffa84
fix: keep the sign-in address that takes its version as a parameter o…
Abdul-Muqadim-Arbisoft Nov 1, 2026
7f188cd
fix: publish the new sign-in with the CSRF requirement the older sign…
Abdul-Muqadim-Arbisoft Nov 1, 2026
ae1e8e3
test: count a database write by its first keyword
Abdul-Muqadim-Arbisoft Nov 1, 2026
de99ae4
test: check that reading a form sends no signal and no event
Abdul-Muqadim-Arbisoft Nov 1, 2026
65a20c3
test: read the forms while a sign-in through another provider is unde…
Abdul-Muqadim-Arbisoft Nov 1, 2026
8876b0d
fix: publish every refusal the password reset token check answers wit…
Abdul-Muqadim-Arbisoft Nov 1, 2026
f6eca9d
test: show both sign-in limits shared in every direction
Abdul-Muqadim-Arbisoft Nov 1, 2026
023201d
fix: publish the registrations that need no password
Abdul-Muqadim-Arbisoft Nov 1, 2026
c9fec71
fix: publish the refusal of a body that cannot be parsed
Abdul-Muqadim-Arbisoft Nov 1, 2026
9bd578e
docs: say that refused registrations count towards the registration l…
Abdul-Muqadim-Arbisoft Nov 1, 2026
e2d0b5a
docs: describe the sign-in answered with 400 after a correct password
Abdul-Muqadim-Arbisoft Nov 1, 2026
e100792
test: describe the order the service really matches the user addresse…
Abdul-Muqadim-Arbisoft Nov 1, 2026
155cfc6
docs: give the true reasons the sign-in and registration routes are c…
Abdul-Muqadim-Arbisoft Nov 1, 2026
7325fef
docs: say which error body the fallback for unknown addresses shares
Abdul-Muqadim-Arbisoft Nov 1, 2026
024dd40
fix: publish what the one sign-in refusal answered with 403 always ca…
Abdul-Muqadim-Arbisoft Nov 1, 2026
4792514
test: hold the members each response promises to the bodies really sent
Abdul-Muqadim-Arbisoft Nov 1, 2026
be57050
fix: describe the CSRF token in every document whose sign-in names it
Abdul-Muqadim-Arbisoft Nov 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@

from cms.lib.xblock.authoring_mixin import AuthoringMixin
from cms.lib.xblock.upstream_sync import UpstreamSyncMixin
from openedx.core.djangoapps.user_api.schema_components import (
CREDENTIAL_SCHEMES,
KEPT_COMPONENT_SCHEMAS,
)
from openedx.core.lib.derived import Derived
from openedx.core.lib.features_setting_proxy import FeaturesProxy
from openedx.envs.common import * # pylint: disable=wildcard-import # noqa: F403
Expand Down Expand Up @@ -1338,8 +1342,30 @@ def _should_send_xblock_events(settings):
'drf_spectacular.hooks.postprocess_schema_enums',
'cms.lib.spectacular.cms_mark_superseded_paths',
'cms.lib.spectacular.cms_mark_migrated_paths',
# Studio mounts the user API at the same addresses the LMS does, and
# both services publish an unauthenticated document at /api-docs/, so
# an address superseded in one is superseded in the other. The list of
# them and the hook that reads it are shared rather than restated here,
# which is what stops a client generated from Studio's document from
# offering an address as current that the LMS document marks as
# superseded.
'openedx.core.djangoapps.user_api.schema.mark_superseded_user_paths',
'openedx.core.apidocs.untag_deprecated_operations',
],
# This service also serves the user API, which names a component no
# operation of it refers to any more. It is appended here, from the
# definition the other service appends as well, so that a client generated
# from either service's document carries that class. Every document built
# from these settings carries it, the service-wide one included.
'APPEND_COMPONENTS': {
'schemas': dict(KEPT_COMPONENT_SCHEMAS),
# The sign-in this service serves at the user API's addresses names the
# CSRF token, so the token is described here from the definition the
# other service appends, and a client generated from either service's
# document can send it. Only that credential is described: no
# operation of this service names any other.
'securitySchemes': {'csrf': CREDENTIAL_SCHEMES['csrf']},
},
# Stripped from each path to derive its tags and operationId, so changing
# it renames every operationId. Paths are published in full, from the
# service root, so /api/contentstore and /api/authoring resolve against the
Expand Down
12 changes: 11 additions & 1 deletion cms/lib/spectacular.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from drf_spectacular.openapi import AutoSchema

from openedx.core.djangoapps.user_api.schema import SupersededUserOperations

# Legacy operations superseded by a conforming address under /api/authoring/.
# Each is listed in the truncated spelling as well as the full one: the routes
# make the final character optional, so both addresses resolve, and which one
Expand Down Expand Up @@ -72,14 +74,22 @@ def cms_mark_superseded_paths(result, generator, request, public): # pylint: di
return result


class CmsAutoSchema(AutoSchema):
class CmsAutoSchema(SupersededUserOperations, AutoSchema):
"""
Give a colliding legacy address a distinct operationId.

Where a legacy mount and its conforming mount tokenize to the same
operationId, drf-spectacular would otherwise break the tie with a numeral
suffix in registration order. The conforming address keeps the clean id;
the deprecated one is suffixed ``_legacy``.

This service also serves the user API at the same addresses the service
that replaces it does, and both publish an unauthenticated document of
everything they serve. The shared behaviour describes those addresses the
one way, so that a client generated from either document reads the same
signature and the same success code for the same request; it is taken from
where that API's own schema lives rather than copied, because two copies
of a description drift.
"""

def get_operation_id(self):
Expand Down
502 changes: 500 additions & 2 deletions cms/lib/tests/test_spectacular.py

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions docs/docs_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
)
from lms.envs.common import * # pylint: disable=wildcard-import # noqa: F403
from openedx.core.apidocs import get_api_docs_settings
from openedx.core.djangoapps.user_api.schema_components import KEPT_COMPONENT_SCHEMAS
from openedx.core.lib.derived import derive_settings

# Turn on all the boolean feature flags, so that conditionally included
Expand Down Expand Up @@ -82,7 +83,22 @@

# OpenAPI 3 security schemes, consumed by drf-spectacular. "Basic" uses the
# OpenAPI 3 http/basic form rather than Swagger 2.0's type: basic.
#
# Whatever the service settings already append is carried through rather than
# replaced, both the keys beside "schemas" and the component names under it:
# those names are what the published reference is expected to carry, and one
# dropped here goes out of the generated reference, and out of every client
# built from it, without changing any count that would show it had gone.
#
# The components a service appends although nothing refers to them are named
# again here, through the one definition both services read, because the
# reference is expected to carry them whatever else a service appends.
SPECTACULAR_SETTINGS["APPEND_COMPONENTS"] = { # noqa: F405
**SPECTACULAR_SETTINGS.get("APPEND_COMPONENTS", {}), # noqa: F405
"schemas": {
**SPECTACULAR_SETTINGS.get("APPEND_COMPONENTS", {}).get("schemas", {}), # noqa: F405
**KEPT_COMPONENT_SCHEMAS,
},
"securitySchemes": {
"Basic": {
"type": "http",
Expand Down
39 changes: 33 additions & 6 deletions lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
from path import Path as path

from openedx.core.djangoapps.plugins.constants import ProjectType, SettingsType
from openedx.core.djangoapps.user_api.schema_components import (
CREDENTIAL_SCHEMES,
KEPT_COMPONENT_SCHEMAS,
)
from openedx.core.lib.derived import Derived
from openedx.core.lib.features_setting_proxy import FeaturesProxy
from openedx.envs.common import * # pylint: disable=wildcard-import # noqa: F403
Expand Down Expand Up @@ -2179,23 +2183,46 @@

add_optional_apps(OPTIONAL_APPS, INSTALLED_APPS) # noqa: F405

###################### drf-spectacular (LMS enrollment schema) ######################
###################### drf-spectacular (LMS API schema) ######################
SPECTACULAR_SETTINGS = {
'TITLE': 'LMS Enrollment API',
'TITLE': 'LMS API',
'VERSION': '0.1.0',
'SERVE_INCLUDE_SCHEMA': False,
'PREPROCESSING_HOOKS': ['lms.lib.spectacular.lms_api_filter'],
# Mark legacy slashless enrollment addresses deprecated. The enum hook is
# drf-spectacular's default, restated because setting this key replaces
# the default list.
# Mark superseded addresses deprecated. The enum hook is drf-spectacular's
# default, restated because setting this key replaces the default list.
'POSTPROCESSING_HOOKS': [
'drf_spectacular.hooks.postprocess_schema_enums',
'lms.lib.spectacular.lms_mark_legacy_paths_deprecated',
'openedx.core.djangoapps.user_api.schema.mark_superseded_user_paths',
'openedx.core.apidocs.untag_deprecated_operations',
],
# Carried by every document built from these settings, including the
# service-wide one, which overlays its own keys over them. The other
# service that serves this API appends the same components, read from the
# same definition, so neither document names one the other does not.
'APPEND_COMPONENTS': {
'schemas': dict(KEPT_COMPONENT_SCHEMAS),
# How a caller sends each credential the operations of this service
# take beside the session cookie. The generator describes the cookie
# and nothing else, so an operation taking a token names these and a
# client generated from the document can offer the caller a way to
# send it.
'securitySchemes': dict(CREDENTIAL_SCHEMES),
},
# A set of choices is published as its own model, named after the field
# carrying it while one field of the service carries that name alone. The
# moment a second field of the same name carries a different set, both are
# renamed after the models they sit in, so the plain name disappears and a
# client generated from the document loses that class. The names below are
# fixed here so that the sets keep them whatever else the service grows.
'ENUM_NAME_OVERRIDES': {
'ScopeEnum': 'openedx.core.djangoapps.user_api.v2.serializers.ERASURE_SCOPE_CHOICES',
'OutcomeEnum': 'openedx.core.djangoapps.user_api.v2.serializers.BATCH_OUTCOME_CHOICES',
},
# Stripped from each path to derive its tags and operationId, so changing
# it renames every operationId. Paths themselves are emitted in full.
'SCHEMA_PATH_PREFIX': '/api/enrollment',
'SCHEMA_PATH_PREFIX': r'/api/(enrollment|user)',
# Serve the Swagger UI and ReDoc assets from drf-spectacular-sidecar rather
# than drf-spectacular's default unpinned jsdelivr CDN URLs, keeping them
# self-hosted and version-pinned as the drf-yasg bundles were.
Expand Down
2 changes: 1 addition & 1 deletion lms/envs/devstack.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ def should_show_debug_toolbar(request): # pylint: disable=missing-function-docs
'COMPLETE',
]

###################### drf-spectacular (LMS enrollment schema) ######################
###################### drf-spectacular (LMS API schema) ######################
SPECTACULAR_SETTINGS['SERVERS'] = [ # noqa: F405
{'url': LMS_ROOT_URL, 'description': 'Local'}, # noqa: F405
]
Expand Down
2 changes: 1 addition & 1 deletion lms/envs/production.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ def get_env_setting(setting):
MIDDLEWARE.extend(_YAML_TOKENS.get('EXTRA_MIDDLEWARE_CLASSES', [])) # noqa: F405


###################### drf-spectacular (LMS enrollment schema) ######################
###################### drf-spectacular (LMS API schema) ######################
SPECTACULAR_SETTINGS['SERVERS'] = [ # noqa: F405
{'url': LMS_ROOT_URL, 'description': 'Local'}, # noqa: F405
]
Expand Down
78 changes: 66 additions & 12 deletions lms/lib/spectacular.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,107 @@

from drf_spectacular.openapi import AutoSchema

from openedx.core.djangoapps.user_api.schema import SupersededUserOperations, is_superseded_user_path

LEGACY_V2_PREFIX = "/api/enrollment/v2/"

# The one legacy address that differs from its conforming twin only by the
# trailing slash, so both tokenize to the same operationId. The other legacy
# addresses already have distinct ids and keep them.
COLLIDING_LEGACY_PATH = "/api/enrollment/v2/enrollments"

PUBLISHED_PATH_PATTERN = re.compile(r"^/api/enrollment/v\d+/")

# The user addresses this schema documents are the ones the version below
# serves, and beside each of those the address it supersedes. Apps older than
# that version are mounted under the same ``/api/user/v2/`` prefix, so matching
# the prefix alone would publish endpoints described nowhere here; the packages
# a view may come from decide instead. One resource of this version lives in
# the app that owns it rather than in the user API's own package.
PUBLISHED_USER_PREFIX = "/api/user/v2/"
PUBLISHED_USER_VIEW_MODULES = (
"openedx.core.djangoapps.user_api.v2.",
"openedx.core.djangoapps.profile_images.v2.",
)

# Every user address, whatever its version, belongs to one tag group. A tag is
# the first path segment left once the configured prefix is removed, which in a
# document that trims ``/api/user`` is the version, so user operations would be
# grouped with the enrollment operations that carry the same version. A document
# that trims nothing already derives a tag naming an area, and regrouping its
# operations would move them in every client generated from it.
USER_PATH_PREFIX = "/api/user/"
USER_TAG = "user"

_VERSION_TAG = re.compile(r"v\d+")

# An address that takes its version as a parameter is left out of the
# superseded addresses this schema admits, though it is still marked wherever
# it is published: posting there to sign in publishes the description its
# handler carries, which is not what that sign-in reads or answers with, and
# nothing here can replace it.
_VERSION_PARAMETER = "{api_version}"


def _is_legacy_path(path):
"""Conforming routes always end in a slash, so a slashless v2 path is a legacy address."""
return path.startswith(LEGACY_V2_PREFIX) and not path.endswith("/")


def _is_published_user_path(path, callback):
"""A user address is documented when this version serves it, or when it is a superseded one admitted above."""
if is_superseded_user_path(path) and _VERSION_PARAMETER not in path:
return True
if not path.startswith(PUBLISHED_USER_PREFIX):
return False
view = getattr(callback, "cls", None)
return getattr(view, "__module__", "").startswith(PUBLISHED_USER_VIEW_MODULES)


def lms_api_filter(endpoints):
"""
Pre-processing hook: keep only enrollment v2 endpoints tagged for the SDK.
Pre-processing hook: keep only the endpoints this schema documents.
"""
filtered = []
ENROLLMENT_PATH_PATTERN = re.compile(r"^/api/enrollment/v\d+/")

for path, path_regex, method, callback in endpoints:
if ENROLLMENT_PATH_PATTERN.match(path):
if PUBLISHED_PATH_PATTERN.match(path) or _is_published_user_path(path, callback):
filtered.append((path, path_regex, method, callback))

return filtered


class LmsAutoSchema(AutoSchema):
class LmsAutoSchema(SupersededUserOperations, AutoSchema):
"""
Give the colliding legacy Enrollment v2 address a distinct operationId.

It and its conforming twin tokenize to the same operationId, so
drf-spectacular would otherwise break the tie with a numeral suffix in
registration order. The conforming address keeps the clean id; the
deprecated one is suffixed ``_legacy``.
Keep colliding addresses distinct and group each operation under its own area.

The slashless Enrollment v2 address tokenizes to the id of its conforming
twin, so the two would be told apart only by a numeral suffix, which says
which of the two addresses sorts first rather than anything about the
operation; it is suffixed ``_legacy`` instead, leaving the conforming
address the clean id, and the address listed above keeps the name it is
already published under. Each operation keeps the tag the generator derives
for it unless that tag names a version, because a client generated from a
document groups the operations it offers by tag.
"""

def get_operation_id(self):
"""Suffix the colliding legacy address so the pair never shares an operationId."""
"""Keep each address that tokenizes to another one's id distinct from it."""
operation_id = super().get_operation_id()
if self.path == COLLIDING_LEGACY_PATH:
return f"{operation_id}_legacy"
return operation_id

def get_tags(self):
"""Name the area whenever the tag derived for a user address would name a version."""
tags = super().get_tags()
if self.path.startswith(USER_PATH_PREFIX) and tags and _VERSION_TAG.fullmatch(tags[0]):
return [USER_TAG]
return tags


def lms_mark_legacy_paths_deprecated(result, generator, request, public): # pylint: disable=unused-argument
"""Mark the legacy slashless Enrollment v2 addresses ``deprecated: true``."""
"""Mark every legacy enrollment address ``deprecated: true``."""
for path, path_item in result.get("paths", {}).items():
if not _is_legacy_path(path):
continue
Expand Down
Loading
Loading