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 rules -
We 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 class - Now, 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, #706 - Added
msgpackparser and renderer, #630 - Added
JsonLinesorJsonLsupport, #607 - Added
pingevents toSSEstreaming, #606 - Added
SSEsupport for non-GETmethods,Bodycomponent parsing, #736 - Added
i18nsupport for user-facing error messages
using Django'sgettext_lazy, #426 - Added
MediaTypevalidation for the defaultencodingfield
and OpenAPI 3.2itemEncodingandprefixEncodingfields, #695 - Added
MediaTypeMetadatametadata item to set required parameters
for theMediaTyperequest body
forBodyandFileMedatacomponents, #695 and #698 - Added support for Swagger, Redoc, and Scalar CDN configuration, #678
- Added TraceCov integration for API coverage tracking in test suites,
including automatic request tracking fordmr_clientand
dmr_async_client, #735. - Added Stoplight Elements UI for OpenAPI documentation, #748
Bugfixes
- Fixed
SSEcontrollers__name__and__doc__generation
via@ssedecorator, #700 - Fixed a bug where
FileMetadatarendered list of schemas incorrectly, #698 - Fixed 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, #693 - Added
$dmr-from-django-ninjaAI agent skill, #693 - Added
$dmr-from-drfAI agent skill, #744 - Added ETag usage docs, #699
- Added multiple translations for the user-facing error messages, #718
- Now
MsgspecJsonRendererandJsonRendererproduce
the samejsonstring in terms of whitespaces, #736
New Contributors
- @cmdtorch made their first contribution in #707
- @Act0r1 made their first contribution in #723
- @vgnatyuk made their first contribution in #725
- @aayushkc made their first contribution in #727
- @tranhoangtu-it made their first contribution in #761
Full Changelog: 0.3.0...0.4.0