django-ninja-aio-crud 2.0.0rc5__py3-none-any.whl → 2.0.0rc7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_ninja_aio_crud-2.0.0rc5.dist-info → django_ninja_aio_crud-2.0.0rc7.dist-info}/METADATA +1 -1
- {django_ninja_aio_crud-2.0.0rc5.dist-info → django_ninja_aio_crud-2.0.0rc7.dist-info}/RECORD +9 -9
- ninja_aio/__init__.py +1 -1
- ninja_aio/decorators.py +73 -0
- ninja_aio/renders.py +8 -2
- ninja_aio/schemas/helpers.py +10 -2
- ninja_aio/views.py +12 -8
- {django_ninja_aio_crud-2.0.0rc5.dist-info → django_ninja_aio_crud-2.0.0rc7.dist-info}/WHEEL +0 -0
- {django_ninja_aio_crud-2.0.0rc5.dist-info → django_ninja_aio_crud-2.0.0rc7.dist-info}/licenses/LICENSE +0 -0
{django_ninja_aio_crud-2.0.0rc5.dist-info → django_ninja_aio_crud-2.0.0rc7.dist-info}/RECORD
RENAMED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
ninja_aio/__init__.py,sha256=
|
|
1
|
+
ninja_aio/__init__.py,sha256=b0K4gr4xA4QngOZ7PrtTuDcxgyk3yFTNwNxsCObFqug,123
|
|
2
2
|
ninja_aio/api.py,sha256=SS1TYUiFkdYjfJLVy6GI90GOzvIHzPEeL-UcqWFRHkM,1684
|
|
3
3
|
ninja_aio/auth.py,sha256=8jaEp7oEJvUUB9EuyE2fOYk-khyAaekT3i80E7AbgOA,5101
|
|
4
|
-
ninja_aio/decorators.py,sha256=
|
|
4
|
+
ninja_aio/decorators.py,sha256=BHoFIiqdIVMFqSxGh-F6WeZFo1xZK4ieDw3dzKfxZIM,8147
|
|
5
5
|
ninja_aio/exceptions.py,sha256=1-iRbrloIyi0CR6Tcrn5YR4_LloA7PPohKIBaxXJ0-8,2596
|
|
6
6
|
ninja_aio/models.py,sha256=aJlo5a64O4o-fB8QESLMUJpoA5kcjRJxPBiAIMxg46k,47652
|
|
7
7
|
ninja_aio/parsers.py,sha256=e_4lGCPV7zs-HTqtdJTc8yQD2KPAn9njbL8nF_Mmgkc,153
|
|
8
|
-
ninja_aio/renders.py,sha256=
|
|
8
|
+
ninja_aio/renders.py,sha256=VtmSliRJyZ6gjyoib8AXMVUBYF1jPNsiceCHujI_mAs,1699
|
|
9
9
|
ninja_aio/types.py,sha256=TJSGlA7bt4g9fvPhJ7gzH5tKbLagPmZUzfgttEOp4xs,468
|
|
10
|
-
ninja_aio/views.py,sha256=
|
|
10
|
+
ninja_aio/views.py,sha256=8vMFw-8au9O0Hpf-79jvB47PwokCzm7P8UY0DDWpN5A,16786
|
|
11
11
|
ninja_aio/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
ninja_aio/helpers/api.py,sha256=kgHxYPfbV6kQbp8Z4qMwQtwFiFIcRAenxI9MDowGtis,20181
|
|
13
13
|
ninja_aio/helpers/query.py,sha256=tE8RjXvSig-WB_0LRQ0LqoE4G_HMHsu0Na5QzTNIm6U,4262
|
|
14
14
|
ninja_aio/schemas/__init__.py,sha256=iLBwHg0pmL9k_UkIui5Q8QIl_gO4fgxSv2JHxDzqnSI,549
|
|
15
15
|
ninja_aio/schemas/api.py,sha256=-VwXhBRhmMsZLIAmWJ-P7tB5klxXS75eukjabeKKYsc,360
|
|
16
16
|
ninja_aio/schemas/generics.py,sha256=frjJsKJMAdM_NdNKv-9ddZNGxYy5PNzjIRGtuycgr-w,112
|
|
17
|
-
ninja_aio/schemas/helpers.py,sha256=
|
|
18
|
-
django_ninja_aio_crud-2.0.
|
|
19
|
-
django_ninja_aio_crud-2.0.
|
|
20
|
-
django_ninja_aio_crud-2.0.
|
|
21
|
-
django_ninja_aio_crud-2.0.
|
|
17
|
+
ninja_aio/schemas/helpers.py,sha256=rmE0D15lJg95Unv8PU44Hbf0VDTcErMCZZFG3D_znTo,2823
|
|
18
|
+
django_ninja_aio_crud-2.0.0rc7.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
|
|
19
|
+
django_ninja_aio_crud-2.0.0rc7.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
20
|
+
django_ninja_aio_crud-2.0.0rc7.dist-info/METADATA,sha256=bxFgTP1QMN1oSLPw4fhV3Nu9-U90pNN92FoiVqJq6hY,8675
|
|
21
|
+
django_ninja_aio_crud-2.0.0rc7.dist-info/RECORD,,
|
ninja_aio/__init__.py
CHANGED
ninja_aio/decorators.py
CHANGED
|
@@ -143,3 +143,76 @@ def unique_view(self: object | str, plural: bool = False):
|
|
|
143
143
|
return func # Return original function (no wrapper)
|
|
144
144
|
|
|
145
145
|
return decorator
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def decorate_view(*decorators):
|
|
149
|
+
"""
|
|
150
|
+
Compose and apply multiple decorators to a view (sync or async) without adding an extra wrapper.
|
|
151
|
+
|
|
152
|
+
This utility was introduced to support class-based patterns where Django Ninja’s
|
|
153
|
+
built-in `decorate_view` does not fit well. For APIs implemented with vanilla
|
|
154
|
+
Django Ninja (function-based style), you should continue using Django Ninja’s
|
|
155
|
+
native `decorate_view`.
|
|
156
|
+
|
|
157
|
+
Behavior:
|
|
158
|
+
- Applies decorators in the same order as Python’s stacking syntax:
|
|
159
|
+
@d1
|
|
160
|
+
@d2
|
|
161
|
+
is equivalent to: view = d1(d2(view))
|
|
162
|
+
- Supports both synchronous and asynchronous views.
|
|
163
|
+
- Ignores None values, enabling conditional decoration.
|
|
164
|
+
- Does not introduce an additional wrapper; composition depends on each
|
|
165
|
+
decorator for signature/metadata preservation (e.g., using functools.wraps).
|
|
166
|
+
|
|
167
|
+
*decorators: Decorator callables to apply to the target view. Any None values
|
|
168
|
+
are skipped.
|
|
169
|
+
|
|
170
|
+
Callable: A decorator that applies the provided decorators in Python stacking order.
|
|
171
|
+
|
|
172
|
+
Method usage in class-based patterns:
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
*decorators: Decorator callables to apply to the target view. Any None
|
|
176
|
+
values are skipped.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
A decorator that applies the provided decorators in Python stacking order.
|
|
180
|
+
|
|
181
|
+
Examples:
|
|
182
|
+
Basic usage:
|
|
183
|
+
class MyAPIViewSet(APIViewSet):
|
|
184
|
+
api = api
|
|
185
|
+
model = MyModel
|
|
186
|
+
|
|
187
|
+
def views(self):
|
|
188
|
+
@self.router.get('some-endpoint/')
|
|
189
|
+
@decorate_view(authenticate, log_request)
|
|
190
|
+
async def some_view(request):
|
|
191
|
+
...
|
|
192
|
+
|
|
193
|
+
Conditional decoration (skips None):
|
|
194
|
+
class MyAPIViewSet(APIViewSet):
|
|
195
|
+
api = api
|
|
196
|
+
model = MyModel
|
|
197
|
+
cache_dec = cache_page(60) if settings.ENABLE_CACHE else None
|
|
198
|
+
def views(self):
|
|
199
|
+
@self.router.get('data/')
|
|
200
|
+
@decorate_view(self.cache_dec, authenticate)
|
|
201
|
+
async def data_view(request):
|
|
202
|
+
...
|
|
203
|
+
|
|
204
|
+
Notes:
|
|
205
|
+
- Each decorator is applied in the order provided, with the first decorator
|
|
206
|
+
wrapping the result of the second, and so on.
|
|
207
|
+
- Ensure that each decorator is compatible with the view’s sync/async nature.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
def _decorator(view):
|
|
211
|
+
wrapped = view
|
|
212
|
+
for dec in reversed(decorators):
|
|
213
|
+
if dec is None:
|
|
214
|
+
continue
|
|
215
|
+
wrapped = dec(wrapped)
|
|
216
|
+
return wrapped
|
|
217
|
+
|
|
218
|
+
return _decorator
|
ninja_aio/renders.py
CHANGED
|
@@ -4,11 +4,13 @@ from typing import Any
|
|
|
4
4
|
|
|
5
5
|
import orjson
|
|
6
6
|
from django.http import HttpRequest
|
|
7
|
+
from django.conf import settings
|
|
7
8
|
from ninja.renderers import BaseRenderer
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class ORJSONRenderer(BaseRenderer):
|
|
11
12
|
media_type = "application/json"
|
|
13
|
+
option = getattr(settings, "NINJA_AIO_ORJSON_RENDERER_OPTION", None)
|
|
12
14
|
|
|
13
15
|
def render(self, request: HttpRequest, data: dict, *, response_status):
|
|
14
16
|
try:
|
|
@@ -16,9 +18,13 @@ class ORJSONRenderer(BaseRenderer):
|
|
|
16
18
|
for k, v in old_d.items():
|
|
17
19
|
if isinstance(v, list):
|
|
18
20
|
data |= {k: self.render_list(v)}
|
|
19
|
-
return
|
|
21
|
+
return self.dumps(self.render_dict(data))
|
|
20
22
|
except AttributeError:
|
|
21
|
-
return
|
|
23
|
+
return self.dumps(data)
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def dumps(cls, data: dict) -> bytes:
|
|
27
|
+
return orjson.dumps(data, option=cls.option)
|
|
22
28
|
|
|
23
29
|
@classmethod
|
|
24
30
|
def render_list(cls, data: list[dict]) -> list[dict]:
|
ninja_aio/schemas/helpers.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Optional, Type
|
|
1
|
+
from typing import List, Optional, Type
|
|
2
2
|
|
|
3
3
|
from ninja import Schema
|
|
4
4
|
from ninja_aio.types import ModelSerializerMeta
|
|
@@ -79,4 +79,12 @@ class QuerySchema(ModelQuerySetSchema):
|
|
|
79
79
|
|
|
80
80
|
class QueryUtilBaseScopesSchema(BaseModel):
|
|
81
81
|
READ: str = "read"
|
|
82
|
-
QUERYSET_REQUEST: str = "queryset_request"
|
|
82
|
+
QUERYSET_REQUEST: str = "queryset_request"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class DecoratorsSchema(Schema):
|
|
86
|
+
list: Optional[List] = []
|
|
87
|
+
retrieve: Optional[List] = []
|
|
88
|
+
create: Optional[List] = []
|
|
89
|
+
update: Optional[List] = []
|
|
90
|
+
delete: Optional[List] = []
|
ninja_aio/views.py
CHANGED
|
@@ -7,7 +7,7 @@ from django.http import HttpRequest
|
|
|
7
7
|
from django.db.models import Model, QuerySet
|
|
8
8
|
from pydantic import create_model
|
|
9
9
|
|
|
10
|
-
from ninja_aio.schemas.helpers import ModelQuerySetSchema, QuerySchema
|
|
10
|
+
from ninja_aio.schemas.helpers import ModelQuerySetSchema, QuerySchema, DecoratorsSchema
|
|
11
11
|
|
|
12
12
|
from .models import ModelSerializer, ModelUtil
|
|
13
13
|
from .schemas import (
|
|
@@ -16,7 +16,7 @@ from .schemas import (
|
|
|
16
16
|
)
|
|
17
17
|
from .helpers.api import ManyToManyAPI
|
|
18
18
|
from .types import ModelSerializerMeta, VIEW_TYPES
|
|
19
|
-
from .decorators import unique_view
|
|
19
|
+
from .decorators import unique_view, decorate_view
|
|
20
20
|
|
|
21
21
|
ERROR_CODES = frozenset({400, 401, 404, 428})
|
|
22
22
|
|
|
@@ -178,6 +178,7 @@ class APIViewSet:
|
|
|
178
178
|
delete_docs = "Delete an object by its primary key."
|
|
179
179
|
m2m_relations: list[M2MRelationSchema] = []
|
|
180
180
|
m2m_auth: list | None = NOT_SET
|
|
181
|
+
extra_decorators: DecoratorsSchema = DecoratorsSchema()
|
|
181
182
|
|
|
182
183
|
def __init__(self) -> None:
|
|
183
184
|
self.error_codes = ERROR_CODES
|
|
@@ -309,7 +310,7 @@ class APIViewSet:
|
|
|
309
310
|
description=self.create_docs,
|
|
310
311
|
response={201: self.schema_out, self.error_codes: GenericMessageSchema},
|
|
311
312
|
)
|
|
312
|
-
@unique_view(self)
|
|
313
|
+
@decorate_view(unique_view(self), *self.extra_decorators.create)
|
|
313
314
|
async def create(request: HttpRequest, data: self.schema_in): # type: ignore
|
|
314
315
|
return 201, await self.model_util.create_s(request, data, self.schema_out)
|
|
315
316
|
|
|
@@ -330,8 +331,11 @@ class APIViewSet:
|
|
|
330
331
|
self.error_codes: GenericMessageSchema,
|
|
331
332
|
},
|
|
332
333
|
)
|
|
333
|
-
@
|
|
334
|
-
|
|
334
|
+
@decorate_view(
|
|
335
|
+
paginate(self.pagination_class),
|
|
336
|
+
unique_view(self, plural=True),
|
|
337
|
+
*self.extra_decorators.list,
|
|
338
|
+
)
|
|
335
339
|
async def list(
|
|
336
340
|
request: HttpRequest,
|
|
337
341
|
filters: Query[self.filters_schema] = None, # type: ignore
|
|
@@ -359,7 +363,7 @@ class APIViewSet:
|
|
|
359
363
|
description=self.retrieve_docs,
|
|
360
364
|
response={200: self.schema_out, self.error_codes: GenericMessageSchema},
|
|
361
365
|
)
|
|
362
|
-
@unique_view(self)
|
|
366
|
+
@decorate_view(unique_view(self), *self.extra_decorators.retrieve)
|
|
363
367
|
async def retrieve(request: HttpRequest, pk: Path[self.path_schema]): # type: ignore
|
|
364
368
|
query_data = self._get_query_data()
|
|
365
369
|
return await self.model_util.read_s(
|
|
@@ -384,7 +388,7 @@ class APIViewSet:
|
|
|
384
388
|
description=self.update_docs,
|
|
385
389
|
response={200: self.schema_out, self.error_codes: GenericMessageSchema},
|
|
386
390
|
)
|
|
387
|
-
@unique_view(self)
|
|
391
|
+
@decorate_view(unique_view(self), *self.extra_decorators.update)
|
|
388
392
|
async def update(
|
|
389
393
|
request: HttpRequest,
|
|
390
394
|
data: self.schema_update, # type: ignore
|
|
@@ -408,7 +412,7 @@ class APIViewSet:
|
|
|
408
412
|
description=self.delete_docs,
|
|
409
413
|
response={204: None, self.error_codes: GenericMessageSchema},
|
|
410
414
|
)
|
|
411
|
-
@unique_view(self)
|
|
415
|
+
@decorate_view(unique_view(self), *self.extra_decorators.delete)
|
|
412
416
|
async def delete(request: HttpRequest, pk: Path[self.path_schema]): # type: ignore
|
|
413
417
|
return 204, await self.model_util.delete_s(request, self._get_pk(pk))
|
|
414
418
|
|
|
File without changes
|
|
File without changes
|