django-ninja-aio-crud 0.10.2__py3-none-any.whl → 2.4.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.
ninja_aio/renders.py CHANGED
@@ -1,13 +1,17 @@
1
1
  import base64
2
2
  from ipaddress import IPv4Address, IPv6Address
3
+ from typing import Any
3
4
 
4
5
  import orjson
5
6
  from django.http import HttpRequest
7
+ from django.conf import settings
6
8
  from ninja.renderers import BaseRenderer
9
+ from pydantic import AnyUrl
7
10
 
8
11
 
9
12
  class ORJSONRenderer(BaseRenderer):
10
13
  media_type = "application/json"
14
+ option = getattr(settings, "NINJA_AIO_ORJSON_RENDERER_OPTION", None)
11
15
 
12
16
  def render(self, request: HttpRequest, data: dict, *, response_status):
13
17
  try:
@@ -15,9 +19,13 @@ class ORJSONRenderer(BaseRenderer):
15
19
  for k, v in old_d.items():
16
20
  if isinstance(v, list):
17
21
  data |= {k: self.render_list(v)}
18
- return orjson.dumps(self.render_dict(data))
22
+ return self.dumps(self.render_dict(data))
19
23
  except AttributeError:
20
- return orjson.dumps(data)
24
+ return self.dumps(data)
25
+
26
+ @classmethod
27
+ def dumps(cls, data: dict) -> bytes:
28
+ return orjson.dumps(data, option=cls.option)
21
29
 
22
30
  @classmethod
23
31
  def render_list(cls, data: list[dict]) -> list[dict]:
@@ -28,27 +36,19 @@ class ORJSONRenderer(BaseRenderer):
28
36
  return cls.parse_data(data)
29
37
 
30
38
  @classmethod
31
- def parse_data(cls, data: dict):
32
- for k, v in data.items():
33
- if isinstance(v, bytes):
34
- data |= {k: base64.b64encode(v).decode()}
35
- continue
36
- if isinstance(v, (IPv4Address, IPv6Address)):
37
- data |= {k: str(v)}
38
- continue
39
- if isinstance(v, dict):
40
- for k_rel, v_rel in v.items():
41
- if isinstance(v_rel, bytes):
42
- v |= {k_rel: base64.b64encode(v_rel).decode()}
43
- if isinstance(v_rel, (IPv4Address, IPv6Address)):
44
- v |= {k_rel: str(v_rel)}
45
- data |= {k: v}
46
- if isinstance(v, list):
47
- for index_rel, f_rel in enumerate(v):
48
- for k_rel, v_rel in f_rel.items():
49
- if isinstance(v_rel, bytes):
50
- v[index_rel] |= {k_rel: base64.b64encode(v_rel).decode()}
51
- if isinstance(v_rel, (IPv4Address, IPv6Address)):
52
- v[index_rel] |= {k_rel: str(v_rel)}
53
- data |= {k: v}
54
- return data
39
+ def transform(cls, value):
40
+ if isinstance(value, bytes):
41
+ return base64.b64encode(value).decode()
42
+ if isinstance(value, (IPv4Address, IPv6Address, AnyUrl)):
43
+ return str(value)
44
+ if isinstance(value, dict):
45
+ return {k: cls.transform(v) for k, v in value.items()}
46
+ if isinstance(value, list):
47
+ return [cls.transform(item) for item in value]
48
+ return value
49
+
50
+ @classmethod
51
+ def parse_data(cls, data: dict | Any):
52
+ if not isinstance(data, dict):
53
+ return cls.transform(data)
54
+ return {k: cls.transform(v) for k, v in data.items()}
@@ -0,0 +1,23 @@
1
+ from .generics import GenericMessageSchema
2
+ from .api import (
3
+ M2MDetailSchema,
4
+ M2MSchemaOut,
5
+ M2MAddSchemaIn,
6
+ M2MRemoveSchemaIn,
7
+ M2MSchemaIn,
8
+ )
9
+ from .helpers import M2MRelationSchema, QuerySchema, ModelQuerySetSchema, ObjectQuerySchema, ObjectsQuerySchema
10
+
11
+ __all__ = [
12
+ "GenericMessageSchema",
13
+ "M2MDetailSchema",
14
+ "M2MSchemaOut",
15
+ "M2MAddSchemaIn",
16
+ "M2MRemoveSchemaIn",
17
+ "M2MSchemaIn",
18
+ "M2MRelationSchema",
19
+ "QuerySchema",
20
+ "ModelQuerySetSchema",
21
+ "ObjectQuerySchema",
22
+ "ObjectsQuerySchema",
23
+ ]
@@ -1,9 +1,4 @@
1
1
  from ninja import Schema
2
- from pydantic import RootModel
3
-
4
-
5
- class GenericMessageSchema(RootModel[dict[str, str]]):
6
- root: dict[str, str]
7
2
 
8
3
 
9
4
  class M2MDetailSchema(Schema):
@@ -0,0 +1,5 @@
1
+ from pydantic import RootModel
2
+
3
+
4
+ class GenericMessageSchema(RootModel[dict[str, str]]):
5
+ root: dict[str, str]
@@ -0,0 +1,170 @@
1
+ from typing import List, Optional, Type
2
+
3
+ from ninja import Schema
4
+ from ninja_aio.types import ModelSerializerMeta
5
+ from django.db.models import Model
6
+ from pydantic import BaseModel, ConfigDict, model_validator
7
+
8
+
9
+ class M2MRelationSchema(BaseModel):
10
+ """
11
+ Configuration schema for declaring and controlling a Many-to-Many (M2M) relation in the API.
12
+
13
+ This schema is used to describe an M2M relationship between a primary resource and its related
14
+ objects, and to automatically provision CRUD-like endpoints for managing that relation
15
+ (add, remove, and get). It supports both direct Django model classes and model serializers,
16
+ and can optionally expose a custom schema for the related output.
17
+
18
+ model (ModelSerializerMeta | Type[Model]):
19
+ The target related entity, provided either as a ModelSerializer (preferred) or a Django model.
20
+ If a plain model is supplied, you must also provide `related_schema`.
21
+ related_name (str):
22
+ The name of the M2M field on the Django model that links to the related objects.
23
+ add (bool):
24
+ Whether to enable an endpoint for adding related objects. Defaults to True.
25
+ remove (bool):
26
+ Whether to enable an endpoint for removing related objects. Defaults to True.
27
+ get (bool):
28
+ Whether to enable an endpoint for listing/retrieving related objects. Defaults to True.
29
+ path (str | None):
30
+ Optional custom URL path segment for the relation endpoints. If empty or None, a path
31
+ is auto-generated based on `related_name`.
32
+ auth (list | None):
33
+ Optional list of authentication backends to protect the relation endpoints.
34
+ filters (dict[str, tuple] | None):
35
+ Optional mapping of queryable filter fields for the GET endpoint, defined as:
36
+ field_name -> (type, default). Example: {"country": ("str", "")}.
37
+ related_schema (Type[Schema] | None):
38
+ Optional explicit schema to represent related objects in responses.
39
+ If `model` is a ModelSerializerMeta, this is auto-derived via `model.generate_related_s()`.
40
+ If `model` is a plain Django model, this must be provided.
41
+ append_slash (bool):
42
+ Whether to append a trailing slash to the generated GET endpoint path. Defaults to False for backward compatibility.
43
+
44
+ Validation:
45
+ - If `model` is not a ModelSerializerMeta, `related_schema` is required.
46
+ - When `model` is a ModelSerializerMeta and `related_schema` is not provided, it will be
47
+ automatically generated.
48
+
49
+ Usage example:
50
+ filters={"country": ("str", "")},
51
+ auth=[AuthBackend],
52
+ add=True,
53
+ remove=True,
54
+ get=True,
55
+ """
56
+
57
+ model: ModelSerializerMeta | Type[Model]
58
+ related_name: str
59
+ add: bool = True
60
+ remove: bool = True
61
+ get: bool = True
62
+ path: Optional[str] = ""
63
+ auth: Optional[list] = None
64
+ filters: Optional[dict[str, tuple]] = None
65
+ related_schema: Optional[Type[Schema]] = None
66
+ append_slash: bool = False
67
+
68
+ model_config = ConfigDict(arbitrary_types_allowed=True)
69
+
70
+ @model_validator(mode="before")
71
+ @classmethod
72
+ def validate_related_schema(cls, data):
73
+ related_schema = data.get("related_schema")
74
+ if related_schema is not None:
75
+ return data
76
+ model = data.get("model")
77
+ if not isinstance(model, ModelSerializerMeta):
78
+ raise ValueError(
79
+ "related_schema must be provided if model is not a ModelSerializer",
80
+ )
81
+ data["related_schema"] = model.generate_related_s()
82
+ return data
83
+
84
+
85
+ class ModelQuerySetSchema(BaseModel):
86
+ select_related: Optional[list[str]] = []
87
+ prefetch_related: Optional[list[str]] = []
88
+
89
+
90
+ class ModelQuerySetExtraSchema(ModelQuerySetSchema):
91
+ """
92
+ Schema defining extra query parameters for model queryset operations in API endpoints.
93
+ Attributes:
94
+ scope (str): The scope defining the level of access for the queryset operation.
95
+ select_related (Optional[list[str]]): List of related fields for select_related optimization.
96
+ prefetch_related (Optional[list[str]]): List of related fields for prefetch_related optimization
97
+ """
98
+ scope: str
99
+
100
+
101
+ class ObjectQuerySchema(ModelQuerySetSchema):
102
+ """
103
+ Schema defining query parameters for single object retrieval in API endpoints.
104
+ Attributes:
105
+ getters (Optional[dict]): A dictionary of getters to apply to the query.
106
+ select_related (Optional[list[str]]): List of related fields for select_related optimization.
107
+ prefetch_related (Optional[list[str]]): List of related fields for prefetch_related optimization
108
+ """
109
+ getters: Optional[dict] = {}
110
+
111
+
112
+ class ObjectsQuerySchema(ModelQuerySetSchema):
113
+ """
114
+ Schema defining query parameters for multiple object retrieval in API endpoints.
115
+ Attributes:
116
+ filters (Optional[dict]): A dictionary of filters to apply to the query.
117
+ select_related (Optional[list[str]]): List of related fields for select_related optimization.
118
+ prefetch_related (Optional[list[str]]): List of related fields for prefetch_related optimization
119
+ """
120
+ filters: Optional[dict] = {}
121
+
122
+
123
+ class QuerySchema(ModelQuerySetSchema):
124
+ """
125
+ Schema defining query parameters for API endpoints.
126
+ Attributes:
127
+ filters (Optional[dict]): A dictionary of filters to apply to the query.
128
+ getters (Optional[dict]): A dictionary of getters to apply to the query.
129
+ select_related (Optional[list[str]]): List of related fields for select_related optimization.
130
+ prefetch_related (Optional[list[str]]): List of related fields for prefetch_related optimization
131
+ """
132
+ filters: Optional[dict] = {}
133
+ getters: Optional[dict] = {}
134
+
135
+
136
+ class QueryUtilBaseScopesSchema(BaseModel):
137
+ """
138
+ Schema defining base scopes for query utilities.
139
+ Attributes:
140
+ READ (str): Scope for read operations.
141
+ QUERYSET_REQUEST (str): Scope for queryset request operations.
142
+ """
143
+ READ: str = "read"
144
+ QUERYSET_REQUEST: str = "queryset_request"
145
+
146
+
147
+ class DecoratorsSchema(Schema):
148
+ """
149
+ Schema defining optional decorator lists for CRUD operations.
150
+
151
+ Attributes:
152
+ list (Optional[List]): Decorators applied to the list endpoint.
153
+ retrieve (Optional[List]): Decorators applied to the retrieve endpoint.
154
+ create (Optional[List]): Decorators applied to the create endpoint.
155
+ update (Optional[List]): Decorators applied to the update endpoint.
156
+ delete (Optional[List]): Decorators applied to the delete endpoint.
157
+
158
+ Notes:
159
+ - Each attribute holds an ordered collection of decorators (callables or decorator references)
160
+ to be applied to the corresponding endpoint.
161
+ - Defaults are empty lists, meaning no decorators are applied unless explicitly provided.
162
+ - Using mutable defaults (empty lists) at the class level may lead to shared state between instances.
163
+ Consider initializing these in __init__ or using default_factory (if using pydantic/dataclasses)
164
+ to avoid unintended side effects.
165
+ """
166
+ list: Optional[List] = []
167
+ retrieve: Optional[List] = []
168
+ create: Optional[List] = []
169
+ update: Optional[List] = []
170
+ delete: Optional[List] = []
ninja_aio/types.py CHANGED
@@ -1,12 +1,14 @@
1
1
  from typing import Literal
2
2
 
3
+ from joserfc import jwk
3
4
  from django.db.models import Model
5
+ from typing import TypeAlias
4
6
 
5
7
  S_TYPES = Literal["read", "create", "update"]
6
8
  F_TYPES = Literal["fields", "customs", "optionals", "excludes"]
7
9
  SCHEMA_TYPES = Literal["In", "Out", "Patch", "Related"]
8
10
  VIEW_TYPES = Literal["list", "retrieve", "create", "update", "delete", "all"]
9
-
11
+ JwtKeys: TypeAlias = jwk.RSAKey | jwk.ECKey | jwk.OctKey
10
12
 
11
13
  class ModelSerializerType(type):
12
14
  def __repr__(self):
@@ -0,0 +1,3 @@
1
+ from .api import APIView, APIViewSet, ReadOnlyViewSet, WriteOnlyViewSet
2
+
3
+ __all__ = ["APIView", "APIViewSet", "ReadOnlyViewSet", "WriteOnlyViewSet"]