django-ninja-aio-crud 2.17.0__py3-none-any.whl → 2.18.1__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.
@@ -0,0 +1,431 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-ninja-aio-crud
3
+ Version: 2.18.1
4
+ Summary: Django Ninja AIO CRUD - Rest Framework
5
+ Author: Giuseppe Casillo
6
+ Requires-Python: >=3.10, <3.15
7
+ Description-Content-Type: text/markdown
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Topic :: Internet
10
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
11
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
+ Classifier: Topic :: Software Development :: Libraries
13
+ Classifier: Topic :: Software Development
14
+ Classifier: Environment :: Web Environment
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Programming Language :: Python :: 3 :: Only
23
+ Classifier: Framework :: Django
24
+ Classifier: Framework :: AsyncIO
25
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
26
+ Classifier: Topic :: Internet :: WWW/HTTP
27
+ License-File: LICENSE
28
+ Requires-Dist: django-ninja >=1.3.0, <1.6
29
+ Requires-Dist: joserfc >=1.0.0, <= 1.4.1
30
+ Requires-Dist: orjson >= 3.10.7, <= 3.11.5
31
+ Requires-Dist: coverage ; extra == "test"
32
+ Project-URL: Documentation, https://django-ninja-aio.com
33
+ Project-URL: Repository, https://github.com/caspel26/django-ninja-aio-crud
34
+ Provides-Extra: test
35
+
36
+ <p align="center">
37
+ <img src="https://raw.githubusercontent.com/caspel26/django-ninja-aio-crud/main/docs/images/logo-full.png" alt="django-ninja-aio-crud">
38
+ </p>
39
+
40
+ <p align="center">
41
+ <strong>Async CRUD framework for Django Ninja</strong><br>
42
+ Automatic schema generation · Filtering · Pagination · Auth · M2M management
43
+ </p>
44
+
45
+ <p align="center">
46
+ <a href="https://github.com/caspel26/django-ninja-aio-crud/actions/workflows/coverage.yml"><img src="https://github.com/caspel26/django-ninja-aio-crud/actions/workflows/coverage.yml/badge.svg" alt="Tests"></a>
47
+ <a href="https://sonarcloud.io/summary/new_code?id=caspel26_django-ninja-aio-crud"><img src="https://sonarcloud.io/api/project_badges/measure?project=caspel26_django-ninja-aio-crud&metric=alert_status" alt="Quality Gate Status"></a>
48
+ <a href="https://codecov.io/gh/caspel26/django-ninja-aio-crud/"><img src="https://codecov.io/gh/caspel26/django-ninja-aio-crud/graph/badge.svg?token=DZ5WDT3S20" alt="codecov"></a>
49
+ <a href="https://pypi.org/project/django-ninja-aio-crud/"><img src="https://img.shields.io/pypi/v/django-ninja-aio-crud?color=g&logo=pypi&logoColor=white" alt="PyPI - Version"></a>
50
+ <a href="LICENSE"><img src="https://img.shields.io/pypi/l/django-ninja-aio-crud" alt="PyPI - License"></a>
51
+ <a href="https://github.com/astral-sh/ruff"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff"></a>
52
+ <a href="https://github.com/caspel26/django-ninja-aio-crud/actions/workflows/performance.yml"><img src="https://github.com/caspel26/django-ninja-aio-crud/actions/workflows/performance.yml/badge.svg" alt="Performance"></a>
53
+ </p>
54
+
55
+ <p align="center">
56
+ <a href="https://django-ninja-aio.com">Documentation</a> ·
57
+ <a href="https://pypi.org/project/django-ninja-aio-crud/">PyPI</a> ·
58
+ <a href="https://caspel26.github.io/django-ninja-aio-crud/">Performance Benchmarks</a> ·
59
+ <a href="https://github.com/caspel26/ninja-aio-blog-example">Example Project</a> ·
60
+ <a href="https://github.com/caspel26/django-ninja-aio-crud/issues">Issues</a>
61
+ </p>
62
+
63
+ ---
64
+
65
+ ## Features
66
+
67
+ | | Feature | Description |
68
+ |---|---|---|
69
+ | **Meta-driven Serializer** | Dynamic schemas | Generate CRUD schemas for existing Django models without changing base classes |
70
+ | **Async CRUD ViewSets** | Full operations | Create, list, retrieve, update, delete — all async |
71
+ | **Auto Schemas** | Pydantic generation | Automatic read/create/update schemas from `ModelSerializer` |
72
+ | **Dynamic Query Params** | Runtime schemas | Built with `pydantic.create_model` for flexible filtering |
73
+ | **Per-method Auth** | Granular control | `auth`, `get_auth`, `post_auth`, etc. |
74
+ | **Async Pagination** | Customizable | Fully async, pluggable pagination classes |
75
+ | **M2M Relations** | Add/remove/list | Endpoints via `M2MRelationSchema` with filtering support |
76
+ | **Reverse Relations** | Nested serialization | Automatic handling of reverse FK and M2M |
77
+ | **Lifecycle Hooks** | Extensible | `before_save`, `after_save`, `custom_actions`, `on_delete`, and more |
78
+ | **Schema Validators** | Pydantic validators | `@field_validator` and `@model_validator` on serializer classes |
79
+ | **ORJSON Renderer** | Performance | Built-in fast JSON rendering via `NinjaAIO` |
80
+
81
+ ---
82
+
83
+ ## Quick Start
84
+
85
+ ### Option A: Meta-driven Serializer (existing models)
86
+
87
+ Use this if you already have Django models and don't want to change their base class.
88
+
89
+ ```python
90
+ from ninja_aio.models import serializers
91
+ from ninja_aio.views import APIViewSet
92
+ from ninja_aio import NinjaAIO
93
+ from . import models
94
+
95
+ class BookSerializer(serializers.Serializer):
96
+ class Meta:
97
+ model = models.Book
98
+ schema_in = serializers.SchemaModelConfig(fields=["title", "published"])
99
+ schema_out = serializers.SchemaModelConfig(fields=["id", "title", "published"])
100
+ schema_update = serializers.SchemaModelConfig(
101
+ optionals=[("title", str), ("published", bool)]
102
+ )
103
+
104
+ api = NinjaAIO()
105
+
106
+ @api.viewset(models.Book)
107
+ class BookViewSet(APIViewSet):
108
+ serializer_class = BookSerializer
109
+ ```
110
+
111
+ ### Option B: ModelSerializer (new projects)
112
+
113
+ Define models with built-in serialization for minimal boilerplate.
114
+
115
+ **models.py**
116
+
117
+ ```python
118
+ from django.db import models
119
+ from ninja_aio.models import ModelSerializer
120
+
121
+ class Book(ModelSerializer):
122
+ title = models.CharField(max_length=120)
123
+ published = models.BooleanField(default=True)
124
+
125
+ class ReadSerializer:
126
+ fields = ["id", "title", "published"]
127
+
128
+ class CreateSerializer:
129
+ fields = ["title", "published"]
130
+
131
+ class UpdateSerializer:
132
+ optionals = [("title", str), ("published", bool)]
133
+ ```
134
+
135
+ **views.py**
136
+
137
+ ```python
138
+ from ninja_aio import NinjaAIO
139
+ from ninja_aio.views import APIViewSet
140
+ from .models import Book
141
+
142
+ api = NinjaAIO()
143
+
144
+ @api.viewset(Book)
145
+ class BookViewSet(APIViewSet):
146
+ pass
147
+ ```
148
+
149
+ > Visit `/docs` — CRUD endpoints ready.
150
+
151
+ ---
152
+
153
+ ## Query Filtering
154
+
155
+ ```python
156
+ @api.viewset(Book)
157
+ class BookViewSet(APIViewSet):
158
+ query_params = {"published": (bool, None), "title": (str, None)}
159
+
160
+ async def query_params_handler(self, queryset, filters):
161
+ if filters.get("published") is not None:
162
+ queryset = queryset.filter(published=filters["published"])
163
+ if filters.get("title"):
164
+ queryset = queryset.filter(title__icontains=filters["title"])
165
+ return queryset
166
+ ```
167
+
168
+ ```
169
+ GET /book/?published=true&title=python
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Many-to-Many Relations
175
+
176
+ ```python
177
+ from ninja_aio.schemas import M2MRelationSchema
178
+
179
+ class Tag(ModelSerializer):
180
+ name = models.CharField(max_length=50)
181
+ class ReadSerializer:
182
+ fields = ["id", "name"]
183
+
184
+ class Article(ModelSerializer):
185
+ title = models.CharField(max_length=120)
186
+ tags = models.ManyToManyField(Tag, related_name="articles")
187
+ class ReadSerializer:
188
+ fields = ["id", "title", "tags"]
189
+
190
+ @api.viewset(Article)
191
+ class ArticleViewSet(APIViewSet):
192
+ m2m_relations = [
193
+ M2MRelationSchema(
194
+ model=Tag,
195
+ related_name="tags",
196
+ filters={"name": (str, "")}
197
+ )
198
+ ]
199
+
200
+ async def tags_query_params_handler(self, queryset, filters):
201
+ n = filters.get("name")
202
+ if n:
203
+ queryset = queryset.filter(name__icontains=n)
204
+ return queryset
205
+ ```
206
+
207
+ **Endpoints:**
208
+
209
+ ```
210
+ GET /article/{pk}/tag?name=dev
211
+ POST /article/{pk}/tag/ body: {"add": [1, 2], "remove": [3]}
212
+ ```
213
+
214
+ ---
215
+
216
+ ## Authentication (JWT)
217
+
218
+ ```python
219
+ from ninja_aio.auth import AsyncJwtBearer
220
+ from joserfc import jwk
221
+
222
+ class JWTAuth(AsyncJwtBearer):
223
+ jwt_public = jwk.RSAKey.import_key("-----BEGIN PUBLIC KEY----- ...")
224
+ jwt_alg = "RS256"
225
+ claims = {"sub": {"essential": True}}
226
+
227
+ async def auth_handler(self, request):
228
+ book_id = self.dcd.claims.get("sub")
229
+ return await Book.objects.aget(id=book_id)
230
+
231
+ @api.viewset(Book)
232
+ class SecureBookViewSet(APIViewSet):
233
+ auth = [JWTAuth()]
234
+ get_auth = None # list/retrieve remain public
235
+ ```
236
+
237
+ ---
238
+
239
+ ## Lifecycle Hooks
240
+
241
+ Available on every save/delete cycle:
242
+
243
+ | Hook | When |
244
+ |---|---|
245
+ | `on_create_before_save` | Before first save |
246
+ | `on_create_after_save` | After first save |
247
+ | `before_save` | Before any save |
248
+ | `after_save` | After any save |
249
+ | `on_delete` | After deletion |
250
+ | `custom_actions(payload)` | Create/update custom field logic |
251
+ | `post_create()` | After create commit |
252
+
253
+ ---
254
+
255
+ ## Custom Endpoints
256
+
257
+ ```python
258
+ from ninja_aio.decorators import api_get
259
+
260
+ @api.viewset(Book)
261
+ class BookViewSet(APIViewSet):
262
+ @api_get("/stats/")
263
+ async def stats(self, request):
264
+ total = await Book.objects.acount()
265
+ return {"total": total}
266
+ ```
267
+
268
+ ---
269
+
270
+ ## Pagination
271
+
272
+ Default: `PageNumberPagination`. Override per ViewSet:
273
+
274
+ ```python
275
+ from ninja.pagination import PageNumberPagination
276
+
277
+ class LargePagination(PageNumberPagination):
278
+ page_size = 50
279
+ max_page_size = 200
280
+
281
+ @api.viewset(Book)
282
+ class BookViewSet(APIViewSet):
283
+ pagination_class = LargePagination
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Schema Validators
289
+
290
+ Add Pydantic `@field_validator` and `@model_validator` directly on serializer classes for input validation.
291
+
292
+ ### ModelSerializer
293
+
294
+ Declare validators on inner serializer classes:
295
+
296
+ ```python
297
+ from django.db import models
298
+ from pydantic import field_validator, model_validator
299
+ from ninja_aio.models import ModelSerializer
300
+
301
+ class Book(ModelSerializer):
302
+ title = models.CharField(max_length=120)
303
+ description = models.TextField(blank=True)
304
+
305
+ class CreateSerializer:
306
+ fields = ["title", "description"]
307
+
308
+ @field_validator("title")
309
+ @classmethod
310
+ def validate_title_min_length(cls, v):
311
+ if len(v) < 3:
312
+ raise ValueError("Title must be at least 3 characters")
313
+ return v
314
+
315
+ class UpdateSerializer:
316
+ optionals = [("title", str), ("description", str)]
317
+
318
+ @field_validator("title")
319
+ @classmethod
320
+ def validate_title_not_empty(cls, v):
321
+ if v is not None and len(v.strip()) == 0:
322
+ raise ValueError("Title cannot be blank")
323
+ return v
324
+
325
+ class ReadSerializer:
326
+ fields = ["id", "title", "description"]
327
+
328
+ @model_validator(mode="after")
329
+ def enrich_output(self):
330
+ # Transform or enrich the output schema
331
+ return self
332
+ ```
333
+
334
+ ### Meta-driven Serializer
335
+
336
+ Use dedicated `{Type}Validators` inner classes:
337
+
338
+ ```python
339
+ from pydantic import field_validator, model_validator
340
+ from ninja_aio.models import serializers
341
+ from . import models
342
+
343
+ class BookSerializer(serializers.Serializer):
344
+ class Meta:
345
+ model = models.Book
346
+ schema_in = serializers.SchemaModelConfig(fields=["title", "description"])
347
+ schema_out = serializers.SchemaModelConfig(fields=["id", "title", "description"])
348
+ schema_update = serializers.SchemaModelConfig(
349
+ optionals=[("title", str), ("description", str)]
350
+ )
351
+
352
+ class CreateValidators:
353
+ @field_validator("title")
354
+ @classmethod
355
+ def validate_title_min_length(cls, v):
356
+ if len(v) < 3:
357
+ raise ValueError("Title must be at least 3 characters")
358
+ return v
359
+
360
+ class UpdateValidators:
361
+ @field_validator("title")
362
+ @classmethod
363
+ def validate_title_not_empty(cls, v):
364
+ if v is not None and len(v.strip()) == 0:
365
+ raise ValueError("Title cannot be blank")
366
+ return v
367
+
368
+ class ReadValidators:
369
+ @model_validator(mode="after")
370
+ def enrich_output(self):
371
+ return self
372
+ ```
373
+
374
+ **Validator class mapping:**
375
+
376
+ | Schema type | ModelSerializer | Serializer (Meta-driven) |
377
+ |---|---|---|
378
+ | Create | `CreateSerializer` | `CreateValidators` |
379
+ | Update | `UpdateSerializer` | `UpdateValidators` |
380
+ | Read | `ReadSerializer` | `ReadValidators` |
381
+ | Detail | `DetailSerializer` | `DetailValidators` |
382
+
383
+ ---
384
+
385
+ ## Disable Operations
386
+
387
+ ```python
388
+ @api.viewset(Book)
389
+ class ReadOnlyBookViewSet(APIViewSet):
390
+ disable = ["update", "delete"]
391
+ ```
392
+
393
+ ---
394
+
395
+ ## Performance
396
+
397
+ View live benchmarks tracking schema generation, serialization, and CRUD throughput:
398
+
399
+ **[Live Performance Report](https://caspel26.github.io/django-ninja-aio-crud/)** — Interactive charts with historical trends
400
+
401
+ ### Performance Tips
402
+
403
+ - Use `queryset_request` classmethod to `select_related` / `prefetch_related`
404
+ - Index frequently filtered fields
405
+ - Keep pagination enabled for large datasets
406
+ - Limit slices (`queryset = queryset[:1000]`) for heavy searches
407
+
408
+ ---
409
+
410
+ ## Contributing
411
+
412
+ 1. Fork the repository
413
+ 2. Create a feature branch
414
+ 3. Add tests for your changes
415
+ 4. Run lint: `ruff check .`
416
+ 5. Open a Pull Request
417
+
418
+ ---
419
+
420
+ ## Support
421
+
422
+ If you find this project useful, consider giving it a star or supporting development:
423
+
424
+ <a href="https://buymeacoffee.com/caspel26"><img src="https://img.shields.io/badge/Buy%20me%20a%20coffee-FFDD00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black" alt="Buy me a coffee"></a>
425
+
426
+ ---
427
+
428
+ ## License
429
+
430
+ MIT License. See [LICENSE](LICENSE).
431
+
@@ -1,10 +1,10 @@
1
- ninja_aio/__init__.py,sha256=2QH5WmYL3ouCf-JfuHrYIk6jk3nDnu1ClpC1bPe5XWw,120
1
+ ninja_aio/__init__.py,sha256=0d83rifGl3AtqjPqmhu8uQRV_EBq6JoUIWpkaU_OSaQ,120
2
2
  ninja_aio/api.py,sha256=tuC7vdvn7s1GkCnSFy9Kn1zv0glZfYptRQVvo8ZRtGQ,2429
3
3
  ninja_aio/auth.py,sha256=f4yk45fLi36Qctu0A0zgHTFedb9yk3ewq5rOMpoPYIE,9035
4
4
  ninja_aio/exceptions.py,sha256=w8QWmVlg88iJvBrNODSDBHSsy8nNpwngaCGWRnkXoPo,3899
5
5
  ninja_aio/parsers.py,sha256=e_4lGCPV7zs-HTqtdJTc8yQD2KPAn9njbL8nF_Mmgkc,153
6
6
  ninja_aio/renders.py,sha256=CW0xDa05Xna-UvL0MZqZeDEgueEaUassV_nG7Rh1-cw,1824
7
- ninja_aio/types.py,sha256=E3yfXbNKkvLVcr8bvkHTSyIiCRZ4zumzJaXj1aiBi5U,735
7
+ ninja_aio/types.py,sha256=fSDTLLraQeZ9-bpoWSQcV5WN8vy2v4Te48D5dnnW25M,1441
8
8
  ninja_aio/decorators/__init__.py,sha256=cDDHD_9EI4CP7c5eL1m2mGNl9bR24i8FAkQsT3_RNGM,371
9
9
  ninja_aio/decorators/operations.py,sha256=L9yt2ku5oo4CpOLixCADmkcFjLGsWAn-cg-sDcjFhMA,343
10
10
  ninja_aio/decorators/views.py,sha256=0RVU4XaM1HvTQ-BOt_NwUtbhwfHau06lh-O8El1LqQk,8139
@@ -14,17 +14,17 @@ ninja_aio/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
14
14
  ninja_aio/helpers/api.py,sha256=va_HvZVBFm1KxwQhH4u09U4F1JS5JrQuRpRmPTHJt7w,21326
15
15
  ninja_aio/helpers/query.py,sha256=Lqv4nrWYr543tC5K-SEcBottLID8cb83aDc26i2Wxj4,5053
16
16
  ninja_aio/models/__init__.py,sha256=L3UQnQAlKoI3F7jinadL-Nn55hkPvnSRPYW0JtnbWFo,114
17
- ninja_aio/models/serializers.py,sha256=IJvBdn4i3nXjD5GSKvEN3SXBrcOAZCVzQ-yMkWft9II,45342
18
- ninja_aio/models/utils.py,sha256=lAXtc3YY7_n4f0jIacX4DSXhUOzMy7y5MsBnInNxtfk,32874
17
+ ninja_aio/models/serializers.py,sha256=pRRa0ci8ObhmJoQkzreDxV0JA6elG0Tyj8mJutRDqpo,64021
18
+ ninja_aio/models/utils.py,sha256=iZ2pmtREtTC9G1isJbHTME-PzgI_BW7r6RBxvnlQJBw,39940
19
19
  ninja_aio/schemas/__init__.py,sha256=dHILiYBKMb51lDcyQdiXRw_0nzqM7Lu81UX2hv7kEfo,837
20
20
  ninja_aio/schemas/api.py,sha256=dGUpJXR1iAf93QNR4kYj1uqIkTjiMfXultCotY6GtaQ,361
21
21
  ninja_aio/schemas/filters.py,sha256=VxzH2xSWok8cUSkyfeqtrGhRewtFVmNHQfHNvY8Aynw,2662
22
22
  ninja_aio/schemas/generics.py,sha256=frjJsKJMAdM_NdNKv-9ddZNGxYy5PNzjIRGtuycgr-w,112
23
23
  ninja_aio/schemas/helpers.py,sha256=CpubwNXsZHtu8jddliyQybF1epwZ-GO60vHIuF5AR1Y,8967
24
24
  ninja_aio/views/__init__.py,sha256=DEzjWA6y3WF0V10nNF8eEurLNEodgxKzyFd09AqVp3s,148
25
- ninja_aio/views/api.py,sha256=AAqkj0xT8J3PmJvsbluZ33cfrmrXJHiV9ARe2BqnfQ8,22492
26
- ninja_aio/views/mixins.py,sha256=Zl6J8gbVagwT85bzDuKyJTk3iFxxFgX0YgYkjiUxZGg,17040
27
- django_ninja_aio_crud-2.17.0.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
28
- django_ninja_aio_crud-2.17.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
29
- django_ninja_aio_crud-2.17.0.dist-info/METADATA,sha256=rU_PLwW8kGegOPLJmCnC07HdNT111NqrqsxJZIv8Rj4,9964
30
- django_ninja_aio_crud-2.17.0.dist-info/RECORD,,
25
+ ninja_aio/views/api.py,sha256=Sj4yIVLVQEVKxwFzVbT6YhiSCxXtcvdlTtWhJfccOus,26191
26
+ ninja_aio/views/mixins.py,sha256=rE4otyuenx6bfOLmnvMiSn10Kh7p0newU0-HarWDWS4,17779
27
+ django_ninja_aio_crud-2.18.1.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
28
+ django_ninja_aio_crud-2.18.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
29
+ django_ninja_aio_crud-2.18.1.dist-info/METADATA,sha256=vMfBlBuF8vq6dNp2ciGcjLSjMM5Gx0I7pqNM2dtGs7I,13404
30
+ django_ninja_aio_crud-2.18.1.dist-info/RECORD,,
ninja_aio/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Django Ninja AIO CRUD - Rest Framework"""
2
2
 
3
- __version__ = "2.17.0"
3
+ __version__ = "2.18.1"
4
4
 
5
5
  from .api import NinjaAIO
6
6