cadwyn 5.1.2__tar.gz → 5.1.4__tar.gz

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.

Files changed (155) hide show
  1. {cadwyn-5.1.2 → cadwyn-5.1.4}/CHANGELOG.md +15 -0
  2. {cadwyn-5.1.2 → cadwyn-5.1.4}/PKG-INFO +2 -2
  3. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/exceptions.py +1 -2
  4. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/schema_generation.py +14 -0
  5. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/structure/versions.py +8 -6
  6. {cadwyn-5.1.2 → cadwyn-5.1.4}/pyproject.toml +4 -3
  7. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_data_migrations.py +41 -1
  8. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_router_generation.py +52 -4
  9. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_schema_generation/test_enum.py +7 -1
  10. {cadwyn-5.1.2 → cadwyn-5.1.4}/uv.lock +171 -171
  11. {cadwyn-5.1.2 → cadwyn-5.1.4}/.github/CODE_OF_CONDUCT.md +0 -0
  12. {cadwyn-5.1.2 → cadwyn-5.1.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  13. {cadwyn-5.1.2 → cadwyn-5.1.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  14. {cadwyn-5.1.2 → cadwyn-5.1.4}/.github/actions/setup-python-uv/action.yaml +0 -0
  15. {cadwyn-5.1.2 → cadwyn-5.1.4}/.github/workflows/ci.yaml +0 -0
  16. {cadwyn-5.1.2 → cadwyn-5.1.4}/.github/workflows/daily_tests.yaml +0 -0
  17. {cadwyn-5.1.2 → cadwyn-5.1.4}/.github/workflows/publish_docs.yaml +0 -0
  18. {cadwyn-5.1.2 → cadwyn-5.1.4}/.github/workflows/release.yaml +0 -0
  19. {cadwyn-5.1.2 → cadwyn-5.1.4}/.gitignore +0 -0
  20. {cadwyn-5.1.2 → cadwyn-5.1.4}/.pre-commit-config.yaml +0 -0
  21. {cadwyn-5.1.2 → cadwyn-5.1.4}/LICENSE +0 -0
  22. {cadwyn-5.1.2 → cadwyn-5.1.4}/Makefile +0 -0
  23. {cadwyn-5.1.2 → cadwyn-5.1.4}/README.md +0 -0
  24. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/__init__.py +0 -0
  25. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/__main__.py +0 -0
  26. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/_asts.py +0 -0
  27. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/_importer.py +0 -0
  28. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/_render.py +0 -0
  29. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/_utils.py +0 -0
  30. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/applications.py +0 -0
  31. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/changelogs.py +0 -0
  32. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/dependencies.py +0 -0
  33. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/middleware.py +0 -0
  34. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/py.typed +0 -0
  35. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/route_generation.py +0 -0
  36. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/routing.py +0 -0
  37. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/static/__init__.py +0 -0
  38. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/static/docs.html +0 -0
  39. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/structure/__init__.py +0 -0
  40. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/structure/common.py +0 -0
  41. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/structure/data.py +0 -0
  42. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/structure/endpoints.py +0 -0
  43. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/structure/enums.py +0 -0
  44. {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/structure/schemas.py +0 -0
  45. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/CNAME +0 -0
  46. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/__init__.py +0 -0
  47. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/api_version_parameter_and_context_variables.md +0 -0
  48. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/beware_of_data_versioning.md +0 -0
  49. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/changelogs.md +0 -0
  50. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/cli.md +0 -0
  51. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/endpoint_migrations.md +0 -0
  52. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/enum_migrations.md +0 -0
  53. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/index.md +0 -0
  54. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/main_app.md +0 -0
  55. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/methodology.md +0 -0
  56. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/schema_generation.md +0 -0
  57. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/schema_migrations.md +0 -0
  58. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/testing.md +0 -0
  59. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/version_changes.md +0 -0
  60. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/where_to_put_the_version_and_how_to_format_it.md +0 -0
  61. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/home/CONTRIBUTING.md +0 -0
  62. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/change_business_logic/index.md +0 -0
  63. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/change_endpoints/index.md +0 -0
  64. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/change_openapi_schemas/add_field.md +0 -0
  65. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/change_openapi_schemas/change_field_type.md +0 -0
  66. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/change_openapi_schemas/change_schema_without_endpoint.md +0 -0
  67. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/change_openapi_schemas/changing_constraints.md +0 -0
  68. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/change_openapi_schemas/remove_field.md +0 -0
  69. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/change_openapi_schemas/rename_a_field_in_schema.md +0 -0
  70. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/index.md +0 -0
  71. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/version_with_paths_and_numbers_instead_of_headers_and_dates.md +0 -0
  72. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/img/dashboard_with_one_version.png +0 -0
  73. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/img/dashboard_with_two_versions.png +0 -0
  74. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/img/get_users_endpoint_from_prior_version.png +0 -0
  75. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/img/simplified_migration_model.png +0 -0
  76. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/img/sponsor_logos/monite.png +0 -0
  77. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/img/unversioned_dashboard.png +0 -0
  78. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/index.md +0 -0
  79. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/plugin.py +0 -0
  80. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/quickstart/setup.md +0 -0
  81. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/quickstart/tutorial.md +0 -0
  82. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/theory/how_to_build_versioning_framework.md +0 -0
  83. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/theory/how_we_got_here.md +0 -0
  84. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/theory/literature.md +0 -0
  85. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/__init__.py +0 -0
  86. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/__init__.py +0 -0
  87. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/change_openapi_schemas/__init__.py +0 -0
  88. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/__init__.py +0 -0
  89. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block001.py +0 -0
  90. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block002.py +0 -0
  91. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/__init__.py +0 -0
  92. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block001.py +0 -0
  93. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block002.py +0 -0
  94. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/__init__.py +0 -0
  95. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/block001.py +0 -0
  96. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/__init__.py +0 -0
  97. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/test_block_001.py +0 -0
  98. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/__init__.py +0 -0
  99. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/setup/__init__.py +0 -0
  100. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/setup/block001.sh +0 -0
  101. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/setup/block002.py +0 -0
  102. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/setup/tests/__init__.py +0 -0
  103. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/setup/tests/test_block002.py +0 -0
  104. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/tutorial/__init__.py +0 -0
  105. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/tutorial/block001.py +0 -0
  106. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/tutorial/block002.py +0 -0
  107. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/tutorial/block003.py +0 -0
  108. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/tutorial/tests/__init__.py +0 -0
  109. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/tutorial/tests/test_block001.py +0 -0
  110. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/tutorial/tests/test_block002.py +0 -0
  111. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/tutorial/tests/test_block003.py +0 -0
  112. {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/ruff.toml +0 -0
  113. {cadwyn-5.1.2 → cadwyn-5.1.4}/mkdocs.yml +0 -0
  114. {cadwyn-5.1.2 → cadwyn-5.1.4}/ruff.toml +0 -0
  115. {cadwyn-5.1.2 → cadwyn-5.1.4}/scripts/fix_links.py +0 -0
  116. {cadwyn-5.1.2 → cadwyn-5.1.4}/scripts/split_md.py +0 -0
  117. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/__init__.py +0 -0
  118. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_data/__init__.py +0 -0
  119. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_data/unversioned_schema_dir/__init__.py +0 -0
  120. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_data/unversioned_schema_dir/unversioned_schemas.py +0 -0
  121. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_data/unversioned_schemas.py +0 -0
  122. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/__init__.py +0 -0
  123. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/app_for_testing_routing.py +0 -0
  124. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/render/__init__.py +0 -0
  125. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/render/classes.py +0 -0
  126. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/render/complex/__init__.py +0 -0
  127. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/render/complex/classes.py +0 -0
  128. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/render/complex/versions.py +0 -0
  129. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/render/versions.py +0 -0
  130. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/utils.py +0 -0
  131. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/versioned_app/__init__.py +0 -0
  132. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/versioned_app/app.py +0 -0
  133. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/versioned_app/v2021_01_01.py +0 -0
  134. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/versioned_app/v2022_01_02.py +0 -0
  135. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/versioned_app/webhooks.py +0 -0
  136. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/conftest.py +0 -0
  137. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_applications.py +0 -0
  138. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_auth_dependencies.py +0 -0
  139. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_changelog.py +0 -0
  140. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_cli.py +0 -0
  141. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_render.py +0 -0
  142. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_router_generation_with_from_future_annotations.py +0 -0
  143. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_routing.py +0 -0
  144. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_schema_generation/__init__.py +0 -0
  145. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_schema_generation/test_schema.py +0 -0
  146. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_schema_generation/test_schema_field.py +0 -0
  147. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_schema_generation/test_schema_validator.py +0 -0
  148. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_schema_generation/test_schema_with_future_annotations.py +0 -0
  149. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_structure.py +0 -0
  150. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/tutorial/__init__.py +0 -0
  151. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/tutorial/main.py +0 -0
  152. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/tutorial/test_example.py +0 -0
  153. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/versioning_styles/__init__.py +0 -0
  154. {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/versioning_styles/test_versioning_formats.py +0 -0
  155. {cadwyn-5.1.2 → cadwyn-5.1.4}/tox.ini +0 -0
@@ -5,6 +5,21 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [5.1.4]
9
+
10
+ ### Fixed
11
+
12
+ * a bug where a custom HTTP exception was not being copied correctly so its unique properties are ignored
13
+
14
+ ## [5.1.3]
15
+
16
+ ### Fixed
17
+
18
+ * `__doc__` attribute is now copied from the original enum to the generated enum
19
+ * Python 3.12 type aliases and their typing_extensions backports are now supported (including `pydantic.JsonValue` and other `typing_extensions.TypeAliasType` instances)
20
+ * The bug when solve_dependencies error on the migration of a request to the latest version responds with a non-json serializable error and cadwyn showed a failed to serialize error instead of the actual error
21
+ * Updated minimum fastapi version to 0.112.4 because embed_body_fields was added in 0.112.4
22
+
8
23
  ## [5.1.2]
9
24
 
10
25
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cadwyn
3
- Version: 5.1.2
3
+ Version: 5.1.4
4
4
  Summary: Production-ready community-driven modern Stripe-like API versioning in FastAPI
5
5
  Project-URL: Source code, https://github.com/zmievsa/cadwyn
6
6
  Project-URL: Documentation, https://docs.cadwyn.dev
@@ -35,7 +35,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
35
35
  Classifier: Typing :: Typed
36
36
  Requires-Python: >=3.9
37
37
  Requires-Dist: backports-strenum<2,>=1.3.1; python_version < '3.11'
38
- Requires-Dist: fastapi>=0.112.3
38
+ Requires-Dist: fastapi>=0.112.4
39
39
  Requires-Dist: issubclass>=0.1.2
40
40
  Requires-Dist: jinja2>=3.1.2
41
41
  Requires-Dist: pydantic>=2.0.0
@@ -1,4 +1,3 @@
1
- import json
2
1
  from typing import Any
3
2
 
4
3
  from fastapi.routing import APIRoute
@@ -21,7 +20,7 @@ class CadwynHeadRequestValidationError(CadwynError):
21
20
  f"We failed to migrate the request with version={self.version}. "
22
21
  "This means that there is some error in your migrations or schema structure that makes it impossible "
23
22
  "to migrate the request of that version to latest.\n"
24
- f"body={self.body}\n\nerrors={json.dumps(self.errors, indent=4, ensure_ascii=False)}"
23
+ f"body={self.body}\n\nerrors={self.errors}"
25
24
  )
26
25
 
27
26
 
@@ -40,6 +40,7 @@ from typing_extensions import (
40
40
  NewType,
41
41
  Self,
42
42
  TypeAlias,
43
+ TypeAliasType,
43
44
  TypeVar,
44
45
  _AnnotatedAlias,
45
46
  assert_never,
@@ -100,6 +101,7 @@ PYDANTIC_DECORATOR_TYPE_TO_DECORATOR_MAP = {
100
101
  ModelSerializerDecoratorInfo: pydantic.model_serializer,
101
102
  ComputedFieldInfo: pydantic.computed_field,
102
103
  }
104
+ _PYDANTIC_ALL_EXPORTED_NAMES = set(pydantic.__all__)
103
105
 
104
106
 
105
107
  VALIDATOR_CONFIG_KEY = "__validators__"
@@ -580,6 +582,17 @@ class _AnnotationTransformer:
580
582
  def _change_version_of_a_non_container_annotation(self, annotation: Any) -> Any:
581
583
  if isinstance(annotation, (_BaseGenericAlias, types.GenericAlias)):
582
584
  return get_origin(annotation)[tuple(self.change_version_of_annotation(arg) for arg in get_args(annotation))]
585
+ elif isinstance(annotation, TypeAliasType):
586
+ if (
587
+ annotation.__module__ is not None and (annotation.__module__.startswith("pydantic."))
588
+ ) or annotation.__name__ in _PYDANTIC_ALL_EXPORTED_NAMES:
589
+ return annotation
590
+ else:
591
+ return TypeAliasType( # pyright: ignore[reportGeneralTypeIssues]
592
+ name=annotation.__name__,
593
+ value=self.change_version_of_annotation(annotation.__value__),
594
+ type_params=self.change_version_of_annotation(annotation.__type_params__),
595
+ )
583
596
  elif isinstance(annotation, fastapi.params.Security):
584
597
  return fastapi.params.Security(
585
598
  self.change_version_of_annotation(annotation.dependency),
@@ -1079,6 +1092,7 @@ class _EnumWrapper(Generic[_T_ENUM]):
1079
1092
  initialization_namespace = self._get_initialization_namespace_for_enum(self.cls) | raw_member_map
1080
1093
  for attr_name, attr in initialization_namespace.items():
1081
1094
  enum_dict[attr_name] = attr
1095
+ enum_dict["__doc__"] = self.cls.__doc__
1082
1096
  model_copy = cast(type[_T_ENUM], type(self.name, self.cls.__bases__, enum_dict))
1083
1097
  model_copy.__cadwyn_original_model__ = self.cls # pyright: ignore[reportAttributeAccessIssue]
1084
1098
  return model_copy
@@ -1,5 +1,6 @@
1
1
  import email.message
2
2
  import functools
3
+ import http
3
4
  import inspect
4
5
  import json
5
6
  from collections import defaultdict
@@ -8,7 +9,7 @@ from contextlib import AsyncExitStack
8
9
  from contextvars import ContextVar
9
10
  from datetime import date
10
11
  from enum import Enum
11
- from typing import TYPE_CHECKING, ClassVar, Union
12
+ from typing import TYPE_CHECKING, ClassVar, Union, cast
12
13
 
13
14
  from fastapi import BackgroundTasks, HTTPException, params
14
15
  from fastapi import Request as FastapiRequest
@@ -616,12 +617,13 @@ class VersionBundle:
616
617
  detail = response_info.body["detail"]
617
618
  else:
618
619
  detail = response_info.body
620
+ if detail is None:
621
+ detail = http.HTTPStatus(response_info.status_code).phrase
622
+ raised_exception.detail = cast(str, detail)
623
+ raised_exception.headers = dict(response_info.headers)
624
+ raised_exception.status_code = response_info.status_code
619
625
 
620
- raise HTTPException(
621
- status_code=response_info.status_code,
622
- detail=detail,
623
- headers=dict(response_info.headers),
624
- )
626
+ raise raised_exception
625
627
  return response_info._response
626
628
  return response_info.body
627
629
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cadwyn"
3
- version = "5.1.2"
3
+ version = "5.1.4"
4
4
  description = "Production-ready community-driven modern Stripe-like API versioning in FastAPI"
5
5
  authors = [{ name = "Stanislav Zmiev", email = "zmievsa@gmail.com" }]
6
6
  license = "MIT"
@@ -51,7 +51,7 @@ classifiers = [
51
51
  "Topic :: Internet :: WWW/HTTP",
52
52
  ]
53
53
  dependencies = [
54
- "fastapi >=0.112.3",
54
+ "fastapi >=0.112.4",
55
55
  "starlette >=0.30.0",
56
56
  "pydantic >=2.0.0",
57
57
  "jinja2 >=3.1.2",
@@ -73,7 +73,8 @@ dev-dependencies = [
73
73
  "svcs ~=24.1.0",
74
74
  "httpx >=0.26.0",
75
75
  "pytest-fixture-classes >=1.0.3",
76
- "pytest >=7.2.1",
76
+ # pytest==8.3.5 has a bug that fails our windows python 3.10 tests
77
+ "pytest==8.3.4",
77
78
  "pytest-cov >=4.0.0",
78
79
  "dirty-equals >=0.6.0",
79
80
  "uvicorn ~=0.23.0",
@@ -3,7 +3,7 @@ import re
3
3
  from collections.abc import Callable, Coroutine
4
4
  from contextvars import ContextVar
5
5
  from io import StringIO
6
- from typing import Any, Literal, Union
6
+ from typing import Any, Literal, Optional, Union
7
7
 
8
8
  import fastapi
9
9
  import pytest
@@ -11,6 +11,7 @@ from dirty_equals import IsPartialDict, IsStr
11
11
  from fastapi import APIRouter, Body, Cookie, File, Header, HTTPException, Query, Request, Response, UploadFile
12
12
  from fastapi.responses import JSONResponse
13
13
  from fastapi.routing import APIRoute
14
+ from fastapi.testclient import TestClient
14
15
  from pydantic import BaseModel, Field, RootModel
15
16
  from starlette.responses import StreamingResponse
16
17
 
@@ -32,6 +33,7 @@ from cadwyn.structure.data import RequestInfo, ResponseInfo
32
33
  from cadwyn.structure.schemas import schema
33
34
  from cadwyn.structure.versions import Version, VersionBundle
34
35
  from tests.conftest import (
36
+ CreateVersionedApp,
35
37
  CreateVersionedClients,
36
38
  client,
37
39
  version_change,
@@ -1226,3 +1228,41 @@ def test__request_and_response_migrations__with_multiple_schemas_in_converters(
1226
1228
  resp_2001 = client_2001.post(f"/{endpoint}", json={"i": ["original_request"]})
1227
1229
  assert resp_2001.status_code == 200
1228
1230
  assert resp_2001.json() == {"i": ["original_request", endpoint]}
1231
+
1232
+
1233
+ def test__response_migrations__with_custom_http_exception(
1234
+ create_versioned_app: CreateVersionedApp,
1235
+ router: VersionedAPIRouter,
1236
+ ) -> None:
1237
+ class CustomHTTPException(HTTPException):
1238
+ error_code: Optional[str] = None
1239
+
1240
+ def __init__(self, detail: str, error_code: Optional[str] = None):
1241
+ self.error_code = error_code
1242
+ super().__init__(status_code=400, detail=detail)
1243
+
1244
+ def http_exception_handler(request, exc):
1245
+ # Check if the exception has an error_code attribute
1246
+ error_code = exc.error_code if hasattr(exc, "error_code") else "generic_error"
1247
+
1248
+ return JSONResponse(
1249
+ status_code=exc.status_code,
1250
+ content={"code": error_code, "message": exc.detail},
1251
+ )
1252
+
1253
+ # Register exception handler for Cadwyn
1254
+
1255
+ @router.post("/test")
1256
+ async def endpoint():
1257
+ raise CustomHTTPException("Cadwyn error occurred", error_code="cadwyn_error")
1258
+
1259
+ app = create_versioned_app(version_change())
1260
+ app.add_exception_handler(HTTPException, http_exception_handler)
1261
+ with TestClient(app) as client:
1262
+ resp = client.post("/test", headers={"X-API-VERSION": "2000-01-01"})
1263
+ assert resp.status_code == 400
1264
+ assert resp.json() == {"code": "cadwyn_error", "message": "Cadwyn error occurred"}
1265
+
1266
+ resp_2001 = client.post("/test", headers={"X-API-VERSION": "2001-01-01"})
1267
+ assert resp_2001.status_code == 400
1268
+ assert resp_2001.json() == {"code": "cadwyn_error", "message": "Cadwyn error occurred"}
@@ -14,10 +14,10 @@ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
14
14
  from fastapi.security.http import HTTPBasic
15
15
  from fastapi.testclient import TestClient
16
16
  from inline_snapshot import snapshot
17
- from pydantic import BaseModel
17
+ from pydantic import BaseModel, JsonValue
18
18
  from pytest_fixture_classes import fixture_class
19
19
  from starlette.responses import FileResponse
20
- from typing_extensions import Any, NewType, TypeAlias, get_args
20
+ from typing_extensions import Any, NewType, TypeAlias, TypeAliasType, get_args
21
21
 
22
22
  from cadwyn import VersionBundle, VersionedAPIRouter
23
23
  from cadwyn.dependencies import current_dependency_solver
@@ -800,7 +800,7 @@ def test__router_generation__using_unversioned_models(
800
800
  assert routes_2001[3].dependant.body_params[0].type_ is schemas["2001-01-01"][UnversionedSchema3]
801
801
 
802
802
 
803
- def test__router_generation__using_weird_typehints(
803
+ def test__router_generation__using_newtype_and_union_typehints(
804
804
  router: VersionedAPIRouter,
805
805
  create_versioned_api_routes: CreateVersionedAPIRoutes,
806
806
  ):
@@ -822,7 +822,7 @@ def test__router_generation__using_weird_typehints(
822
822
  assert getattr(routes_2001[1].dependant.body_params[1], TYPE_ATTR) == Union[str, int]
823
823
 
824
824
 
825
- def test__router_generation__using_pydantic_typehints__internal_pydantic_typehints_should_work(
825
+ def test__router_generation__using_uploadfile_typehint(
826
826
  router: VersionedAPIRouter,
827
827
  create_versioned_api_routes: CreateVersionedAPIRoutes,
828
828
  ):
@@ -837,6 +837,54 @@ def test__router_generation__using_pydantic_typehints__internal_pydantic_typehin
837
837
  # We are intentionally not checking anything here. Our goal is to validate that there is no exception
838
838
 
839
839
 
840
+ def test__router_generation__using_jsonvalue_typehint(
841
+ router: VersionedAPIRouter,
842
+ create_versioned_clients: CreateVersionedClients,
843
+ ):
844
+ @router.post("/test")
845
+ async def test(param1: JsonValue = Body()):
846
+ return param1
847
+
848
+ clients = create_versioned_clients(
849
+ version_change(endpoint("/test", ["POST"]).had(response_model=dict[str, str])),
850
+ )
851
+ assert clients["2000-01-01"].post("/test", json={"foo": "bar"}).json() == {"foo": "bar"}
852
+ assert clients["2001-01-01"].post("/test", json={"foo": "bar"}).json() == {"foo": "bar"}
853
+
854
+
855
+ def test__router_generation__using_typealias_type_typehint(
856
+ router: VersionedAPIRouter,
857
+ create_versioned_clients: CreateVersionedClients,
858
+ ):
859
+ class MySchema(BaseModel):
860
+ foo: str
861
+
862
+ typealias = TypeAliasType("typealias", list[MySchema]) # pyright: ignore[reportGeneralTypeIssues]
863
+
864
+ @router.post("/test")
865
+ async def test(param1: typealias = Body()):
866
+ return param1
867
+
868
+ clients = create_versioned_clients(
869
+ version_change(schema(MySchema).field("foo").had(name="baz")),
870
+ )
871
+ assert clients["2000-01-01"].post("/test", json=[{"foo": "bar"}]).json() == snapshot(
872
+ {
873
+ "detail": [
874
+ {
875
+ "type": "missing",
876
+ "loc": ["body", 0, "baz"],
877
+ "msg": "Field required",
878
+ "input": {
879
+ "foo": "bar",
880
+ },
881
+ }
882
+ ]
883
+ }
884
+ )
885
+ assert clients["2001-01-01"].post("/test", json=[{"foo": "bar"}]).json() == [{"foo": "bar"}]
886
+
887
+
840
888
  def test__router_generation__updating_request_depends(
841
889
  router: VersionedAPIRouter, create_versioned_app: CreateVersionedApp
842
890
  ):
@@ -11,7 +11,7 @@ from tests.conftest import CreateRuntimeSchemas, serialize_enum, version_change
11
11
 
12
12
 
13
13
  class EmptyEnum(Enum):
14
- pass
14
+ """My docstring"""
15
15
 
16
16
 
17
17
  class EnumWithOneMember(Enum):
@@ -34,6 +34,12 @@ class EnumWithOneMemberAndMethods(Enum):
34
34
  return cls.world_member # pyright: ignore[reportAttributeAccessIssue]
35
35
 
36
36
 
37
+ def test__enum_had__should_copy_docstring_too(create_runtime_schemas: CreateRuntimeSchemas):
38
+ models = create_runtime_schemas(version_change(enum(EmptyEnum).had(b=auto())))
39
+ assert models["2000-01-01"][EmptyEnum].__doc__ == "My docstring"
40
+ assert models["2001-01-01"][EmptyEnum].__doc__ == "My docstring"
41
+
42
+
37
43
  def test__enum_had__original_enum_is_empty(create_runtime_schemas: CreateRuntimeSchemas):
38
44
  models = create_runtime_schemas(version_change(enum(EmptyEnum).had(b=auto())))
39
45
  assert serialize_enum(models["2000-01-01"][EmptyEnum]) == {"b": 1}