cadwyn 5.1.0a1__tar.gz → 5.1.2__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.0a1 → cadwyn-5.1.2}/CHANGELOG.md +16 -0
  2. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/PKG-INFO +1 -1
  3. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/__init__.py +2 -0
  4. cadwyn-5.1.2/cadwyn/dependencies.py +5 -0
  5. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/schema_generation.py +32 -4
  6. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/structure/versions.py +9 -1
  7. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/pyproject.toml +4 -1
  8. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_router_generation.py +56 -0
  9. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_router_generation_with_from_future_annotations.py +7 -3
  10. cadwyn-5.1.2/tests/test_schema_generation/test_schema_with_future_annotations.py +38 -0
  11. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/uv.lock +18 -1
  12. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.github/CODE_OF_CONDUCT.md +0 -0
  13. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  14. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  15. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.github/actions/setup-python-uv/action.yaml +0 -0
  16. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.github/workflows/ci.yaml +0 -0
  17. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.github/workflows/daily_tests.yaml +0 -0
  18. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.github/workflows/publish_docs.yaml +0 -0
  19. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.github/workflows/release.yaml +0 -0
  20. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.gitignore +0 -0
  21. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.pre-commit-config.yaml +0 -0
  22. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/LICENSE +0 -0
  23. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/Makefile +0 -0
  24. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/README.md +0 -0
  25. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/__main__.py +0 -0
  26. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/_asts.py +0 -0
  27. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/_importer.py +0 -0
  28. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/_render.py +0 -0
  29. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/_utils.py +0 -0
  30. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/applications.py +0 -0
  31. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/changelogs.py +0 -0
  32. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/exceptions.py +0 -0
  33. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/middleware.py +0 -0
  34. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/py.typed +0 -0
  35. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/route_generation.py +0 -0
  36. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/routing.py +0 -0
  37. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/static/__init__.py +0 -0
  38. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/static/docs.html +0 -0
  39. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/structure/__init__.py +0 -0
  40. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/structure/common.py +0 -0
  41. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/structure/data.py +0 -0
  42. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/structure/endpoints.py +0 -0
  43. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/structure/enums.py +0 -0
  44. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/structure/schemas.py +0 -0
  45. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/CNAME +0 -0
  46. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/__init__.py +0 -0
  47. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/api_version_parameter_and_context_variables.md +0 -0
  48. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/beware_of_data_versioning.md +0 -0
  49. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/changelogs.md +0 -0
  50. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/cli.md +0 -0
  51. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/endpoint_migrations.md +0 -0
  52. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/enum_migrations.md +0 -0
  53. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/index.md +0 -0
  54. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/main_app.md +0 -0
  55. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/methodology.md +0 -0
  56. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/schema_generation.md +0 -0
  57. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/schema_migrations.md +0 -0
  58. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/testing.md +0 -0
  59. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/version_changes.md +0 -0
  60. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/where_to_put_the_version_and_how_to_format_it.md +0 -0
  61. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/home/CONTRIBUTING.md +0 -0
  62. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/change_business_logic/index.md +0 -0
  63. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/change_endpoints/index.md +0 -0
  64. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/change_openapi_schemas/add_field.md +0 -0
  65. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/change_openapi_schemas/change_field_type.md +0 -0
  66. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/change_openapi_schemas/change_schema_without_endpoint.md +0 -0
  67. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/change_openapi_schemas/changing_constraints.md +0 -0
  68. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/change_openapi_schemas/remove_field.md +0 -0
  69. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/change_openapi_schemas/rename_a_field_in_schema.md +0 -0
  70. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/index.md +0 -0
  71. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/version_with_paths_and_numbers_instead_of_headers_and_dates.md +0 -0
  72. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/img/dashboard_with_one_version.png +0 -0
  73. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/img/dashboard_with_two_versions.png +0 -0
  74. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/img/get_users_endpoint_from_prior_version.png +0 -0
  75. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/img/simplified_migration_model.png +0 -0
  76. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/img/sponsor_logos/monite.png +0 -0
  77. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/img/unversioned_dashboard.png +0 -0
  78. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/index.md +0 -0
  79. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/plugin.py +0 -0
  80. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/quickstart/setup.md +0 -0
  81. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/quickstart/tutorial.md +0 -0
  82. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/theory/how_to_build_versioning_framework.md +0 -0
  83. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/theory/how_we_got_here.md +0 -0
  84. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/theory/literature.md +0 -0
  85. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/__init__.py +0 -0
  86. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/__init__.py +0 -0
  87. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/change_openapi_schemas/__init__.py +0 -0
  88. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/__init__.py +0 -0
  89. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block001.py +0 -0
  90. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block002.py +0 -0
  91. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/__init__.py +0 -0
  92. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block001.py +0 -0
  93. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block002.py +0 -0
  94. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/__init__.py +0 -0
  95. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/block001.py +0 -0
  96. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/__init__.py +0 -0
  97. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/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.0a1 → cadwyn-5.1.2}/docs_src/quickstart/__init__.py +0 -0
  99. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/setup/__init__.py +0 -0
  100. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/setup/block001.sh +0 -0
  101. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/setup/block002.py +0 -0
  102. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/setup/tests/__init__.py +0 -0
  103. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/setup/tests/test_block002.py +0 -0
  104. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/tutorial/__init__.py +0 -0
  105. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/tutorial/block001.py +0 -0
  106. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/tutorial/block002.py +0 -0
  107. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/tutorial/block003.py +0 -0
  108. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/tutorial/tests/__init__.py +0 -0
  109. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/tutorial/tests/test_block001.py +0 -0
  110. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/tutorial/tests/test_block002.py +0 -0
  111. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/tutorial/tests/test_block003.py +0 -0
  112. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/ruff.toml +0 -0
  113. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/mkdocs.yml +0 -0
  114. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/ruff.toml +0 -0
  115. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/scripts/fix_links.py +0 -0
  116. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/scripts/split_md.py +0 -0
  117. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/__init__.py +0 -0
  118. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_data/__init__.py +0 -0
  119. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_data/unversioned_schema_dir/__init__.py +0 -0
  120. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_data/unversioned_schema_dir/unversioned_schemas.py +0 -0
  121. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_data/unversioned_schemas.py +0 -0
  122. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/__init__.py +0 -0
  123. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/app_for_testing_routing.py +0 -0
  124. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/render/__init__.py +0 -0
  125. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/render/classes.py +0 -0
  126. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/render/complex/__init__.py +0 -0
  127. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/render/complex/classes.py +0 -0
  128. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/render/complex/versions.py +0 -0
  129. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/render/versions.py +0 -0
  130. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/utils.py +0 -0
  131. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/versioned_app/__init__.py +0 -0
  132. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/versioned_app/app.py +0 -0
  133. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/versioned_app/v2021_01_01.py +0 -0
  134. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/versioned_app/v2022_01_02.py +0 -0
  135. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/versioned_app/webhooks.py +0 -0
  136. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/conftest.py +0 -0
  137. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_applications.py +0 -0
  138. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_auth_dependencies.py +0 -0
  139. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_changelog.py +0 -0
  140. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_cli.py +0 -0
  141. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_data_migrations.py +0 -0
  142. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_render.py +0 -0
  143. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_routing.py +0 -0
  144. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_schema_generation/__init__.py +0 -0
  145. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_schema_generation/test_enum.py +0 -0
  146. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_schema_generation/test_schema.py +0 -0
  147. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_schema_generation/test_schema_field.py +0 -0
  148. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_schema_generation/test_schema_validator.py +0 -0
  149. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_structure.py +0 -0
  150. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/tutorial/__init__.py +0 -0
  151. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/tutorial/main.py +0 -0
  152. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/tutorial/test_example.py +0 -0
  153. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/versioning_styles/__init__.py +0 -0
  154. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/versioning_styles/test_versioning_formats.py +0 -0
  155. {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tox.ini +0 -0
@@ -5,6 +5,22 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [5.1.2]
9
+
10
+ ### Fixed
11
+
12
+ * Generators not being called when fastapi validates the initial request
13
+
14
+ ### Added
15
+
16
+ * `cadwyn.current_dependency_solver` function to check whether cadwyn or fastapi is currently solving dependencies. It can be used as a dependency for versioned endpoints like so: `current_dependency_solver: Annotated[Literal["fastapi", "cadwyn"], Depends(current_dependency_solver)]`. If your dependency has side effects, you would likely want to only run it once per request. If you want to run it once we migrated the request to the latest version, you should only run it when `current_dependency_solver` is `"cadwyn"`. If you want your dependency to run at the very beginning of handling the request, you should only run it when `current_dependency_solver` is `"fastapi"`.
17
+
18
+ ## [5.1.1]
19
+
20
+ ### Fixed
21
+
22
+ * Support for `WithJsonSchema` in schema generation
23
+
8
24
  ## [5.1.0]
9
25
 
10
26
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cadwyn
3
- Version: 5.1.0a1
3
+ Version: 5.1.2
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
@@ -2,6 +2,7 @@ import importlib.metadata
2
2
 
3
3
  from .applications import Cadwyn
4
4
  from .changelogs import hidden
5
+ from .dependencies import current_dependency_solver
5
6
  from .route_generation import VersionedAPIRouter, generate_versioned_routers
6
7
  from .schema_generation import generate_versioned_models, migrate_response_body
7
8
  from .structure import (
@@ -32,6 +33,7 @@ __all__ = [
32
33
  "VersionedAPIRouter",
33
34
  "convert_request_to_next_version_for",
34
35
  "convert_response_to_previous_version_for",
36
+ "current_dependency_solver",
35
37
  "endpoint",
36
38
  "enum",
37
39
  "generate_versioned_models",
@@ -0,0 +1,5 @@
1
+ from cadwyn.structure.versions import _CURRENT_DEPENDENCY_SOLVER_OPTIONS, _CURRENT_DEPENDENCY_SOLVER_VAR
2
+
3
+
4
+ async def current_dependency_solver() -> _CURRENT_DEPENDENCY_SOLVER_OPTIONS:
5
+ return _CURRENT_DEPENDENCY_SOLVER_VAR.get("fastapi")
@@ -20,6 +20,7 @@ import pydantic
20
20
  import pydantic._internal._decorators
21
21
  import typing_extensions
22
22
  from fastapi import Response
23
+ from fastapi.dependencies.utils import is_async_gen_callable, is_coroutine_callable, is_gen_callable
23
24
  from fastapi.routing import APIRoute
24
25
  from pydantic import BaseModel, Field, RootModel
25
26
  from pydantic._internal import _decorators
@@ -77,6 +78,7 @@ from cadwyn.structure.versions import _CADWYN_REQUEST_PARAM_NAME, _CADWYN_RESPON
77
78
  if TYPE_CHECKING:
78
79
  from cadwyn.structure.versions import HeadVersion, Version, VersionBundle
79
80
 
81
+
80
82
  if sys.version_info >= (3, 10):
81
83
  from typing import _BaseGenericAlias # pyright: ignore[reportAttributeAccessIssue]
82
84
  else:
@@ -264,10 +266,20 @@ def _wrap_pydantic_model(model: type[_T_PYDANTIC_MODEL]) -> "_PydanticModelWrapp
264
266
 
265
267
  wrapped_validator = _wrap_validator(decorator_wrapper.func, decorator_wrapper.shim, decorator_wrapper.info)
266
268
  validators[decorator_wrapper.cls_var_name] = wrapped_validator
269
+
270
+ def _rebuild_annotated(name: str):
271
+ if field_info := model.model_fields.get(name):
272
+ if not field_info.metadata:
273
+ return field_info.annotation
274
+
275
+ if sys.version_info >= (3, 13):
276
+ return Annotated.__getitem__((field_info.annotation, *field_info.metadata)) # pyright: ignore[reportAttributeAccessIssue]
277
+ else:
278
+ return Annotated.__class_getitem__((field_info.annotation, *field_info.metadata)) # pyright: ignore[reportAttributeAccessIssue]
279
+ return model.__annotations__[name] # pragma: no cover
280
+
267
281
  annotations = {
268
- name: value
269
- if not isinstance(value, str)
270
- else model.model_fields[name].annotation or model.__annotations__[name]
282
+ name: value if not isinstance(value, str) else _rebuild_annotated(name)
271
283
  for name, value in model.__annotations__.items()
272
284
  }
273
285
 
@@ -476,6 +488,7 @@ class _CallableWrapper:
476
488
  self._original_callable = original_callable
477
489
  if not is_regular_function(original_callable):
478
490
  original_callable = original_callable.__call__
491
+
479
492
  functools.update_wrapper(self, original_callable)
480
493
 
481
494
  @property
@@ -500,6 +513,17 @@ class _AsyncCallableWrapper(_CallableWrapper):
500
513
  return await self._original_callable(*args, **kwargs)
501
514
 
502
515
 
516
+ class _GeneratorCallableWrapper(_CallableWrapper):
517
+ def __call__(self, *args: Any, **kwargs: Any):
518
+ yield from self._original_callable(*args, **kwargs)
519
+
520
+
521
+ class _AsyncGeneratorCallableWrapper(_CallableWrapper):
522
+ async def __call__(self, *args: Any, **kwargs: Any):
523
+ async for value in self._original_callable(*args, **kwargs):
524
+ yield value
525
+
526
+
503
527
  @final
504
528
  class _AnnotationTransformer:
505
529
  def __init__(self, generator: "SchemaGenerator") -> None:
@@ -680,8 +704,12 @@ class _AnnotationTransformer:
680
704
  actual_call = call.__call__
681
705
  else:
682
706
  actual_call = call
683
- if inspect.iscoroutinefunction(actual_call):
707
+ if is_async_gen_callable(actual_call):
708
+ return _AsyncGeneratorCallableWrapper(call)
709
+ elif is_coroutine_callable(actual_call):
684
710
  return _AsyncCallableWrapper(call)
711
+ elif is_gen_callable(actual_call):
712
+ return _GeneratorCallableWrapper(call)
685
713
  else:
686
714
  return _CallableWrapper(call)
687
715
 
@@ -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 Any, ParamSpec, TypeAlias, TypeVar, assert_never, deprecated, get_args
26
+ from typing_extensions import Any, Literal, ParamSpec, TypeAlias, TypeVar, assert_never, deprecated, get_args
27
27
 
28
28
  from cadwyn._utils import classproperty
29
29
  from cadwyn.exceptions import (
@@ -51,6 +51,11 @@ _CADWYN_REQUEST_PARAM_NAME = "cadwyn_request_param"
51
51
  _CADWYN_RESPONSE_PARAM_NAME = "cadwyn_response_param"
52
52
  _P = ParamSpec("_P")
53
53
  _R = TypeVar("_R")
54
+ _CURRENT_DEPENDENCY_SOLVER_OPTIONS = Literal["cadwyn", "fastapi"]
55
+ _CURRENT_DEPENDENCY_SOLVER_VAR: ContextVar[_CURRENT_DEPENDENCY_SOLVER_OPTIONS] = ContextVar(
56
+ "cadwyn_dependencies_dry_run"
57
+ )
58
+
54
59
  PossibleInstructions: TypeAlias = Union[
55
60
  AlterSchemaSubInstruction, AlterEndpointSubInstruction, AlterEnumSubInstruction, SchemaHadInstruction, staticmethod
56
61
  ]
@@ -276,6 +281,7 @@ class VersionBundle:
276
281
 
277
282
  if api_version_var is None:
278
283
  api_version_var = ContextVar("cadwyn_api_version")
284
+
279
285
  self.version_values = tuple(version.value for version in self.versions)
280
286
  self.reversed_version_values = tuple(reversed(self.version_values))
281
287
  self.api_version_var = api_version_var
@@ -379,6 +385,8 @@ class VersionBundle:
379
385
  instruction(request_info)
380
386
  request.scope["headers"] = tuple((key.encode(), value.encode()) for key, value in request_info.headers.items())
381
387
  del request._headers
388
+ # This gives us the ability to tell the user whether cadwyn is running its dependencies or FastAPI
389
+ _CURRENT_DEPENDENCY_SOLVER_VAR.set("cadwyn")
382
390
  # Remember this: if len(body_params) == 1, then route.body_schema == route.dependant.body_params[0]
383
391
  result = await solve_dependencies(
384
392
  request=request,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cadwyn"
3
- version = "5.1.0a1"
3
+ version = "5.1.2"
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"
@@ -87,6 +87,7 @@ dev-dependencies = [
87
87
  "mike >=2.1.2, <3",
88
88
  "pdbpp>=0.10.3",
89
89
  "markdown-include-variants>=0.0.4",
90
+ "inline-snapshot>=0.20.7",
90
91
  ]
91
92
 
92
93
  [project.urls]
@@ -97,6 +98,8 @@ Documentation = "https://docs.cadwyn.dev"
97
98
  [project.scripts]
98
99
  cadwyn = "cadwyn.__main__:app"
99
100
 
101
+ [tool.inline-snapshot]
102
+ format-command = "ruff format --stdin-filename {filename}"
100
103
 
101
104
  [tool.coverage.run]
102
105
  parallel = true
@@ -13,12 +13,14 @@ from fastapi.routing import APIRoute
13
13
  from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
14
14
  from fastapi.security.http import HTTPBasic
15
15
  from fastapi.testclient import TestClient
16
+ from inline_snapshot import snapshot
16
17
  from pydantic import BaseModel
17
18
  from pytest_fixture_classes import fixture_class
18
19
  from starlette.responses import FileResponse
19
20
  from typing_extensions import Any, NewType, TypeAlias, get_args
20
21
 
21
22
  from cadwyn import VersionBundle, VersionedAPIRouter
23
+ from cadwyn.dependencies import current_dependency_solver
22
24
  from cadwyn.exceptions import CadwynError, RouterGenerationError, RouterPathParamsModifiedError
23
25
  from cadwyn.route_generation import generate_versioned_routers
24
26
  from cadwyn.schema_generation import generate_versioned_models
@@ -1205,6 +1207,60 @@ def test__basic_router_generation__subclass_of_security_class_based_dependency_w
1205
1207
  ]
1206
1208
 
1207
1209
 
1210
+ def test__router_generation__with_generator_dependencies(
1211
+ router: VersionedAPIRouter,
1212
+ create_versioned_app: CreateVersionedApp,
1213
+ ):
1214
+ dependency_cache = []
1215
+
1216
+ async def my_async_dependency(current_dependency_runner: Annotated[str, Depends(current_dependency_solver)]):
1217
+ dependency_cache.append(f"{current_dependency_runner} async dependency start")
1218
+ yield "async dependency"
1219
+ dependency_cache.append(f"{current_dependency_runner} async dependency end")
1220
+
1221
+ def my_sync_dependency(current_dependency_runner: Annotated[str, Depends(current_dependency_solver)]):
1222
+ dependency_cache.append(f"{current_dependency_runner} sync dependency start")
1223
+ yield "sync dependency"
1224
+ dependency_cache.append(f"{current_dependency_runner} sync dependency end")
1225
+
1226
+ @router.get("/test")
1227
+ async def test(
1228
+ my_async_dep: Annotated[str, Depends(my_async_dependency)],
1229
+ my_sync_dep: Annotated[str, Depends(my_sync_dependency)],
1230
+ ):
1231
+ assert my_async_dep == "async dependency"
1232
+ assert my_sync_dep == "sync dependency"
1233
+
1234
+ client = TestClient(create_versioned_app(version_change()))
1235
+ assert client.get("/test", headers={"x-api-version": "2000-01-01"}).status_code == 200
1236
+ assert dependency_cache == snapshot(
1237
+ [
1238
+ "fastapi async dependency start",
1239
+ "fastapi sync dependency start",
1240
+ "cadwyn async dependency start",
1241
+ "cadwyn sync dependency start",
1242
+ "cadwyn sync dependency end",
1243
+ "cadwyn async dependency end",
1244
+ "fastapi sync dependency end",
1245
+ "fastapi async dependency end",
1246
+ ]
1247
+ )
1248
+ dependency_cache.clear()
1249
+ assert client.get("/test", headers={"x-api-version": "2001-01-01"}).status_code == 200
1250
+ assert dependency_cache == snapshot(
1251
+ [
1252
+ "fastapi async dependency start",
1253
+ "fastapi sync dependency start",
1254
+ "cadwyn async dependency start",
1255
+ "cadwyn sync dependency start",
1256
+ "cadwyn sync dependency end",
1257
+ "cadwyn async dependency end",
1258
+ "fastapi sync dependency end",
1259
+ "fastapi async dependency end",
1260
+ ]
1261
+ )
1262
+
1263
+
1208
1264
  ######################
1209
1265
  # External lib testing
1210
1266
  ######################
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import Annotated
4
+
3
5
  from fastapi.testclient import TestClient
4
- from pydantic import BaseModel, Field
6
+ from pydantic import BaseModel, Field, WithJsonSchema
5
7
 
6
8
  from cadwyn.applications import Cadwyn
7
9
  from cadwyn.route_generation import VersionedAPIRouter
@@ -12,6 +14,8 @@ from cadwyn.structure.versions import Version, VersionBundle, VersionChange
12
14
  class OuterSchema(BaseModel):
13
15
  bar: MySchema
14
16
 
17
+ extra_annotated: Annotated[str, WithJsonSchema({"type": "string", "description": "Hello"})] = ""
18
+
15
19
 
16
20
  class MySchema(BaseModel):
17
21
  foo: str = Field(coerce_numbers_to_str=True)
@@ -56,7 +60,7 @@ def test__router_generation__using_forwardref_outer_global_schema_in_body():
56
60
  unversioned_client = TestClient(app)
57
61
  client_2000 = TestClient(app, headers={app.router.api_version_parameter_name: "2000-01-01"})
58
62
  client_2001 = TestClient(app, headers={app.router.api_version_parameter_name: "2001-01-01"})
59
- assert client_2000.post("/test2", json={"bar": {"foo": 1}}).json() == {"bar": {"foo": 1}}
60
- assert client_2001.post("/test2", json={"bar": {"foo": 1}}).json() == {"bar": {"foo": "1"}}
63
+ assert client_2000.post("/test2", json={"bar": {"foo": 1}}).json() == {"bar": {"foo": 1}, "extra_annotated": ""}
64
+ assert client_2001.post("/test2", json={"bar": {"foo": 1}}).json() == {"bar": {"foo": "1"}, "extra_annotated": ""}
61
65
  assert unversioned_client.get("/openapi.json?version=2000-01-01").status_code == 200
62
66
  assert unversioned_client.get("/openapi.json?version=2001-01-01").status_code == 200
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import Annotated
5
+
6
+ import pytest
7
+ from pydantic import BaseModel, WithJsonSchema
8
+ from typing_extensions import TypeAlias
9
+
10
+ from cadwyn.exceptions import InvalidGenerationInstructionError
11
+ from cadwyn.structure.schemas import schema
12
+ from tests.conftest import CreateRuntimeSchemas, assert_models_are_equal, version_change
13
+
14
+ Foo: TypeAlias = int
15
+
16
+
17
+ class MySchema(BaseModel):
18
+ foo: Foo
19
+ bar: Annotated[str, WithJsonSchema({"type": "string", "description": "Hi"})]
20
+
21
+
22
+ def test__schema_had_name(create_runtime_schemas: CreateRuntimeSchemas):
23
+ schemas = create_runtime_schemas(version_change(schema(MySchema).had(name="Aww")))
24
+
25
+ assert_models_are_equal(schemas["2000-01-01"][MySchema], MySchema)
26
+ assert schemas["2000-01-01"][MySchema].__name__ == "Aww"
27
+ assert schemas["2000-01-01"][MySchema].__qualname__ == "Aww"
28
+
29
+
30
+ def test__schema_had_name__with_the_same_name__should_raise_error(create_runtime_schemas: CreateRuntimeSchemas):
31
+ with pytest.raises(
32
+ InvalidGenerationInstructionError,
33
+ match=re.escape(
34
+ 'You tried to change the name of "MySchema" in "MyVersionChange" '
35
+ "but it already has the name you tried to assign."
36
+ ),
37
+ ):
38
+ create_runtime_schemas(version_change(schema(MySchema).had(name="MySchema")))
@@ -94,7 +94,7 @@ wheels = [
94
94
 
95
95
  [[package]]
96
96
  name = "cadwyn"
97
- version = "5.1.0a1"
97
+ version = "5.1.2"
98
98
  source = { editable = "." }
99
99
  dependencies = [
100
100
  { name = "backports-strenum", marker = "python_full_version < '3.11'" },
@@ -117,6 +117,7 @@ dev = [
117
117
  { name = "better-devtools" },
118
118
  { name = "dirty-equals" },
119
119
  { name = "httpx" },
120
+ { name = "inline-snapshot" },
120
121
  { name = "markdown-include-variants" },
121
122
  { name = "mdx-include" },
122
123
  { name = "mike" },
@@ -153,6 +154,7 @@ dev = [
153
154
  { name = "better-devtools", specifier = "~=0.13.3" },
154
155
  { name = "dirty-equals", specifier = ">=0.6.0" },
155
156
  { name = "httpx", specifier = ">=0.26.0" },
157
+ { name = "inline-snapshot", specifier = ">=0.20.7" },
156
158
  { name = "markdown-include-variants", specifier = ">=0.0.4" },
157
159
  { name = "mdx-include", specifier = "~=1.4.2" },
158
160
  { name = "mike", specifier = ">=2.1.2,<3" },
@@ -597,6 +599,21 @@ wheels = [
597
599
  { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
598
600
  ]
599
601
 
602
+ [[package]]
603
+ name = "inline-snapshot"
604
+ version = "0.20.7"
605
+ source = { registry = "https://pypi.org/simple" }
606
+ dependencies = [
607
+ { name = "asttokens" },
608
+ { name = "executing" },
609
+ { name = "rich" },
610
+ { name = "tomli", marker = "python_full_version < '3.11'" },
611
+ ]
612
+ sdist = { url = "https://files.pythonhosted.org/packages/b0/41/9bd2ecd10ef789e8aff6fb68dcc7677dc31b33b2d27c306c0d40fc982fbc/inline_snapshot-0.20.7.tar.gz", hash = "sha256:d55bbb6254d0727dc304729ca7998cde1c1e984c4bf50281514aa9d727a56cf2", size = 92643 }
613
+ wheels = [
614
+ { url = "https://files.pythonhosted.org/packages/01/8f/1bf23da63ad1a0b14ca2d9114700123ef76732e375548f4f9ca94052817e/inline_snapshot-0.20.7-py3-none-any.whl", hash = "sha256:2df6dd8710d1f0def2c1f9d6c25fd03d7beba01f3addf52fc370343d9ee9959f", size = 48108 },
615
+ ]
616
+
600
617
  [[package]]
601
618
  name = "issubclass"
602
619
  version = "0.1.2"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes