django-ninja-aio-crud 2.1.0__py3-none-any.whl → 2.2.0__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.1.0.dist-info → django_ninja_aio_crud-2.2.0.dist-info}/METADATA +2 -2
- {django_ninja_aio_crud-2.1.0.dist-info → django_ninja_aio_crud-2.2.0.dist-info}/RECORD +9 -9
- ninja_aio/__init__.py +1 -1
- ninja_aio/api.py +24 -0
- ninja_aio/auth.py +3 -3
- ninja_aio/types.py +1 -1
- ninja_aio/views/api.py +99 -32
- {django_ninja_aio_crud-2.1.0.dist-info → django_ninja_aio_crud-2.2.0.dist-info}/WHEEL +0 -0
- {django_ninja_aio_crud-2.1.0.dist-info → django_ninja_aio_crud-2.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-ninja-aio-crud
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Django Ninja AIO CRUD - Rest Framework
|
|
5
5
|
Author: Giuseppe Casillo
|
|
6
|
-
Requires-Python: >=3.10
|
|
6
|
+
Requires-Python: >=3.10, <=3.14
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
Classifier: Operating System :: OS Independent
|
|
9
9
|
Classifier: Topic :: Internet
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
ninja_aio/__init__.py,sha256=
|
|
2
|
-
ninja_aio/api.py,sha256=
|
|
3
|
-
ninja_aio/auth.py,sha256=
|
|
1
|
+
ninja_aio/__init__.py,sha256=Jgj89rpJ3n4pkGfoSYm9NYrwBjGzwhiOeU5c6mr-J8Q,119
|
|
2
|
+
ninja_aio/api.py,sha256=tuC7vdvn7s1GkCnSFy9Kn1zv0glZfYptRQVvo8ZRtGQ,2429
|
|
3
|
+
ninja_aio/auth.py,sha256=4sWdFPjKiQgUL1d_CSGDblVjnY5ptP6LQha6XXdluJA,9157
|
|
4
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
8
|
ninja_aio/renders.py,sha256=VtmSliRJyZ6gjyoib8AXMVUBYF1jPNsiceCHujI_mAs,1699
|
|
9
|
-
ninja_aio/types.py,sha256=
|
|
9
|
+
ninja_aio/types.py,sha256=nFqWEopm7eoEaHRzbi6EyA9WZ5Cneyd602ilFKypeQI,577
|
|
10
10
|
ninja_aio/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
ninja_aio/helpers/api.py,sha256=BTe7OL-X7YgWYeXmka8TmN4-gA43FVZhtH7q0dRjYX0,20238
|
|
12
12
|
ninja_aio/helpers/query.py,sha256=tE8RjXvSig-WB_0LRQ0LqoE4G_HMHsu0Na5QzTNIm6U,4262
|
|
@@ -15,9 +15,9 @@ ninja_aio/schemas/api.py,sha256=-VwXhBRhmMsZLIAmWJ-P7tB5klxXS75eukjabeKKYsc,360
|
|
|
15
15
|
ninja_aio/schemas/generics.py,sha256=frjJsKJMAdM_NdNKv-9ddZNGxYy5PNzjIRGtuycgr-w,112
|
|
16
16
|
ninja_aio/schemas/helpers.py,sha256=rmE0D15lJg95Unv8PU44Hbf0VDTcErMCZZFG3D_znTo,2823
|
|
17
17
|
ninja_aio/views/__init__.py,sha256=DEzjWA6y3WF0V10nNF8eEurLNEodgxKzyFd09AqVp3s,148
|
|
18
|
-
ninja_aio/views/api.py,sha256=
|
|
18
|
+
ninja_aio/views/api.py,sha256=O_QQBxk-MltU-bPjxOSu5fFpHaDNP5J3Wk6E5rSBgi4,19744
|
|
19
19
|
ninja_aio/views/mixins.py,sha256=Jh6BG8Cs823nurVlODlzCquTxKrLH7Pmo5udPqUGZek,11378
|
|
20
|
-
django_ninja_aio_crud-2.
|
|
21
|
-
django_ninja_aio_crud-2.
|
|
22
|
-
django_ninja_aio_crud-2.
|
|
23
|
-
django_ninja_aio_crud-2.
|
|
20
|
+
django_ninja_aio_crud-2.2.0.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
|
|
21
|
+
django_ninja_aio_crud-2.2.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
22
|
+
django_ninja_aio_crud-2.2.0.dist-info/METADATA,sha256=lJiJIHnNMaKXAM-aOwuKlcAmaJt8oV_wtiDGSFvjjJE,8680
|
|
23
|
+
django_ninja_aio_crud-2.2.0.dist-info/RECORD,,
|
ninja_aio/__init__.py
CHANGED
ninja_aio/api.py
CHANGED
|
@@ -5,10 +5,13 @@ from ninja.throttling import BaseThrottle
|
|
|
5
5
|
from ninja import NinjaAPI
|
|
6
6
|
from ninja.openapi.docs import DocsBase, Swagger
|
|
7
7
|
from ninja.constants import NOT_SET, NOT_SET_TYPE
|
|
8
|
+
from django.db import models
|
|
8
9
|
|
|
9
10
|
from .parsers import ORJSONParser
|
|
10
11
|
from .renders import ORJSONRenderer
|
|
11
12
|
from .exceptions import set_api_exception_handlers
|
|
13
|
+
from .views import APIView, APIViewSet
|
|
14
|
+
from .models import ModelSerializer
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
class NinjaAIO(NinjaAPI):
|
|
@@ -49,3 +52,24 @@ class NinjaAIO(NinjaAPI):
|
|
|
49
52
|
def set_default_exception_handlers(self):
|
|
50
53
|
set_api_exception_handlers(self)
|
|
51
54
|
super().set_default_exception_handlers()
|
|
55
|
+
|
|
56
|
+
def view(self, prefix: str, tags: list[str] = None) -> Any:
|
|
57
|
+
def wrapper(view: type[APIView]):
|
|
58
|
+
instance = view(api=self, prefix=prefix, tags=tags)
|
|
59
|
+
instance.add_views_to_route()
|
|
60
|
+
return instance
|
|
61
|
+
|
|
62
|
+
return wrapper
|
|
63
|
+
|
|
64
|
+
def viewset(
|
|
65
|
+
self,
|
|
66
|
+
model: models.Model | ModelSerializer,
|
|
67
|
+
prefix: str = None,
|
|
68
|
+
tags: list[str] = None,
|
|
69
|
+
) -> None:
|
|
70
|
+
def wrapper(viewset: type[APIViewSet]):
|
|
71
|
+
instance = viewset(api=self, model=model, prefix=prefix, tags=tags)
|
|
72
|
+
instance.add_views_to_route()
|
|
73
|
+
return instance
|
|
74
|
+
|
|
75
|
+
return wrapper
|
ninja_aio/auth.py
CHANGED
|
@@ -124,7 +124,7 @@ def validate_key(key: Optional[JwtKeys], setting_name: str) -> JwtKeys:
|
|
|
124
124
|
key = getattr(settings, setting_name, None)
|
|
125
125
|
if key is None:
|
|
126
126
|
raise ValueError(f"{setting_name} is required")
|
|
127
|
-
if not isinstance(key, (jwk.RSAKey, jwk.ECKey)):
|
|
127
|
+
if not isinstance(key, (jwk.RSAKey, jwk.ECKey, jwk.OctKey)):
|
|
128
128
|
raise ValueError(
|
|
129
129
|
f"{setting_name} must be an instance of jwk.RSAKey or jwk.ECKey"
|
|
130
130
|
)
|
|
@@ -143,7 +143,7 @@ def validate_mandatory_claims(claims: dict) -> dict:
|
|
|
143
143
|
|
|
144
144
|
|
|
145
145
|
def encode_jwt(
|
|
146
|
-
claims: dict, duration: int, private_key:
|
|
146
|
+
claims: dict, duration: int, private_key: JwtKeys = None, algorithm: str = None
|
|
147
147
|
) -> str:
|
|
148
148
|
"""
|
|
149
149
|
Encode and sign a JWT.
|
|
@@ -192,7 +192,7 @@ def encode_jwt(
|
|
|
192
192
|
|
|
193
193
|
def decode_jwt(
|
|
194
194
|
token: str,
|
|
195
|
-
public_key:
|
|
195
|
+
public_key: JwtKeys = None,
|
|
196
196
|
algorithms: list[str] = None,
|
|
197
197
|
) -> jwt.Token:
|
|
198
198
|
"""
|
ninja_aio/types.py
CHANGED
|
@@ -8,7 +8,7 @@ S_TYPES = Literal["read", "create", "update"]
|
|
|
8
8
|
F_TYPES = Literal["fields", "customs", "optionals", "excludes"]
|
|
9
9
|
SCHEMA_TYPES = Literal["In", "Out", "Patch", "Related"]
|
|
10
10
|
VIEW_TYPES = Literal["list", "retrieve", "create", "update", "delete", "all"]
|
|
11
|
-
JwtKeys: TypeAlias = jwk.RSAKey | jwk.ECKey
|
|
11
|
+
JwtKeys: TypeAlias = jwk.RSAKey | jwk.ECKey | jwk.OctKey
|
|
12
12
|
|
|
13
13
|
class ModelSerializerType(type):
|
|
14
14
|
def __repr__(self):
|
ninja_aio/views/api.py
CHANGED
|
@@ -18,19 +18,16 @@ from ninja_aio.helpers.api import ManyToManyAPI
|
|
|
18
18
|
from ninja_aio.types import ModelSerializerMeta, VIEW_TYPES
|
|
19
19
|
from ninja_aio.decorators import unique_view, decorate_view
|
|
20
20
|
|
|
21
|
-
ERROR_CODES = frozenset({400, 401, 404
|
|
21
|
+
ERROR_CODES = frozenset({400, 401, 404})
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
class
|
|
25
|
-
api: NinjaAPI
|
|
26
|
-
router_tag: str
|
|
27
|
-
|
|
24
|
+
class API:
|
|
25
|
+
api: NinjaAPI = None
|
|
26
|
+
router_tag: str = ""
|
|
27
|
+
router_tags: list[str] = []
|
|
28
|
+
api_route_path: str = ""
|
|
28
29
|
auth: list | None = NOT_SET
|
|
29
30
|
|
|
30
|
-
def __init__(self) -> None:
|
|
31
|
-
self.router = Router(tags=[self.router_tag])
|
|
32
|
-
self.error_codes = ERROR_CODES
|
|
33
|
-
|
|
34
31
|
def views(self):
|
|
35
32
|
"""
|
|
36
33
|
Override this method to add your custom views. For example:
|
|
@@ -38,7 +35,7 @@ class APIView:
|
|
|
38
35
|
async def some_method(request, *args, **kwargs):
|
|
39
36
|
pass
|
|
40
37
|
|
|
41
|
-
You can add
|
|
38
|
+
You can add views just doing:
|
|
42
39
|
|
|
43
40
|
@self.router.get(some_path, response=some_schema)
|
|
44
41
|
async def some_method(request, *args, **kwargs):
|
|
@@ -63,20 +60,80 @@ class APIView:
|
|
|
63
60
|
async def some_method(request, *args, **kwargs):
|
|
64
61
|
pass
|
|
65
62
|
"""
|
|
63
|
+
pass
|
|
66
64
|
|
|
67
65
|
def _add_views(self):
|
|
68
|
-
|
|
69
|
-
return self.router
|
|
66
|
+
raise NotImplementedError("_add_views must be implemented in subclasses")
|
|
70
67
|
|
|
71
68
|
def add_views_to_route(self):
|
|
72
69
|
return self.api.add_router(f"{self.api_route_path}", self._add_views())
|
|
73
70
|
|
|
74
71
|
|
|
75
|
-
class
|
|
72
|
+
class APIView(API):
|
|
73
|
+
"""
|
|
74
|
+
Base class to register custom, non-CRUD endpoints on a Ninja Router.
|
|
75
|
+
|
|
76
|
+
Usage:
|
|
77
|
+
@api.view(prefix="/custom", tags=["Custom"])
|
|
78
|
+
class CustomAPIView(APIView):
|
|
79
|
+
def views(self):
|
|
80
|
+
@self.router.get("/hello", response=SomeSchema)
|
|
81
|
+
async def hello(request):
|
|
82
|
+
return SomeSchema(...)
|
|
83
|
+
|
|
84
|
+
or
|
|
85
|
+
|
|
86
|
+
class CustomAPIView(APIView):
|
|
87
|
+
api = api
|
|
88
|
+
api_route_path = "/custom"
|
|
89
|
+
router_tags = ["Custom"]
|
|
90
|
+
|
|
91
|
+
def views(self):
|
|
92
|
+
@self.router.get("/hello", response=SomeSchema)
|
|
93
|
+
async def hello(request):
|
|
94
|
+
return SomeSchema(...)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
CustomAPIView().add_views_to_route()
|
|
98
|
+
|
|
99
|
+
Attributes:
|
|
100
|
+
api: NinjaAPI instance used to mount the router.
|
|
101
|
+
router_tag: Single tag used if router_tags is not provided.
|
|
102
|
+
router_tags: List of tags assigned to the router.
|
|
103
|
+
api_route_path: Base path where the router is mounted.
|
|
104
|
+
auth: Default auth list or NOT_SET for unauthenticated endpoints.
|
|
105
|
+
router: Router instance where views are registered.
|
|
106
|
+
error_codes: Common error codes returned by endpoints.
|
|
107
|
+
|
|
108
|
+
Overridable methods:
|
|
109
|
+
views(): Register your endpoints using self.router.get/post/patch/delete.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(
|
|
113
|
+
self, api: NinjaAPI = None, prefix: str = None, tags: list[str] = None
|
|
114
|
+
) -> None:
|
|
115
|
+
self.api = api or self.api
|
|
116
|
+
self.api_route_path = prefix or self.api_route_path
|
|
117
|
+
self.router_tags = tags or self.router_tags or [self.router_tag]
|
|
118
|
+
self.router = Router(tags=self.router_tags)
|
|
119
|
+
self.error_codes = ERROR_CODES
|
|
120
|
+
|
|
121
|
+
def _add_views(self):
|
|
122
|
+
self.views()
|
|
123
|
+
return self.router
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class APIViewSet(API):
|
|
76
127
|
"""
|
|
77
128
|
Base viewset generating async CRUD + optional M2M endpoints for a Django model.
|
|
78
129
|
|
|
79
130
|
Usage:
|
|
131
|
+
@api.viewset(model=MyModel)
|
|
132
|
+
class MyModelViewSet(APIViewSet):
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
or
|
|
136
|
+
|
|
80
137
|
class MyModelViewSet(APIViewSet):
|
|
81
138
|
model = MyModel
|
|
82
139
|
api = api
|
|
@@ -109,8 +166,8 @@ class APIViewSet:
|
|
|
109
166
|
dict, and must return the (optionally) filtered queryset.
|
|
110
167
|
|
|
111
168
|
Example:
|
|
169
|
+
@api.viewset(model=models.User)
|
|
112
170
|
class UserViewSet(APIViewSet):
|
|
113
|
-
model = models.User
|
|
114
171
|
m2m_relations = [
|
|
115
172
|
M2MRelationSchema(
|
|
116
173
|
model=models.Tag,
|
|
@@ -149,7 +206,7 @@ class APIViewSet:
|
|
|
149
206
|
<related_name>_query_params_handler(queryset, filters): Async hook for per-M2M filtering.
|
|
150
207
|
|
|
151
208
|
Error responses:
|
|
152
|
-
All endpoints may return GenericMessageSchema for codes in ERROR_CODES (400,401,404
|
|
209
|
+
All endpoints may return GenericMessageSchema for codes in ERROR_CODES (400,401,404).
|
|
153
210
|
|
|
154
211
|
Internal:
|
|
155
212
|
Dynamic path/filter schemas built with pydantic.create_model.
|
|
@@ -157,12 +214,9 @@ class APIViewSet:
|
|
|
157
214
|
"""
|
|
158
215
|
|
|
159
216
|
model: ModelSerializer | Model
|
|
160
|
-
api: NinjaAPI
|
|
161
|
-
router_tag: str = ""
|
|
162
217
|
schema_in: Schema | None = None
|
|
163
218
|
schema_out: Schema | None = None
|
|
164
219
|
schema_update: Schema | None = None
|
|
165
|
-
auth: list | None = NOT_SET
|
|
166
220
|
get_auth: list | None = NOT_SET
|
|
167
221
|
post_auth: list | None = NOT_SET
|
|
168
222
|
patch_auth: list | None = NOT_SET
|
|
@@ -170,7 +224,6 @@ class APIViewSet:
|
|
|
170
224
|
pagination_class: type[AsyncPaginationBase] = PageNumberPagination
|
|
171
225
|
query_params: dict[str, tuple[type, ...]] = {}
|
|
172
226
|
disable: list[type[VIEW_TYPES]] = []
|
|
173
|
-
api_route_path: str = ""
|
|
174
227
|
list_docs = "List all objects."
|
|
175
228
|
create_docs = "Create a new object."
|
|
176
229
|
retrieve_docs = "Retrieve a specific object by its primary key."
|
|
@@ -180,8 +233,16 @@ class APIViewSet:
|
|
|
180
233
|
m2m_auth: list | None = NOT_SET
|
|
181
234
|
extra_decorators: DecoratorsSchema = DecoratorsSchema()
|
|
182
235
|
|
|
183
|
-
def __init__(
|
|
236
|
+
def __init__(
|
|
237
|
+
self,
|
|
238
|
+
api: NinjaAPI = None,
|
|
239
|
+
model: Model | ModelSerializer = None,
|
|
240
|
+
prefix: str = None,
|
|
241
|
+
tags: list[str] = None,
|
|
242
|
+
) -> None:
|
|
243
|
+
self.api = api or self.api
|
|
184
244
|
self.error_codes = ERROR_CODES
|
|
245
|
+
self.model = model or self.model
|
|
185
246
|
self.model_util = (
|
|
186
247
|
ModelUtil(self.model)
|
|
187
248
|
if not isinstance(self.model, ModelSerializerMeta)
|
|
@@ -191,16 +252,17 @@ class APIViewSet:
|
|
|
191
252
|
self.path_schema = self._generate_path_schema()
|
|
192
253
|
self.filters_schema = self._generate_filters_schema()
|
|
193
254
|
self.model_verbose_name = self.model._meta.verbose_name.capitalize()
|
|
194
|
-
self.router_tag =
|
|
195
|
-
|
|
196
|
-
)
|
|
197
|
-
self.router = Router(tags=[self.router_tag])
|
|
255
|
+
self.router_tag = self.router_tag or self.model_verbose_name
|
|
256
|
+
self.router_tags = self.router_tags or tags or [self.router_tag]
|
|
257
|
+
self.router = Router(tags=self.router_tags)
|
|
198
258
|
self.path = "/"
|
|
199
259
|
self.get_path = ""
|
|
200
260
|
self.path_retrieve = f"{{{self.model_util.model_pk_name}}}/"
|
|
201
261
|
self.get_path_retrieve = f"{{{self.model_util.model_pk_name}}}"
|
|
202
262
|
self.api_route_path = (
|
|
203
|
-
self.api_route_path
|
|
263
|
+
self.api_route_path
|
|
264
|
+
or prefix
|
|
265
|
+
or self.model_util.verbose_name_path_resolver()
|
|
204
266
|
)
|
|
205
267
|
self.m2m_api = (
|
|
206
268
|
None
|
|
@@ -450,18 +512,18 @@ class APIViewSet:
|
|
|
450
512
|
|
|
451
513
|
return self._set_additional_views()
|
|
452
514
|
|
|
453
|
-
def add_views_to_route(self):
|
|
454
|
-
"""
|
|
455
|
-
Attach router with registered endpoints to the NinjaAPI instance.
|
|
456
|
-
"""
|
|
457
|
-
return self.api.add_router(f"{self.api_route_path}", self._add_views())
|
|
458
|
-
|
|
459
515
|
|
|
460
516
|
class ReadOnlyViewSet(APIViewSet):
|
|
461
517
|
"""
|
|
462
518
|
ReadOnly viewset generating async List + Retrieve endpoints for a Django model.
|
|
463
519
|
|
|
464
520
|
Usage:
|
|
521
|
+
@api.viewset(model=MyModel)
|
|
522
|
+
class MyModelReadOnlyViewSet(ReadOnlyViewSet):
|
|
523
|
+
pass
|
|
524
|
+
|
|
525
|
+
or
|
|
526
|
+
|
|
465
527
|
class MyModelReadOnlyViewSet(ReadOnlyViewSet):
|
|
466
528
|
model = MyModel
|
|
467
529
|
api = api
|
|
@@ -476,10 +538,15 @@ class WriteOnlyViewSet(APIViewSet):
|
|
|
476
538
|
WriteOnly viewset generating async Create + Update + Delete endpoints for a Django model.
|
|
477
539
|
|
|
478
540
|
Usage:
|
|
541
|
+
@api.viewset(model=MyModel)
|
|
542
|
+
class MyModelWriteOnlyViewSet(WriteOnlyViewSet):
|
|
543
|
+
pass
|
|
544
|
+
|
|
545
|
+
or
|
|
546
|
+
|
|
479
547
|
class MyModelWriteOnlyViewSet(WriteOnlyViewSet):
|
|
480
548
|
model = MyModel
|
|
481
549
|
api = api
|
|
482
|
-
MyModelWriteOnlyViewSet().add_views_to_route()
|
|
483
550
|
"""
|
|
484
551
|
|
|
485
552
|
disable = ["list", "retrieve"]
|
|
File without changes
|
{django_ninja_aio_crud-2.1.0.dist-info → django_ninja_aio_crud-2.2.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|