django-ninja-aio-crud 0.9.1__py3-none-any.whl → 0.10.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-0.9.1.dist-info → django_ninja_aio_crud-0.10.0.dist-info}/METADATA +1 -1
- django_ninja_aio_crud-0.10.0.dist-info/RECORD +14 -0
- ninja_aio/__init__.py +1 -1
- ninja_aio/auth.py +11 -3
- ninja_aio/models.py +7 -0
- ninja_aio/schemas.py +24 -0
- ninja_aio/views.py +168 -18
- django_ninja_aio_crud-0.9.1.dist-info/RECORD +0 -14
- {django_ninja_aio_crud-0.9.1.dist-info → django_ninja_aio_crud-0.10.0.dist-info}/WHEEL +0 -0
- {django_ninja_aio_crud-0.9.1.dist-info → django_ninja_aio_crud-0.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
ninja_aio/__init__.py,sha256=c74pynHsTwHF_-RI5P-DJphEOG7jfzZyVasd5Xddw3M,120
|
|
2
|
+
ninja_aio/api.py,sha256=Fe6l3YCy7MW5TY4-Lbl80CFuK2NT2Y7tHfmqPk6Mqak,1735
|
|
3
|
+
ninja_aio/auth.py,sha256=c_ILAySswjbSIqnE9Y0G5n1qreXzAtSAaWfrhyer-i8,1283
|
|
4
|
+
ninja_aio/exceptions.py,sha256=gPnZX1Do2GXudbU8wDYkwhO70Qj0ZNrIJJ2UXRs9vYk,2241
|
|
5
|
+
ninja_aio/models.py,sha256=lMipICWuLV5zETVp56b-29R-P0gBWEzXT_z3uaTlkYE,18769
|
|
6
|
+
ninja_aio/parsers.py,sha256=e_4lGCPV7zs-HTqtdJTc8yQD2KPAn9njbL8nF_Mmgkc,153
|
|
7
|
+
ninja_aio/renders.py,sha256=0eYklRKd59aV4cZDom5vLZyA99Ob17OwkpMybsRXvyg,1970
|
|
8
|
+
ninja_aio/schemas.py,sha256=Fzu2ko3kUxkOnrjG5QYdmOXZd2gcpYGjVuocCW44NfQ,473
|
|
9
|
+
ninja_aio/types.py,sha256=TJSGlA7bt4g9fvPhJ7gzH5tKbLagPmZUzfgttEOp4xs,468
|
|
10
|
+
ninja_aio/views.py,sha256=qPRMMWddazQo2sEZAFzb-GxwExUQDRmfPYYv4qMoU80,20906
|
|
11
|
+
django_ninja_aio_crud-0.10.0.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
|
|
12
|
+
django_ninja_aio_crud-0.10.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
13
|
+
django_ninja_aio_crud-0.10.0.dist-info/METADATA,sha256=Jzl1pwyne8LHRlWHfuSDhix2EdbX17NYZziFbua4J8I,14139
|
|
14
|
+
django_ninja_aio_crud-0.10.0.dist-info/RECORD,,
|
ninja_aio/__init__.py
CHANGED
ninja_aio/auth.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from joserfc import jwt, jwk
|
|
1
|
+
from joserfc import jwt, jwk, errors
|
|
2
2
|
from django.http.request import HttpRequest
|
|
3
3
|
from ninja.security.http import HttpBearer
|
|
4
4
|
|
|
@@ -25,11 +25,19 @@ class AsyncJwtBearer(HttpBearer):
|
|
|
25
25
|
pass
|
|
26
26
|
|
|
27
27
|
async def authenticate(self, request: HttpRequest, token: str):
|
|
28
|
+
"""
|
|
29
|
+
Authenticate the request and return the user if authentication is successful.
|
|
30
|
+
If authentication fails, returns false.
|
|
31
|
+
"""
|
|
28
32
|
try:
|
|
29
33
|
self.dcd = jwt.decode(token, self.jwt_public, algorithms=self.algorithms)
|
|
30
34
|
except ValueError as exc:
|
|
31
|
-
raise AuthError(", ".join(exc.args), 401)
|
|
35
|
+
# raise AuthError(", ".join(exc.args), 401)
|
|
36
|
+
return False
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
try:
|
|
39
|
+
self.validate_claims(self.dcd.claims)
|
|
40
|
+
except errors.JoseError as exc:
|
|
41
|
+
return False
|
|
34
42
|
|
|
35
43
|
return await self.auth_handler(request)
|
ninja_aio/models.py
CHANGED
|
@@ -60,6 +60,10 @@ class ModelUtil:
|
|
|
60
60
|
filters: dict = None,
|
|
61
61
|
getters: dict = None,
|
|
62
62
|
with_qs_request=True,
|
|
63
|
+
) -> (
|
|
64
|
+
type["ModelSerializer"]
|
|
65
|
+
| models.Model
|
|
66
|
+
| models.QuerySet[type["ModelSerializer"] | models.Model]
|
|
63
67
|
):
|
|
64
68
|
get_q = {self.model_pk_name: pk} if pk is not None else {}
|
|
65
69
|
if getters:
|
|
@@ -73,6 +77,9 @@ class ModelUtil:
|
|
|
73
77
|
if filters:
|
|
74
78
|
obj_qs = obj_qs.filter(**filters)
|
|
75
79
|
|
|
80
|
+
if not get_q:
|
|
81
|
+
return obj_qs
|
|
82
|
+
|
|
76
83
|
try:
|
|
77
84
|
obj = await obj_qs.aget(**get_q)
|
|
78
85
|
except ObjectDoesNotExist:
|
ninja_aio/schemas.py
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
|
+
from ninja import Schema
|
|
1
2
|
from pydantic import RootModel
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
class GenericMessageSchema(RootModel[dict[str, str]]):
|
|
5
6
|
root: dict[str, str]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class M2MDetailSchema(Schema):
|
|
10
|
+
count: int
|
|
11
|
+
details: list[str]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class M2MSchemaOut(Schema):
|
|
15
|
+
errors: M2MDetailSchema
|
|
16
|
+
results: M2MDetailSchema
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class M2MAddSchemaIn(Schema):
|
|
20
|
+
add: list = []
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class M2MRemoveSchemaIn(Schema):
|
|
24
|
+
remove: list = []
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class M2MSchemaIn(Schema):
|
|
28
|
+
add: list = []
|
|
29
|
+
remove: list = []
|
ninja_aio/views.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
from typing import List
|
|
2
3
|
|
|
3
4
|
from ninja import NinjaAPI, Router, Schema, Path, Query
|
|
@@ -8,7 +9,13 @@ from django.db.models import Model, QuerySet
|
|
|
8
9
|
from pydantic import create_model
|
|
9
10
|
|
|
10
11
|
from .models import ModelSerializer, ModelUtil
|
|
11
|
-
from .schemas import
|
|
12
|
+
from .schemas import (
|
|
13
|
+
GenericMessageSchema,
|
|
14
|
+
M2MSchemaOut,
|
|
15
|
+
M2MSchemaIn,
|
|
16
|
+
M2MAddSchemaIn,
|
|
17
|
+
M2MRemoveSchemaIn,
|
|
18
|
+
)
|
|
12
19
|
from .types import ModelSerializerMeta, VIEW_TYPES
|
|
13
20
|
|
|
14
21
|
ERROR_CODES = frozenset({400, 401, 404, 428})
|
|
@@ -93,6 +100,11 @@ class APIViewSet:
|
|
|
93
100
|
- **retrieve_docs** (`str`): Documentation for the retrieve view.
|
|
94
101
|
- **update_docs** (`str`): Documentation for the update view.
|
|
95
102
|
- **delete_docs** (`str`): Documentation for the delete view.
|
|
103
|
+
- **m2m_relations** (`tuple[ModelSerializer | Model, str]`): Many-to-many relations to manage.
|
|
104
|
+
- **m2m_add** (`bool`): Enable add operation for M2M relations.
|
|
105
|
+
- **m2m_remove** (`bool`): Enable remove operation for M2M relations.
|
|
106
|
+
- **m2m_get** (`bool`): Enable get operation for M2M relations.
|
|
107
|
+
- **m2m_auth** (`list | None`): Authentication for M2M views.
|
|
96
108
|
|
|
97
109
|
## Notes:
|
|
98
110
|
If the model is a ModelSerializer instance, schemas are generated
|
|
@@ -137,6 +149,11 @@ class APIViewSet:
|
|
|
137
149
|
retrieve_docs = "Retrieve a specific object by its primary key."
|
|
138
150
|
update_docs = "Update an object by its primary key."
|
|
139
151
|
delete_docs = "Delete an object by its primary key."
|
|
152
|
+
m2m_relations: tuple[ModelSerializer | Model, str] = []
|
|
153
|
+
m2m_add = True
|
|
154
|
+
m2m_remove = True
|
|
155
|
+
m2m_get = True
|
|
156
|
+
m2m_auth: list | None = NOT_SET
|
|
140
157
|
|
|
141
158
|
def __init__(self) -> None:
|
|
142
159
|
self.error_codes = ERROR_CODES
|
|
@@ -219,13 +236,12 @@ class APIViewSet:
|
|
|
219
236
|
return queryset
|
|
220
237
|
|
|
221
238
|
def create_view(self):
|
|
222
|
-
@self.
|
|
223
|
-
|
|
239
|
+
@self.router.post(
|
|
240
|
+
self.path,
|
|
224
241
|
auth=self.post_view_auth(),
|
|
225
242
|
summary=f"Create {self.model._meta.verbose_name.capitalize()}",
|
|
226
243
|
description=self.create_docs,
|
|
227
244
|
response={201: self.schema_out, self.error_codes: GenericMessageSchema},
|
|
228
|
-
tags=[self.router_tag],
|
|
229
245
|
)
|
|
230
246
|
async def create(request: HttpRequest, data: self.schema_in): # type: ignore
|
|
231
247
|
return 201, await self.model_util.create_s(request, data, self.schema_out)
|
|
@@ -234,8 +250,8 @@ class APIViewSet:
|
|
|
234
250
|
return create
|
|
235
251
|
|
|
236
252
|
def list_view(self):
|
|
237
|
-
@self.
|
|
238
|
-
self.
|
|
253
|
+
@self.router.get(
|
|
254
|
+
self.get_path,
|
|
239
255
|
auth=self.get_view_auth(),
|
|
240
256
|
summary=f"List {self.model._meta.verbose_name_plural.capitalize()}",
|
|
241
257
|
description=self.list_docs,
|
|
@@ -243,7 +259,6 @@ class APIViewSet:
|
|
|
243
259
|
200: List[self.schema_out],
|
|
244
260
|
self.error_codes: GenericMessageSchema,
|
|
245
261
|
},
|
|
246
|
-
tags=[self.router_tag],
|
|
247
262
|
)
|
|
248
263
|
@paginate(self.pagination_class)
|
|
249
264
|
async def list(
|
|
@@ -268,13 +283,12 @@ class APIViewSet:
|
|
|
268
283
|
return list
|
|
269
284
|
|
|
270
285
|
def retrieve_view(self):
|
|
271
|
-
@self.
|
|
272
|
-
|
|
286
|
+
@self.router.get(
|
|
287
|
+
self.get_path_retrieve,
|
|
273
288
|
auth=self.get_view_auth(),
|
|
274
289
|
summary=f"Retrieve {self.model._meta.verbose_name.capitalize()}",
|
|
275
290
|
description=self.retrieve_docs,
|
|
276
291
|
response={200: self.schema_out, self.error_codes: GenericMessageSchema},
|
|
277
|
-
tags=[self.router_tag],
|
|
278
292
|
)
|
|
279
293
|
async def retrieve(request: HttpRequest, pk: Path[self.path_schema]): # type: ignore
|
|
280
294
|
obj = await self.model_util.get_object(request, self._get_pk(pk))
|
|
@@ -284,13 +298,12 @@ class APIViewSet:
|
|
|
284
298
|
return retrieve
|
|
285
299
|
|
|
286
300
|
def update_view(self):
|
|
287
|
-
@self.
|
|
288
|
-
|
|
301
|
+
@self.router.patch(
|
|
302
|
+
self.path_retrieve,
|
|
289
303
|
auth=self.patch_view_auth(),
|
|
290
304
|
summary=f"Update {self.model._meta.verbose_name.capitalize()}",
|
|
291
305
|
description=self.update_docs,
|
|
292
306
|
response={200: self.schema_out, self.error_codes: GenericMessageSchema},
|
|
293
|
-
tags=[self.router_tag],
|
|
294
307
|
)
|
|
295
308
|
async def update(
|
|
296
309
|
request: HttpRequest,
|
|
@@ -305,13 +318,12 @@ class APIViewSet:
|
|
|
305
318
|
return update
|
|
306
319
|
|
|
307
320
|
def delete_view(self):
|
|
308
|
-
@self.
|
|
309
|
-
|
|
321
|
+
@self.router.delete(
|
|
322
|
+
self.path_retrieve,
|
|
310
323
|
auth=self.delete_view_auth(),
|
|
311
324
|
summary=f"Delete {self.model._meta.verbose_name.capitalize()}",
|
|
312
325
|
description=self.delete_docs,
|
|
313
326
|
response={204: None, self.error_codes: GenericMessageSchema},
|
|
314
|
-
tags=[self.router_tag],
|
|
315
327
|
)
|
|
316
328
|
async def delete(request: HttpRequest, pk: Path[self.path_schema]): # type: ignore
|
|
317
329
|
return 204, await self.model_util.delete_s(request, self._get_pk(pk))
|
|
@@ -352,8 +364,145 @@ class APIViewSet:
|
|
|
352
364
|
pass
|
|
353
365
|
"""
|
|
354
366
|
|
|
367
|
+
async def _check_m2m_objs(
|
|
368
|
+
self,
|
|
369
|
+
request: HttpRequest,
|
|
370
|
+
objs_pks: list,
|
|
371
|
+
model: ModelSerializer | Model,
|
|
372
|
+
related_manager: QuerySet,
|
|
373
|
+
remove: bool = False,
|
|
374
|
+
):
|
|
375
|
+
errors, objs_detail, objs = [], [], []
|
|
376
|
+
rel_objs = [rel_obj async for rel_obj in related_manager.select_related().all()]
|
|
377
|
+
rel_model_name = model._meta.verbose_name.capitalize()
|
|
378
|
+
for obj_pk in objs_pks:
|
|
379
|
+
rel_obj = await (
|
|
380
|
+
await ModelUtil(model).get_object(request, filters={"pk": obj_pk})
|
|
381
|
+
).afirst()
|
|
382
|
+
if rel_obj is None:
|
|
383
|
+
errors.append(f"{rel_model_name} with pk {obj_pk} not found.")
|
|
384
|
+
continue
|
|
385
|
+
if remove ^ (rel_obj in rel_objs):
|
|
386
|
+
errors.append(
|
|
387
|
+
f"{rel_model_name} with id {obj_pk} is {'not ' if remove else ''} in {self.model_util.model_name}"
|
|
388
|
+
)
|
|
389
|
+
continue
|
|
390
|
+
objs.append(rel_obj)
|
|
391
|
+
objs_detail.append(
|
|
392
|
+
f"{rel_model_name} with id {obj_pk} successfully {'removed' if remove else 'added'}"
|
|
393
|
+
)
|
|
394
|
+
return errors, objs_detail, objs
|
|
395
|
+
|
|
396
|
+
def _m2m_views(self):
|
|
397
|
+
for model, related_name in self.m2m_relations:
|
|
398
|
+
rel_util = ModelUtil(model)
|
|
399
|
+
rel_path = rel_util.verbose_name_path_resolver()
|
|
400
|
+
if self.m2m_get:
|
|
401
|
+
|
|
402
|
+
@self.router.get(
|
|
403
|
+
f"{self.path_retrieve}{rel_path}",
|
|
404
|
+
response={
|
|
405
|
+
200: List[
|
|
406
|
+
model.generate_related_s(),
|
|
407
|
+
],
|
|
408
|
+
self.error_codes : GenericMessageSchema,
|
|
409
|
+
},
|
|
410
|
+
auth=self.m2m_auth,
|
|
411
|
+
summary=f"Get {rel_util.model._meta.verbose_name_plural.capitalize()}",
|
|
412
|
+
description=f"Get all related {rel_util.model._meta.verbose_name_plural.capitalize()}",
|
|
413
|
+
)
|
|
414
|
+
async def get_related(request: HttpRequest, pk: Path[self.path_schema]): # type: ignore
|
|
415
|
+
obj = await self.model_util.get_object(request, self._get_pk(pk))
|
|
416
|
+
related_manager = getattr(obj, related_name)
|
|
417
|
+
related_qs = related_manager.all()
|
|
418
|
+
related_objs = [
|
|
419
|
+
await rel_util.read_s(
|
|
420
|
+
request, rel_obj, model.generate_related_s()
|
|
421
|
+
)
|
|
422
|
+
async for rel_obj in related_qs
|
|
423
|
+
]
|
|
424
|
+
return related_objs
|
|
425
|
+
get_related.__name__ = f"get_{self.model_util.model_name}_{rel_path}"
|
|
426
|
+
|
|
427
|
+
if self.m2m_add or self.m2m_remove:
|
|
428
|
+
if self.m2m_add and self.m2m_remove:
|
|
429
|
+
schema_in = M2MSchemaIn
|
|
430
|
+
elif self.m2m_add:
|
|
431
|
+
schema_in = M2MAddSchemaIn
|
|
432
|
+
else: # self.m2m_remove must be True
|
|
433
|
+
schema_in = M2MRemoveSchemaIn
|
|
434
|
+
|
|
435
|
+
@self.router.post(
|
|
436
|
+
f"{self.path_retrieve}{rel_path}/",
|
|
437
|
+
response={
|
|
438
|
+
200: M2MSchemaOut,
|
|
439
|
+
self.error_codes: GenericMessageSchema,
|
|
440
|
+
},
|
|
441
|
+
auth=self.m2m_auth,
|
|
442
|
+
summary=f"Add or Remove {rel_util.model._meta.verbose_name_plural.capitalize()}",
|
|
443
|
+
description=f"Add or remove {rel_util.model._meta.verbose_name_plural.capitalize()}"
|
|
444
|
+
)
|
|
445
|
+
async def add_and_remove_related(
|
|
446
|
+
request: HttpRequest,
|
|
447
|
+
pk: Path[self.path_schema], # type: ignore
|
|
448
|
+
data: schema_in, # type: ignore
|
|
449
|
+
):
|
|
450
|
+
obj = await self.model_util.get_object(
|
|
451
|
+
request, self._get_pk(pk)
|
|
452
|
+
)
|
|
453
|
+
related_manager: QuerySet = getattr(obj, related_name)
|
|
454
|
+
(
|
|
455
|
+
add_errors,
|
|
456
|
+
add_details,
|
|
457
|
+
add_objs,
|
|
458
|
+
remove_errors,
|
|
459
|
+
remove_details,
|
|
460
|
+
remove_objs,
|
|
461
|
+
) = [], [], [], [], [], []
|
|
462
|
+
if self.m2m_add and hasattr(data, "add"):
|
|
463
|
+
(
|
|
464
|
+
add_errors,
|
|
465
|
+
add_details,
|
|
466
|
+
add_objs,
|
|
467
|
+
) = await self._check_m2m_objs(
|
|
468
|
+
request, data.add, model, related_manager
|
|
469
|
+
)
|
|
470
|
+
if self.m2m_remove and hasattr(data, "remove"):
|
|
471
|
+
(
|
|
472
|
+
remove_errors,
|
|
473
|
+
remove_details,
|
|
474
|
+
remove_objs,
|
|
475
|
+
) = await self._check_m2m_objs(
|
|
476
|
+
request,
|
|
477
|
+
data.remove,
|
|
478
|
+
model,
|
|
479
|
+
related_manager,
|
|
480
|
+
remove=True,
|
|
481
|
+
)
|
|
482
|
+
await asyncio.gather(
|
|
483
|
+
related_manager.aadd(*add_objs),
|
|
484
|
+
related_manager.aremove(*remove_objs),
|
|
485
|
+
)
|
|
486
|
+
results = add_details + remove_details
|
|
487
|
+
errors = add_errors + remove_errors
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
"results": {
|
|
491
|
+
"count": len(results),
|
|
492
|
+
"details": results,
|
|
493
|
+
},
|
|
494
|
+
"errors": {
|
|
495
|
+
"count": len(errors),
|
|
496
|
+
"details": errors,
|
|
497
|
+
},
|
|
498
|
+
}
|
|
499
|
+
add_and_remove_related.__name__ = f"add_and_remove_{self.model_util.model_name}_{rel_path}"
|
|
500
|
+
|
|
501
|
+
|
|
355
502
|
def _add_views(self):
|
|
356
503
|
if "all" in self.disable:
|
|
504
|
+
if self.m2m_relations:
|
|
505
|
+
self._m2m_views()
|
|
357
506
|
self.views()
|
|
358
507
|
return self.router
|
|
359
508
|
|
|
@@ -364,8 +513,9 @@ class APIViewSet:
|
|
|
364
513
|
view()
|
|
365
514
|
|
|
366
515
|
self.views()
|
|
516
|
+
if self.m2m_relations:
|
|
517
|
+
self._m2m_views()
|
|
367
518
|
return self.router
|
|
368
519
|
|
|
369
520
|
def add_views_to_route(self):
|
|
370
|
-
self._add_views()
|
|
371
|
-
return self.api.add_router(f"{self.api_route_path}", self.router, tags=[self.router_tag])
|
|
521
|
+
return self.api.add_router(f"{self.api_route_path}", self._add_views())
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
ninja_aio/__init__.py,sha256=Kjuqnytju5QU_0PDzq6g4K9q2ulTLq4twGo-Cqcqd1k,119
|
|
2
|
-
ninja_aio/api.py,sha256=Fe6l3YCy7MW5TY4-Lbl80CFuK2NT2Y7tHfmqPk6Mqak,1735
|
|
3
|
-
ninja_aio/auth.py,sha256=fKboioU4sezPukKJukIwiboxml_KV7irhCH3vGYt5pU,1008
|
|
4
|
-
ninja_aio/exceptions.py,sha256=gPnZX1Do2GXudbU8wDYkwhO70Qj0ZNrIJJ2UXRs9vYk,2241
|
|
5
|
-
ninja_aio/models.py,sha256=Kw9PCg4vgo1Y33XCxmwUGg9V1-0wkVxFByHdbDt5P78,18588
|
|
6
|
-
ninja_aio/parsers.py,sha256=e_4lGCPV7zs-HTqtdJTc8yQD2KPAn9njbL8nF_Mmgkc,153
|
|
7
|
-
ninja_aio/renders.py,sha256=0eYklRKd59aV4cZDom5vLZyA99Ob17OwkpMybsRXvyg,1970
|
|
8
|
-
ninja_aio/schemas.py,sha256=EgRkfhnzZqwGvdBmqlZixMtMcoD1ZxV_qzJ3fmaAy20,113
|
|
9
|
-
ninja_aio/types.py,sha256=TJSGlA7bt4g9fvPhJ7gzH5tKbLagPmZUzfgttEOp4xs,468
|
|
10
|
-
ninja_aio/views.py,sha256=9U4b1avYRHwxf-uXPqP_-aIMap8ORn2uHFA0JPK4KcY,14226
|
|
11
|
-
django_ninja_aio_crud-0.9.1.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
|
|
12
|
-
django_ninja_aio_crud-0.9.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
13
|
-
django_ninja_aio_crud-0.9.1.dist-info/METADATA,sha256=Ud2eavv0Ai9iZZX_D8zkYJjkEsOC2_-h8oKRnR1Fp8U,14138
|
|
14
|
-
django_ninja_aio_crud-0.9.1.dist-info/RECORD,,
|
|
File without changes
|
{django_ninja_aio_crud-0.9.1.dist-info → django_ninja_aio_crud-0.10.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|