Releases: wemake-services/django-modern-rest
Version 0.8.0
In this version we focused on reworking on throttling internal API and our DjangoSession auth support.
Breaking changes
- Breaking: Renamed
APIRedirectErrortoRedirectTo, #922 - Breaking: Split
BaseThrottleBackendintoBaseThrottleAsyncBackend
andBaseThrottleSyncBackend, #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
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.backend.DjangoCache`
with `dmr.throttling.backend.SyncDjangoCache` for sync throttles
and with `dmr.throttling.backend.AsyncDjangoCache` for async throttlesFeatures
- Added
SyncRedisandAsyncRedisthrottling backends, #977 - Added
RefreshTokenSyncControllerandRefreshTokenAsyncController
to 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
Bugfixes
- Fixed OpenAPI schema for Django session auth
whenCSRF_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 without
application/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
forBaseSchemaGenerator.get_schema, #961
Misc
- Optimized
dmr_clientanddmr_rftest fixtures to usemsgspec
for JSON encoding and decoding when available, #889 and #976 - Optimized how per-endpoint throttle locks are used, #942
New Contributors
- @sh1rokovs made their first contribution in #711
- @azad-mammedov made their first contribution in #913
- @Stranger6667 made their first contribution in #917
- @hydrauluu made their first contribution in #940
- @Peopl3s made their first contribution in #947
- @mvanhorn made their first contribution in #950
- @MikeVL made their first contribution in #964
Full Changelog: 0.7.0...0.8.0
Version 0.7.0
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_dump
if 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
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` methodsFeatures
- 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 better
request: 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, #847
SwaggerView,RedocView,ScalarView, andStoplightView
now 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.
Fixes
- 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
noapi_endpointsas non-abstract, #894 - Fixed 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, #886 - Switched from
Maketojust
as a command runner
New Contributors
- @PNoryk made their first contribution in #879
- @Ai-chan-0411 made their first contribution in #900
- @drlinggg made their first contribution in #906
Full Changelog: 0.6.0...0.7.0
Version 0.6.0
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 deserializejson
objects directly, #830 - Added support for complex
pydanticfields inside
TypedDict,@dataclass, etc models, when usingPydanticSerializer
andmsgspecparsers / 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 toJsonParserandJsonRenderer
to support alternative JSON backends likeorjson, #857
Fixes
- Fixed package metadata, #824
- Fixed missing
style,phone,colorformats fromOpenAPIFormat, #842 - Fixes Django 5.2.13+ compat in
DMRAsyncRequestFactory, #853
New Contributors
- @kodsurfer made their first contribution in #832
- @subnix made their first contribution in #834
- @Nikolay-Chillev made their first contribution in #848
- @muratclk made their first contribution in #859
Full Changelog: 0.5.0...0.6.0
Version 0.5.0
AKA "The first compiled version"
This release will focus on better errors, performance, and stability.
No breaking changes will be made.
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 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_responses
andno_validate_http_specsettings withNone - Added a new way to resolve annotations for controllers:
AnnotationsContext, #787 - Added
yamlview for OpenAPI schema, #745
Fixes
- Fixed
StreamingValidatorswallowing errors
whenvalidate_eventswasTrue, but no event model was resolved, #780 - Fixed
dataclassinstances serialization withPydanticSerializer
withoutmsgspecjson renderer, #795 - Fixed missing
passwordOpenAPI format, #805 - Fixes incorrect settings validation, #821
Misc
- Added
QuerySettutorial, #792 - Migrated from
poetrytouvfor dependency management - Set up automated secure publishing to PyPI, #823
- Added CodSpeed integration for continous performance monitoring, #810
New Contributors
- @albertedwardson made their first contribution in #779
- @Copilot made their first contribution in #788
- @neocim made their first contribution in #791
- @Satevg made their first contribution in #797
- @karpetrosyan made their first contribution in #811
- @zeel2104 made their first contribution in #766
Full Changelog: 0.4.0...0.5.0
Version 0.4.0
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
Version 0.3.0
What's Changed
Features
- Added
FileResponseSpecand improvedFileResponse
schema generation, #682 - Added
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, #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
Misc
- Improved "Returning responses" docs, #684
New Contributors
Full Changelog: 0.2.0...0.3.0
Version 0.2.0
What's Changed
Features
- 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 withoutPathcomponent, #659 - Do not allow
'\x00',\n, and\r
asSSEvent.idandSSEvent.event, #667
Bugfixes
- 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
Misc
- Improved components and auth docs
New Contributors
- @RenameMe1 made their first contribution in #642
- @maksimzayats made their first contribution in #658
Full Changelog: 0.1.0...0.2.0
Version 0.1.0 – First public release!
django-modern-rest
Modern REST framework with types and async support!
Learn: https://django-modern-rest.readthedocs.io
Download: https://pypi.org/project/django-modern-rest
Contributors
- @milssky made their first contribution in #11
- @kondratevdev made their first contribution in #10
- @sobolevn made their first contribution in #14
- @dependabot[bot] made their first contribution in #31
- @Nifacy made their first contribution in #54
- @Sehat1137 made their first contribution in #67
- @F0RRZZ made their first contribution in #71
- @thesayfulla made their first contribution in #80
- @pre-commit-ci[bot] made their first contribution in #113
- @K-dash made their first contribution in #111
- @oek1ng made their first contribution in #125
- @max0nT made their first contribution in #134
- @amirbekazimov made their first contribution in #127
- @danfimov made their first contribution in #149
- @Tosinibikunle made their first contribution in #143
- @acostyle made their first contribution in #155
- @wimble3 made their first contribution in #147
- @Eclips4 made their first contribution in #163
- @asidowner made their first contribution in #165
- @AlexanderZharyuk made their first contribution in #161
- @golovindev made their first contribution in #168
- @proofit404 made their first contribution in #174
- @sergey-miryanov made their first contribution in #206
- @deliro made their first contribution in #183
- @Tapeline made their first contribution in #211
- @Evgenmater made their first contribution in #213
- @gimntut made their first contribution in #232
- @TakiMoysha made their first contribution in #243
- @ApostolFet made their first contribution in #257
- @MukievMukhammad made their first contribution in #261
- @MaximGit1 made their first contribution in #278
- @Matvey1109 made their first contribution in #301
- @bagowix made their first contribution in #384
- @thepabloaguilar made their first contribution in #386
- @nikolay-kovalenko91 made their first contribution in #354
- @Syed-Zeeyan made their first contribution in #277
- @JaeHyuckSa made their first contribution in #279
- @cicwak made their first contribution in #409
- @Mr-Sunglasses made their first contribution in #411
- @wrongnull made their first contribution in #430
- @mikhailofff made their first contribution in #459
- @johnmakarov made their first contribution in #467
- @voidcommit-afk made their first contribution in #492
- @fennr made their first contribution in #515
- @Chyrets made their first contribution in #522
- @arturboyun made their first contribution in #556
- @dimk00z made their first contribution in #548
- @Parth-Vasave made their first contribution in #593
- @dgolov made their first contribution in #604
- @sesav made their first contribution in #615
- @pomponchik made their first contribution in #614
- @asm-1 made their first contribution in #629
Full Changelog: https://github.com/wemake-services/django-modern-rest/commits/0.1.0