cadwyn 5.2.0__tar.gz → 5.2.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 (158) hide show
  1. {cadwyn-5.2.0 → cadwyn-5.2.2}/CHANGELOG.md +12 -0
  2. {cadwyn-5.2.0 → cadwyn-5.2.2}/PKG-INFO +1 -1
  3. cadwyn-5.2.2/cadwyn/_internal/context_vars.py +9 -0
  4. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/applications.py +7 -1
  5. cadwyn-5.2.2/cadwyn/dependencies.py +5 -0
  6. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/middleware.py +6 -1
  7. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/route_generation.py +59 -25
  8. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/routing.py +10 -1
  9. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/schema_generation.py +1 -2
  10. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/structure/versions.py +19 -23
  11. {cadwyn-5.2.0 → cadwyn-5.2.2}/pyproject.toml +1 -1
  12. {cadwyn-5.2.0 → cadwyn-5.2.2}/ruff.toml +1 -1
  13. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_applications.py +13 -0
  14. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_router_generation.py +46 -0
  15. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_routing.py +54 -1
  16. cadwyn-5.2.2/tests/versioning_styles/__init__.py +0 -0
  17. {cadwyn-5.2.0 → cadwyn-5.2.2}/uv.lock +1 -1
  18. cadwyn-5.2.0/cadwyn/dependencies.py +0 -5
  19. {cadwyn-5.2.0 → cadwyn-5.2.2}/.github/CODE_OF_CONDUCT.md +0 -0
  20. {cadwyn-5.2.0 → cadwyn-5.2.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  21. {cadwyn-5.2.0 → cadwyn-5.2.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  22. {cadwyn-5.2.0 → cadwyn-5.2.2}/.github/actions/setup-python-uv/action.yaml +0 -0
  23. {cadwyn-5.2.0 → cadwyn-5.2.2}/.github/workflows/ci.yaml +0 -0
  24. {cadwyn-5.2.0 → cadwyn-5.2.2}/.github/workflows/daily_tests.yaml +0 -0
  25. {cadwyn-5.2.0 → cadwyn-5.2.2}/.github/workflows/publish_docs.yaml +0 -0
  26. {cadwyn-5.2.0 → cadwyn-5.2.2}/.github/workflows/release.yaml +0 -0
  27. {cadwyn-5.2.0 → cadwyn-5.2.2}/.gitignore +0 -0
  28. {cadwyn-5.2.0 → cadwyn-5.2.2}/.pre-commit-config.yaml +0 -0
  29. {cadwyn-5.2.0 → cadwyn-5.2.2}/LICENSE +0 -0
  30. {cadwyn-5.2.0 → cadwyn-5.2.2}/Makefile +0 -0
  31. {cadwyn-5.2.0 → cadwyn-5.2.2}/README.md +0 -0
  32. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/__init__.py +0 -0
  33. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/__main__.py +0 -0
  34. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/_asts.py +0 -0
  35. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/_importer.py +0 -0
  36. {cadwyn-5.2.0/cadwyn/static → cadwyn-5.2.2/cadwyn/_internal}/__init__.py +0 -0
  37. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/_render.py +0 -0
  38. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/_utils.py +0 -0
  39. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/changelogs.py +0 -0
  40. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/exceptions.py +0 -0
  41. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/py.typed +0 -0
  42. {cadwyn-5.2.0/docs → cadwyn-5.2.2/cadwyn/static}/__init__.py +0 -0
  43. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/static/docs.html +0 -0
  44. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/structure/__init__.py +0 -0
  45. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/structure/common.py +0 -0
  46. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/structure/data.py +0 -0
  47. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/structure/endpoints.py +0 -0
  48. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/structure/enums.py +0 -0
  49. {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/structure/schemas.py +0 -0
  50. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/CNAME +0 -0
  51. {cadwyn-5.2.0/docs_src → cadwyn-5.2.2/docs}/__init__.py +0 -0
  52. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/api_version_parameter_and_context_variables.md +0 -0
  53. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/beware_of_data_versioning.md +0 -0
  54. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/changelogs.md +0 -0
  55. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/cli.md +0 -0
  56. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/endpoint_migrations.md +0 -0
  57. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/enum_migrations.md +0 -0
  58. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/index.md +0 -0
  59. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/main_app.md +0 -0
  60. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/methodology.md +0 -0
  61. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/schema_generation.md +0 -0
  62. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/schema_migrations.md +0 -0
  63. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/testing.md +0 -0
  64. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/version_changes.md +0 -0
  65. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/where_to_put_the_version_and_how_to_format_it.md +0 -0
  66. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/home/CONTRIBUTING.md +0 -0
  67. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_business_logic/index.md +0 -0
  68. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_endpoints/index.md +0 -0
  69. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_openapi_schemas/add_field.md +0 -0
  70. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_openapi_schemas/change_field_type.md +0 -0
  71. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_openapi_schemas/change_schema_without_endpoint.md +0 -0
  72. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_openapi_schemas/changing_constraints.md +0 -0
  73. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_openapi_schemas/remove_field.md +0 -0
  74. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_openapi_schemas/rename_a_field_in_schema.md +0 -0
  75. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/index.md +0 -0
  76. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/version_with_paths_and_numbers_instead_of_headers_and_dates.md +0 -0
  77. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/img/dashboard_with_one_version.png +0 -0
  78. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/img/dashboard_with_two_versions.png +0 -0
  79. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/img/get_users_endpoint_from_prior_version.png +0 -0
  80. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/img/simplified_migration_model.png +0 -0
  81. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/img/sponsor_logos/monite.png +0 -0
  82. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/img/unversioned_dashboard.png +0 -0
  83. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/index.md +0 -0
  84. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/plugin.py +0 -0
  85. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/quickstart/setup.md +0 -0
  86. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/quickstart/tutorial.md +0 -0
  87. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/theory/how_to_build_versioning_framework.md +0 -0
  88. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/theory/how_we_got_here.md +0 -0
  89. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/theory/literature.md +0 -0
  90. {cadwyn-5.2.0/docs_src/how_to → cadwyn-5.2.2/docs_src}/__init__.py +0 -0
  91. {cadwyn-5.2.0/docs_src/how_to/change_openapi_schemas → cadwyn-5.2.2/docs_src/how_to}/__init__.py +0 -0
  92. {cadwyn-5.2.0/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint → cadwyn-5.2.2/docs_src/how_to/change_openapi_schemas}/__init__.py +0 -0
  93. {cadwyn-5.2.0/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests → cadwyn-5.2.2/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint}/__init__.py +0 -0
  94. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block001.py +0 -0
  95. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block002.py +0 -0
  96. {cadwyn-5.2.0/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates → cadwyn-5.2.2/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests}/__init__.py +0 -0
  97. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block001.py +0 -0
  98. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block002.py +0 -0
  99. {cadwyn-5.2.0/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests → cadwyn-5.2.2/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates}/__init__.py +0 -0
  100. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/block001.py +0 -0
  101. {cadwyn-5.2.0/docs_src/quickstart → cadwyn-5.2.2/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests}/__init__.py +0 -0
  102. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/test_block_001.py +0 -0
  103. {cadwyn-5.2.0/docs_src/quickstart/setup → cadwyn-5.2.2/docs_src/quickstart}/__init__.py +0 -0
  104. {cadwyn-5.2.0/docs_src/quickstart/setup/tests → cadwyn-5.2.2/docs_src/quickstart/setup}/__init__.py +0 -0
  105. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/setup/block001.sh +0 -0
  106. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/setup/block002.py +0 -0
  107. {cadwyn-5.2.0/docs_src/quickstart/tutorial → cadwyn-5.2.2/docs_src/quickstart/setup/tests}/__init__.py +0 -0
  108. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/setup/tests/test_block002.py +0 -0
  109. {cadwyn-5.2.0/docs_src/quickstart/tutorial/tests → cadwyn-5.2.2/docs_src/quickstart/tutorial}/__init__.py +0 -0
  110. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/tutorial/block001.py +0 -0
  111. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/tutorial/block002.py +0 -0
  112. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/tutorial/block003.py +0 -0
  113. {cadwyn-5.2.0/tests/_data → cadwyn-5.2.2/docs_src/quickstart/tutorial/tests}/__init__.py +0 -0
  114. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/tutorial/tests/test_block001.py +0 -0
  115. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/tutorial/tests/test_block002.py +0 -0
  116. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/tutorial/tests/test_block003.py +0 -0
  117. {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/ruff.toml +0 -0
  118. {cadwyn-5.2.0 → cadwyn-5.2.2}/mkdocs.yml +0 -0
  119. {cadwyn-5.2.0 → cadwyn-5.2.2}/scripts/fix_links.py +0 -0
  120. {cadwyn-5.2.0 → cadwyn-5.2.2}/scripts/split_md.py +0 -0
  121. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/__init__.py +0 -0
  122. {cadwyn-5.2.0/tests/_resources → cadwyn-5.2.2/tests/_data}/__init__.py +0 -0
  123. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_data/unversioned_schema_dir/__init__.py +0 -0
  124. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_data/unversioned_schema_dir/unversioned_schemas.py +0 -0
  125. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_data/unversioned_schemas.py +0 -0
  126. {cadwyn-5.2.0/tests/_resources/render → cadwyn-5.2.2/tests/_resources}/__init__.py +0 -0
  127. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/app_for_testing_routing.py +0 -0
  128. {cadwyn-5.2.0/tests/_resources/render/complex → cadwyn-5.2.2/tests/_resources/render}/__init__.py +0 -0
  129. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/render/classes.py +0 -0
  130. {cadwyn-5.2.0/tests/_resources/versioned_app → cadwyn-5.2.2/tests/_resources/render/complex}/__init__.py +0 -0
  131. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/render/complex/classes.py +0 -0
  132. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/render/complex/versions.py +0 -0
  133. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/render/versions.py +0 -0
  134. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/utils.py +0 -0
  135. {cadwyn-5.2.0/tests/test_schema_generation → cadwyn-5.2.2/tests/_resources/versioned_app}/__init__.py +0 -0
  136. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/versioned_app/app.py +0 -0
  137. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/versioned_app/v2021_01_01.py +0 -0
  138. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/versioned_app/v2022_01_02.py +0 -0
  139. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/versioned_app/webhooks.py +0 -0
  140. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/conftest.py +0 -0
  141. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_auth_dependencies.py +0 -0
  142. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_changelog.py +0 -0
  143. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_cli.py +0 -0
  144. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_data_migrations.py +0 -0
  145. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_render.py +0 -0
  146. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_router_generation_with_from_future_annotations.py +0 -0
  147. {cadwyn-5.2.0/tests/tutorial → cadwyn-5.2.2/tests/test_schema_generation}/__init__.py +0 -0
  148. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_schema_generation/test_enum.py +0 -0
  149. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_schema_generation/test_schema.py +0 -0
  150. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_schema_generation/test_schema_field.py +0 -0
  151. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_schema_generation/test_schema_validator.py +0 -0
  152. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_schema_generation/test_schema_with_future_annotations.py +0 -0
  153. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_structure.py +0 -0
  154. {cadwyn-5.2.0/tests/versioning_styles → cadwyn-5.2.2/tests/tutorial}/__init__.py +0 -0
  155. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/tutorial/main.py +0 -0
  156. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/tutorial/test_example.py +0 -0
  157. {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/versioning_styles/test_versioning_formats.py +0 -0
  158. {cadwyn-5.2.0 → cadwyn-5.2.2}/tox.ini +0 -0
@@ -5,6 +5,18 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [5.2.2]
9
+
10
+ ### Fixed
11
+
12
+ * Whenever a route was migrated by path, we used the path and methods of the request. So if the request wanted "/v1/webhook_settings" and we renamed them to "/v1/webhook_subscriptions" -- all migrations for "/v1/webhook_subscriptions" would not get applied to any requests that wanted "/v1/webhook_settings". This effectively means that previously route renamings were incompatible with by path converters. Now we use the head route id instead of the path and methods of the request for matching so we always know which migrations to apply.
13
+
14
+ ## [5.2.1]
15
+
16
+ ### Fixed
17
+
18
+ * [#268](https://github.com/zmievsa/cadwyn/issues/268) A bug where we received 404 for all unversioned routes when a default api version was passed to Cadwyn at initialization
19
+
8
20
  ## [5.2.0]
9
21
 
10
22
  ### Removed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cadwyn
3
- Version: 5.2.0
3
+ Version: 5.2.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
@@ -0,0 +1,9 @@
1
+ from contextvars import ContextVar
2
+
3
+ from typing_extensions import Literal
4
+
5
+ DEFAULT_API_VERSION_VAR: "ContextVar[str | None]" = ContextVar("cadwyn_default_api_version")
6
+ CURRENT_DEPENDENCY_SOLVER_OPTIONS = Literal["cadwyn", "fastapi"]
7
+ CURRENT_DEPENDENCY_SOLVER_VAR: ContextVar[CURRENT_DEPENDENCY_SOLVER_OPTIONS] = ContextVar(
8
+ "cadwyn_dependencies_current_dependency_solver"
9
+ )
@@ -133,6 +133,12 @@ class Cadwyn(FastAPI):
133
133
  stacklevel=2,
134
134
  )
135
135
  api_version_parameter_name = api_version_header_name
136
+ if api_version_default_value is not None and api_version_location == "path":
137
+ raise CadwynStructureError(
138
+ "You tried to pass an api_version_default_value while putting the API version in Path. "
139
+ "This is not currently supported by Cadwyn. "
140
+ "Please, open an issue on our github if you'd like to have it."
141
+ )
136
142
 
137
143
  super().__init__(
138
144
  debug=debug,
@@ -231,8 +237,8 @@ class Cadwyn(FastAPI):
231
237
  versioning_middleware_class,
232
238
  api_version_parameter_name=api_version_parameter_name,
233
239
  api_version_manager=self._api_version_manager,
234
- api_version_default_value=api_version_default_value,
235
240
  api_version_var=self.versions.api_version_var,
241
+ api_version_default_value=api_version_default_value,
236
242
  )
237
243
  if self.api_version_format == "date" and (
238
244
  sorted(self.versions.versions, key=lambda v: v.value, reverse=True) != list(self.versions.versions)
@@ -0,0 +1,5 @@
1
+ from cadwyn._internal.context_vars 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")
@@ -7,10 +7,12 @@ from collections.abc import Awaitable, Callable
7
7
  from contextvars import ContextVar
8
8
  from typing import Annotated, Any, Literal, Protocol, Union
9
9
 
10
+ import fastapi
10
11
  from fastapi import Request
11
12
  from starlette.middleware.base import BaseHTTPMiddleware, DispatchFunction, RequestResponseEndpoint
12
13
  from starlette.types import ASGIApp
13
14
 
15
+ from cadwyn._internal.context_vars import DEFAULT_API_VERSION_VAR
14
16
  from cadwyn.structure.common import VersionType
15
17
 
16
18
 
@@ -69,6 +71,8 @@ def _generate_api_version_dependency(
69
71
  annotation=Annotated[
70
72
  validation_data_type, fastapi_depends_class(openapi_examples={"default": {"value": default_value}})
71
73
  ],
74
+ # Path-based parameters do not support a default value in FastAPI :(
75
+ default=default_value if fastapi_depends_class != fastapi.Path else inspect.Signature.empty,
72
76
  ),
73
77
  ],
74
78
  )
@@ -103,10 +107,11 @@ class VersionPickingMiddleware(BaseHTTPMiddleware):
103
107
  api_version = self._api_version_manager.get(request)
104
108
 
105
109
  if api_version is None:
106
- if callable(self.api_version_default_value): # pragma: no cover # TODO
110
+ if callable(self.api_version_default_value):
107
111
  api_version = await self.api_version_default_value(request)
108
112
  else:
109
113
  api_version = self.api_version_default_value
114
+ DEFAULT_API_VERSION_VAR.set(api_version)
110
115
 
111
116
  self.api_version_var.set(api_version)
112
117
  response = await call_next(request)
@@ -37,11 +37,13 @@ from cadwyn.schema_generation import (
37
37
  )
38
38
  from cadwyn.structure import Version, VersionBundle
39
39
  from cadwyn.structure.common import Endpoint, VersionType
40
+ from cadwyn.structure.data import _AlterRequestByPathInstruction, _AlterResponseByPathInstruction
40
41
  from cadwyn.structure.endpoints import (
41
42
  EndpointDidntExistInstruction,
42
43
  EndpointExistedInstruction,
43
44
  EndpointHadInstruction,
44
45
  )
46
+ from cadwyn.structure.versions import VersionChange
45
47
 
46
48
  if TYPE_CHECKING:
47
49
  from fastapi.dependencies.models import Dependant
@@ -52,6 +54,9 @@ _WR = TypeVar("_WR", bound=APIRouter, default=APIRouter)
52
54
  _RouteT = TypeVar("_RouteT", bound=BaseRoute)
53
55
  # This is a hack we do because we can't guarantee how the user will use the router.
54
56
  _DELETED_ROUTE_TAG = "_CADWYN_DELETED_ROUTE"
57
+ _RoutePath = str
58
+ _RouteMethod = str
59
+ _RouteId = int
55
60
 
56
61
 
57
62
  @dataclass(**DATACLASS_SLOTS, frozen=True, eq=True)
@@ -123,6 +128,7 @@ class _EndpointTransformer(Generic[_R, _WR]):
123
128
  ]
124
129
 
125
130
  def transform(self) -> GeneratedRouters[_R, _WR]:
131
+ # Copy MUST keep the order and number of routes. Otherwise, a ton of code below will break.
126
132
  router = copy_router(self.parent_router)
127
133
  webhook_router = copy_router(self.parent_webhooks_router)
128
134
  routers: dict[VersionType, _R] = {}
@@ -132,7 +138,7 @@ class _EndpointTransformer(Generic[_R, _WR]):
132
138
  self.schema_generators[str(version.value)].annotation_transformer.migrate_router_to_version(router)
133
139
  self.schema_generators[str(version.value)].annotation_transformer.migrate_router_to_version(webhook_router)
134
140
 
135
- self._validate_all_data_converters_are_applied(router, version)
141
+ self._attach_routes_to_data_converters(router, self.parent_router, version)
136
142
 
137
143
  routers[version.value] = router
138
144
  webhook_routers[version.value] = webhook_router
@@ -193,9 +199,11 @@ class _EndpointTransformer(Generic[_R, _WR]):
193
199
  ]
194
200
  return GeneratedRouters(routers, webhook_routers)
195
201
 
196
- def _validate_all_data_converters_are_applied(self, router: APIRouter, version: Version):
197
- path_to_route_methods_mapping, head_response_models, head_request_bodies = self._extract_all_routes_identifiers(
198
- router
202
+ def _attach_routes_to_data_converters(self, router: APIRouter, head_router: APIRouter, version: Version):
203
+ # This method is way out of its league in terms of complexity. We gotta refactor it.
204
+
205
+ path_to_route_methods_mapping, head_response_models, head_request_bodies = (
206
+ self._extract_all_routes_identifiers_for_route_to_converter_matching(router)
199
207
  )
200
208
 
201
209
  for version_change in version.changes:
@@ -204,21 +212,10 @@ class _EndpointTransformer(Generic[_R, _WR]):
204
212
  *version_change.alter_request_by_path_instructions.values(),
205
213
  ]:
206
214
  for by_path_converter in by_path_converters:
207
- missing_methods = by_path_converter.methods.difference(
208
- path_to_route_methods_mapping[by_path_converter.path]
215
+ self._attach_routes_by_path_converter(
216
+ head_router, path_to_route_methods_mapping, version_change, by_path_converter
209
217
  )
210
218
 
211
- if missing_methods:
212
- raise RouteByPathConverterDoesNotApplyToAnythingError(
213
- f"{by_path_converter.repr_name} "
214
- f'"{version_change.__name__}.{by_path_converter.transformer.__name__}" '
215
- f"failed to find routes with the following methods: {list(missing_methods)}. "
216
- f"This means that you are trying to apply this converter to non-existing endpoint(s). "
217
- "Please, check whether the path and methods are correct. (hint: path must include "
218
- "all path variables and have a name that was used in the version that this "
219
- "VersionChange resides in)"
220
- )
221
-
222
219
  for by_schema_converters in version_change.alter_request_by_schema_instructions.values():
223
220
  for by_schema_converter in by_schema_converters:
224
221
  if not by_schema_converter.check_usage: # pragma: no cover
@@ -249,14 +246,50 @@ class _EndpointTransformer(Generic[_R, _WR]):
249
246
  f"{version_change.__name__}.{by_schema_converter.transformer.__name__}"
250
247
  )
251
248
 
252
- def _extract_all_routes_identifiers(
253
- self, router: APIRouter
254
- ) -> tuple[defaultdict[str, set[str]], set[Any], set[Any]]:
255
- response_models: set[Any] = set()
256
- request_bodies: set[Any] = set()
257
- path_to_route_methods_mapping: dict[str, set[str]] = defaultdict(set)
249
+ def _attach_routes_by_path_converter(
250
+ self,
251
+ head_router: APIRouter,
252
+ path_to_route_methods_mapping: dict[_RoutePath, dict[_RouteMethod, set[_RouteId]]],
253
+ version_change: type[VersionChange],
254
+ by_path_converter: Union[_AlterResponseByPathInstruction, _AlterRequestByPathInstruction],
255
+ ):
256
+ missing_methods = set()
257
+ for method in by_path_converter.methods:
258
+ if method in path_to_route_methods_mapping[by_path_converter.path]:
259
+ for route_index in path_to_route_methods_mapping[by_path_converter.path][method]:
260
+ route = head_router.routes[route_index]
261
+ if isinstance(by_path_converter, _AlterResponseByPathInstruction):
262
+ version_change._route_to_response_migration_mapping[id(route)].append(by_path_converter)
263
+ else:
264
+ version_change._route_to_request_migration_mapping[id(route)].append(by_path_converter)
265
+ else:
266
+ missing_methods.add(method)
267
+
268
+ if missing_methods:
269
+ raise RouteByPathConverterDoesNotApplyToAnythingError(
270
+ f"{by_path_converter.repr_name} "
271
+ f'"{version_change.__name__}.{by_path_converter.transformer.__name__}" '
272
+ f"failed to find routes with the following methods: {list(missing_methods)}. "
273
+ f"This means that you are trying to apply this converter to non-existing endpoint(s). "
274
+ "Please, check whether the path and methods are correct. (hint: path must include "
275
+ "all path variables and have a name that was used in the version that this "
276
+ "VersionChange resides in)"
277
+ )
258
278
 
259
- for route in router.routes:
279
+ def _extract_all_routes_identifiers_for_route_to_converter_matching(
280
+ self, router: APIRouter
281
+ ) -> tuple[dict[_RoutePath, dict[_RouteMethod, set[_RouteId]]], set[Any], set[Any]]:
282
+ # int is the index of the route in the router.routes list.
283
+ # So we essentially keep track of which routes have which response models and request bodies.
284
+ # and their indices in the router.routes list. The indices will allow us to match them to the same
285
+ # routes in the head version. This gives us the ability to later apply changes to these routes
286
+ # without thinking about any renamings or response model changes.
287
+
288
+ response_models = set()
289
+ request_bodies = set()
290
+ path_to_route_methods_mapping: dict[str, dict[str, set[int]]] = defaultdict(lambda: defaultdict(set))
291
+
292
+ for index, route in enumerate(router.routes):
260
293
  if isinstance(route, APIRoute):
261
294
  if route.response_model is not None and lenient_issubclass(route.response_model, BaseModel):
262
295
  response_models.add(route.response_model)
@@ -265,7 +298,8 @@ class _EndpointTransformer(Generic[_R, _WR]):
265
298
  annotation = route.body_field.field_info.annotation
266
299
  if annotation is not None and lenient_issubclass(annotation, BaseModel):
267
300
  request_bodies.add(annotation)
268
- path_to_route_methods_mapping[route.path] |= route.methods
301
+ for method in route.methods:
302
+ path_to_route_methods_mapping[route.path][method].add(index)
269
303
 
270
304
  head_response_models = {model.__cadwyn_original_model__ for model in response_models}
271
305
  head_request_bodies = {getattr(body, "__cadwyn_original_model__", body) for body in request_bodies}
@@ -11,6 +11,7 @@ from starlette.responses import RedirectResponse
11
11
  from starlette.routing import BaseRoute, Match
12
12
  from starlette.types import Receive, Scope, Send
13
13
 
14
+ from cadwyn._internal.context_vars import DEFAULT_API_VERSION_VAR
14
15
  from cadwyn._utils import same_definition_as_in
15
16
  from cadwyn.middleware import APIVersionFormat
16
17
  from cadwyn.structure.common import VersionType
@@ -70,8 +71,8 @@ class _RootCadwynAPIRouter(APIRouter):
70
71
  if scope["type"] == "lifespan":
71
72
  await self.lifespan(scope, receive, send)
72
73
  return
73
-
74
74
  version = self.api_version_var.get(None)
75
+ default_version_that_was_picked = DEFAULT_API_VERSION_VAR.get(None)
75
76
 
76
77
  # if version is None, then it's an unversioned request and we need to use the unversioned routes
77
78
  # if there will be a value, we search for the most suitable version
@@ -81,6 +82,14 @@ class _RootCadwynAPIRouter(APIRouter):
81
82
  routes = self.versioned_routers[version].routes
82
83
  else:
83
84
  routes = await self._get_routes_from_closest_suitable_version(version)
85
+ if default_version_that_was_picked:
86
+ # We add unversioned routes to versioned routes because otherwise unversioned routes
87
+ # will be completely unavailable when a default version is passed. So routes such as
88
+ # /docs will not be accessible at all.
89
+
90
+ # We use this order because if versioned routes go first and there is a versioned route that is
91
+ # the same as an unversioned route -- the unversioned one becomes impossible to match.
92
+ routes = self.unversioned_routes + routes
84
93
  await self.process_request(scope=scope, receive=receive, send=send, routes=routes)
85
94
 
86
95
  @cached_property
@@ -196,8 +196,7 @@ def migrate_response_body(
196
196
  response,
197
197
  current_version=version,
198
198
  head_response_model=latest_response_model,
199
- path="\0\0\0",
200
- method="GET",
199
+ head_route=None,
201
200
  )
202
201
 
203
202
  versioned_response_model: type[pydantic.BaseModel] = generate_versioned_models(versions)[str(version)][
@@ -24,8 +24,9 @@ from fastapi.routing import APIRoute, _prepare_response_content
24
24
  from pydantic import BaseModel
25
25
  from pydantic_core import PydanticUndefined
26
26
  from starlette._utils import is_async_callable
27
- from typing_extensions import Any, Literal, ParamSpec, TypeAlias, TypeVar, assert_never, deprecated, get_args
27
+ from typing_extensions import Any, ParamSpec, TypeAlias, TypeVar, assert_never, deprecated, get_args
28
28
 
29
+ from cadwyn._internal.context_vars import CURRENT_DEPENDENCY_SOLVER_VAR
29
30
  from cadwyn._utils import classproperty
30
31
  from cadwyn.exceptions import (
31
32
  CadwynError,
@@ -52,10 +53,7 @@ _CADWYN_REQUEST_PARAM_NAME = "cadwyn_request_param"
52
53
  _CADWYN_RESPONSE_PARAM_NAME = "cadwyn_response_param"
53
54
  _P = ParamSpec("_P")
54
55
  _R = TypeVar("_R")
55
- _CURRENT_DEPENDENCY_SOLVER_OPTIONS = Literal["cadwyn", "fastapi"]
56
- _CURRENT_DEPENDENCY_SOLVER_VAR: ContextVar[_CURRENT_DEPENDENCY_SOLVER_OPTIONS] = ContextVar(
57
- "cadwyn_dependencies_dry_run"
58
- )
56
+ _RouteId = int
59
57
 
60
58
  PossibleInstructions: TypeAlias = Union[
61
59
  AlterSchemaSubInstruction, AlterEndpointSubInstruction, AlterEnumSubInstruction, SchemaHadInstruction, staticmethod
@@ -88,6 +86,8 @@ class VersionChange:
88
86
  alter_response_by_schema_instructions: ClassVar[dict[type, list[_AlterResponseBySchemaInstruction]]] = Sentinel
89
87
  alter_response_by_path_instructions: ClassVar[dict[str, list[_AlterResponseByPathInstruction]]] = Sentinel
90
88
  _bound_version_bundle: "Union[VersionBundle, None]"
89
+ _route_to_request_migration_mapping: ClassVar[dict[_RouteId, list[_AlterRequestByPathInstruction]]] = Sentinel
90
+ _route_to_response_migration_mapping: ClassVar[dict[_RouteId, list[_AlterResponseByPathInstruction]]] = Sentinel
91
91
 
92
92
  def __init_subclass__(cls, _abstract: bool = False) -> None:
93
93
  super().__init_subclass__()
@@ -99,6 +99,8 @@ class VersionChange:
99
99
  cls._extract_body_instructions_into_correct_containers()
100
100
  cls._check_no_subclassing()
101
101
  cls._bound_version_bundle = None
102
+ cls._route_to_request_migration_mapping = defaultdict(list)
103
+ cls._route_to_response_migration_mapping = defaultdict(list)
102
104
 
103
105
  @classmethod
104
106
  def _extract_body_instructions_into_correct_containers(cls):
@@ -361,7 +363,6 @@ class VersionBundle:
361
363
  self,
362
364
  body_type: Union[type[BaseModel], None],
363
365
  head_dependant: Dependant,
364
- path: str,
365
366
  request: FastapiRequest,
366
367
  response: FastapiResponse,
367
368
  request_info: RequestInfo,
@@ -372,22 +373,21 @@ class VersionBundle:
372
373
  embed_body_fields: bool,
373
374
  background_tasks: Union[BackgroundTasks, None],
374
375
  ) -> dict[str, Any]:
375
- method = request.method
376
-
377
376
  start = self.reversed_version_values.index(current_version)
377
+ head_route_id = id(head_route)
378
378
  for v in self.reversed_versions[start + 1 :]:
379
379
  for version_change in v.changes:
380
380
  if body_type is not None and body_type in version_change.alter_request_by_schema_instructions:
381
381
  for instruction in version_change.alter_request_by_schema_instructions[body_type]:
382
382
  instruction(request_info)
383
- if path in version_change.alter_request_by_path_instructions:
384
- for instruction in version_change.alter_request_by_path_instructions[path]:
385
- if method in instruction.methods: # pragma: no branch # safe branch to skip
386
- instruction(request_info)
383
+ if head_route_id in version_change._route_to_request_migration_mapping:
384
+ for instruction in version_change._route_to_request_migration_mapping[head_route_id]:
385
+ instruction(request_info)
386
+
387
387
  request.scope["headers"] = tuple((key.encode(), value.encode()) for key, value in request_info.headers.items())
388
388
  del request._headers
389
389
  # This gives us the ability to tell the user whether cadwyn is running its dependencies or FastAPI
390
- _CURRENT_DEPENDENCY_SOLVER_VAR.set("cadwyn")
390
+ CURRENT_DEPENDENCY_SOLVER_VAR.set("cadwyn")
391
391
  # Remember this: if len(body_params) == 1, then route.body_schema == route.dependant.body_params[0]
392
392
  result = await solve_dependencies(
393
393
  request=request,
@@ -409,10 +409,10 @@ class VersionBundle:
409
409
  self,
410
410
  response_info: ResponseInfo,
411
411
  current_version: VersionType,
412
- head_response_model: type[BaseModel],
413
- path: str,
414
- method: str,
412
+ head_response_model: Union[type[BaseModel], None],
413
+ head_route: Union[APIRoute, None],
415
414
  ) -> ResponseInfo:
415
+ head_route_id = id(head_route)
416
416
  end = self.version_values.index(current_version)
417
417
  for v in self.versions[:end]:
418
418
  for version_change in v.changes:
@@ -423,10 +423,8 @@ class VersionBundle:
423
423
  version_change.alter_response_by_schema_instructions[head_response_model]
424
424
  )
425
425
 
426
- if path in version_change.alter_response_by_path_instructions:
427
- for instruction in version_change.alter_response_by_path_instructions[path]:
428
- if method in instruction.methods: # pragma: no branch # Safe branch to skip
429
- migrations_to_apply.append(instruction) # noqa: PERF401
426
+ if head_route_id in version_change._route_to_response_migration_mapping:
427
+ migrations_to_apply.extend(version_change._route_to_response_migration_mapping[head_route_id])
430
428
 
431
429
  for migration in migrations_to_apply:
432
430
  if response_info.status_code < 300 or migration.migrate_http_errors:
@@ -579,8 +577,7 @@ class VersionBundle:
579
577
  response_info,
580
578
  api_version,
581
579
  head_route.response_model,
582
- route.path,
583
- method,
580
+ head_route,
584
581
  )
585
582
  if isinstance(response_or_response_body, FastapiResponse):
586
583
  # a webserver (uvicorn for instance) calculates the body at the endpoint level.
@@ -674,7 +671,6 @@ class VersionBundle:
674
671
  new_kwargs = await self._migrate_request(
675
672
  head_body_field,
676
673
  head_dependant,
677
- route.path,
678
674
  request,
679
675
  response,
680
676
  request_info,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cadwyn"
3
- version = "5.2.0"
3
+ version = "5.2.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"
@@ -135,7 +135,7 @@ ignore = [
135
135
  ]
136
136
 
137
137
  [lint.mccabe]
138
- max-complexity = 14
138
+ max-complexity = 15
139
139
 
140
140
  [format]
141
141
  quote-style = "double"
@@ -8,6 +8,7 @@ from fastapi.testclient import TestClient
8
8
  from pydantic import BaseModel
9
9
 
10
10
  from cadwyn import Cadwyn
11
+ from cadwyn.exceptions import CadwynStructureError
11
12
  from cadwyn.route_generation import VersionedAPIRouter
12
13
  from cadwyn.structure.endpoints import endpoint
13
14
  from cadwyn.structure.schemas import schema
@@ -405,3 +406,15 @@ def test__api_version_header_name_is_deprecated_and_translates_to_api_version_pa
405
406
  with pytest.warns(DeprecationWarning):
406
407
  cadwyn = Cadwyn(api_version_header_name="x-api-version", versions=VersionBundle(Version("2022-11-16")))
407
408
  assert cadwyn.api_version_parameter_name == "x-api-version"
409
+
410
+
411
+ def test__api_version_default_value_with_path_location__should_raise_error():
412
+ with pytest.raises(
413
+ CadwynStructureError,
414
+ match="You tried to pass an api_version_default_value while putting the API version in Path",
415
+ ):
416
+ Cadwyn(
417
+ versions=VersionBundle(HeadVersion(), Version("2022-11-16")),
418
+ api_version_default_value="2022-11-16",
419
+ api_version_location="path",
420
+ )
@@ -20,11 +20,13 @@ from starlette.responses import FileResponse
20
20
  from typing_extensions import Any, NewType, TypeAlias, TypeAliasType, get_args
21
21
 
22
22
  from cadwyn import VersionBundle, VersionedAPIRouter
23
+ from cadwyn.applications import Cadwyn
23
24
  from cadwyn.dependencies import current_dependency_solver
24
25
  from cadwyn.exceptions import CadwynError, RouterGenerationError, RouterPathParamsModifiedError
25
26
  from cadwyn.route_generation import generate_versioned_routers
26
27
  from cadwyn.schema_generation import generate_versioned_models
27
28
  from cadwyn.structure import Version, convert_request_to_next_version_for, endpoint, schema
29
+ from cadwyn.structure.data import RequestInfo, ResponseInfo, convert_response_to_previous_version_for
28
30
  from cadwyn.structure.enums import enum
29
31
  from cadwyn.structure.versions import VersionChange
30
32
  from tests._data.unversioned_schema_dir import UnversionedSchema2
@@ -252,6 +254,50 @@ def test__endpoint_had_another_path_variable(
252
254
  )
253
255
 
254
256
 
257
+ def test__endpoint_had__another_path_with_the_other_migration_at_the_same_time__should_require_old_name(
258
+ create_versioned_app: CreateVersionedApp,
259
+ ):
260
+ router = VersionedAPIRouter()
261
+
262
+ @router.post("/A")
263
+ async def test_endpoint_post(body: list[str]):
264
+ return body
265
+
266
+ @router.put("/A")
267
+ async def test_endpoint_put(body: list[str]):
268
+ return body
269
+
270
+ @convert_response_to_previous_version_for("/A", ["POST"])
271
+ def response_migration(response: ResponseInfo):
272
+ response.body.append("response")
273
+
274
+ @convert_request_to_next_version_for("/A", ["POST"])
275
+ def request_migration(request: RequestInfo):
276
+ request.body.append("request")
277
+
278
+ app = Cadwyn(
279
+ versions=VersionBundle(
280
+ Version(
281
+ "2001-01-01",
282
+ version_change(
283
+ endpoint("/A", ["POST"]).had(path="/B"),
284
+ request_migration=request_migration,
285
+ response_migration=response_migration,
286
+ ),
287
+ ),
288
+ Version("2000-01-01"),
289
+ )
290
+ )
291
+ app.generate_and_include_versioned_routers(router)
292
+
293
+ with TestClient(app) as client:
294
+ assert client.post("/B", headers={"X-API-VERSION": "2000-01-01"}, json=[]).json() == ["request", "response"]
295
+ assert client.post("/A", headers={"X-API-VERSION": "2001-01-01"}, json=[]).json() == []
296
+
297
+ assert client.put("/A", headers={"X-API-VERSION": "2000-01-01"}, json=[]).json() == []
298
+ assert client.put("/A", headers={"X-API-VERSION": "2001-01-01"}, json=[]).json() == []
299
+
300
+
255
301
  def test__endpoint_had_dependencies(
256
302
  test_endpoint: Endpoint,
257
303
  test_path: str,
@@ -1,3 +1,5 @@
1
+ from typing import Callable
2
+
1
3
  import pytest
2
4
  from starlette.requests import Request
3
5
  from starlette.responses import PlainTextResponse
@@ -5,7 +7,8 @@ from starlette.routing import Match, NoMatchFound
5
7
  from starlette.testclient import TestClient
6
8
 
7
9
  from cadwyn import Cadwyn
8
- from cadwyn.structure.versions import Version, VersionBundle
10
+ from cadwyn.route_generation import VersionedAPIRouter
11
+ from cadwyn.structure.versions import HeadVersion, Version, VersionBundle
9
12
  from tests._resources.app_for_testing_routing import mixed_hosts_app
10
13
 
11
14
 
@@ -153,3 +156,53 @@ def test__host_routing__partial_match__404():
153
156
 
154
157
  response = client.get("/v1/doggies/tom")
155
158
  assert response.status_code == 200
159
+
160
+
161
+ async def get_default_version(req: Request):
162
+ return "2023-04-14"
163
+
164
+
165
+ @pytest.mark.parametrize("default_version", ["2023-04-14", get_default_version])
166
+ def test__get_unversioned_endpoints__with_default_version(default_version: "str | Callable"):
167
+ app = Cadwyn(
168
+ versions=VersionBundle(HeadVersion(), Version("2023-04-14"), Version("2022-11-16")),
169
+ api_version_default_value=default_version,
170
+ )
171
+
172
+ router = VersionedAPIRouter()
173
+
174
+ @app.get("/my_duplicated_route")
175
+ def get_my_unversioned_number():
176
+ return 11
177
+
178
+ @router.get("/my_duplicated_route")
179
+ def get_my_versioned_number():
180
+ return 83
181
+
182
+ @router.get("/my_single_route")
183
+ def get_my_versioned_number_2():
184
+ return 52
185
+
186
+ app.generate_and_include_versioned_routers(router)
187
+
188
+ with TestClient(app) as client:
189
+ resp = client.get("/docs")
190
+ assert resp.status_code == 200, resp.json()
191
+
192
+ resp = client.get("/docs?version=2023-04-14")
193
+ assert resp.status_code == 200, resp.json()
194
+
195
+ resp = client.get("/docs?version=2022-11-16")
196
+ assert resp.status_code == 200, resp.json()
197
+
198
+ resp = client.get("/my_duplicated_route")
199
+ assert resp.status_code == 200, resp.json()
200
+ assert resp.json() == 11
201
+
202
+ resp = client.get("/my_duplicated_route", headers={"X-API-VERSION": "2023-04-14"})
203
+ assert resp.status_code == 200, resp.json()
204
+ assert resp.json() == 83
205
+
206
+ resp = client.get("/my_single_route")
207
+ assert resp.status_code == 200, resp.json()
208
+ assert resp.json() == 52
File without changes
@@ -94,7 +94,7 @@ wheels = [
94
94
 
95
95
  [[package]]
96
96
  name = "cadwyn"
97
- version = "5.2.0"
97
+ version = "5.2.2"
98
98
  source = { editable = "." }
99
99
  dependencies = [
100
100
  { name = "backports-strenum", marker = "python_full_version < '3.11'" },
@@ -1,5 +0,0 @@
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")
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
File without changes
File without changes
File without changes