List of changes#
Version history#
We will follow Semantic Versions since 1.0.0 release.
While in Development Status :: 3 - Alpha - we will break
all the things without any notices.
After Development Status :: 4 - Beta we will still break things
but with a deprecation period.
What is a public API for us (all criteria must be met)?
Things that have public names
Things that live in public modules
Things that don’t live in
internal/orcompiled/Things that are explicitly documented in the docs
Later on we will make the API more stable and decrease the amount of requirements for an API to count as public.
All migration prompts since 0.13.0 release
are stored as descriptions in version releases on GitHub, example:
https://github.com/wemake-services/django-modern-rest/releases/tag/0.13.0
Prompts of the three latest breaking releases also live in the
dmr-upgrade agent skill,
ask your coding agent to use $dmr-upgrade to upgrade a project.
0.17.0 WIP#
Breaking changes#
Replaced
Sentinelclass in all typing annotations to beEMPTYsentinel value, since all type-checkers support this now, #1687Replaced multiple types from
Anyto beTypeForm[Any], #1688
Misc#
Agent skills#
Suggest
concrete_viewsinstead ofviewsby default indmrskill, #1670
0.16.0 (2026-10-01)#
This release was a part of our community event #opensource_september.
Breaking changes#
Configuration#
auth,throttling,parsers,renderers,responses,tags,exclude_validate_responses,exclude_semantic_responses, andno_validate_http_specare not merged anymore from settings, router, controller, and endpoint levels. Now, endpoint values override controller values, controller values override settings values. Merging is still possible, but it must be explicit, like@modify(auth=[*auth, other_auth]), or customized withController.metadata_merger_cls. This allows a better composition and better value overrides, #1576. Explicit empty values likeauth=[]orthrottling=()are now taken literally as “nothing on this level”, exactly likeNone, they do not fall through to the next level anymore. OnlyEMPTY(or not setting the value at all) uses the next level, #1499auth,throttling,parsers,renderers,responses,tags,exclude_validate_responses,exclude_semantic_responses, andno_validate_http_specare not merged anymore from settings, router, controller, and endpoint levels. Now, endpoint values override controller values, controller values override settings values. Merging is still possible, but it must be explicit, like@modify(auth=[*auth, other_auth]), or customized withController.metadata_merger_cls.All endpoint, controller, and settings values now default to
EMPTY, which means “not set on this level”, instead ofNone.Noneis only allowed where it disables something explicitly, likeauth=None. This affectsvalidate_responses,semantic_responses,validate_events,validate_negotiation,ignore_from_spec,error_handler,status_code,headers,cookies,operation_id,external_docs,callbacks,servers,links,response_description, andSettings.openapi_examples_seed, #1576deprecatedof@modifyand@validatenow defaults toEMPTYinstead ofFalseandEndpointMetadata.deprecatedcan beEMPTY.Router.deprecatedis now the last level fordeprecated, like it is fortags, instead of being combined withOR: an endpoint or a controller with an explicitdeprecated=Falseis not deprecated anymore, even insideRouter(deprecated=True),Changed how
deprecatedis calculated, now any explicit value wins, just like all other settings, #1660Controller.serversis now resolved intoEndpointMetadata.serversof every endpoint, the first explicitly defined level wins, and dumped on each operation instead of the path item. The effective servers of every operation stay the same,servers=Noneon an endpoint now disables the controller value, #1660
Controllers and endpoints#
Controller.as_viewnow raisesEndpointMetadataErrorwhen it is called on an abstract controller: one without an exact serializer type or without any endpoints. Previously such controllers could be added toRouterand tourlpatternssilently, but they could not serve any requests, #1445Endpointobjects are not callable anymore, use.funcattribute to make the call instead, #1456
Components and serializers#
Component annotations hidden inside other types are now rejected. Things like
parsed_body: Body[Model] | None = Noneused to be silently ignored, since the component is hidden behind a union, now they raiseUnsolvableAnnotationsError. UseBody[Model | None] = Noneinstead, #1494MsgspecJsonParser.strictandMsgpackParser.strictnow default toFalseinstead ofTrue. They only affectBodyMsgspec, and lax mode matches how regular request bodies are validated, #1661BaseSerializer.is_supportedis replaced withBaseSerializer.validate, which receivescontroller_clsand the completeEndpointMetadataand raisesEndpointMetadataErrorinstead of returning abool. It runs last in the endpoint validation, so it can check parsers, renderers, components, and everything else at once, #1661BaseSerializer.build_context_modelis a new abstract method, custom serializers must implement it. It builds the model that parses all components of an endpoint at once, previously it was always aTypedDictbuilt bySerializerContext, #1494ComponentParserSpecmoved fromdmr.metadatatodmr.components, it is now a named tuple ofparser,model,model_meta, anddefaultfields. Previously it was a regular tuple of three elements, #1494ComponentParser.provide_context_datanow accepts default keyword parameter, custom components must accept it as well. It is the default value of the endpoint parameter orEMPTYwhen there’s none, #1494ComponentParser.get_schemasignature was changed to accept controller_cls instead of serializer, #1620SupportsFileParsing.schema_metadatasignature was changed to accept controller_cls instead of serializer,FileBodyLike.media_typesignature was changed, #1620 now it also accepts metadata and controller_cls parameters, #1620
Auth and security#
Controllernow setslogin_required = Falseby default in order to exempt controllers from Django’sLoginRequiredMiddleware. Users should configure authentication indjango-modern-restfor controllers, #1551jwt_ensure_csrfwas removed from reusable JWT cookie views, it is now always mandatory, #1574CookieJWTSyncAuthandCookieJWTAsyncAuthnow usejwt_cookieas the defaultsecurity_scheme_nameinstead ofjwt. Previously it was the same as theHeaderJWTSyncAuthandHeaderJWTAsyncAuthone, so using both of them in a single endpoint was generating a singlejwtsecurity scheme and requirement, #1587security_schemesAPI for auth classes was changed, acceptsmetadataandcontroller_cls, and now it is a method, not a property, #1521security_requirementAPI for auth classes was changed, now it is now namedsecurity_requirements, returns a list ofSecurityRequirement, acceptsmetadataandcontroller_cls, and now it is a method, not a property, #1521SyncOrAsyncAuthandSyncOrAsyncThrottlechanged.resolveparameter from auth_cls and throttle_cls respectively to is_async kw-parameter, #1562
Throttling#
Removed
Settings.throttling_allow_unsafe_cache, thethrottling_allow_unsafe_cachecontroller attribute and endpoint parameter, andEndpointMetadata.throttling_allow_unsafe_cache. Useallow_unsafe_cacheparameter ofSyncDjangoCacheandAsyncDjangoCacheinstead, likeSyncDjangoCache(allow_unsafe_cache=False).None(default) now emitsUnsafeCacheBackendWarningonly whensettings.DEBUGisFalse,Truenow disables the check completely,Falsestill raisesEndpointMetadataError, #1611
Streaming#
validate_eventsparameter was removed from@modifyand@validate, it was silently ignored for non-streaming controllers. Useextras=Streaming(validate_events=...)withdmr.streaming.modifyanddmr.streaming.validateinstead.Controller.validate_eventswas removed as well, useextras = Streaming(validate_events=...)on streaming controllers, #1612StreamingController.streaming_ping_secondswas removed, useextras = Streaming(ping_seconds=...)on streaming controllers orextras=Streaming(ping_seconds=...)per endpoint instead.SSEControllerstill sends pings every 15 seconds by default, #1623Renamed
dmr.streaming.metadata.StreamResponseModificationtoStreamingResponseModification, #1456
OpenAPI#
OpenAPIConfignow raisesValueErrorforopenapi_versionbelow3.1.0. OpenAPI3.0.xwas never really supported: it predates JSON Schema, which is whatpydanticandmsgspecgenerate for our models, #1435Removed OpenAPI
3.0styleReferenceobjects from schema positions, #1648Autogenerated OpenAPI examples now land on the JSON Schema
exampleslist instead of the OASexamplekeyword, which OpenAPI 3.2 deprecates inside Schema Objects.examplesis valid in every version we support. Examples that you write by hand are never rewritten, #1485summaryanddescriptionof@modifyand@validateare now resolved one at a time. Passing just one of them used to drop the endpoint’s docstring entirely, now the other one is still parsed from it. They also default toEMPTYinstead ofNone, so passingNoneexplicitly now means “generate nothing” instead of “use the docstring”, #1446OpenAPI object fields that can have
Noneas a real value now default toEMPTYinstead ofNone:Schema.const,Schema.default,Schema.example,Example.value,Example.data_value,Header.example,MediaType.example,MediaTypeMetadata.example,ParameterMetadata.example,Parameter.example, andLink.request_body. PassingNoneto them explicitly now dumpsnullinto the schema, #1619XML.attributeandXML.wrappedare nowbool | Noneand default toNone, so they are no longer dumped asfalseinto everyxmlobject. Both are deprecated in OpenAPI 3.2 in favour ofXML.node_type, #1485MediaType.prefix_encodingandMediaTypeMetadata.prefix_encodingare nowlist[Encoding] | None. The spec definesprefixEncodingas an array of positional encodings, we used to type it as a singleEncoding, #1485Parameter.param_inis now typed asParameterLocationinstead of a plainstr, #1485Components.path_itemsis now typed asdict[str, PathItem] | None. The spec only allows Path Item Objects there,Referenceobjects are not permitted, #1491Removed
PHONE,COLOR, andSTYLEmembers fromOpenAPIFormat, such formats are now loaded as plain strings, #1489
Internals#
Reworked how OpenAPI schema components are registered.
SchemaGenerator.__call__lostskip_registrationandregister_referenced_componentsparameters, useSchemaGenerator.loadto get a schema with its components kept locally, andSchemaGenerator.registerto register only the components that the final result references. RemovedSchemaRegistry.try_unregisterand the unuseddmr.openapi.core.registry.SchemaCallbackprotocol.ResponseGenerator.get_schemaacceptscontent_schemato skip generating a schema from the return type,FileResponseSpecuses it instead of removingFileBodyafterwards, #1647OpenAPIContext.register_schemais removed, just like all custom override features. Instead, one must use native serializer tools for custom JSON schema generation, #1558SecuritySchemeGenerator.__call__API was changed, now it acceptsmetadataandcontroller_clsas parameters, #1521ResponseGenerator.__call__API was changed, now it acceptsmetadataandcontroller_clsas parameters, #1521ResponseSpec.get_schemaAPI was changed, now it acceptscontroller_clsas parameter instead ofserializer, #1521ComponentParserGenerator.__call__signature was changed to accept route_metadata and controller_cls instead of path and serializer, #1502OperationIdGenerator.__call__signature was changed to accept controller_cls instead of suffix, #1502Controller.get_schemasignature was changed to accept route_metadata instead of path and route, #1502Endpoint.get_schemasignature was changed to accept route_metadata instead of path and route, #1502Endpoint.get_operation_idwas removed, instead customize theOperationIdGeneratorinstance oroperation_idmetadata parameter to the endpoint, #1502ParameterGenerator.__call__signature was changed to accept metadata and controller_cls instead of serializer and context, #1620dmr.openapi.mappers.schema_loader.load_schemadoes not takeshould_generate_example,annotation, andserializeranymore. Example generation moved to the schema generator, #1485SecuritySchemeRegistry.schemesis now a read-only property that returns registered security schemes sorted by name, #1557
Performance improvements#
Controllers and endpoints#
Improved checks performance, now we don’t call checks that are not defined for an endpoint. For example, if there’s no throttle, the check function won’t even be called. Previously, it was called and early returned from it, #1454
Optimized
Controller.as_view()for cases withcsrf_exempt=True, which is the default, #1456
Components and serializers#
Improved speed of
PydanticFastSerializer.deserializex2.2 andPydanticFastSerializer.serializex1.33 times, #1662Added
BodyMsgspeccomponent todmr.plugins.msgspec, a drop-in replacement forBodyforMsgspecSerializerthat parses and validates the request body with a different semantics, but, it is x1.6 faster in our benchmarks, #1661MsgspecSerializernow parses all components of an endpoint into amsgspec.Structwithgc=Falseinstead of aTypedDict. Validation of the parsed context is around x2 faster, #1494PydanticSerializerkeeps using aTypedDict, it is the fastest model forpydanticwhen there are no defaults, #1494Now uses
msgspec@0.22, which is much faster in many cases, #1653
Throttling#
Optimized
SyncDjangoCacheandAsyncDjangoCachejson parsing, #1456
Responses, headers, and cookies#
Changed how
@modifyresponses are created, we now returnHttpResponseBaseobjects directly fromResponseValidator. Which allows us to do less calls and not to create intermediate heavy objects, #1456Improved
NewCookiecreation and conversion times andCookieSpec.is_equalexecution time, #1456Improved
set_cookiesexecution time: we now don’t call it if no cookies are to be set,response.set_cookiecall is optimized, #1456Improved
NewHeader.to_specexecution time, #1456
Content negotiation#
RequestNegotiatorandResponseNegotiatornow memoize their decisions per header value. Almost every client sends the very sameContent-Type: application/jsonandAccept: application/jsonheaders, so there’s no point in running the negotiation over and over again. Headers that hit the exact match are around x1.2 faster, headers that have to go through the full negotiation (like theAcceptheader that any browser sends) are up to x65 faster for renderers and x15 faster for parsers. The cache is bound byDMR_MAX_CACHE_SIZEand is not shared between endpoints, #1455Improved the renderer selection for the common cases, #1456
Increased default
DMR_MAX_CACHE_SIZEfrom256to1024, #1448
Features#
Configuration#
Added
extras=parameter to@modifyand@validatefor custom controllers: subclassdmr.endpoint.Extras, create typed decorators withModifyEndpoint(YourExtras)andValidateEndpoint(YourExtras), and assignextras = YourExtras(...)on the controller to enable them and to provide controller-level defaults, #1612Added
Controller.metadata_merger_clsanddmr.validation.MetadataMergerto customize how endpoint, controller, and settings values are resolved into the endpoint metadata. All layers of every field go through a single call, so it can be used to bring back merging ofauthor other sequences from all levels, #1576
Controllers and endpoints#
Reusable controllers now support
TypeVardefaults from PEP 696. A subclass that does not pass some of the type args gets their defaults, just like type-checkers do it: both for a bareclass Sub(Reusable): ...and for a partialclass Sub(Reusable[PydanticSerializer]): .... Defaults that are type vars themselves are resolved as well. The controller that declares the defaults is not affected: it stays abstract, because its own type vars are not exact types, #1452Explicit
is_abstract = Truecontroller definitions are now respected. A controller with an exact serializer can be marked as abstract to be reused without being routed: it does not build any endpoints. Subclasses that don’t declareis_abstractthemselves are concrete, #1458
Components and serializers#
Component parameters can now have default values, like
parsed_body: Body[Model | None] = Noneorparsed_query: Query[Filters | None] = None. When a request has no data for a component, the endpoint receives its default as-is, without any parsing. Bodies with defaults are documented withrequired: false, parameters of other components with defaults are documented as not required in the OpenAPI schema, #1494Added
FunctionDefaultsandComponentParserBuilder.defaults_clsto customize how defaults of component parameters are found, #1494
Auth and security#
Added
concrete_viewsnext toviewsfor every auth flow:dmr.security.jwt.concrete_views,dmr.security.token.concrete_views, anddmr.security.django_session.concrete_views. They are controllers that only need a serializer, everything else is optional:token_clsdefaults to theTokenmodel ofdmr.security.token.app, andjwt_refresh_cookie_pathdefaults to'/'. Each of them has its own typedas_view, which takes the fields that controller requires and passes everything else to Django asinitkwargs, so there is no view code at all:path('login/', ObtainTokenSyncController.as_view( serializer=PydanticSerializer, token_cls=Token)). They all setauth = None, so auth from the settings never makes the login endpoints themselves require auth. They are@final: use them for the common cases, custom logic goes to the reusable controllers inviews, #1457Added better CSRF support, now controllers with
csrf_exempt=Falseget the correct response specs andbuild_csrf_handlerallows customizingCSRF_FAILURE_VIEWDjango setting to return actual REST responses, instead of HTML ones, #1521Added
CSRF_USE_SESSIONS=TrueCSRF security scheme support, #1608Now
security_requirementscan return bothANDandORauth strategies, previously it was only possible to representORstrategy, #1521Auth and throttling instances now provide a
validatehook for enforcing instance-specific constraints during endpoint construction, #1600
Responses, headers, and cookies#
NewCookie.expiresandCookieSpec.expirescan now bedt.datetime, #1456Added
HttpSpec.cookie_semanticsvalidation rule, #1555
Routing#
external_pathcan now be nested anywhere in the URL resolution tree, #1567external_re_pathwas added to support the same use-case asexternal_path, but for regex patterns, #1567
Error handling#
Now we can change the error type / instance that we are handling from layer to layer, for example: endpoint-level handler can raise a new error and controller-level error handler will receive this new error, #1561
Added class-level overrides for
ProblemDetailsModelinProblemDetailsError, #1556
Pagination#
Added
CursorPaginationsupport todrm.pagination, #1428
Content negotiation#
Added
clear_cachemethod toRequestNegotiatorandResponseNegotiatorto drop the memoized negotiation results. Needed when parsers or renderers of an endpoint are modified in place, #1455
OpenAPI#
Added
x_extensionsto all OpenAPI objects that allow specification extensions,x-keys are dumped next to the object’s own keys. OnlyReferenceandSecurityRequirementcannot be extended. AddedOpenAPIConfig.x_extensionsfor the root document,Controller.x_extensionsfor path items,@modify(x_extensions=...)and@validate(x_extensions=...)for operations,ParameterMetadata.x_extensions,MediaTypeMetadata.x_extensions, andResponseSpec.x_extensionsfor parameters, media types, and responses. Each setting describes its own object only, extensions are never inherited or merged across levels.load_schemanow keepsx-keys instead of silently dropping them, #1664Added the missing OpenAPI 3.2 fields to our spec objects, #1485:
OpenAPIConfig.self_uriandOpenAPI.self_urifor$selfServer.nameTag.summary,Tag.parent, andTag.kind'querystring'as aParameter.param_invalueComponents.media_typesResponse.summaryandResponseSpec.summaryMediaType.descriptionEncoding.encoding,Encoding.item_encoding, andEncoding.prefix_encodingExample.data_valueandExample.serialized_valueXML.node_typeDiscriminator.default_mappingOAuthFlows.device_authorizationandOAuthFlow.device_authorization_urlSecurityScheme.oauth2_metadata_urlandSecurityScheme.deprecated
Added
securitycontroller attribute and@modify/@validateargument to document security mechanisms that are not implemented byauth: API gateways, service meshes, etc.OpenAPIConfig.securityis used as the settings level, the first explicitly defined level wins. The result is merged with the requirements generated fromauthby the newSecuritySchemeGenerator.security_mergerclass variable, by default they are added as alternatives and duplicated requirements raiseEndpointMetadataError. Schemes used there must be declared incomponentsofOpenAPIConfig, #1499Added
tagscontroller attribute to apply OpenAPI tags to all endpoints of this controller. They are merged with router-level and endpoint-level tags, #1434Added
deprecated,external_docs, andcallbackscontroller attributes as defaults for all endpoints of a controller, #1660summaryanddescriptionof aPathItemare now parsed from the controller’s docstring, just like they are parsed from the endpoint’s docstring for anOperation. SettingController.summaryorController.descriptionexplicitly still wins,Nonemeans that nothing is generated at all, #1446Added
Settings.semantic_schema_providerswith the ability to add custom default response spec providers. For example, if all controller return some specific status code and schema, now it can be configured properly. We configure response validation and CSRF response codes there, #1521Added
semantic_schema,semantic_auth, andexclude_semantic_authendpoint, controller, settings, and metadata parameters to disable all semantic schema generation or semantic auth injection respectively, #1586Added
json_schema_kwargsattribute toPydanticSchemaGeneratorto pass extra keyword arguments likeby_alias,union_format, andschema_generatortopydantic’sTypeAdapter.json_schema, #1462Added
schema_hookclass method toMsgspecSchemaGeneratorto customize JSON schema generation for custom types, #1462Added
json_schema_dialectattribute toOpenAPIConfig.OpenAPI.json_schema_dialectexisted, but there was no way to set it, so thejsonSchemaDialectfield was never generated, #1486URL parameters of
re_path()routes now havepatternin their schema, it is copied from the sub-pattern of the matching named group:r'^v(?P<version>\d+)/$'documentsversionas{'type': 'string', 'pattern': '^(?:\d+)$'}, #1439slugandpathurl converters now describe themselves in the schema:slugadds thepatternof its own regex,pathadds adescriptionabout slashes, #1441Set-Cookieresponse headers now take their example value from the regular example generation, so they respectSettings.openapi_examples_seedand produce no example at all when it is disabled. They used to always document a static<name>=123, #1485contentofResponse,RequestBody,Parameter, andHeadernow also acceptsReferencevalues, so they can point at#/components/mediaTypes/, #1485Added all formats from the OpenAPI Format Registry to
OpenAPIFormat, #1489Allow all bool values in
dmr.openapi.objectsalso acceptNone, #1437Added
semantic_schemamodule withSecurityProviderinterface, #1521Added
SecurityRequirementMergerinterface andOrSecurityRequirementMergerdefault implementation tosemantic_schemamodule, #1499
Internals#
Added class-level overrides for
OpenAPIContextgenerators, registries, andConfigMerger, allowing custom operation ID generation and schema customization through context subclasses, #1461, #1487, #1556Added
dmr.metadata.MergeableMetadatabase class. Subclass it to define how your ownAnnotatedmetadata combines when it is placed on members of a union type.ResponseSpecMetadatais the first one to use it, #1460
Bugfixes#
Controllers and endpoints#
Fixed
@modifyand@validatetypes: nowtags,servers, andextra_responsesare typed asSequence, not aslist, #1563Fixed typing error in callable cases of
@modify.lazyand@validate.lazy, #1607
Components and serializers#
get_annotated_metadatanow unwraps type aliases, which fixes every metadata lookup we do: component parsers,ResponseSpecMetadata,ParameterMetadata,MediaTypeMetadata, and conditional types. Most visibly, a component hidden behind atype X = Body[User]alias (or aTypeAliasTypeobject) used to be silently dropped fromcomponent_parsers, and the endpoint then failed with aTypeErrorabout a missing argument on every request.X: TypeAlias = Body[User]was never affected: it is a plain assignment in runtime. Aliases of aliases and subscripted generic aliases are unwrapped as well, #1460__dmr_split_commas__now strips the optional whitespace around each','when splitting a header value.X-Tag: 1, 2used to be parsed as['1', ' 2'], which failed validation forlist[int]and silently kept a leading space forlist[str]. RFC 9110 treatsa, banda,bas the same list, #1526Fixed that
pydanticserializer was not dumping@dataclassinstances correctly withoutmsgspecinstalled, #1560
Auth and security#
Fixed OpenAPI generation for cookie-based auth classes, now we don’t add
csrfauth requirement to safe methods, #1572Fixed reusable views
error_modeldefinition for401response, #1573Fixed missing
@sensitive_variables()decorator on critical security parts inauthand tokens’ methods, #1552
Throttling#
Fixed Redis throttling reset values sometimes being 1 second above the configured window due to clock skew between Redis server time and Python time. The Redis backend now uses the
ttlreturned by Redis directly instead of computing an absolute timestamp, #1308
Routing#
Fixed
Router.include()andbuild_schemaraisingKeyErrorfor routes with an empty pattern, likeRouter('users', [path('', ...)]). Router metadata was stored under/users/, while the schema generation looked it up as/users, #1657
Error handling#
Now prefixes like
api/inbuild_404_handlerandbuild_500_handleronly cover full URLs like/api/v1and do not cover partials like/apiary/v1, #1606
OpenAPI#
OpenAPI schema generation is now deterministic: every generated mapping is sorted by a stable key instead of the definition order, #1557
Fixed seeded OpenAPI examples depending on
PYTHONHASHSEED.Controller.api_endpointswas built in the iteration order ofallowed_http_methods, which is afrozenset, so endpoints got their examples in a different order in every process. Nowapi_endpointsis sorted by controller method names, #1629Fixed every autogenerated OpenAPI example of the same type getting the same value. The factory was reseeded before each example, which restarted its random stream, so all strings in a schema came out equal. It is now seeded once per schema build: examples of one schema differ from each other, and a schema no longer depends on how many examples were generated before it, #1548
Fixed seeded OpenAPI examples of
datetime,date, andtimechanging with the current time. Faker’s defaults end at the current time, now these examples are generated between2000-01-01and2026-01-01.timedeltaexamples are not alwaysP0Danymore, #1632Fixed generated OpenAPI examples that are
Nonebeing dropped, for example, for-> Noneand-> int | Noneresponses.dmr.openapi.mappers.example.generate_examplenow returnsEMPTYinstead ofNonewhen there’s no example, #1626Fixed
default: nullbeing dropped from the OpenAPI schema, for example, forfield: str | None = Nonemodel fields. The same was true forconst: nullandexample: null, #1619Fixed models that are only used as
Query,Headers,Cookies,Path, andFileMetadatacomponents, being added tocomponents.schemasof the OpenAPI schema. Such models are inlined and never referenced, now only components that are referenced from the final schema are registered, #1647Path parameters now always have
required: truein the OpenAPI schema, even when theirPathmodel fields have default values. Previously, such parameters generated an invalid schema, #1610SchemaRegistry.maybe_resolve_referencenow puts the keywords that sit next to$ref, likedefaultanddescription, on top of the component’s own schema without modifying the component itself. Customx-schema extensions are now kept through loading and dumping, for bothpydanticandmsgspec, #1491Fixes that endpoints with
auth=Noneinherited document-levelOpenAPIConfig.securityrequirements, now they emitsecurity: [], #1497Fixed nested
Routerpatterns OpenAPI parameter spec generation, #1502ResponseSpecMetadataon a member of a union return type is no longer ignored:-> Annotated[User, ResponseSpecMetadata(...)] | strnow documents the headers and cookies thatUserresponses carry. They are documented asrequired=False, because astrresponse is returned without them. Annotate the whole union (Annotated[User | str, ResponseSpecMetadata(...)]) to require them everywhere, that behaviour is unchanged, #1460Fixed
exampleofNewHeaderandHeaderSpecnot being in the OpenAPI schema. Now it is set on theHeaderobject, and such headers don’t get generated examples, #1627Fixed an empty
descriptionbeing generated for the mergedrequestBodyin the OpenAPI schema, when a controller has several request body components and none of them provides a description, #1495Default OpenAPI
operation_idgeneration now preserves capitalization in controller class names, #1500Fixed a bug that custom
formatvalues, likename-emailfrompydantic.NameEmail, were raisingValueErrorduring schema generation, #1489Fixed a bug that empty
schemasandsecuritySchemescomponent containers were rendered in the OpenAPI, #1521Fixed that
linksandcallbacksin endpoints definitions can be anyMapping, not justdict, #1576load_schemanow fillsSchema.anchor,Schema.commentandSchema.schema_urifrom$anchor,$commentand$schema. All three attributes existed and were alwaysNone, so those keywords were lost on the way in.$refis left alone on purpose: it needs theReference | Schemahandling fixed in every call site at once, #1490Fixed a bug that
load_schemawas not loadinganchor,comment, andschema_urifields, #1490
Misc#
Agent skills#
Agent skills now ship inside the
dmrpackage asdmr/.agents/skills, souvx library-skillsinstalls the skills matching the installed version into any project, the Claude Code marketplace keeps workingAdded
dmr-upgradeagent skill with the migration prompts of the three latest breaking releasesSplit the
dmrskill into a shortSKILL.mdand topic references, fixed skill descriptions to trigger on natural requests, addedagentskills validatetojust lintFixed
dmr-from-dj-rest-authentry in the Claude Code marketplace
Docs#
Docs: every page is also published as Markdown (
<page>.md), theM↓button next to its title copies it,llms-full.txtnow includes the code of every example,llms.txtnow carries the version
0.15.0 (2026-09-11)#
Breaking changes#
check_authofRefreshTokenSyncController,RefreshTokenAsyncController,VerifyTokenSyncController, andVerifyTokenAsyncControllernow takes the decodedtokenas its second argument and typesuserasAbstractBaseUserinstead ofAny. It used to differ fromcheck_authof the auth classes, which is whyJWTokenBlocklistSyncMixinandJWTokenBlocklistAsyncMixincould not be mixed into these controllers, #1290dmr.security.jwt.viewsis now a package ofbase,body, andcookiemodules. Every public name is still importable fromdmr.security.jwt.views, only the private bases moved, #1290JWToken.encodenow raisesJWTokenError(a token-layer semantic error) instead of the HTTP-layerInternalServerErrorwhen encoding fails. The error is converted back toInternalServerErrorat the HTTP boundary inBaseTokenController.create_jwt_token, so request-serving paths keep their 500 contract while non-request callers (management commands, Celery tasks, test factories) get a meaningful exception. The originalpyjwtcause is preserved in the traceback (no morefrom None).Renamed
json_dumptojson_dumpsindmr.openapi.dumpanddmr.internal.jsonto follow standard string-serialization conventions, #1399Removed
QueryTokenSyncAuthandQueryTokenAsyncAuthauth classes, because they were insecure, you can use older existing versions, #1288Removed
FileResponseSpec.file_body, useFileResponseSpec.return_typeinstead, #1278Removed
FileMetadataComponent.schema_metadata, now we useSupportsFileParsing.schema_metadatainstead, #1278SSEventdoes not checkidandeventfields for null bytes and line breaks on creation anymore, this is now a part of the events validation pipeline, so it respectsvalidate_events, #1329check_event_fieldnow raisesValidationErrorinstead ofValueError, so a wrong field is streamed as anerrorevent and does not break the whole stream, #1329401responses now carry aWWW-Authenticateheader as required by RFC 9110, when the endpoint’s auth can express a challenge. Note that browsers show their native login prompt on aBasicchallenge, passwww_authenticate=Falseto the auth instance to opt out, #1334SyncAuthandAsyncAuthnow have an abstractwww_authenticate_challengeproperty, so custom auth classes must say what challenge they send, or returnNonewhen they cannot be expressed as one, #1334Removed init-only
leewayargument ofJWToken, it is only used byJWToken.decodenow, #1324JWTokendoes not validateexpandiaton creation anymore, nowJWToken.encodevalidates them instead, #1324JWTokendoes not allow dataclass instances inextrasanymore, #1408Throttling cache keys are now hashed to keep their length bounded, #1337
HTTP Basic Auth credentials are no longer URL-decoded, so percent-encoded characters such as
%40are preserved as-is, #1363HttpBasicSyncAuthandHttpBasicAsyncAuthnow require theauth_schemeheader prefix, it isBasicby default and is matched exactly, credentials sent without it are not accepted anymore. Passauth_scheme=''to keep reading prefixless credentials like the older versions did, #1330HttpBasicSyncAuthandHttpBasicAsyncAuthnow raiseNotAuthenticatedErrorwhen credentials have the rightauth_schemeprefix, but cannot be decoded, previously the next auth in the chain was tried, #1330ToJsonKwargsandToModelKwargsare nowclosed=Truetyped dicts, #1430
Features#
Added
@modify.lazyand@validate.lazydecorators for reusable controllers, #1409Added
exclude_validate_responsessetting, controller attribute, and@modify/@validateargument to skip response validation for the given status codes, like500, #1370Added
WWW-Authenticatesupport for auth classes that read theAuthorizationheader:HttpBasicSyncAuth,HttpBasicAsyncAuth,HeaderJWTSyncAuth,HeaderJWTAsyncAuth,HeaderTokenSyncAuth, andHeaderTokenAsyncAuth. Cookie-based and custom-header auth send no challenge, because there is none to express. Configurable via the newwww_authenticate=andrealm=arguments and theSyncAuth.www_authenticate_challengeproperty, #1334Added
dmr.security.add_www_authenticatefunction to add theWWW-Authenticateheader to aNotAuthenticatedError.global_error_handlercalls it, so replacing that handler is how you change or drop this behavior, #1334Added
CookieJWTSyncAuthandCookieJWTAsyncAuthto read JWT tokens from cookies instead of headers, #1193Added
CookieObtainTokensSyncController,CookieObtainTokensAsyncController,CookieRefreshTokensSyncController,CookieRefreshTokensAsyncController,CookieLogoutSyncController, andCookieLogoutAsyncControllerto issue, rotate, and drop JWT tokens as cookies thatCookieJWTSyncAuthandCookieJWTAsyncAuthread back. Cookies arehttponly,secure, andsamesite='lax'by default, the refresh cookie is scoped to the refresh endpoint, and refresh and logout enforce CSRF, #1290Added
DEFAULT_ACCESS_COOKIEandDEFAULT_REFRESH_COOKIEconstants todmr.security.jwt.auth.cookie, they are the default cookie names of both the cookie auth and the cookie views, #1290Added
NewCookie.from_specto build a response cookie from itsCookieSpec, so runtime cookies of@validateendpoints cannot drift away from the spec they are validated against, #1290JWTokenBlocklistSyncMixinandJWTokenBlocklistAsyncMixincan now be mixed into the refresh controllers, both the body and the cookie ones, so a blocklisted token cannot buy a new pair of tokens. They used to only work with auth classes, #1290Added
get_usertoRefreshTokenSyncControllerandRefreshTokenAsyncController, the user lookup used to be inlined intorefreshwith no way to override it alone, #1290Added
response_headersandresponse_headers_specto the cookie controllers, sovalidate_speccan be redefined without repeating theCache-Controlheader by hand, #1290CookieSpec.pathandNewCookie.pathnow accept lazy strings, so a cookie can be scoped to areverse_lazyurl, #1290Added
HeaderJWTSyncAuthandHeaderJWTAsyncAuth,JWTSyncAuthandJWTAsyncAuthare kept as their aliases, #1193Added
XSessionTokenSyncAuthandXSessionTokenAsyncAuthto authenticatedjango-allauthheadless session tokens, you would need to installdjango-allauthseparately, #1193Added
querymethod support forPathItemOpenAPI 3.2.0 spec, #1300Added
Parser.validatemethod for import-time validation of parser configuration, #1304Added
Renderer.validatemethod for import-time validation of renderer configuration, #1306Added
Router.ignore_from_specto exclude entire router subtrees from the generated OpenAPI specification, #1309Added
FileMetadataconditional types, #1278Added
SupportsFileParsing.schema_metadatamethod to customize file schema from the parser, #1278Added
validate_event_fieldsto theSSEStreamingValidatorpipeline, it checksidandeventfields of all event types, including custom ones, #1329Added
JWToken.validate_issued_claimsmethod to customize the checks we run before signing a token, #1324Added
security.NO_STORE_HEADERS, all auth views we ship now return theCache-Control: no-storeheader and document it in the OpenAPI schema, #1335Added
@modify.lazysupport andmodify_specmethod to all all views we ship, #1423JWT tokens are now encoded and decoded with
msgspecwhen it is installed, which makesJWToken.encodeabout 1.3x andJWToken.decodeabout 1.15x faster. Note that only json-native values inJWToken.extrasare guaranteed to be encoded identically with and withoutmsgspec, #1390Optimized
JWTokenencoding and decoding algorithms, #1408Added
BaseThrottleSyncBackend.lockandBaseThrottleAsyncBackend.lockto control the in-process lock forincr,SyncRedisandAsyncRedisskip it because Lua scripts are atomic, #1339OpenAPI.convert()now caches and returns the same dictionary per instance, #1402accepted_typeandaccepted_headerare faster now, media types without parameters skip the regex based parsing of parameters and of theqweight entirely, #1407dmr.openapi.OpenAPInow hascache_clearmethod to drop all cached internal state, #1431
Bugfixes#
Fixed
CookieSpec(max_age=0)never matching the response cookie it describes,0was treated as a missing value. It is how a cookie is dropped, so it could not be described at all, #1290Fixed
PydanticFastSerializerfailing to validate an empty response body, so@validateendpoints that return204with it raised a serialization error instead of the response, #1290Fixed
@modifyand@validatetyping: passing asyncauthorthrottlingto a sync endpoint (and sync ones to an async endpoint) is now a type error,linksis now also accepted by all@modifyoverloads, #1393Fixed
@validatereturn type inference for all the type-checkers, now it does not change the originalHttpResponseBasesubtype, #1409Fixed
EndpointMetadata.validate_responsesbeing annotated asbool | None, it is always resolved from the settings, the controller, and the endpoint, #1370Fixed
responsesofObtainTokenSyncController,ObtainTokenAsyncController,DjangoSessionSyncController, andDjangoSessionAsyncControllerbeing narrowed to a fixed-size tuple, subclasses could not change it, #1371Added missing
@sensitive_variablesdecorator to all auth views, so credentials and tokens are hidden in error reporting middlewares and logs, #1323Parsed request data is no longer stored as a local variable of the endpoint’s frame, because it was shown in error reports of any endpoint, #1323
Fixed
JWToken.encoderaising a bareTypeErrorwhenextrascannot be serialized to json, #1373JWT auth, refresh, and verify now return
401instead of500when the token subject cannot be a value of the user lookup field, for example a non-numericsubwith the default integerpk, #1284Fixed
DjangoSessionSyncAuth,DjangoSessionAsyncAuth,CookieTokenSyncAuth, andCookieTokenAsyncAuthto check CSRF only when this auth class is actually used and not skipped, #1289Allow using lazy translations in many places, like
Controller.summary,ResponseSpec.description,HeaderSpec.description, #1298Fixed
Router.includedroppingtagsanddeprecatedmetadata, #1299Fixed
PathItemto supportadditionalOperationsfield for custom HTTP methods (likePURGE,LINK), #1300Fixed a bug when non-file parsers were listed in the response schema for file responses, #1278
Fixed
SimpleRatethrottling reports with redis backends, it used to error on missing throttling stats, #1333SSE events are not validated at all when
validate_eventsisFalse,idandeventfields used to be checked even then, #1329Custom SSE event types now have their
idandeventfields validated just likeSSEventdoes, #1329Fixed
JWToken.decodevalidatingexpandiattwice, nowleeway,verify_exp, andverify_iatare respected and invalid tokens return401instead of500, #1324Fixed the JWT blocklist being silently bypassed by tokens without
jti,JWTokenBlocklistSyncMixinandJWTokenBlocklistAsyncMixinnow addjtitorequire_claims, so such tokens get401. Blocklisting them returns401as well instead of failing with a databaseIntegrityError, #1322JWT authentication now rejects refresh tokens when access tokens are expected, #1320
Fixed a bug when request data might be copied in
parse_as_post, #1328Fixed postponed annotation resolution for wrapped endpoint functions whose decorators are defined in another module, #1417
Detailed CSRF failure reasons are now included in error responses only in debug mode and not in production, #1332
Empty response body checks now cover
1xx,205 Reset Content, andHEAD, #1340Media types with
q=0in theAcceptheader are not selected for the response anymore,q=0means “not acceptable”, so such requests now get a406response. This matchesdjango.http.HttpRequest.accepted_types, #1407Fixed
Acceptheaders with out of rangeqvalues returning500,q=infused to raiseOverflowErrorwhile sorting media types. Out of range weights are now discarded and treated asq=1, just likedjango.http.request.MediaTypedoes, #1407
Misc#
Documented that
500must be described or excluded from validation, when running withvalidate_responsesenabled, #1370Fixes AI docs and plugin install instructions, #1311
Documented safe use of user-provided redirect targets with
RedirectTo, #1326Added
dmr-from-dj-rest-authagent skill to migratedj-rest-authinstallations todjango-modern-restanddjango-allauthheadless, #1193Documented why and how to remove expired
BlocklistedJWTokenandTokenrows on a schedule, #1336Added a guide on writing your own auth class for transports we don’t ship, #1366
0.14.0 (2026-08-14)#
Breaking changes#
Refactored public
routing.ExternalURLinto protected_ExternalURL, useexternal_path()function instead, #1262
Features#
Added initial
tysupport, #1257Added new checks for HTTP spec validation, #1341
Added support of reusable controllers with
@validate, #1259Added default value to
prefixparameter inRouter.__init__, #1267Added
to_urlpatternsfunction to includeRouterinstances intourlpatternsor other routers, #1262
Bugfixes#
OpenAPIConfig.openapi_versionnow support anystrargument, #1252
Misc#
Improve reusable controllers docs, #1259
0.13.0 (2026-08-10)#
This release was focused on better routing and better OpenAPI support. See https://github.com/wemake-services/django-modern-rest/releases/tag/0.13.0
Breaking changes#
Since this release, we would only publish migration prompts on the releases page: https://github.com/wemake-services/django-modern-rest/releases
Schema.thenis renamed to beSchema.schema_thento be consistent with other similar names, #1221dmr.openapi.objects.openapi.convertfunction is renamed and moved todmr.openapi.mappers.schema_normalization.dump_schema, #1221dmr.openapi.objects.openapi.normalize_keyanddmr.openapi.objects.openapi.normalize_valuefunctions are removed, #1221dmr.openapi.objects.openapi.ConvertedSchemais renamed and moved todmr.openapi.mappers.schema_normalization.DumpedSchema, #1221dmr.openapi.views.base.DumpedSchemais removed, it was just astrtype alias, #1221dmr.openapi.objects.OpenAPIis moved todmr.openapi.openapi.OpenAPI, #1222rebuild_namespaceparameter inPydanticSerializer.from_pythonwas renamed toextra_namespace, #1222Changed
skip_validationparameter to be kw-only onOpenAPIView.as_view()and all its subclasses, #1229Renamed
dmr.controller.Controller.get_path_itemtoget_schema, so all methods will be consistent, #1238Removed
dmr_assert_throttlinganddmr_assert_async_throttlingfixtures frompytest, because there was ever no need to make them fixtures, use regular functions instead, #1245Removed
dmr.test.typesmodule, because it was only needed fordmr_pytestthrottling fixtures, #1245Moved
dmr.test.types.ThrottlingWhentodmr.test.throttling, #1245
Features#
Django 6.1 official support, #1214
Added
--skip-validationto thedmr_export_schemamanagement command, #1225Added
extra_namespaceparameter toBaseSerializer.from_pythonand all its existing subclasses, #1222Added an ability to load external OpenAPI schemas into our typed dataclasses, #1222
Added
external_path()function, so we can load external views, #1239Added
Router.include()method to include one router into another one, #1244Added an option to skip some controllers / endpoints from the OpenAPI spec, #1238
Added
dmr.test.disabled_authtest helper to disable auth to speed up tests, #1216
Bugfixes#
Fixed a bug that
OpenAPIConfig.componentswere silently ignored when defined with custom user’s data, #1229Fixed missing
$ref,$anchor,$comment, and$schemafields inSchema, #1232Fixed
OpenAPIFormat.IRIvalue, #1228
Misc#
Improved testing docs, #1216
0.12.1 (2026-07-31)#
Bugfixes#
Added missing
@sensitive_post_parametersdecorator to all auth views, #1189Fixed
@endpoint_decoratorpassing incorrect parameters to the endpoint function, #1189Fixed
@endpoint_decoratornot working properly with async endpoints, #1189
0.12.0 (2026-07-30)#
Features#
Added “Opaque Token” auth backend, #1051
Added
VerifyTokenSyncControllerandVerifyTokenAsyncControllerreusable controllers to verify JWT access tokens, #1129Added test helpers in
dmr.testfor asserting that endpoints are throttled, #1167
Bugfixes#
Streaming with
streaming_ping_secondsno longer leaves the pending ping timer task behind on every produced event, #1046Fixed
500error on request bodies containing invalidutf-8bytes insidemsgspec’s json and msgpack parsers, now400is correctly returned, #1135Properly warn users that use our
pytestplugin, but do not havepytest_djangoinstalled, #1167CSRF is now ensured before any other actions in Django-Session auth, #1180,
Fixed that
jwtextra was required inthrottlingcode, #1178Fixed many places that were missing
__slots__, #1185
Misc#
Enabled stricter
__slots__checks in CI, #1183Improved
pytestplugin docsAdded
nanodjangoand µDjango examples to the micro-framework docs page, #1049
Version 0.11.0 (2026-06-27)#
In this release we significantly improved the DX of defining common auth and throttling types in the settings that could be used for both sync and async controllers at the same time.
Breaking changes#
Dropped macOS x86_64 wheel support
Dropped Django 4.2 support
Features#
Added
SyncOrAsyncThrottleclass to apply a single throttle rule to both sync and async endpoints via global settings, #1075Added
SyncOrAsyncAuthclass to apply a single auth rule to both sync and async endpoints via global settings, #1102
Bugfixes#
Fixed several compatibility issues on older Django 5.x versions, #1096
Fixed
LeakyBucketthrottling algorithm corner cases, #1044Fixed OpenAPI schema generation for enum values used in path, query, header, and cookie parameters, #1059
Fixed that
dmr.plugins.msgspec.msgpackcache was not cleared onclear_settings_cachecalls
Misc#
Renamed the default OpenAPI title from
Django Modern ResttoYour Awesome Projectand documented allOpenAPIConfigfields, #1021
Version 0.10.0 (2026-05-26)#
Breaking changes#
Breaking:
FileResponseSpec()now describes inline file responses and does not includeContent-Dispositionby default. UseFileResponseSpec(as_attachment=True)when returning Django’sFileResponse(..., as_attachment=True), #1020
Migrations prompt#
User-facing changes:
Change all existing `dmr.files.FileResponseSpec` usages
to include `as_attachment=True` parameter.
Features#
Added support for JSON Schema 2020-12 dynamic reference keywords (
$dynamicRef,$dynamicAnchor,$defs) in OpenAPI schema generation. These can now be propagated throughextra_json_schemafor generic type definitions, #1039
Misc#
Use
typing_extensions.Sentinelfordmr.types.EMPTY, #995pyrefly@1.0official support, #1015mypy@2.0andmypy@2.1official support, #1013
Version 0.9.0 (2026-05-07)#
Features#
Added
throttling_allow_unsafe_cachesetting to control whether unsafe cache backends (LocMemCache,DummyCache) are allowed for throttling. EmitsUnsafeCacheBackendWarningby default, raisesImproperlyConfiguredwhen explicitly set toFalse, #978Added
--no-ensure-asciiflag todmr_export_schemamanagement command
Bugfixes#
Fixed how
msgspecgeneratesnullinanyOf, it is now always the last item, #990Fixed minimum allowed django version, #1008
Fixed
ImportErrorwhile using withdjango==5.2.0, #1006
Version 0.8.0 (2026-04-26)#
Breaking changes#
Breaking: Renamed
APIRedirectErrortoRedirectTo, #922Breaking: Split
BaseThrottleBackendintoBaseThrottleAsyncBackendandBaseThrottleSyncBackend, #942Breaking: Renamed
DjangoCacheintoSyncDjangoCache, addedAsyncDjangoCache, #942Breaking: Changed
BaseThrottleBackendAPI: now it requires.incrand.getmethods, the first one should ideally be an atomic increment, the second one is for reading objects only, #942Breaking: Removed
BaseThrottleAlgorithm.recordmethod, nowBaseThrottleAlgorithm.accessmust also record accesses. This will help to make throttling more atomic, #942
Migrations prompt#
User-facing changes:
Apply this change to the code that uses `django-modern-rest`:
1. Replace `dmr.response.APIRedirectError` with `dmr.response.RedirectTo`
2. Replace `dmr.throttling.backends.DjangoCache`
with `dmr.throttling.backends.SyncDjangoCache` for sync throttles
and with `dmr.throttling.backends.AsyncDjangoCache` for async throttles
Features#
Added
SyncRedisandAsyncRedisthrottling backends, #977Added
RefreshTokenSyncControllerandRefreshTokenAsyncControllerto issue new access/refresh token pairs from a valid refresh token, #907Added
validate_negotiationmetadata flag, so we can explicitly validate, that returned response followed the negotiation process, #711Added
accepted_headeras a faster alternative todjango’sHttpRequest.accepts, #854Added
dmr_export_schemamanagement command to export OpenAPI schemas, #909
Bugfixes#
Fixed OpenAPI schema for Django session auth when
CSRF_USE_SESSIONS=True, #674Fixed that
itemSchemawas possible to be rendered in OpenAPI3.0.0and3.1.0, #908Fixed response validation when global error handler returns
HttpResponsewith a different content type than the negotiated renderer, #711Fixed
collectstaticfailure when usingManifestStaticFilesStorage, #927Fixed
datetimevalidation when using.to_response, #938Fixed a bug that
ObtainTokensAsyncControllerwas not setting therequest.auserattribute, #953Fixed a bug that
JWTSyncAuthwas not settingrequest.auser, #953Fixed
ResponseNegotiatorraisingNotAcceptableErroron streaming endpoints whenAccept: text/event-streamwas sent withoutapplication/json(the default browserEventSourcecase), which made 4xx/5xx error bodies and response validation crash with a 500 instead of rendering the configured non-streaming default, #962Fixed that original traceback was not shown for
BaseSchemaGenerator.get_schema, #961
Misc#
Optimized
dmr_clientanddmr_rftest fixtures to usemsgspecfor JSON encoding and decoding when available, #889 and #976Optimized how per-endpoint throttle locks are used, #942
Version 0.7.0 (2026-04-14)#
Breaking changes#
Removed public
OpenAPIView.dumpscustomization hook, #847 If you customized schema output forOpenAPIJsonView, subclass the concrete view and override.get()instead. For JSON output, usedmr.openapi.core.dump.json_dumpif you need the framework’s default serializerBreaking:
get_jwtis renamed torequest_jwt, #868Breaking:
ResponseSpecProvider.provide_response_specsis now an instance method, #877Breaking: new required
routerparameter added toEndpoint.get_schemaandController.get_path_item, #879
Migration Prompt#
Apply this change to the code that uses `django-modern-rest`:
1. Replace `OpenAPIView.dumps` usage with `dmr.openapi.core.dump.json_dump`
usage
2. Change `dmr.security.jwt.auth.get_jwt` function
to use `dmr.security.jwt.auth.request_jwt` instead, if user expects
to always get a token back, add `strict=True` argument
3. Change `provide_response_specs` class method to be instance method,
replace all `cls` usage with `self`
4. Add `router: Router` parameter to `Endpoint.get_schema`
and `Controller.get_path_item` methods
Features#
Added official PyPy 3.11+ support, #870
Added
dmr.throttlingpackage, #877Added
request.__drm_auth__on all successful auth workflows, #868Added
request_authhelper function, #868Added
AuthenticatedHttpRequesttype for betterrequest: AuthenticatedHttpRequest[User]type annotations in controllers, #888Added
strictparameter torequest_rendererandrequest_parser, added@overloads to both of these functions, #869Added
ResponseSpecMetadatatype to represent headers and cookies with annotations, useful for error models, #882Allow individual
OpenAPIviews to skip schema validation, #867Added endpoint validator to prevent sync and async generator HTTP endpoints, #843
Added CSP-friendly templates for shipped
OpenAPIUI views, #847SwaggerView,RedocView,ScalarView, andStoplightViewnow avoid inline scripts in DMR-managed templates. Final CSP compatibility still depends on the upstream renderer bundle.Added
tagsanddeprecatedparameters toRouterfor OpenAPI metadata, #872. All operations in a router can now be grouped and marked as deprecated.
Bugfixes#
Fixed that
OpenAPIwas revalidated on every.convertcall, #867Fixed missing
request.auser()afterJWTAsyncAuth, #884Fixed
ParameterMetadatamissing__slots__, #890Fixed
SSEventmissing__slots__, #901Fixed
SSEprotocol typing, #894Fixed a bug when we were treating controllers with no
api_endpointsas non-abstract, #894Fixed a bug when you were not able to subclass a controller with a serializer, #873
Misc#
Added
dmrskill for agents to write betterdjango-modern-restcode, #886Switched from
Maketojustas a command runner
Version 0.6.0 (2026-04-09)#
In this release we significantly increased the performance of pydantic
workflows by introducing PydanticFastSerializer.
No breaking changes in this release.
Features#
Added
PydanticFastSerializerto serialize and deserializejsonobjects directly, #830Added support for complex
pydanticfields insideTypedDict,@dataclass, etc models, when usingPydanticSerializerandmsgspecparsers / renderers, #842Introduced official
to_json_kwargsandto_model_kwargsclass-level API formsgspecandpydanticserializers, #842Added “Problem Details” or RFC-9457 support, #78
Added customizable
json_moduleparameter toJsonParserandJsonRendererto support alternative JSON backends likeorjson, #857
Bugfixes#
Fixed package metadata, #824
Fixed missing
style,phone,colorformats fromOpenAPIFormat, #842Fixes Django 5.2.13+ compat in
DMRAsyncRequestFactory, #853
Misc#
Improved “Plugins” section in the docs, #835
Bumped
msgspecto0.21.0, #856Added official
SECURITY.mdpolicy
Version 0.5.0 (2026-04-05)#
AKA “The first compiled version”.
This release will focus on better errors, performance, and stability.
No breaking changes in this release.
Features#
Added
mypycsupport for compiling parts of the framework to run significantly faster, for example our compiled content negotiation is now 35 times faster then the Django’s default one, #202 See our https://django-modern-rest.readthedocs.io/en/latest/pages/deep-dive/performance.html#mypyc-compilation docs about thatAdded older Django versions
4.2,5.0,5.1official support, #803Added official
NamedTuplesupport, #774Added
timezoneandpydantic-extra-typesdependencies with[pydantic]extra, #802Added
exclude_semantic_responsesoptions, #786Added an option to override
exclude_semantic_responsesandno_validate_http_specsettings withNoneAdded a new way to resolve annotations for controllers:
AnnotationsContext, #787Added
yamlview for OpenAPI schema, #745
Bugfixes#
Fixed
StreamingValidatorswallowing errors whenvalidate_eventswasTrue, but no event model was resolved, #780Fixed
dataclassinstances serialization withPydanticSerializerwithoutmsgspecjson renderer, #795Fixed missing
passwordOpenAPI format, #805Fixes incorrect settings validation, #821
Misc#
Added
QuerySettutorial, #792Migrated from
poetrytouvfor dependency managementSet up automated secure publishing to PyPI, #823
Added CodSpeed integration for continuous performance monitoring, #810
Version 0.4.0 (2026-03-29)#
AKA “The first version that I enjoy”.
Breaking changes#
We changed how components are defined in controllers, #738 Now components will be defined in method parameters, not in base classes.
We removed
dmr.controller.Blueprint, because it is not needed anymore. It was used to compose different classes with different parsing strategies. Since, it was only used for different parsing rulesWe removed
dmr.routing.compose_blueprintsfunction, because there noBlueprints anymore :)We completely changed our SSE and streaming API, see #736 Old API was removed, new one was introduced.
dmr.ssepackage was moved todmr.streaming.sse
We always ship AI prompts to all breaking changes. So, it would be easier for you to migrate to a newer version using AI tool of your choice.
Migration Prompt#
To migrate django-modern-rest to version 0.4.0 and above, you need to:
Load the latest documentation from https://django-modern-rest.readthedocs.io/llms-full.txt
Convert component parsing from old class-based API to new method-based API. Before:
from dmr import Blueprint, Body
from dmr.routing import compose_blueprints
from dmr.plugins.pydantic import PydanticSerializer
class UserCreateBlueprint(
Body[_UserInput], # <- needs a request body
Blueprint[PydanticSerializer],
):
def post(self) -> _UserOutput:
return _UserOutput(
uid=uuid.uuid4(),
email=self.parsed_body.email,
age=self.parsed_body.age,
)
class UserListBlueprint(Blueprint[PydanticSerializer]):
def get(self) -> list[_UserInput]:
return [
_UserInput(email='first@example.org', age=1),
_UserInput(email='second@example.org', age=2),
]
UsersController = compose_blueprints(UserCreateBlueprint, UserListBlueprint)
To:
from dmr import Controller, Body
from dmr.plugins.pydantic import PydanticSerializer
class UsersController(Controller[PydanticSerializer]):
def get(self) -> list[_UserInput]:
return [
_UserInput(email='first@example.org', age=1),
_UserInput(email='second@example.org', age=2),
]
def post(self, parsed_body: Body[_UserInput]) -> _UserOutput:
return _UserOutput(
uid=uuid.uuid4(),
email=self.parsed_body.email,
age=self.parsed_body.age,
)
Replace all
Blueprintandcompose_blueprintsreferences with a new API: Instead you must useControllerand different methods under a single classNow, change all
@sse-based controllers to newSSEControllerAPI, from:
from collections.abc import AsyncIterator
import msgspec
from django.http import HttpRequest
from dmr.components import Headers
from dmr.plugins.msgspec import MsgspecSerializer
from dmr.sse import SSEContext, SSEResponse, SSEvent, sse
class HeaderModel(msgspec.Struct):
last_event_id: int | None = msgspec.field(
default=None,
name='Last-Event-ID',
)
async def produce_user_events(
request_headers: HeaderModel,
) -> AsyncIterator[SSEvent[str]]:
if request_headers.last_event_id:
yield SSEvent(f'starting from {request_headers.last_event_id}')
else:
yield SSEvent('starting from scratch')
@sse(MsgspecSerializer, headers=Headers[HeaderModel])
async def user_events(
request: HttpRequest,
context: SSEContext[None, None, HeaderModel],
) -> SSEResponse[SSEvent[str]]:
return SSEResponse(produce_user_events(context.parsed_headers))
To:
from collections.abc import AsyncIterator
import msgspec
from dmr.components import Headers
from dmr.plugins.msgspec import MsgspecSerializer
from dmr.streaming.sse import SSEController, SSEvent
class HeaderModel(msgspec.Struct):
last_event_id: int | None = msgspec.field(
default=None,
name='Last-Event-ID',
)
class UserEventsController(SSEController[MsgspecSerializer]):
def get(
self,
parsed_headers: Headers[HeaderModel],
) -> AsyncIterator[SSEvent[str]]:
return self.produce_user_events(parsed_headers)
async def produce_user_events(
self,
parsed_headers: HeaderModel,
) -> AsyncIterator[SSEvent[str]]:
if parsed_headers.last_event_id is None:
yield SSEvent('starting from scratch')
else:
yield SSEvent(f'starting from {parsed_headers.last_event_id}')
Replace old
dmr.sseimports with newdmr.streaming.ssealternatives
Features#
Added
@attrs.defineofficial support, #706Added
msgpackparser and renderer, #630Added
JsonLinesorJsonLsupport, #607Added
pingevents toSSEstreaming, #606Added
SSEsupport for non-GETmethods,Bodycomponent parsing, #736Added
i18nsupport for user-facing error messages using Django’sgettext_lazy, #426Added
MediaTypevalidation for the defaultencodingfield and OpenAPI 3.2itemEncodingandprefixEncodingfields, #695Added
MediaTypeMetadatametadata item to set required parameters for theMediaTyperequest body forBodyandFileMetadatacomponents, #695 and #698Added support for Swagger, Redoc, and Scalar CDN configuration, #678
Added TraceCov integration for API coverage tracking in test suites, including automatic request tracking for
dmr_clientanddmr_async_client, #735.Added Stoplight Elements UI for OpenAPI documentation, #748
Added better
settingsfixture support forpytestplugin, #769
Bugfixes#
Fixed
SSEcontrollers__name__and__doc__generation via@ssedecorator, #700Fixed a bug where
FileMetadatarendered list of schemas incorrectly, #698
Misc#
Added
$dmr-openapi-skeletonAI agent skill, #693Added
$dmr-from-django-ninjaAI agent skill, #693Added
$dmr-from-drfAI agent skill, #744Added ETag usage docs, #699
Added multiple translations for the user-facing error messages, #718
Now
MsgspecJsonRendererandJsonRendererproduce the samejsonstring in terms of whitespaces, #736
Version 0.3.0 (2026-03-17)#
Features#
Added
FileResponseSpecand improvedFileResponseschema generation, #682Added
encoding:support for file media types inFileMetadata, #682
Bugfixes#
Fixed OpenAPI schema for custom HTTP Basic auth headers, #672
Fixed JWT claim validation and error handling in
JWToken.decode, #675Fixed incorrect OpenAPI schema for
FileResponse, #682Fixed that
404was not listed in the endpoint’s metadata, when usingURLRoutewithoutPathcomponent, #685Fixed that
404was not documented in the OpenAPI whenPathcomponent was not used, butURLPatternhad parameters, #685Fixed
ValueErroron operation id generation, #685
Misc#
Improved “Returning responses” docs, #684
Version 0.2.0 (2026-03-15)#
Features#
Breaking: Renamed
schema_onlyparameter toskip_validationAdded
dmr.routing.build_500_handlerhandler, #661Added support for
__dmr_split_commas__inHeaderscomponent, #659Added support for native Django urls to be rendered in the OpenAPI, now OpenAPI parameters will be generated even without
Pathcomponent, #659Do not allow
'\x00',\n, and\rasSSEvent.idandSSEvent.event, #667
Bugfixes#
Fixes how
SSEResponseSpec.headers['Connection']header is specified, #654Fixed an
operation_idgeneration bug, #652Fixed a bug with parameter schemas were registered with no uses in the OpenAPI
Fixed a bug, when request to a missing page with wrong
Acceptheader was raising an error. Now it returns 406 as it should, #656Fixed fake examples generation, #638
Fixed OpenAPI schema for custom JWT auth parameters, #660
Fixed
Bodycomponent was not able to properly parse lists withmultipart/form-dataparser, #644Fixed that not options were passed to
JWToken._build_options, #671
Misc#
Improved components and auth docs
Version 0.1.0 (2026-03-13)#
Initial release