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.
- Added
throttling_allow_unsafe_cachesetting to control whether unsafe cache backends (LocMemCache,DummyCache) are allowed for throttling. EmitsUnsafeCacheBackendWarningby default, raisesImproperlyConfiguredwhen explicitly set toFalse, #978 - Added
--no-ensure-asciiflag todmr_export_schemamanagement command
- Fixed how
msgspecgeneratesnullinanyOf, it is now always the last item, #990 - Fixed minimum allowed django version, #1008
- Fixed
ImportErrorwhile using withdjango==5.2.0, #1006
- Breaking: Renamed
APIRedirectErrortoRedirectTo, #922 - Breaking: Split
BaseThrottleBackendintoBaseThrottleAsyncBackendandBaseThrottleSyncBackend, #942 - Breaking: Renamed
DjangoCacheintoSyncDjangoCache, addedAsyncDjangoCache, #942 - Breaking: Changed
BaseThrottleBackendAPI: now it requires.incrand.getmethods, the first one should ideally be an atomic increment, the second one is for reading objects only, #942 - Breaking: Removed
BaseThrottleAlgorithm.recordmethod, nowBaseThrottleAlgorithm.accessmust also record accesses. This will help to make throttling more atomic, #942
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.backend.DjangoCache`
with `dmr.throttling.backend.SyncDjangoCache` for sync throttles
and with `dmr.throttling.backend.AsyncDjangoCache` for async throttles- Added
SyncRedisandAsyncRedisthrottling backends, #977 - Added
RefreshTokenSyncControllerandRefreshTokenAsyncControllerto issue new access/refresh token pairs from a valid refresh token, #907 - Added
validate_negotiationmetadata flag, so we can explicitly validate, that returned response followed the negotiation process, #711 - Added
accepted_headeras a faster alternative todjango'sHttpRequest.accepts, #854 - Added
dmr_export_schemamanagement command to export OpenAPI schemas, #909
- Fixed OpenAPI schema for Django session auth
when
CSRF_USE_SESSIONS=True, #674 - Fixed that
itemSchemawas possible to be rendered in OpenAPI3.0.0and3.1.0, #908 - Fixed response validation when global error handler returns
HttpResponsewith a different content type than the negotiated renderer, #711 - Fixed
collectstaticfailure when usingManifestStaticFilesStorage, #927 - Fixed
datetimevalidation when using.to_response, #938 - Fixed a bug that
ObtainTokensAsyncControllerwas not setting therequest.auserattribute, #953 - Fixed a bug that
JWTSyncAuthwas not settingrequest.auser, #953 - Fixed
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, #962 - Fixed that original traceback was not shown
for
BaseSchemaGenerator.get_schema, #961
- Optimized
dmr_clientanddmr_rftest fixtures to usemsgspecfor JSON encoding and decoding when available, #889 and #976 - Optimized how per-endpoint throttle locks are used, #942
- 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 serializer - Breaking:
get_jwtis renamed torequest_jwt, #868 - Breaking:
ResponseSpecProvider.provide_response_specsis now an instance method, #877 - Breaking: new required
routerparameter added toEndpoint.get_schemaandController.get_path_item, #879
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- Added official PyPy 3.11+ support, #870
- Added
dmr.throttlingpackage, #877 - Added
request.__drm_auth__on all successful auth workflows, #868 - Added
request_authhelper function, #868 - Added
AuthenticatedHttpRequesttype for betterrequest: AuthenticatedHttpRequest[User]type annotations in controllers, #888 - Added
strictparameter torequest_rendererandrequest_parser, added@overloads to both of these functions, #869 - Added
ResponseSpecMetadatatype to represent headers and cookies with annotations, useful for error models, #882 - Allow individual
OpenAPIviews to skip schema validation, #867 - Added 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.
- Fixed that
OpenAPIwas revalidated on every.convertcall, #867 - Fixed missing
request.auser()afterJWTAsyncAuth, #884 - Fixed
ParameterMetadatamissing__slots__, #890 - Fixed
SSEventmissing__slots__, #901 - Fixed
SSEprotocol typing, #894 - Fixed a bug when we were treating controllers with
no
api_endpointsas non-abstract, #894 - Fixed a bug when you were not able to subclass a controller with a serializer, #873
- Added
dmrskill for agents to write betterdjango-modern-restcode, #886 - Switched from
Maketojustas a command runner
In this release we significantly increased the performance of pydantic
workflows by introducing PydanticFastSerializer.
No breaking changes in this release.
- Added
PydanticFastSerializerto serialize and deserializejsonobjects directly, #830 - Added support for complex
pydanticfields insideTypedDict,@dataclass, etc models, when usingPydanticSerializerandmsgspecparsers / renderers, #842 - Introduced official
to_json_kwargsandto_model_kwargsclass-level API formsgspecandpydanticserializers, #842 - Added "Problem Details" or RFC-9457 support, #78
- Added customizable
json_moduleparameter toJsonParserandJsonRendererto support alternative JSON backends likeorjson, #857
- Fixed package metadata, #824
- Fixed missing
style,phone,colorformats fromOpenAPIFormat, #842 - Fixes Django 5.2.13+ compat in
DMRAsyncRequestFactory, #853
- Improved "Plugins" section in the docs, #835
- Bumped
msgspecto0.21.0, #856 - Added official
SECURITY.mdpolicy
AKA "The first compiled version".
This release will focus on better errors, performance, and stability.
No breaking changes in this release.
- 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 that - Added older Django versions
4.2,5.0,5.1official support, #803 - Added official
NamedTuplesupport, #774 - Added
timezoneandpydantic-extra-typesdependencies with[pydantic]extra, #802 - Added
exclude_semantic_responsesoptions, #786 - Added an option to override
exclude_semantic_responsesandno_validate_http_specsettings withNone - Added a new way to resolve annotations for controllers:
AnnotationsContext, #787 - Added
yamlview for OpenAPI schema, #745
- Fixed
StreamingValidatorswallowing errors whenvalidate_eventswasTrue, but no event model was resolved, #780 - Fixed
dataclassinstances serialization withPydanticSerializerwithoutmsgspecjson renderer, #795 - Fixed missing
passwordOpenAPI format, #805 - Fixes incorrect settings validation, #821
- Added
QuerySettutorial, #792 - Migrated from
poetrytouvfor dependency management - Set up automated secure publishing to PyPI, #823
- Added CodSpeed integration for continuous performance monitoring, #810
AKA "The first version that I enjoy".
-
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
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.
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
- 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 forBodyandFileMetadatacomponents, #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 for
dmr_clientanddmr_async_client, #735. - Added Stoplight Elements UI for OpenAPI documentation, #748
- Added better
settingsfixture support forpytestplugin, #769
- Fixed
SSEcontrollers__name__and__doc__generation via@ssedecorator, #700 - Fixed a bug where
FileMetadatarendered list of schemas incorrectly, #698
- 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
- Added
FileResponseSpecand improvedFileResponseschema generation, #682 - Added
encoding:support for file media types inFileMetadata, #682
- Fixed OpenAPI schema for custom HTTP Basic auth headers, #672
- Fixed JWT claim validation and error handling in
JWToken.decode, #675 - Fixed incorrect OpenAPI schema for
FileResponse, #682 - Fixed that
404was not listed in the endpoint's metadata, when usingURLRoutewithoutPathcomponent, #685 - Fixed that
404was not documented in the OpenAPI whenPathcomponent was not used, butURLPatternhad parameters, #685 - Fixed
ValueErroron operation id generation, #685
- Improved "Returning responses" docs, #684
- Breaking: Renamed
schema_onlyparameter toskip_validation - Added
dmr.routing.build_500_handlerhandler, #661 - Added support for
__dmr_split_commas__inHeaderscomponent, #659 - Added support for native Django urls to be rendered in the OpenAPI,
now OpenAPI parameters will be generated even without
Pathcomponent, #659 - Do not allow
'\x00',\n, and\rasSSEvent.idandSSEvent.event, #667
- Fixes how
SSEResponseSpec.headers['Connection']header is specified, #654 - Fixed an
operation_idgeneration bug, #652 - Fixed 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, #656 - Fixed 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, #644 - Fixed that not options were passed to
JWToken._build_options, #671
- Improved components and auth docs
- Initial release