cadwyn 5.0.0__py3-none-any.whl → 5.1.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.

Potentially problematic release.


This version of cadwyn might be problematic. Click here for more details.

@@ -1,33 +1,24 @@
1
+ import ast
1
2
  import copy
2
3
  import dataclasses
3
4
  import functools
4
5
  import inspect
6
+ import sys
7
+ import textwrap
5
8
  import types
6
9
  import typing
7
10
  from collections.abc import Callable, Sequence
8
11
  from datetime import date
9
12
  from enum import Enum
10
13
  from functools import cache
11
- from typing import (
12
- TYPE_CHECKING,
13
- Annotated,
14
- Any,
15
- Generic,
16
- TypeAlias,
17
- TypeVar,
18
- _BaseGenericAlias, # pyright: ignore[reportAttributeAccessIssue]
19
- cast,
20
- final,
21
- get_args,
22
- get_origin,
23
- overload,
24
- )
14
+ from typing import TYPE_CHECKING, Annotated, Generic, Union, cast
25
15
 
26
16
  import fastapi.params
27
17
  import fastapi.security.base
28
18
  import fastapi.utils
29
19
  import pydantic
30
20
  import pydantic._internal._decorators
21
+ import typing_extensions
31
22
  from fastapi import Response
32
23
  from fastapi.routing import APIRoute
33
24
  from pydantic import BaseModel, Field, RootModel
@@ -42,9 +33,30 @@ from pydantic._internal._decorators import (
42
33
  )
43
34
  from pydantic._internal._typing_extra import try_eval_type as pydantic_try_eval_type
44
35
  from pydantic.fields import ComputedFieldInfo, FieldInfo
45
- from typing_extensions import Doc, Self, _AnnotatedAlias, assert_never
36
+ from typing_extensions import (
37
+ Any,
38
+ Doc,
39
+ NewType,
40
+ Self,
41
+ TypeAlias,
42
+ TypeVar,
43
+ _AnnotatedAlias,
44
+ assert_never,
45
+ final,
46
+ get_args,
47
+ get_origin,
48
+ overload,
49
+ )
46
50
 
47
- from cadwyn._utils import Sentinel, UnionType, fully_unwrap_decorator, lenient_issubclass
51
+ from cadwyn._utils import (
52
+ DATACLASS_KW_ONLY,
53
+ DATACLASS_SLOTS,
54
+ Sentinel,
55
+ UnionType,
56
+ fully_unwrap_decorator,
57
+ get_name_of_function_wrapped_in_pydantic_validator,
58
+ lenient_issubclass,
59
+ )
48
60
  from cadwyn.exceptions import CadwynError, InvalidGenerationInstructionError
49
61
  from cadwyn.structure.common import VersionType
50
62
  from cadwyn.structure.data import ResponseInfo
@@ -65,10 +77,15 @@ from cadwyn.structure.versions import _CADWYN_REQUEST_PARAM_NAME, _CADWYN_RESPON
65
77
  if TYPE_CHECKING:
66
78
  from cadwyn.structure.versions import HeadVersion, Version, VersionBundle
67
79
 
80
+ if sys.version_info >= (3, 10):
81
+ from typing import _BaseGenericAlias # pyright: ignore[reportAttributeAccessIssue]
82
+ else:
83
+ from typing_extensions import _BaseGenericAlias # pyright: ignore[reportAttributeAccessIssue]
84
+
68
85
  _Call = TypeVar("_Call", bound=Callable[..., Any])
69
86
 
70
87
  _FieldName: TypeAlias = str
71
- _T_ANY_MODEL = TypeVar("_T_ANY_MODEL", bound=BaseModel | Enum)
88
+ _T_ANY_MODEL = TypeVar("_T_ANY_MODEL", bound=Union[BaseModel, Enum])
72
89
  _T_ENUM = TypeVar("_T_ENUM", bound=Enum)
73
90
 
74
91
  _T_PYDANTIC_MODEL = TypeVar("_T_PYDANTIC_MODEL", bound=BaseModel)
@@ -98,7 +115,7 @@ _empty_field_info = Field()
98
115
  dict_of_empty_field_info = {k: getattr(_empty_field_info, k) for k in FieldInfo.__slots__}
99
116
 
100
117
 
101
- @dataclasses.dataclass(slots=True)
118
+ @dataclasses.dataclass(**DATACLASS_SLOTS)
102
119
  class PydanticFieldWrapper:
103
120
  """We DO NOT maintain field.metadata at all"""
104
121
 
@@ -136,16 +153,16 @@ def _extract_passed_field_attributes(field_info: FieldInfo):
136
153
  return attributes
137
154
 
138
155
 
139
- @dataclasses.dataclass(slots=True)
156
+ @dataclasses.dataclass(**DATACLASS_SLOTS)
140
157
  class _ModelBundle:
141
158
  enums: dict[type[Enum], "_EnumWrapper"]
142
159
  schemas: dict[type[BaseModel], "_PydanticModelWrapper"]
143
160
 
144
161
 
145
- @dataclasses.dataclass(slots=True, kw_only=True)
162
+ @dataclasses.dataclass(**DATACLASS_SLOTS, **DATACLASS_KW_ONLY)
146
163
  class _RuntimeSchemaGenContext:
147
164
  version_bundle: "VersionBundle"
148
- current_version: "Version | HeadVersion"
165
+ current_version: "Union[Version, HeadVersion]"
149
166
  models: _ModelBundle
150
167
  latest_version: "Version" = dataclasses.field(init=False)
151
168
 
@@ -158,7 +175,7 @@ def migrate_response_body(
158
175
  latest_response_model: type[pydantic.BaseModel],
159
176
  *,
160
177
  latest_body: Any,
161
- version: VersionType | date,
178
+ version: Union[VersionType, date],
162
179
  ) -> Any:
163
180
  """Convert the data to a specific version
164
181
 
@@ -191,7 +208,7 @@ def _unwrap_model(model: type[_T_ANY_MODEL]) -> type[_T_ANY_MODEL]:
191
208
  return model
192
209
 
193
210
 
194
- @dataclasses.dataclass(slots=True, kw_only=True)
211
+ @dataclasses.dataclass(**DATACLASS_SLOTS, **DATACLASS_KW_ONLY)
195
212
  class _ValidatorWrapper:
196
213
  kwargs: dict[str, Any]
197
214
  func: Callable
@@ -199,9 +216,9 @@ class _ValidatorWrapper:
199
216
  is_deleted: bool = False
200
217
 
201
218
 
202
- @dataclasses.dataclass(slots=True, kw_only=True)
219
+ @dataclasses.dataclass(**DATACLASS_SLOTS, **DATACLASS_KW_ONLY)
203
220
  class _PerFieldValidatorWrapper(_ValidatorWrapper):
204
- fields: list[str]
221
+ fields: list[str] = dataclasses.field(default_factory=list)
205
222
 
206
223
 
207
224
  def _wrap_validator(func: Callable, is_pydantic_v1_style_validator: Any, decorator_info: _decorators.DecoratorInfo):
@@ -247,7 +264,6 @@ def _wrap_pydantic_model(model: type[_T_PYDANTIC_MODEL]) -> "_PydanticModelWrapp
247
264
 
248
265
  wrapped_validator = _wrap_validator(decorator_wrapper.func, decorator_wrapper.shim, decorator_wrapper.info)
249
266
  validators[decorator_wrapper.cls_var_name] = wrapped_validator
250
-
251
267
  annotations = {
252
268
  name: value
253
269
  if not isinstance(value, str)
@@ -255,6 +271,21 @@ def _wrap_pydantic_model(model: type[_T_PYDANTIC_MODEL]) -> "_PydanticModelWrapp
255
271
  for name, value in model.__annotations__.items()
256
272
  }
257
273
 
274
+ if sys.version_info >= (3, 10):
275
+ defined_fields = model.__annotations__
276
+ else:
277
+ # Before 3.9, pydantic fills model_fields with all fields -- even the ones that were inherited.
278
+ # So we need to get the list of fields from the AST.
279
+ try:
280
+ defined_fields, _ = _get_field_and_validator_names_from_model(model)
281
+ except OSError: # pragma: no cover
282
+ defined_fields = model.model_fields
283
+ annotations = {
284
+ name: value
285
+ for name, value in annotations.items()
286
+ # We need to filter out fields that were inherited
287
+ if name not in model.model_fields or name in defined_fields
288
+ }
258
289
  fields = {
259
290
  field_name: PydanticFieldWrapper(
260
291
  model.model_fields[field_name],
@@ -262,6 +293,7 @@ def _wrap_pydantic_model(model: type[_T_PYDANTIC_MODEL]) -> "_PydanticModelWrapp
262
293
  field_name,
263
294
  )
264
295
  for field_name in model.__annotations__
296
+ if field_name in defined_fields
265
297
  }
266
298
 
267
299
  main_attributes = fields | validators
@@ -271,6 +303,7 @@ def _wrap_pydantic_model(model: type[_T_PYDANTIC_MODEL]) -> "_PydanticModelWrapp
271
303
  if attr_name not in main_attributes
272
304
  and not (_is_dunder(attr_name) or attr_name in {"_abc_impl", "model_fields", "model_computed_fields"})
273
305
  }
306
+
274
307
  other_attributes |= {
275
308
  "model_config": model.model_config,
276
309
  "__module__": model.__module__,
@@ -287,12 +320,49 @@ def _wrap_pydantic_model(model: type[_T_PYDANTIC_MODEL]) -> "_PydanticModelWrapp
287
320
  )
288
321
 
289
322
 
323
+ @cache
324
+ def _get_field_and_validator_names_from_model(cls: type) -> tuple[set[_FieldName], set[str]]:
325
+ fields = cls.model_fields
326
+ source = inspect.getsource(cls)
327
+ cls_ast = cast(ast.ClassDef, ast.parse(textwrap.dedent(source)).body[0])
328
+ validator_names = (
329
+ _get_validator_info_or_none(node)
330
+ for node in cls_ast.body
331
+ if isinstance(node, ast.FunctionDef) and node.decorator_list
332
+ )
333
+ validator_names = {name for name in validator_names if name is not None}
334
+
335
+ return (
336
+ {
337
+ node.target.id
338
+ for node in cls_ast.body
339
+ if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name) and node.target.id in fields
340
+ },
341
+ validator_names,
342
+ )
343
+
344
+
345
+ def _get_validator_info_or_none(method: ast.FunctionDef) -> Union[str, None]:
346
+ for decorator in method.decorator_list:
347
+ # The cases we handle here:
348
+ # * `Name(id="root_validator")`
349
+ # * `Call(func=Name(id="validator"), args=[Constant(value="foo")])`
350
+ # * `Attribute(value=Name(id="pydantic"), attr="root_validator")`
351
+ # * `Call(func=Attribute(value=Name(id="pydantic"), attr="root_validator"), args=[])`
352
+
353
+ if (isinstance(decorator, ast.Call) and ast.unparse(decorator.func).endswith("validator")) or (
354
+ isinstance(decorator, (ast.Name, ast.Attribute)) and ast.unparse(decorator).endswith("validator")
355
+ ):
356
+ return method.name
357
+ return None
358
+
359
+
290
360
  @final
291
- @dataclasses.dataclass(slots=True)
361
+ @dataclasses.dataclass(**DATACLASS_SLOTS)
292
362
  class _PydanticModelWrapper(Generic[_T_PYDANTIC_MODEL]):
293
363
  cls: type[_T_PYDANTIC_MODEL] = dataclasses.field(repr=False)
294
364
  name: str
295
- doc: str | None = dataclasses.field(repr=False)
365
+ doc: Union[str, None] = dataclasses.field(repr=False)
296
366
  fields: Annotated[
297
367
  dict["_FieldName", PydanticFieldWrapper],
298
368
  Doc(
@@ -300,10 +370,10 @@ class _PydanticModelWrapper(Generic[_T_PYDANTIC_MODEL]):
300
370
  "I.e. The ones that were either defined or overridden "
301
371
  ),
302
372
  ] = dataclasses.field(repr=False)
303
- validators: dict[str, _PerFieldValidatorWrapper | _ValidatorWrapper] = dataclasses.field(repr=False)
373
+ validators: dict[str, Union[_PerFieldValidatorWrapper, _ValidatorWrapper]] = dataclasses.field(repr=False)
304
374
  other_attributes: dict[str, Any] = dataclasses.field(repr=False)
305
375
  annotations: dict[str, Any] = dataclasses.field(repr=False)
306
- _parents: list[Self] | None = dataclasses.field(init=False, default=None, repr=False)
376
+ _parents: Union[list[Self], None] = dataclasses.field(init=False, default=None, repr=False)
307
377
 
308
378
  def __post_init__(self):
309
379
  # This isn't actually supposed to run, it's just a precaution
@@ -375,7 +445,6 @@ class _PydanticModelWrapper(Generic[_T_PYDANTIC_MODEL]):
375
445
  if not validator.is_deleted and type(validator) == _ValidatorWrapper # noqa: E721
376
446
  }
377
447
  fields = {name: field.generate_field_copy(generator) for name, field in self.fields.items()}
378
-
379
448
  model_copy = type(self.cls)(
380
449
  self.name,
381
450
  tuple(generator[cast(type[BaseModel], base)] for base in self.cls.__bases__),
@@ -389,13 +458,12 @@ class _PydanticModelWrapper(Generic[_T_PYDANTIC_MODEL]):
389
458
  "__qualname__": self.cls.__qualname__.removesuffix(self.cls.__name__) + self.name,
390
459
  },
391
460
  )
392
-
393
461
  model_copy.__cadwyn_original_model__ = self.cls
394
462
  return model_copy
395
463
 
396
464
 
397
465
  def is_regular_function(call: Callable):
398
- return isinstance(call, types.FunctionType | types.MethodType)
466
+ return isinstance(call, (types.FunctionType, types.MethodType))
399
467
 
400
468
 
401
469
  class _CallableWrapper:
@@ -457,7 +525,7 @@ class _AnnotationTransformer:
457
525
  for key, value in annotation.items()
458
526
  }
459
527
 
460
- elif isinstance(annotation, list | tuple):
528
+ elif isinstance(annotation, (list, tuple)):
461
529
  return type(annotation)(self.change_version_of_annotation(v) for v in annotation)
462
530
  else:
463
531
  return self.change_versions_of_a_non_container_annotation(annotation)
@@ -486,7 +554,7 @@ class _AnnotationTransformer:
486
554
  self._remake_endpoint_dependencies(route)
487
555
 
488
556
  def _change_version_of_a_non_container_annotation(self, annotation: Any) -> Any:
489
- if isinstance(annotation, _BaseGenericAlias | types.GenericAlias):
557
+ if isinstance(annotation, (_BaseGenericAlias, types.GenericAlias)):
490
558
  return get_origin(annotation)[tuple(self.change_version_of_annotation(arg) for arg in get_args(annotation))]
491
559
  elif isinstance(annotation, fastapi.params.Security):
492
560
  return fastapi.params.Security(
@@ -499,12 +567,12 @@ class _AnnotationTransformer:
499
567
  self.change_version_of_annotation(annotation.dependency),
500
568
  use_cache=annotation.use_cache,
501
569
  )
502
- elif isinstance(annotation, UnionType):
570
+ elif isinstance(annotation, UnionType): # pragma: no cover
503
571
  getitem = typing.Union.__getitem__ # pyright: ignore[reportAttributeAccessIssue]
504
572
  return getitem(
505
573
  tuple(self.change_version_of_annotation(a) for a in get_args(annotation)),
506
574
  )
507
- elif annotation is Any or isinstance(annotation, typing.NewType):
575
+ elif annotation is typing.Any or annotation is typing_extensions.Any or isinstance(annotation, NewType):
508
576
  return annotation
509
577
  elif isinstance(annotation, type):
510
578
  return self._change_version_of_type(annotation)
@@ -527,7 +595,7 @@ class _AnnotationTransformer:
527
595
  return annotation
528
596
 
529
597
  def _change_version_of_type(self, annotation: type):
530
- if lenient_issubclass(annotation, BaseModel | Enum):
598
+ if lenient_issubclass(annotation, (BaseModel, Enum)):
531
599
  return self.generator[annotation]
532
600
  else:
533
601
  return annotation
@@ -648,7 +716,7 @@ class SchemaGenerator:
648
716
  def __getitem__(self, model: type[_T_ANY_MODEL], /) -> type[_T_ANY_MODEL]:
649
717
  if (
650
718
  not isinstance(model, type)
651
- or not lenient_issubclass(model, BaseModel | Enum)
719
+ or not lenient_issubclass(model, (BaseModel, Enum))
652
720
  or model in (BaseModel, RootModel)
653
721
  ):
654
722
  return model
@@ -668,8 +736,8 @@ class SchemaGenerator:
668
736
  def _get_wrapper_for_model(self, model: type[Enum]) -> "_EnumWrapper[Enum]": ...
669
737
 
670
738
  def _get_wrapper_for_model(
671
- self, model: type[BaseModel | Enum]
672
- ) -> "_PydanticModelWrapper[BaseModel] | _EnumWrapper[Enum]":
739
+ self, model: type[Union[BaseModel, Enum]]
740
+ ) -> "Union[_PydanticModelWrapper[BaseModel], _EnumWrapper[Enum]]":
673
741
  model = _unwrap_model(model)
674
742
 
675
743
  if model in self.model_bundle.schemas:
@@ -730,14 +798,14 @@ def _migrate_classes(context: _RuntimeSchemaGenContext) -> None:
730
798
 
731
799
  def _apply_alter_schema_instructions(
732
800
  modified_schemas: dict[type, _PydanticModelWrapper],
733
- alter_schema_instructions: Sequence[AlterSchemaSubInstruction | SchemaHadInstruction],
801
+ alter_schema_instructions: Sequence[Union[AlterSchemaSubInstruction, SchemaHadInstruction]],
734
802
  version_change_name: str,
735
803
  ) -> None:
736
804
  for alter_schema_instruction in alter_schema_instructions:
737
805
  schema_info = modified_schemas[alter_schema_instruction.schema]
738
806
  if isinstance(alter_schema_instruction, FieldExistedAsInstruction):
739
807
  _add_field_to_model(schema_info, modified_schemas, alter_schema_instruction, version_change_name)
740
- elif isinstance(alter_schema_instruction, FieldHadInstruction | FieldDidntHaveInstruction):
808
+ elif isinstance(alter_schema_instruction, (FieldHadInstruction, FieldDidntHaveInstruction)):
741
809
  _change_field_in_model(
742
810
  schema_info,
743
811
  modified_schemas,
@@ -747,7 +815,7 @@ def _apply_alter_schema_instructions(
747
815
  elif isinstance(alter_schema_instruction, FieldDidntExistInstruction):
748
816
  _delete_field_from_model(schema_info, alter_schema_instruction.name, version_change_name)
749
817
  elif isinstance(alter_schema_instruction, ValidatorExistedInstruction):
750
- validator_name = alter_schema_instruction.validator.__name__
818
+ validator_name = get_name_of_function_wrapped_in_pydantic_validator(alter_schema_instruction.validator)
751
819
  raw_validator = cast(
752
820
  pydantic._internal._decorators.PydanticDescriptorProxy, alter_schema_instruction.validator
753
821
  )
@@ -838,7 +906,7 @@ def _add_field_to_model(
838
906
  def _change_field_in_model(
839
907
  model: _PydanticModelWrapper,
840
908
  schemas: "dict[type, _PydanticModelWrapper]",
841
- alter_schema_instruction: FieldHadInstruction | FieldDidntHaveInstruction,
909
+ alter_schema_instruction: Union[FieldHadInstruction, FieldDidntHaveInstruction],
842
910
  version_change_name: str,
843
911
  ):
844
912
  defined_annotations = model._get_defined_annotations_through_mro(schemas)
@@ -879,7 +947,7 @@ def _change_field(
879
947
  version_change_name: str,
880
948
  defined_annotations: dict[str, Any],
881
949
  field: PydanticFieldWrapper,
882
- annotation: Any | None,
950
+ annotation: Union[Any, None],
883
951
  ):
884
952
  if alter_schema_instruction.type is not Sentinel:
885
953
  if field.annotation == alter_schema_instruction.type:
@@ -1,8 +1,10 @@
1
1
  from collections.abc import Callable
2
2
  from dataclasses import dataclass
3
- from typing import ParamSpec, TypeAlias, TypeVar
4
3
 
5
4
  from pydantic import BaseModel
5
+ from typing_extensions import ParamSpec, TypeAlias, TypeVar
6
+
7
+ from cadwyn._utils import DATACLASS_KW_ONLY, DATACLASS_SLOTS
6
8
 
7
9
  VersionedModel = BaseModel
8
10
  VersionType: TypeAlias = str
@@ -11,6 +13,6 @@ _R = TypeVar("_R")
11
13
  Endpoint: TypeAlias = Callable[_P, _R]
12
14
 
13
15
 
14
- @dataclass(slots=True, kw_only=True)
16
+ @dataclass(**DATACLASS_SLOTS, **DATACLASS_KW_ONLY)
15
17
  class _HiddenAttributeMixin:
16
- is_hidden_from_changelog: bool = False
18
+ is_hidden_from_changelog: bool
cadwyn/structure/data.py CHANGED
@@ -2,10 +2,11 @@ import functools
2
2
  import inspect
3
3
  from collections.abc import Callable
4
4
  from dataclasses import dataclass, field
5
- from typing import Any, ClassVar, ParamSpec, cast, overload
5
+ from typing import ClassVar, Union, cast
6
6
 
7
7
  from fastapi import Request, Response
8
8
  from starlette.datastructures import MutableHeaders
9
+ from typing_extensions import Any, ParamSpec, overload
9
10
 
10
11
  from cadwyn._utils import same_definition_as_in
11
12
  from cadwyn.structure.endpoints import _validate_that_strings_are_valid_http_methods
@@ -82,7 +83,7 @@ class _AlterDataInstruction:
82
83
  def __set_name__(self, owner: type, name: str):
83
84
  self.owner = owner
84
85
 
85
- def __call__(self, __request_or_response: RequestInfo | ResponseInfo, /) -> None:
86
+ def __call__(self, __request_or_response: Union[RequestInfo, ResponseInfo], /) -> None:
86
87
  return self.transformer(__request_or_response)
87
88
 
88
89
 
@@ -127,8 +128,8 @@ def convert_request_to_next_version_for(path: str, methods: list[str], /) -> "ty
127
128
 
128
129
 
129
130
  def convert_request_to_next_version_for(
130
- schema_or_path: type | str,
131
- methods_or_second_schema: list[str] | None | type = None,
131
+ schema_or_path: Union[type, str],
132
+ methods_or_second_schema: Union[list[str], None, type] = None,
132
133
  /,
133
134
  *additional_schemas: type,
134
135
  check_usage: bool = True,
@@ -199,8 +200,8 @@ def convert_response_to_previous_version_for(
199
200
 
200
201
 
201
202
  def convert_response_to_previous_version_for(
202
- schema_or_path: type | str,
203
- methods_or_second_schema: list[str] | type | None = None,
203
+ schema_or_path: Union[type, str],
204
+ methods_or_second_schema: Union[list[str], type, None] = None,
204
205
  /,
205
206
  *additional_schemas: type,
206
207
  migrate_http_errors: bool = False,
@@ -233,7 +234,9 @@ def convert_response_to_previous_version_for(
233
234
 
234
235
 
235
236
  def _validate_decorator_args(
236
- schema_or_path: type | str, methods_or_second_schema: list[str] | type | None, additional_schemas: tuple[type, ...]
237
+ schema_or_path: Union[type, str],
238
+ methods_or_second_schema: Union[list[str], type, None],
239
+ additional_schemas: tuple[type, ...],
237
240
  ) -> None:
238
241
  if isinstance(schema_or_path, str):
239
242
  if not isinstance(methods_or_second_schema, list):
@@ -1,7 +1,7 @@
1
1
  from collections.abc import Callable, Collection, Sequence
2
2
  from dataclasses import dataclass
3
3
  from enum import Enum
4
- from typing import Any
4
+ from typing import Any, Union
5
5
 
6
6
  from fastapi import Response
7
7
  from fastapi.params import Depends
@@ -10,13 +10,13 @@ from starlette.routing import BaseRoute
10
10
 
11
11
  from cadwyn.exceptions import LintingError
12
12
 
13
- from .._utils import Sentinel
13
+ from .._utils import DATACLASS_SLOTS, Sentinel
14
14
  from .common import _HiddenAttributeMixin
15
15
 
16
16
  HTTP_METHODS = {"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"}
17
17
 
18
18
 
19
- @dataclass(slots=True)
19
+ @dataclass(**DATACLASS_SLOTS)
20
20
  class EndpointAttributesPayload:
21
21
  # Fastapi API routes also have "endpoint" and "dependency_overrides_provider" fields.
22
22
  # We do not use them because:
@@ -33,7 +33,7 @@ class EndpointAttributesPayload:
33
33
  path: str
34
34
  response_model: Any
35
35
  status_code: int
36
- tags: list[str | Enum]
36
+ tags: list[Union[str, Enum]]
37
37
  # Adding/removing dependencies between versions seems like a bad choice.
38
38
  # It makes the system overly complex. Instead, we allow people to
39
39
  # overwrite all dependencies of a route at once. Hence you always know exactly
@@ -43,7 +43,7 @@ class EndpointAttributesPayload:
43
43
  summary: str
44
44
  description: str
45
45
  response_description: str
46
- responses: dict[int | str, dict[str, Any]]
46
+ responses: dict[Union[int, str], dict[str, Any]]
47
47
  deprecated: bool
48
48
  methods: set[str]
49
49
  operation_id: str
@@ -55,47 +55,49 @@ class EndpointAttributesPayload:
55
55
  generate_unique_id_function: Callable[[APIRoute], str]
56
56
 
57
57
 
58
- @dataclass(slots=True)
58
+ @dataclass(**DATACLASS_SLOTS)
59
59
  class EndpointHadInstruction(_HiddenAttributeMixin):
60
60
  endpoint_path: str
61
61
  endpoint_methods: set[str]
62
- endpoint_func_name: str | None
62
+ endpoint_func_name: Union[str, None]
63
63
  attributes: EndpointAttributesPayload
64
64
 
65
65
 
66
- @dataclass(slots=True)
66
+ @dataclass(**DATACLASS_SLOTS)
67
67
  class EndpointExistedInstruction(_HiddenAttributeMixin):
68
68
  endpoint_path: str
69
69
  endpoint_methods: set[str]
70
- endpoint_func_name: str | None
70
+ endpoint_func_name: Union[str, None]
71
71
 
72
72
 
73
- @dataclass(slots=True)
73
+ @dataclass(**DATACLASS_SLOTS)
74
74
  class EndpointDidntExistInstruction(_HiddenAttributeMixin):
75
75
  endpoint_path: str
76
76
  endpoint_methods: set[str]
77
- endpoint_func_name: str | None
77
+ endpoint_func_name: Union[str, None]
78
78
 
79
79
 
80
- @dataclass(slots=True)
80
+ @dataclass(**DATACLASS_SLOTS)
81
81
  class EndpointInstructionFactory:
82
82
  endpoint_path: str
83
83
  endpoint_methods: set[str]
84
- endpoint_func_name: str | None
84
+ endpoint_func_name: Union[str, None]
85
85
 
86
86
  @property
87
87
  def didnt_exist(self) -> EndpointDidntExistInstruction:
88
88
  return EndpointDidntExistInstruction(
89
- self.endpoint_path,
90
- self.endpoint_methods,
89
+ is_hidden_from_changelog=False,
90
+ endpoint_path=self.endpoint_path,
91
+ endpoint_methods=self.endpoint_methods,
91
92
  endpoint_func_name=self.endpoint_func_name,
92
93
  )
93
94
 
94
95
  @property
95
96
  def existed(self) -> EndpointExistedInstruction:
96
97
  return EndpointExistedInstruction(
97
- self.endpoint_path,
98
- self.endpoint_methods,
98
+ is_hidden_from_changelog=False,
99
+ endpoint_path=self.endpoint_path,
100
+ endpoint_methods=self.endpoint_methods,
99
101
  endpoint_func_name=self.endpoint_func_name,
100
102
  )
101
103
 
@@ -105,12 +107,12 @@ class EndpointInstructionFactory:
105
107
  path: str = Sentinel,
106
108
  response_model: Any = Sentinel,
107
109
  status_code: int = Sentinel,
108
- tags: list[str | Enum] = Sentinel,
110
+ tags: list[Union[str, Enum]] = Sentinel,
109
111
  dependencies: Sequence[Depends] = Sentinel,
110
112
  summary: str = Sentinel,
111
113
  description: str = Sentinel,
112
114
  response_description: str = Sentinel,
113
- responses: dict[int | str, dict[str, Any]] = Sentinel,
115
+ responses: dict[Union[int, str], dict[str, Any]] = Sentinel,
114
116
  deprecated: bool = Sentinel,
115
117
  methods: list[str] = Sentinel,
116
118
  operation_id: str = Sentinel,
@@ -122,6 +124,7 @@ class EndpointInstructionFactory:
122
124
  generate_unique_id_function: Callable[[APIRoute], str] = Sentinel,
123
125
  ):
124
126
  return EndpointHadInstruction(
127
+ is_hidden_from_changelog=False,
125
128
  endpoint_path=self.endpoint_path,
126
129
  endpoint_methods=self.endpoint_methods,
127
130
  endpoint_func_name=self.endpoint_func_name,
@@ -148,7 +151,7 @@ class EndpointInstructionFactory:
148
151
  )
149
152
 
150
153
 
151
- def endpoint(path: str, methods: list[str], /, *, func_name: str | None = None) -> EndpointInstructionFactory:
154
+ def endpoint(path: str, methods: list[str], /, *, func_name: Union[str, None] = None) -> EndpointInstructionFactory:
152
155
  _validate_that_strings_are_valid_http_methods(methods)
153
156
 
154
157
  return EndpointInstructionFactory(path, set(methods), func_name)
@@ -164,4 +167,4 @@ def _validate_that_strings_are_valid_http_methods(methods: Collection[str]):
164
167
  )
165
168
 
166
169
 
167
- AlterEndpointSubInstruction = EndpointDidntExistInstruction | EndpointExistedInstruction | EndpointHadInstruction
170
+ AlterEndpointSubInstruction = Union[EndpointDidntExistInstruction, EndpointExistedInstruction, EndpointHadInstruction]
cadwyn/structure/enums.py CHANGED
@@ -1,36 +1,42 @@
1
1
  from collections.abc import Mapping
2
2
  from dataclasses import dataclass
3
3
  from enum import Enum
4
- from typing import Any
4
+ from typing import Any, Union
5
+
6
+ from cadwyn._utils import DATACLASS_SLOTS
5
7
 
6
8
  from .common import _HiddenAttributeMixin
7
9
 
8
10
 
9
- @dataclass(slots=True)
11
+ @dataclass(**DATACLASS_SLOTS)
10
12
  class EnumHadMembersInstruction(_HiddenAttributeMixin):
11
13
  enum: type[Enum]
12
14
  members: Mapping[str, Any]
13
15
 
14
16
 
15
- @dataclass(slots=True)
17
+ @dataclass(**DATACLASS_SLOTS)
16
18
  class EnumDidntHaveMembersInstruction(_HiddenAttributeMixin):
17
19
  enum: type[Enum]
18
20
  members: tuple[str, ...]
19
21
 
20
22
 
21
- @dataclass(slots=True)
23
+ @dataclass(**DATACLASS_SLOTS)
22
24
  class EnumInstructionFactory:
23
25
  enum_class: type[Enum]
24
26
 
25
27
  def had(self, **enum_member_to_value_mapping: Any) -> EnumHadMembersInstruction:
26
- return EnumHadMembersInstruction(self.enum_class, enum_member_to_value_mapping)
28
+ return EnumHadMembersInstruction(
29
+ is_hidden_from_changelog=False, enum=self.enum_class, members=enum_member_to_value_mapping
30
+ )
27
31
 
28
32
  def didnt_have(self, *enum_members: str) -> EnumDidntHaveMembersInstruction:
29
- return EnumDidntHaveMembersInstruction(self.enum_class, enum_members)
33
+ return EnumDidntHaveMembersInstruction(
34
+ is_hidden_from_changelog=False, enum=self.enum_class, members=enum_members
35
+ )
30
36
 
31
37
 
32
38
  def enum(enum_class: type[Enum], /) -> EnumInstructionFactory:
33
39
  return EnumInstructionFactory(enum_class)
34
40
 
35
41
 
36
- AlterEnumSubInstruction = EnumHadMembersInstruction | EnumDidntHaveMembersInstruction
42
+ AlterEnumSubInstruction = Union[EnumHadMembersInstruction, EnumDidntHaveMembersInstruction]