django-ninja-aio-crud 2.6.1__py3-none-any.whl → 2.7.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-ninja-aio-crud
3
- Version: 2.6.1
3
+ Version: 2.7.0
4
4
  Summary: Django Ninja AIO CRUD - Rest Framework
5
5
  Author: Giuseppe Casillo
6
6
  Requires-Python: >=3.10, <3.15
@@ -1,4 +1,4 @@
1
- ninja_aio/__init__.py,sha256=ww2hnE9C7lFXzVyNnpZ97wv8erYcXdxhjzu8PRiM8bc,119
1
+ ninja_aio/__init__.py,sha256=FkPurmyPz8Yv7VAiIOZYbFEghPXZ7aeLYX5xBwFSMtk,119
2
2
  ninja_aio/api.py,sha256=tuC7vdvn7s1GkCnSFy9Kn1zv0glZfYptRQVvo8ZRtGQ,2429
3
3
  ninja_aio/auth.py,sha256=4sWdFPjKiQgUL1d_CSGDblVjnY5ptP6LQha6XXdluJA,9157
4
4
  ninja_aio/exceptions.py,sha256=_3xFqfFCOfrrMhSA0xbMqgXy8R0UQjhXaExrFvaDAjY,3891
@@ -14,7 +14,7 @@ ninja_aio/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
14
14
  ninja_aio/helpers/api.py,sha256=YMzuZ4-ZpUrJBQIabE26gb_GYwsH2rVosWRE95YfdPQ,20775
15
15
  ninja_aio/helpers/query.py,sha256=lzaH-htswoJVRT-W736HGMkpMba1VmN98TBLv5cZx9Q,4549
16
16
  ninja_aio/models/__init__.py,sha256=L3UQnQAlKoI3F7jinadL-Nn55hkPvnSRPYW0JtnbWFo,114
17
- ninja_aio/models/serializers.py,sha256=zV3gRI4isnCuLCAQbPhQlut3nKT0XuQOLy2ABiaJ6Y4,31372
17
+ ninja_aio/models/serializers.py,sha256=AlZIkwIiOt-nGBfMDoXu9tJotfzH5L13fv3Rw_4sKUg,35697
18
18
  ninja_aio/models/utils.py,sha256=P-YfbVyzUfxm_s1BrgSd6Zs0HIGdZ79PU1qM0Ud9-Xs,30492
19
19
  ninja_aio/schemas/__init__.py,sha256=iLBwHg0pmL9k_UkIui5Q8QIl_gO4fgxSv2JHxDzqnSI,549
20
20
  ninja_aio/schemas/api.py,sha256=-VwXhBRhmMsZLIAmWJ-P7tB5klxXS75eukjabeKKYsc,360
@@ -23,7 +23,7 @@ ninja_aio/schemas/helpers.py,sha256=W6IeHi5Tmbjh3FXwDYqjqlLBTVj5uTYq3_JVkNUWayo,
23
23
  ninja_aio/views/__init__.py,sha256=DEzjWA6y3WF0V10nNF8eEurLNEodgxKzyFd09AqVp3s,148
24
24
  ninja_aio/views/api.py,sha256=GRtjCvAv7jAp3TxpOirsbMVKpBd8hymSMILdE-JLxvI,21327
25
25
  ninja_aio/views/mixins.py,sha256=Jh6BG8Cs823nurVlODlzCquTxKrLH7Pmo5udPqUGZek,11378
26
- django_ninja_aio_crud-2.6.1.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
27
- django_ninja_aio_crud-2.6.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
28
- django_ninja_aio_crud-2.6.1.dist-info/METADATA,sha256=aBT8gpjs9TVS_jcs_c4m--5K8YFLjpAUpNPa0ib2T-0,9963
29
- django_ninja_aio_crud-2.6.1.dist-info/RECORD,,
26
+ django_ninja_aio_crud-2.7.0.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
27
+ django_ninja_aio_crud-2.7.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
28
+ django_ninja_aio_crud-2.7.0.dist-info/METADATA,sha256=_eQi4SR0xvfnLM6ShB-yvkxffFzUsxNCB39spSHWTOU,9963
29
+ django_ninja_aio_crud-2.7.0.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.6.1"
3
+ __version__ = "2.7.0"
4
4
 
5
5
  from .api import NinjaAIO
6
6
 
@@ -1,4 +1,4 @@
1
- from typing import Any, List, Optional
1
+ from typing import Any, List, Optional, Union, get_args, get_origin, ForwardRef
2
2
  import warnings
3
3
  import sys
4
4
 
@@ -69,19 +69,14 @@ class BaseSerializer:
69
69
  raise NotImplementedError
70
70
 
71
71
  @classmethod
72
- def _resolve_serializer_reference(cls, serializer_ref: str | type) -> type:
72
+ def _resolve_string_reference(cls, string_ref: str) -> type:
73
73
  """
74
- Resolve a serializer reference that may be a string or a class.
75
-
76
- This method performs lazy resolution, meaning it will attempt to resolve
77
- string references only when called, allowing for forward references and
78
- circular dependencies between serializers in the same module.
74
+ Resolve a string serializer reference to an actual class.
79
75
 
80
76
  Parameters
81
77
  ----------
82
- serializer_ref : str | type
83
- Either a string reference to a serializer class name in the same module,
84
- or an actual serializer class.
78
+ string_ref : str
79
+ String reference (local class name or absolute import path).
85
80
 
86
81
  Returns
87
82
  -------
@@ -93,35 +88,152 @@ class BaseSerializer:
93
88
  ValueError
94
89
  If the string reference cannot be resolved.
95
90
  """
96
- # If it's already a class, return it directly
97
- if not isinstance(serializer_ref, str):
98
- return serializer_ref
91
+ # Check if it's an absolute import path (contains dots)
92
+ if "." in string_ref:
93
+ # Absolute import path: "myapp.serializers.UserSerializer"
94
+ module_path, class_name = string_ref.rsplit(".", 1)
99
95
 
100
- # Get the module where the current serializer class is defined
96
+ try:
97
+ # Try to get or import the module
98
+ module = sys.modules.get(module_path)
99
+ if module is None:
100
+ import importlib
101
+ module = importlib.import_module(module_path)
102
+
103
+ # Get the serializer class from the module
104
+ serializer_class = getattr(module, class_name, None)
105
+
106
+ if serializer_class is None:
107
+ raise ValueError(
108
+ f"Cannot resolve serializer reference '{string_ref}': "
109
+ f"class '{class_name}' not found in module '{module_path}'."
110
+ )
111
+
112
+ return serializer_class
113
+ except ImportError as e:
114
+ raise ValueError(
115
+ f"Cannot resolve serializer reference '{string_ref}': "
116
+ f"failed to import module '{module_path}': {e}"
117
+ )
118
+
119
+ # Local reference: simple class name in the same module
101
120
  module = sys.modules.get(cls.__module__)
102
121
 
103
122
  if module is None:
104
123
  raise ValueError(
105
- f"Cannot resolve serializer reference '{serializer_ref}': "
124
+ f"Cannot resolve serializer reference '{string_ref}': "
106
125
  f"module '{cls.__module__}' not found in sys.modules."
107
126
  )
108
127
 
109
- # Try to get the serializer class from the module
110
- serializer_class = getattr(module, serializer_ref, None)
128
+ serializer_class = getattr(module, string_ref, None)
111
129
 
112
130
  if serializer_class is None:
113
131
  raise ValueError(
114
- f"Cannot resolve serializer reference '{serializer_ref}' in module '{cls.__module__}'. "
115
- f"Make sure the serializer class '{serializer_ref}' is defined in the same module as {cls.__name__}."
132
+ f"Cannot resolve serializer reference '{string_ref}' in module '{cls.__module__}'. "
133
+ f"Make sure the serializer class '{string_ref}' is defined in the same module as {cls.__name__}."
116
134
  )
117
135
 
118
136
  return serializer_class
119
137
 
138
+ @classmethod
139
+ def _resolve_serializer_reference(cls, serializer_ref: str | type | Any) -> type | Any:
140
+ """
141
+ Resolve a serializer reference that may be a string, a class, or a Union of serializers.
142
+
143
+ This method performs lazy resolution, meaning it will attempt to resolve
144
+ string references only when called, allowing for forward references and
145
+ circular dependencies between serializers in the same module.
146
+
147
+ Parameters
148
+ ----------
149
+ serializer_ref : str | type | Union
150
+ Either a string reference to a serializer class, an actual serializer class,
151
+ or a Union of serializer references. String references can be:
152
+ - A class name in the same module (e.g., "UserSerializer")
153
+ - An absolute import path (e.g., "myapp.serializers.UserSerializer")
154
+
155
+ Returns
156
+ -------
157
+ type | Union
158
+ The resolved serializer class or Union of serializer classes.
159
+
160
+ Raises
161
+ ------
162
+ ValueError
163
+ If the string reference cannot be resolved.
164
+
165
+ Examples
166
+ --------
167
+ >>> # Single reference
168
+ >>> cls._resolve_serializer_reference("UserSerializer")
169
+ >>> cls._resolve_serializer_reference(UserSerializer)
170
+ >>>
171
+ >>> # Union reference
172
+ >>> from typing import Union
173
+ >>> cls._resolve_serializer_reference(Union[UserSerializer, AdminSerializer])
174
+ >>> cls._resolve_serializer_reference(Union["UserSerializer", "AdminSerializer"])
175
+ """
176
+ # Handle Union types
177
+ origin = get_origin(serializer_ref)
178
+ if origin is Union:
179
+ resolved_types = tuple(
180
+ cls._resolve_serializer_reference(arg)
181
+ for arg in get_args(serializer_ref)
182
+ )
183
+ # Optimize single-type unions
184
+ if len(resolved_types) == 1:
185
+ return resolved_types[0]
186
+ # Create Union using indexing syntax for Python 3.10+ compatibility
187
+ return Union[resolved_types]
188
+
189
+ # Handle ForwardRef (created when using Union["StringType"])
190
+ if isinstance(serializer_ref, ForwardRef):
191
+ return cls._resolve_serializer_reference(serializer_ref.__forward_arg__)
192
+
193
+ # Handle string references
194
+ if isinstance(serializer_ref, str):
195
+ return cls._resolve_string_reference(serializer_ref)
196
+
197
+ # Already a class, return as-is
198
+ return serializer_ref
199
+
120
200
  @classmethod
121
201
  def _get_relations_serializers(cls) -> dict[str, "Serializer"]:
122
202
  # Optional in subclasses. Default to no explicit relation serializers.
123
203
  return {}
124
204
 
205
+ @classmethod
206
+ def _generate_union_schema(cls, resolved_union: Any) -> Any:
207
+ """
208
+ Generate a Union schema from multiple resolved serializers.
209
+
210
+ Parameters
211
+ ----------
212
+ resolved_union : Union
213
+ A Union type containing resolved serializer classes.
214
+
215
+ Returns
216
+ -------
217
+ Schema | Union[Schema, ...] | None
218
+ Union of generated schemas or None if all schemas are None.
219
+ """
220
+ # Generate schemas for each serializer in the Union
221
+ schemas = tuple(
222
+ serializer_type.generate_related_s()
223
+ for serializer_type in get_args(resolved_union)
224
+ if serializer_type.generate_related_s() is not None
225
+ )
226
+
227
+ if not schemas:
228
+ return None
229
+
230
+ # Optimize single-schema unions
231
+ if len(schemas) == 1:
232
+ return schemas[0]
233
+
234
+ # Create Union of schemas using indexing syntax for Python 3.10+ compatibility
235
+ return Union[schemas]
236
+
125
237
  @classmethod
126
238
  def _resolve_relation_schema(cls, field_name: str, rel_model: models.Model):
127
239
  """
@@ -136,23 +248,30 @@ class BaseSerializer:
136
248
 
137
249
  Returns
138
250
  -------
139
- Schema | None
140
- Generated schema or None if cannot be resolved.
251
+ Schema | Union[Schema, ...] | None
252
+ Generated schema, Union of schemas, or None if cannot be resolved.
141
253
  """
142
- # Check if related model is a ModelSerializer with readable fields
254
+ # Auto-resolve ModelSerializer with readable fields
143
255
  if isinstance(rel_model, ModelSerializerMeta):
144
256
  if rel_model.get_fields("read") or rel_model.get_custom_fields("read"):
145
257
  return rel_model.generate_related_s()
146
258
  return None
147
259
 
148
- # Fall back to explicit serializer mapping
260
+ # Resolve from explicit serializer mapping
149
261
  rel_serializers = cls._get_relations_serializers() or {}
150
262
  serializer_ref = rel_serializers.get(field_name)
151
- if serializer_ref:
152
- serializer = cls._resolve_serializer_reference(serializer_ref)
153
- return serializer.generate_related_s()
154
263
 
155
- return None
264
+ if not serializer_ref:
265
+ return None
266
+
267
+ resolved = cls._resolve_serializer_reference(serializer_ref)
268
+
269
+ # Handle Union of serializers
270
+ if get_origin(resolved) is Union:
271
+ return cls._generate_union_schema(resolved)
272
+
273
+ # Handle single serializer
274
+ return resolved.generate_related_s()
156
275
 
157
276
  @classmethod
158
277
  def _is_special_field(