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.
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
drm.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 forBodyandFileMedatacomponents, #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
Bugfixes¶
Fixed
SSEcontrollers__name__and__doc__generation via@ssedecorator, #700Fixed a bug where
FileMetadatarendered list of schemas incorrectly, #698Fixed that we were using
typing.get_type_hintsin some places, now always usingtyping_extensions.get_type_hints, #768
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