cadwyn 4.1.0__py3-none-any.whl → 4.2.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.

Potentially problematic release.


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

@@ -20,6 +20,7 @@ from typing import (
20
20
  final,
21
21
  get_args,
22
22
  get_origin,
23
+ overload,
23
24
  )
24
25
 
25
26
  import fastapi.params
@@ -104,6 +105,8 @@ class PydanticFieldWrapper:
104
105
  init_model_field: dataclasses.InitVar[FieldInfo]
105
106
 
106
107
  annotation: Any
108
+ name_from_newer_version: str
109
+
107
110
  passed_field_attributes: dict[str, Any] = dataclasses.field(init=False)
108
111
 
109
112
  def __post_init__(self, init_model_field: FieldInfo):
@@ -136,7 +139,7 @@ def _extract_passed_field_attributes(field_info: FieldInfo):
136
139
  @dataclasses.dataclass(slots=True)
137
140
  class _ModelBundle:
138
141
  enums: dict[type[Enum], "_EnumWrapper"]
139
- schemas: dict[type[BaseModel], "_PydanticRuntimeModelWrapper"]
142
+ schemas: dict[type[BaseModel], "_PydanticModelWrapper"]
140
143
 
141
144
 
142
145
  @dataclasses.dataclass(slots=True, kw_only=True)
@@ -222,7 +225,7 @@ def _is_dunder(attr_name: str):
222
225
  return attr_name.startswith("__") and attr_name.endswith("__")
223
226
 
224
227
 
225
- def _wrap_pydantic_model(model: type[_T_PYDANTIC_MODEL]) -> "_PydanticRuntimeModelWrapper[_T_PYDANTIC_MODEL]":
228
+ def _wrap_pydantic_model(model: type[_T_PYDANTIC_MODEL]) -> "_PydanticModelWrapper[_T_PYDANTIC_MODEL]":
226
229
  decorators = _get_model_decorators(model)
227
230
  validators = {}
228
231
  for decorator_wrapper in decorators:
@@ -232,7 +235,7 @@ def _wrap_pydantic_model(model: type[_T_PYDANTIC_MODEL]) -> "_PydanticRuntimeMod
232
235
  wrapped_validator = _wrap_validator(decorator_wrapper.func, decorator_wrapper.shim, decorator_wrapper.info)
233
236
  validators[decorator_wrapper.cls_var_name] = wrapped_validator
234
237
  fields = {
235
- field_name: PydanticFieldWrapper(model.model_fields[field_name], model.__annotations__[field_name])
238
+ field_name: PydanticFieldWrapper(model.model_fields[field_name], model.__annotations__[field_name], field_name)
236
239
  for field_name in model.__annotations__
237
240
  }
238
241
 
@@ -248,7 +251,7 @@ def _wrap_pydantic_model(model: type[_T_PYDANTIC_MODEL]) -> "_PydanticRuntimeMod
248
251
  "__module__": model.__module__,
249
252
  "__qualname__": model.__qualname__,
250
253
  }
251
- return _PydanticRuntimeModelWrapper(
254
+ return _PydanticModelWrapper(
252
255
  model,
253
256
  name=model.__name__,
254
257
  doc=model.__doc__,
@@ -261,20 +264,20 @@ def _wrap_pydantic_model(model: type[_T_PYDANTIC_MODEL]) -> "_PydanticRuntimeMod
261
264
 
262
265
  @final
263
266
  @dataclasses.dataclass(slots=True)
264
- class _PydanticRuntimeModelWrapper(Generic[_T_PYDANTIC_MODEL]):
265
- cls: type[_T_PYDANTIC_MODEL]
267
+ class _PydanticModelWrapper(Generic[_T_PYDANTIC_MODEL]):
268
+ cls: type[_T_PYDANTIC_MODEL] = dataclasses.field(repr=False)
266
269
  name: str
267
- doc: str | None
270
+ doc: str | None = dataclasses.field(repr=False)
268
271
  fields: Annotated[
269
272
  dict["_FieldName", PydanticFieldWrapper],
270
273
  Doc(
271
274
  "Fields that belong to this model, not to its parents. I.e. The ones that were either defined or overriden "
272
275
  ),
273
- ]
274
- validators: dict[str, _PerFieldValidatorWrapper | _ValidatorWrapper]
275
- other_attributes: dict[str, Any]
276
- annotations: dict[str, Any]
277
- _parents: list[Self] | None = dataclasses.field(init=False, default=None)
276
+ ] = dataclasses.field(repr=False)
277
+ validators: dict[str, _PerFieldValidatorWrapper | _ValidatorWrapper] = dataclasses.field(repr=False)
278
+ other_attributes: dict[str, Any] = dataclasses.field(repr=False)
279
+ annotations: dict[str, Any] = dataclasses.field(repr=False)
280
+ _parents: list[Self] | None = dataclasses.field(init=False, default=None, repr=False)
278
281
 
279
282
  def __post_init__(self):
280
283
  # This isn't actually supposed to run, it's just a precaution
@@ -291,7 +294,7 @@ class _PydanticRuntimeModelWrapper(Generic[_T_PYDANTIC_MODEL]):
291
294
  )
292
295
 
293
296
  def __deepcopy__(self, memo: dict[int, Any]):
294
- result = _PydanticRuntimeModelWrapper(
297
+ result = _PydanticModelWrapper(
295
298
  self.cls,
296
299
  name=self.name,
297
300
  doc=self.doc,
@@ -303,6 +306,9 @@ class _PydanticRuntimeModelWrapper(Generic[_T_PYDANTIC_MODEL]):
303
306
  memo[id(self)] = result
304
307
  return result
305
308
 
309
+ def __hash__(self) -> int:
310
+ return hash(id(self))
311
+
306
312
  def _get_parents(self, schemas: "dict[type, Self]"):
307
313
  if self._parents is not None:
308
314
  return self._parents
@@ -345,7 +351,7 @@ class _PydanticRuntimeModelWrapper(Generic[_T_PYDANTIC_MODEL]):
345
351
  fields = {name: field.generate_field_copy(generator) for name, field in self.fields.items()}
346
352
  model_copy = type(self.cls)(
347
353
  self.name,
348
- tuple(generator[base] for base in self.cls.__bases__),
354
+ tuple(generator[cast(type[BaseModel], base)] for base in self.cls.__bases__),
349
355
  self.other_attributes
350
356
  | per_field_validators
351
357
  | root_validators
@@ -599,9 +605,9 @@ class SchemaGenerator:
599
605
  for k, wrapper in (self.model_bundle.schemas | self.model_bundle.enums).items()
600
606
  }
601
607
 
602
- def __getitem__(self, model: type, /) -> Any:
608
+ def __getitem__(self, model: type[_T_ANY_MODEL], /) -> type[_T_ANY_MODEL]:
603
609
  if not isinstance(model, type) or not issubclass(model, BaseModel | Enum) or model in (BaseModel, RootModel):
604
- return model
610
+ return model # pyright: ignore[reportReturnType]
605
611
  model = _unwrap_model(model)
606
612
 
607
613
  if model in self.concrete_models:
@@ -614,9 +620,14 @@ class SchemaGenerator:
614
620
  self.concrete_models[model] = model_copy
615
621
  return model_copy
616
622
 
623
+ @overload
624
+ def _get_wrapper_for_model(self, model: type[BaseModel]) -> "_PydanticModelWrapper[BaseModel]": ...
625
+ @overload
626
+ def _get_wrapper_for_model(self, model: type[Enum]) -> "_EnumWrapper[Enum]": ...
627
+
617
628
  def _get_wrapper_for_model(
618
629
  self, model: type[BaseModel | Enum]
619
- ) -> "_PydanticRuntimeModelWrapper[BaseModel] | _EnumWrapper[Enum]":
630
+ ) -> "_PydanticModelWrapper[BaseModel] | _EnumWrapper[Enum]":
620
631
  model = _unwrap_model(model)
621
632
 
622
633
  if model in self.model_bundle.schemas:
@@ -660,7 +671,7 @@ def _create_model_bundle(versions: "VersionBundle"):
660
671
 
661
672
 
662
673
  def _migrate_classes(context: _RuntimeSchemaGenContext) -> None:
663
- for version_change in context.current_version.version_changes:
674
+ for version_change in context.current_version.changes:
664
675
  _apply_alter_schema_instructions(
665
676
  context.models.schemas,
666
677
  version_change.alter_schema_instructions,
@@ -674,7 +685,7 @@ def _migrate_classes(context: _RuntimeSchemaGenContext) -> None:
674
685
 
675
686
 
676
687
  def _apply_alter_schema_instructions(
677
- modified_schemas: dict[type, _PydanticRuntimeModelWrapper],
688
+ modified_schemas: dict[type, _PydanticModelWrapper],
678
689
  alter_schema_instructions: Sequence[AlterSchemaSubInstruction | SchemaHadInstruction],
679
690
  version_change_name: str,
680
691
  ) -> None:
@@ -747,7 +758,7 @@ def _apply_alter_enum_instructions(
747
758
 
748
759
 
749
760
  def _change_model(
750
- model: _PydanticRuntimeModelWrapper,
761
+ model: _PydanticModelWrapper,
751
762
  alter_schema_instruction: SchemaHadInstruction,
752
763
  version_change_name: str,
753
764
  ):
@@ -761,8 +772,8 @@ def _change_model(
761
772
 
762
773
 
763
774
  def _add_field_to_model(
764
- model: _PydanticRuntimeModelWrapper,
765
- schemas: "dict[type, _PydanticRuntimeModelWrapper]",
775
+ model: _PydanticModelWrapper,
776
+ schemas: "dict[type, _PydanticModelWrapper]",
766
777
  alter_schema_instruction: FieldExistedAsInstruction,
767
778
  version_change_name: str,
768
779
  ):
@@ -773,14 +784,16 @@ def _add_field_to_model(
773
784
  f'in "{version_change_name}" but there is already a field with that name.',
774
785
  )
775
786
 
776
- field = PydanticFieldWrapper(alter_schema_instruction.field, alter_schema_instruction.field.annotation)
787
+ field = PydanticFieldWrapper(
788
+ alter_schema_instruction.field, alter_schema_instruction.field.annotation, alter_schema_instruction.name
789
+ )
777
790
  model.fields[alter_schema_instruction.name] = field
778
791
  model.annotations[alter_schema_instruction.name] = alter_schema_instruction.field.annotation
779
792
 
780
793
 
781
794
  def _change_field_in_model(
782
- model: _PydanticRuntimeModelWrapper,
783
- schemas: "dict[type, _PydanticRuntimeModelWrapper]",
795
+ model: _PydanticModelWrapper,
796
+ schemas: "dict[type, _PydanticModelWrapper]",
784
797
  alter_schema_instruction: FieldHadInstruction | FieldDidntHaveInstruction,
785
798
  version_change_name: str,
786
799
  ):
@@ -817,7 +830,7 @@ def _change_field_in_model(
817
830
 
818
831
 
819
832
  def _change_field(
820
- model: _PydanticRuntimeModelWrapper,
833
+ model: _PydanticModelWrapper,
821
834
  alter_schema_instruction: FieldHadInstruction,
822
835
  version_change_name: str,
823
836
  defined_annotations: dict[str, Any],
@@ -861,7 +874,7 @@ def _change_field(
861
874
 
862
875
 
863
876
  def _delete_field_attributes(
864
- model: _PydanticRuntimeModelWrapper,
877
+ model: _PydanticModelWrapper,
865
878
  alter_schema_instruction: FieldDidntHaveInstruction,
866
879
  version_change_name: str,
867
880
  field: PydanticFieldWrapper,
@@ -884,7 +897,7 @@ def _delete_field_attributes(
884
897
  )
885
898
 
886
899
 
887
- def _delete_field_from_model(model: _PydanticRuntimeModelWrapper, field_name: str, version_change_name: str):
900
+ def _delete_field_from_model(model: _PydanticModelWrapper, field_name: str, version_change_name: str):
888
901
  if field_name not in model.fields:
889
902
  raise InvalidGenerationInstructionError(
890
903
  f'You tried to delete a field "{field_name}" from "{model.name}" '
@@ -906,10 +919,11 @@ class _DummyEnum(Enum):
906
919
 
907
920
  @final
908
921
  class _EnumWrapper(Generic[_T_ENUM]):
909
- __slots__ = "cls", "members"
922
+ __slots__ = "cls", "members", "name"
910
923
 
911
924
  def __init__(self, cls: type[_T_ENUM]):
912
925
  self.cls = _unwrap_model(cls)
926
+ self.name = cls.__name__
913
927
  self.members = {member.name: member.value for member in cls}
914
928
 
915
929
  def __deepcopy__(self, memo: Any):
@@ -919,13 +933,13 @@ class _EnumWrapper(Generic[_T_ENUM]):
919
933
  return result
920
934
 
921
935
  def generate_model_copy(self, generator: "SchemaGenerator") -> type[_T_ENUM]:
922
- enum_dict = Enum.__prepare__(self.cls.__name__, self.cls.__bases__)
936
+ enum_dict = Enum.__prepare__(self.name, self.cls.__bases__)
923
937
 
924
938
  raw_member_map = {k: v.value if isinstance(v, Enum) else v for k, v in self.members.items()}
925
939
  initialization_namespace = self._get_initialization_namespace_for_enum(self.cls) | raw_member_map
926
940
  for attr_name, attr in initialization_namespace.items():
927
941
  enum_dict[attr_name] = attr
928
- model_copy = cast(type[_T_ENUM], type(self.cls.__name__, self.cls.__bases__, enum_dict))
942
+ model_copy = cast(type[_T_ENUM], type(self.name, self.cls.__bases__, enum_dict))
929
943
  model_copy.__cadwyn_original_model__ = self.cls # pyright: ignore[reportAttributeAccessIssue]
930
944
  return model_copy
931
945
 
@@ -1,5 +1,6 @@
1
1
  import datetime
2
2
  from collections.abc import Callable
3
+ from dataclasses import dataclass
3
4
  from typing import ParamSpec, TypeAlias, TypeVar
4
5
 
5
6
  from pydantic import BaseModel
@@ -9,3 +10,8 @@ VersionDate = datetime.date
9
10
  _P = ParamSpec("_P")
10
11
  _R = TypeVar("_R")
11
12
  Endpoint: TypeAlias = Callable[_P, _R]
13
+
14
+
15
+ @dataclass(slots=True, kw_only=True)
16
+ class _HiddenAttributeMixin:
17
+ is_hidden_from_changelog: bool = False
@@ -11,6 +11,7 @@ from starlette.routing import BaseRoute
11
11
  from cadwyn.exceptions import LintingError
12
12
 
13
13
  from .._utils import Sentinel
14
+ from .common import _HiddenAttributeMixin
14
15
 
15
16
  HTTP_METHODS = {"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"}
16
17
 
@@ -44,7 +45,7 @@ class EndpointAttributesPayload:
44
45
  response_description: str
45
46
  responses: dict[int | str, dict[str, Any]]
46
47
  deprecated: bool
47
- methods: list[str]
48
+ methods: set[str]
48
49
  operation_id: str
49
50
  include_in_schema: bool
50
51
  response_class: type[Response]
@@ -55,7 +56,7 @@ class EndpointAttributesPayload:
55
56
 
56
57
 
57
58
  @dataclass(slots=True)
58
- class EndpointHadInstruction:
59
+ class EndpointHadInstruction(_HiddenAttributeMixin):
59
60
  endpoint_path: str
60
61
  endpoint_methods: set[str]
61
62
  endpoint_func_name: str | None
@@ -63,14 +64,14 @@ class EndpointHadInstruction:
63
64
 
64
65
 
65
66
  @dataclass(slots=True)
66
- class EndpointExistedInstruction:
67
+ class EndpointExistedInstruction(_HiddenAttributeMixin):
67
68
  endpoint_path: str
68
69
  endpoint_methods: set[str]
69
70
  endpoint_func_name: str | None
70
71
 
71
72
 
72
73
  @dataclass(slots=True)
73
- class EndpointDidntExistInstruction:
74
+ class EndpointDidntExistInstruction(_HiddenAttributeMixin):
74
75
  endpoint_path: str
75
76
  endpoint_methods: set[str]
76
77
  endpoint_func_name: str | None
@@ -135,7 +136,7 @@ class EndpointInstructionFactory:
135
136
  response_description=response_description,
136
137
  responses=responses,
137
138
  deprecated=deprecated,
138
- methods=methods,
139
+ methods=set(methods) if methods is not Sentinel else Sentinel,
139
140
  operation_id=operation_id,
140
141
  include_in_schema=include_in_schema,
141
142
  response_class=response_class,
cadwyn/structure/enums.py CHANGED
@@ -3,15 +3,17 @@ from dataclasses import dataclass
3
3
  from enum import Enum
4
4
  from typing import Any
5
5
 
6
+ from .common import _HiddenAttributeMixin
7
+
6
8
 
7
9
  @dataclass(slots=True)
8
- class EnumHadMembersInstruction:
10
+ class EnumHadMembersInstruction(_HiddenAttributeMixin):
9
11
  enum: type[Enum]
10
12
  members: Mapping[str, Any]
11
13
 
12
14
 
13
15
  @dataclass(slots=True)
14
- class EnumDidntHaveMembersInstruction:
16
+ class EnumDidntHaveMembersInstruction(_HiddenAttributeMixin):
15
17
  enum: type[Enum]
16
18
  members: tuple[str, ...]
17
19
 
@@ -10,6 +10,8 @@ from pydantic.fields import FieldInfo
10
10
  from cadwyn._utils import Sentinel, fully_unwrap_decorator
11
11
  from cadwyn.exceptions import CadwynStructureError
12
12
 
13
+ from .common import _HiddenAttributeMixin
14
+
13
15
  if TYPE_CHECKING:
14
16
  from pydantic.typing import AbstractSetIntStr, MappingIntStrAny
15
17
 
@@ -21,24 +23,21 @@ PossibleFieldAttributes = Literal[
21
23
  "title",
22
24
  "description",
23
25
  "exclude",
24
- "include",
25
26
  "const",
26
27
  "gt",
27
28
  "ge",
28
29
  "lt",
29
30
  "le",
31
+ "deprecated",
32
+ "fail_fast",
30
33
  "strict",
31
34
  "multiple_of",
32
35
  "allow_inf_nan",
33
36
  "max_digits",
34
37
  "decimal_places",
35
- "min_items",
36
- "max_items",
37
- "unique_items",
38
38
  "min_length",
39
39
  "max_length",
40
40
  "allow_mutation",
41
- "regex",
42
41
  "pattern",
43
42
  "discriminator",
44
43
  "repr",
@@ -53,8 +52,9 @@ class FieldChanges:
53
52
  title: str
54
53
  description: str
55
54
  exclude: "AbstractSetIntStr | MappingIntStrAny | Any"
56
- include: "AbstractSetIntStr | MappingIntStrAny | Any"
57
55
  const: bool
56
+ deprecated: bool
57
+ fail_fast: bool
58
58
  gt: float
59
59
  ge: float
60
60
  lt: float
@@ -64,20 +64,16 @@ class FieldChanges:
64
64
  allow_inf_nan: bool
65
65
  max_digits: int
66
66
  decimal_places: int
67
- min_items: int
68
- max_items: int
69
- unique_items: bool
70
67
  min_length: int
71
68
  max_length: int
72
69
  allow_mutation: bool
73
- regex: str
74
70
  pattern: str
75
71
  discriminator: str
76
72
  repr: bool
77
73
 
78
74
 
79
75
  @dataclass(slots=True)
80
- class FieldHadInstruction:
76
+ class FieldHadInstruction(_HiddenAttributeMixin):
81
77
  schema: type[BaseModel]
82
78
  name: str
83
79
  type: type
@@ -86,20 +82,20 @@ class FieldHadInstruction:
86
82
 
87
83
 
88
84
  @dataclass(slots=True)
89
- class FieldDidntHaveInstruction:
85
+ class FieldDidntHaveInstruction(_HiddenAttributeMixin):
90
86
  schema: type[BaseModel]
91
87
  name: str
92
88
  attributes: tuple[str, ...]
93
89
 
94
90
 
95
91
  @dataclass(slots=True)
96
- class FieldDidntExistInstruction:
92
+ class FieldDidntExistInstruction(_HiddenAttributeMixin):
97
93
  schema: type[BaseModel]
98
94
  name: str
99
95
 
100
96
 
101
97
  @dataclass(slots=True)
102
- class FieldExistedAsInstruction:
98
+ class FieldExistedAsInstruction(_HiddenAttributeMixin):
103
99
  schema: type[BaseModel]
104
100
  name: str
105
101
  field: FieldInfo
@@ -122,41 +118,25 @@ class AlterFieldInstructionFactory:
122
118
  title: str = Sentinel,
123
119
  description: str = Sentinel,
124
120
  exclude: "AbstractSetIntStr | MappingIntStrAny | Any" = Sentinel,
125
- include: "AbstractSetIntStr | MappingIntStrAny | Any" = Sentinel,
126
121
  const: bool = Sentinel,
127
122
  gt: float = Sentinel,
128
123
  ge: float = Sentinel,
129
124
  lt: float = Sentinel,
130
125
  le: float = Sentinel,
131
126
  strict: bool = Sentinel,
127
+ deprecated: bool = Sentinel,
132
128
  multiple_of: float = Sentinel,
133
129
  allow_inf_nan: bool = Sentinel,
134
130
  max_digits: int = Sentinel,
135
131
  decimal_places: int = Sentinel,
136
- min_items: int = Sentinel,
137
- max_items: int = Sentinel,
138
- unique_items: bool = Sentinel,
139
132
  min_length: int = Sentinel,
140
133
  max_length: int = Sentinel,
141
134
  allow_mutation: bool = Sentinel,
142
- regex: str = Sentinel,
143
135
  pattern: str = Sentinel,
144
136
  discriminator: str = Sentinel,
145
137
  repr: bool = Sentinel,
138
+ fail_fast: bool = Sentinel,
146
139
  ) -> FieldHadInstruction:
147
- if regex is not Sentinel:
148
- raise CadwynStructureError("`regex` was removed in Pydantic 2. Use `pattern` instead")
149
- if include is not Sentinel:
150
- raise CadwynStructureError("`include` was removed in Pydantic 2. Use `exclude` instead")
151
- if min_items is not Sentinel:
152
- raise CadwynStructureError("`min_items` was removed in Pydantic 2. Use `min_length` instead")
153
- if max_items is not Sentinel:
154
- raise CadwynStructureError("`max_items` was removed in Pydantic 2. Use `max_length` instead")
155
- if unique_items is not Sentinel:
156
- raise CadwynStructureError(
157
- "`unique_items` was removed in Pydantic 2. Use `Set` type annotation instead"
158
- "(this feature is discussed in https://github.com/pydantic/pydantic-core/issues/296)",
159
- )
160
140
  return FieldHadInstruction(
161
141
  schema=self.schema,
162
142
  name=self.name,
@@ -169,27 +149,24 @@ class AlterFieldInstructionFactory:
169
149
  title=title,
170
150
  description=description,
171
151
  exclude=exclude,
172
- include=include,
173
152
  const=const,
174
153
  gt=gt,
175
154
  ge=ge,
176
155
  lt=lt,
177
156
  le=le,
157
+ deprecated=deprecated,
178
158
  strict=strict,
179
159
  multiple_of=multiple_of,
180
160
  allow_inf_nan=allow_inf_nan,
181
161
  max_digits=max_digits,
182
162
  decimal_places=decimal_places,
183
- min_items=min_items,
184
- max_items=max_items,
185
- unique_items=unique_items,
186
163
  min_length=min_length,
187
164
  max_length=max_length,
188
165
  allow_mutation=allow_mutation,
189
- regex=regex,
190
166
  pattern=pattern,
191
167
  discriminator=discriminator,
192
168
  repr=repr,
169
+ fail_fast=fail_fast,
193
170
  ),
194
171
  )
195
172
 
@@ -266,7 +243,7 @@ AlterSchemaSubInstruction = (
266
243
 
267
244
 
268
245
  @dataclass(slots=True)
269
- class SchemaHadInstruction:
246
+ class SchemaHadInstruction(_HiddenAttributeMixin):
270
247
  schema: type[BaseModel]
271
248
  name: str
272
249
 
@@ -23,7 +23,7 @@ from fastapi.routing import APIRoute, _prepare_response_content
23
23
  from pydantic import BaseModel
24
24
  from pydantic_core import PydanticUndefined
25
25
  from starlette._utils import is_async_callable
26
- from typing_extensions import assert_never
26
+ from typing_extensions import assert_never, deprecated
27
27
 
28
28
  from cadwyn._utils import classproperty
29
29
  from cadwyn.exceptions import (
@@ -64,6 +64,7 @@ IdentifierPythonPath = str
64
64
 
65
65
  class VersionChange:
66
66
  description: ClassVar[str] = Sentinel
67
+ is_hidden_from_changelog: bool = False
67
68
  instructions_to_migrate_to_previous_version: ClassVar[Sequence[PossibleInstructions]] = Sentinel
68
69
  alter_schema_instructions: ClassVar[list[AlterSchemaSubInstruction | SchemaHadInstruction]] = Sentinel
69
70
  alter_enum_instructions: ClassVar[list[AlterEnumSubInstruction]] = Sentinel
@@ -198,24 +199,29 @@ class VersionChangeWithSideEffects(VersionChange, _abstract=True):
198
199
 
199
200
 
200
201
  class Version:
201
- def __init__(self, value: VersionDate | str, *version_changes: type[VersionChange]) -> None:
202
+ def __init__(self, value: VersionDate | str, *changes: type[VersionChange]) -> None:
202
203
  super().__init__()
203
204
 
204
205
  if isinstance(value, str):
205
206
  value = date.fromisoformat(value)
206
207
  self.value = value
207
- self.version_changes = version_changes
208
+ self.changes = changes
209
+
210
+ @property
211
+ @deprecated("'version_changes' attribute is deprecated and will be removed in Cadwyn 5.x.x. Use 'changes' instead.")
212
+ def version_changes(self): # pragma: no cover
213
+ return self.changes
208
214
 
209
215
  def __repr__(self) -> str:
210
216
  return f"Version('{self.value}')"
211
217
 
212
218
 
213
219
  class HeadVersion:
214
- def __init__(self, *version_changes: type[VersionChange]) -> None:
220
+ def __init__(self, *changes: type[VersionChange]) -> None:
215
221
  super().__init__()
216
- self.version_changes = version_changes
222
+ self.changes = changes
217
223
 
218
- for version_change in version_changes:
224
+ for version_change in changes:
219
225
  if any(
220
226
  [
221
227
  version_change.alter_request_by_path_instructions,
@@ -228,6 +234,11 @@ class HeadVersion:
228
234
  f"HeadVersion does not support request or response migrations but {version_change} contained one."
229
235
  )
230
236
 
237
+ @property
238
+ @deprecated("'version_changes' attribute is deprecated and will be removed in Cadwyn 5.x.x. Use 'changes' instead.")
239
+ def version_changes(self): # pragma: no cover
240
+ return self.changes
241
+
231
242
 
232
243
  def get_cls_pythonpath(cls: type) -> IdentifierPythonPath:
233
244
  return f"{cls.__module__}.{cls.__name__}"
@@ -260,7 +271,7 @@ class VersionBundle:
260
271
  )
261
272
  if not self.versions:
262
273
  raise CadwynStructureError("You must define at least one non-head version in a VersionBundle.")
263
- if self.versions[-1].version_changes:
274
+ if self.versions[-1].changes:
264
275
  raise CadwynStructureError(
265
276
  f'The first version "{self.versions[-1].value}" cannot have any version changes. '
266
277
  "Version changes are defined to migrate to/from a previous version so you "
@@ -275,7 +286,7 @@ class VersionBundle:
275
286
  f"You tried to define two versions with the same value in the same "
276
287
  f"{VersionBundle.__name__}: '{version.value}'.",
277
288
  )
278
- for version_change in version.version_changes:
289
+ for version_change in version.changes:
279
290
  if version_change._bound_version_bundle is not None:
280
291
  raise CadwynStructureError(
281
292
  f"You tried to bind version change '{version_change.__name__}' to two different versions. "
@@ -295,14 +306,14 @@ class VersionBundle:
295
306
  altered_schemas = {
296
307
  get_cls_pythonpath(instruction.schema): instruction.schema
297
308
  for version in self._all_versions
298
- for version_change in version.version_changes
309
+ for version_change in version.changes
299
310
  for instruction in list(version_change.alter_schema_instructions)
300
311
  }
301
312
 
302
313
  migrated_schemas = {
303
314
  get_cls_pythonpath(schema): schema
304
315
  for version in self._all_versions
305
- for version_change in version.version_changes
316
+ for version_change in version.changes
306
317
  for schema in list(version_change.alter_request_by_schema_instructions.keys())
307
318
  }
308
319
 
@@ -313,7 +324,7 @@ class VersionBundle:
313
324
  return {
314
325
  get_cls_pythonpath(instruction.enum): instruction.enum
315
326
  for version in self._all_versions
316
- for version_change in version.version_changes
327
+ for version_change in version.changes
317
328
  for instruction in version_change.alter_enum_instructions
318
329
  }
319
330
 
@@ -327,9 +338,7 @@ class VersionBundle:
327
338
  def _version_changes_to_version_mapping(
328
339
  self,
329
340
  ) -> dict[type[VersionChange] | type[VersionChangeWithSideEffects], VersionDate]:
330
- return {
331
- version_change: version.value for version in self.versions for version_change in version.version_changes
332
- }
341
+ return {version_change: version.value for version in self.versions for version_change in version.changes}
333
342
 
334
343
  async def _migrate_request(
335
344
  self,
@@ -347,7 +356,7 @@ class VersionBundle:
347
356
  for v in reversed(self.versions):
348
357
  if v.value <= current_version:
349
358
  continue
350
- for version_change in v.version_changes:
359
+ for version_change in v.changes:
351
360
  if body_type is not None and body_type in version_change.alter_request_by_schema_instructions:
352
361
  for instruction in version_change.alter_request_by_schema_instructions[body_type]:
353
362
  instruction(request_info)
@@ -394,7 +403,7 @@ class VersionBundle:
394
403
  for v in self.versions:
395
404
  if v.value <= current_version:
396
405
  break
397
- for version_change in v.version_changes:
406
+ for version_change in v.changes:
398
407
  migrations_to_apply: list[_BaseAlterResponseInstruction] = []
399
408
 
400
409
  if head_response_model and head_response_model in version_change.alter_response_by_schema_instructions:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cadwyn
3
- Version: 4.1.0
3
+ Version: 4.2.1
4
4
  Summary: Production-ready community-driven modern Stripe-like API versioning in FastAPI
5
5
  Home-page: https://github.com/zmievsa/cadwyn
6
6
  License: MIT
@@ -32,6 +32,7 @@ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
32
32
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
33
33
  Classifier: Typing :: Typed
34
34
  Provides-Extra: cli
35
+ Requires-Dist: backports-strenum (>=1.3.1,<2.0.0) ; python_version < "3.11"
35
36
  Requires-Dist: fastapi (>=0.110.0)
36
37
  Requires-Dist: issubclass (>=0.1.2,<0.2.0)
37
38
  Requires-Dist: jinja2 (>=3.1.2)