from collections.abc import Awaitable
from typing import TYPE_CHECKING, Any, ClassVar, TypeVar
from django.test import AsyncClient, AsyncRequestFactory, Client, RequestFactory
_ThingT = TypeVar('_ThingT')
class _DMRMixin: # noqa: WPS338
default_content_type: ClassVar[str] = 'application/json'
if TYPE_CHECKING: # noqa: WPS604 # pragma: no cover
def _encode_data(
self,
data: dict[str, Any],
content_type: str,
) -> bytes: ...
else: # pragma: no cover
# This code is autogenerated, please do not touch it.
# It lives under `if not TYPE_CHECKING` so it would not ruin
# typing done by `django-stubs`.
# ALL THIS CODE DOES IS JUST A DEFAULT CONTENT_TYPE CHANGE, WTF!
def post(self, path, data='', content_type=None, *args, **kwargs):
if content_type is None:
content_type = self.default_content_type
return super().post(path, data, content_type, *args, **kwargs)
def options(self, path, data='', content_type=None, *args, **kwargs):
if content_type is None:
content_type = self.default_content_type
return super().options(path, data, content_type, *args, **kwargs)
def put(self, path, data='', content_type=None, *args, **kwargs):
if content_type is None:
content_type = self.default_content_type
return super().put(path, data, content_type, *args, **kwargs)
def patch(self, path, data='', content_type=None, *args, **kwargs):
if content_type is None:
content_type = self.default_content_type
return super().patch(path, data, content_type, *args, **kwargs)
def delete(self, path, data='', content_type=None, *args, **kwargs):
if content_type is None:
content_type = self.default_content_type
return super().delete(path, data, content_type, *args, **kwargs)
def generic( # noqa: WPS211
self,
method,
path,
data='',
content_type=None,
*args,
**kwargs,
):
if isinstance(self, AsyncRequestFactory) or content_type is None:
# See https://github.com/django/django/commit/caf90a971f09323775ed0cacf94eadaf39d040e0
# Django versions 5.2.12 and 5.2.13 behave differently now:
headers = kwargs.get('headers', {}) or {}
content_type = (
headers.pop(
'Content-Type',
self.default_content_type,
)
if isinstance(self, AsyncRequestFactory)
else headers.get(
'Content-Type',
self.default_content_type,
)
)
return super().generic(
method,
path,
data,
content_type,
*args,
**kwargs,
)
[docs]
class DMRRequestFactory(_DMRMixin, RequestFactory):
"""
Test utility for testing apps using ``django-modern-rest``.
Based on :class:`django.test.RequestFactory`.
See their docs for advanced usage:
https://docs.djangoproject.com/en/dev/topics/testing/tools
This type, in contrast to a regular ``RequestFactory``,
sets ``content-type`` as ``application/json``.
Sets WSGI environment.
"""
[docs]
class DMRAsyncRequestFactory(_DMRMixin, AsyncRequestFactory):
"""
Version of :class:`DMRRequestFactory` but for ASGI environment.
Uses the exactly the same API.
"""
if TYPE_CHECKING: # noqa: WPS604
def wrap(self, thing: _ThingT) -> Awaitable[_ThingT]: # pyright: ignore[reportReturnType] # pyrefly: ignore [bad-return]
"""Does nothing."""
else:
[docs]
def wrap(self, thing):
"""
Utility method for testing.
Pretends to wrap async controllers into async functions for typing.
But in reality does nothing.
This happens due to the fact that ``View`` is typed as sync object.
"""
return thing
[docs]
class DMRClient(_DMRMixin, Client):
"""
Test utility for testing apps using ``django-modern-rest``.
Based on :class:`django.test.Client`.
See their docs for advanced usage:
https://docs.djangoproject.com/en/dev/topics/testing/tools/
This type, in contrast to a regular ``Client``,
sets ``content-type`` as ``application/json``.
"""
[docs]
class DMRAsyncClient(_DMRMixin, AsyncClient):
"""
Async version of :class:`DMRClient`.
Uses ``async`` API.
Requires you to ``await`` calls to ``.get``, ``.post``, etc.
"""