cadwyn 5.4.2__tar.gz → 5.4.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 (160) hide show
  1. cadwyn-5.4.4/.all-contributorsrc +4 -0
  2. {cadwyn-5.4.2 → cadwyn-5.4.4}/CHANGELOG.md +12 -0
  3. {cadwyn-5.4.2 → cadwyn-5.4.4}/PKG-INFO +24 -1
  4. {cadwyn-5.4.2 → cadwyn-5.4.4}/README.md +23 -0
  5. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/applications.py +3 -2
  6. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/schema_generation.py +28 -7
  7. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/home/CONTRIBUTING.md +1 -0
  8. {cadwyn-5.4.2 → cadwyn-5.4.4}/pyproject.toml +1 -1
  9. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/test_schema_generation/test_schema_field.py +79 -1
  10. {cadwyn-5.4.2 → cadwyn-5.4.4}/uv.lock +1 -1
  11. {cadwyn-5.4.2 → cadwyn-5.4.4}/.github/CODE_OF_CONDUCT.md +0 -0
  12. {cadwyn-5.4.2 → cadwyn-5.4.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  13. {cadwyn-5.4.2 → cadwyn-5.4.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  14. {cadwyn-5.4.2 → cadwyn-5.4.4}/.github/actions/setup-python-uv/action.yaml +0 -0
  15. {cadwyn-5.4.2 → cadwyn-5.4.4}/.github/workflows/ci.yaml +0 -0
  16. {cadwyn-5.4.2 → cadwyn-5.4.4}/.github/workflows/daily_tests.yaml +0 -0
  17. {cadwyn-5.4.2 → cadwyn-5.4.4}/.github/workflows/publish_docs.yaml +0 -0
  18. {cadwyn-5.4.2 → cadwyn-5.4.4}/.github/workflows/release.yaml +0 -0
  19. {cadwyn-5.4.2 → cadwyn-5.4.4}/.gitignore +0 -0
  20. {cadwyn-5.4.2 → cadwyn-5.4.4}/.pre-commit-config.yaml +0 -0
  21. {cadwyn-5.4.2 → cadwyn-5.4.4}/LICENSE +0 -0
  22. {cadwyn-5.4.2 → cadwyn-5.4.4}/Makefile +0 -0
  23. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/__init__.py +0 -0
  24. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/__main__.py +0 -0
  25. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/_asts.py +0 -0
  26. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/_importer.py +0 -0
  27. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/_internal/__init__.py +0 -0
  28. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/_internal/context_vars.py +0 -0
  29. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/_render.py +0 -0
  30. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/_utils.py +0 -0
  31. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/changelogs.py +0 -0
  32. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/dependencies.py +0 -0
  33. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/exceptions.py +0 -0
  34. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/middleware.py +0 -0
  35. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/py.typed +0 -0
  36. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/route_generation.py +0 -0
  37. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/routing.py +0 -0
  38. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/static/__init__.py +0 -0
  39. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/static/docs.html +0 -0
  40. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/structure/__init__.py +0 -0
  41. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/structure/common.py +0 -0
  42. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/structure/data.py +0 -0
  43. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/structure/endpoints.py +0 -0
  44. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/structure/enums.py +0 -0
  45. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/structure/schemas.py +0 -0
  46. {cadwyn-5.4.2 → cadwyn-5.4.4}/cadwyn/structure/versions.py +0 -0
  47. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/CNAME +0 -0
  48. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/__init__.py +0 -0
  49. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/concepts/api_version_parameter.md +0 -0
  50. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/concepts/beware_of_data_versioning.md +0 -0
  51. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/concepts/changelogs.md +0 -0
  52. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/concepts/cli.md +0 -0
  53. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/concepts/endpoint_migrations.md +0 -0
  54. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/concepts/enum_migrations.md +0 -0
  55. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/concepts/index.md +0 -0
  56. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/concepts/main_app.md +0 -0
  57. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/concepts/methodology.md +0 -0
  58. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/concepts/schema_generation.md +0 -0
  59. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/concepts/schema_migrations.md +0 -0
  60. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/concepts/testing.md +0 -0
  61. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/concepts/version_changes.md +0 -0
  62. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/how_to/change_business_logic/index.md +0 -0
  63. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/how_to/change_endpoints/index.md +0 -0
  64. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/how_to/change_openapi_schemas/add_field.md +0 -0
  65. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/how_to/change_openapi_schemas/change_field_type.md +0 -0
  66. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/how_to/change_openapi_schemas/change_schema_without_endpoint.md +0 -0
  67. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/how_to/change_openapi_schemas/changing_constraints.md +0 -0
  68. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/how_to/change_openapi_schemas/remove_field.md +0 -0
  69. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/how_to/change_openapi_schemas/rename_a_field_in_schema.md +0 -0
  70. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/how_to/index.md +0 -0
  71. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/how_to/version_with_paths_and_numbers_instead_of_headers_and_dates.md +0 -0
  72. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/img/dashboard_with_one_version.png +0 -0
  73. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/img/dashboard_with_two_versions.png +0 -0
  74. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/img/get_users_endpoint_from_prior_version.png +0 -0
  75. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/img/logos/cadwyn_icon_transparent.svg +0 -0
  76. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/img/logos/cadwyn_transparent.svg +0 -0
  77. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/img/logos/cadwyn_with_background.svg +0 -0
  78. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/img/simplified_migration_model.png +0 -0
  79. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/img/unversioned_dashboard.png +0 -0
  80. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/index.md +0 -0
  81. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/plugin.py +0 -0
  82. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/quickstart/setup.md +0 -0
  83. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/quickstart/tutorial.md +0 -0
  84. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/stylesheets/extra.css +0 -0
  85. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/theory/how_to_build_versioning_framework.md +0 -0
  86. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/theory/how_we_got_here.md +0 -0
  87. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs/theory/literature.md +0 -0
  88. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/__init__.py +0 -0
  89. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/how_to/__init__.py +0 -0
  90. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/how_to/change_openapi_schemas/__init__.py +0 -0
  91. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/__init__.py +0 -0
  92. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block001.py +0 -0
  93. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block002.py +0 -0
  94. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/__init__.py +0 -0
  95. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block001.py +0 -0
  96. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block002.py +0 -0
  97. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/__init__.py +0 -0
  98. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/block001.py +0 -0
  99. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/__init__.py +0 -0
  100. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/test_block_001.py +0 -0
  101. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/quickstart/__init__.py +0 -0
  102. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/quickstart/setup/__init__.py +0 -0
  103. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/quickstart/setup/block001.sh +0 -0
  104. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/quickstart/setup/block002.py +0 -0
  105. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/quickstart/setup/tests/__init__.py +0 -0
  106. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/quickstart/setup/tests/test_block002.py +0 -0
  107. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/quickstart/tutorial/__init__.py +0 -0
  108. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/quickstart/tutorial/block001.py +0 -0
  109. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/quickstart/tutorial/block002.py +0 -0
  110. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/quickstart/tutorial/block003.py +0 -0
  111. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/quickstart/tutorial/tests/__init__.py +0 -0
  112. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/quickstart/tutorial/tests/test_block001.py +0 -0
  113. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/quickstart/tutorial/tests/test_block002.py +0 -0
  114. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/quickstart/tutorial/tests/test_block003.py +0 -0
  115. {cadwyn-5.4.2 → cadwyn-5.4.4}/docs_src/ruff.toml +0 -0
  116. {cadwyn-5.4.2 → cadwyn-5.4.4}/mkdocs.yml +0 -0
  117. {cadwyn-5.4.2 → cadwyn-5.4.4}/ruff.toml +0 -0
  118. {cadwyn-5.4.2 → cadwyn-5.4.4}/scripts/fix_links.py +0 -0
  119. {cadwyn-5.4.2 → cadwyn-5.4.4}/scripts/split_md.py +0 -0
  120. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/__init__.py +0 -0
  121. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_data/__init__.py +0 -0
  122. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_data/unversioned_schema_dir/__init__.py +0 -0
  123. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_data/unversioned_schema_dir/unversioned_schemas.py +0 -0
  124. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_data/unversioned_schemas.py +0 -0
  125. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_resources/__init__.py +0 -0
  126. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_resources/app_for_testing_routing.py +0 -0
  127. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_resources/render/__init__.py +0 -0
  128. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_resources/render/classes.py +0 -0
  129. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_resources/render/complex/__init__.py +0 -0
  130. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_resources/render/complex/classes.py +0 -0
  131. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_resources/render/complex/versions.py +0 -0
  132. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_resources/render/versions.py +0 -0
  133. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_resources/utils.py +0 -0
  134. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_resources/versioned_app/__init__.py +0 -0
  135. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_resources/versioned_app/app.py +0 -0
  136. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_resources/versioned_app/v2021_01_01.py +0 -0
  137. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_resources/versioned_app/v2022_01_02.py +0 -0
  138. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/_resources/versioned_app/webhooks.py +0 -0
  139. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/conftest.py +0 -0
  140. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/test_applications.py +0 -0
  141. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/test_auth_dependencies.py +0 -0
  142. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/test_changelog.py +0 -0
  143. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/test_cli.py +0 -0
  144. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/test_data_migrations.py +0 -0
  145. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/test_render.py +0 -0
  146. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/test_router_generation.py +0 -0
  147. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/test_router_generation_with_from_future_annotations.py +0 -0
  148. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/test_routing.py +0 -0
  149. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/test_schema_generation/__init__.py +0 -0
  150. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/test_schema_generation/test_enum.py +0 -0
  151. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/test_schema_generation/test_schema.py +0 -0
  152. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/test_schema_generation/test_schema_validator.py +0 -0
  153. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/test_schema_generation/test_schema_with_future_annotations.py +0 -0
  154. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/test_structure.py +0 -0
  155. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/tutorial/__init__.py +0 -0
  156. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/tutorial/main.py +0 -0
  157. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/tutorial/test_example.py +0 -0
  158. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/versioning_styles/__init__.py +0 -0
  159. {cadwyn-5.4.2 → cadwyn-5.4.4}/tests/versioning_styles/test_versioning_formats.py +0 -0
  160. {cadwyn-5.4.2 → cadwyn-5.4.4}/tox.ini +0 -0
@@ -0,0 +1,4 @@
1
+ {
2
+ "projectName": "cadwyn",
3
+ "projectOwner": "zmievsa"
4
+ }
@@ -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.4.4]
9
+
10
+ ### Fixed
11
+
12
+ * Fixed KeyError when versioning Pydantic models containing ClassVar annotations
13
+
14
+ ## [5.4.3]
15
+
16
+ ### Fixed
17
+
18
+ * XSS vulnerability in `/docs` and `/redoc` endpoints where the `version` parameter was not properly sanitized, allowing potential cross-site scripting attacks
19
+
8
20
  ## [5.4.2]
9
21
 
10
22
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cadwyn
3
- Version: 5.4.2
3
+ Version: 5.4.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
@@ -88,3 +88,26 @@ The [documentation](https://docs.cadwyn.dev) has everything you need to succeed.
88
88
  ## Sponsors
89
89
 
90
90
  These are our gorgeous sponsors. They are using Cadwyn and are sponsoring it through various means. Contact [me](https://github.com/zmievsa) if you would like to become one, too!
91
+
92
+ ## Contributors
93
+
94
+ <details>
95
+
96
+ <summary>Thanks goes to these wonderful people:</summary>
97
+ <a href="https://allcontributors.org/docs/en/emoji-key">Emoji Key </a>
98
+
99
+ <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
100
+ <!-- prettier-ignore-start -->
101
+ <!-- markdownlint-disable -->
102
+
103
+ <!-- markdownlint-restore -->
104
+ <!-- prettier-ignore-end -->
105
+
106
+ <!-- ALL-CONTRIBUTORS-LIST:END -->
107
+
108
+ This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification.
109
+ Contributions are welcome!
110
+
111
+ </details>
112
+
113
+ <!-- contributors-end -->
@@ -40,3 +40,26 @@ The [documentation](https://docs.cadwyn.dev) has everything you need to succeed.
40
40
  ## Sponsors
41
41
 
42
42
  These are our gorgeous sponsors. They are using Cadwyn and are sponsoring it through various means. Contact [me](https://github.com/zmievsa) if you would like to become one, too!
43
+
44
+ ## Contributors
45
+
46
+ <details>
47
+
48
+ <summary>Thanks goes to these wonderful people:</summary>
49
+ <a href="https://allcontributors.org/docs/en/emoji-key">Emoji Key </a>
50
+
51
+ <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
52
+ <!-- prettier-ignore-start -->
53
+ <!-- markdownlint-disable -->
54
+
55
+ <!-- markdownlint-restore -->
56
+ <!-- prettier-ignore-end -->
57
+
58
+ <!-- ALL-CONTRIBUTORS-LIST:END -->
59
+
60
+ This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification.
61
+ Contributions are welcome!
62
+
63
+ </details>
64
+
65
+ <!-- contributors-end -->
@@ -5,6 +5,7 @@ from datetime import date
5
5
  from logging import getLogger
6
6
  from pathlib import Path
7
7
  from typing import TYPE_CHECKING, Annotated, Any, Optional, Union, cast
8
+ from urllib.parse import quote
8
9
 
9
10
  import fastapi
10
11
  from fastapi import APIRouter, FastAPI, HTTPException, routing
@@ -389,7 +390,7 @@ class Cadwyn(FastAPI):
389
390
 
390
391
  if version:
391
392
  root_path = self._extract_root_path(req)
392
- openapi_url = root_path + f"{self.openapi_url}?version={version}"
393
+ openapi_url = root_path + f"{self.openapi_url}?version={quote(version, safe='')}"
393
394
  oauth2_redirect_url = self.swagger_ui_oauth2_redirect_url
394
395
  if oauth2_redirect_url:
395
396
  oauth2_redirect_url = root_path + oauth2_redirect_url
@@ -407,7 +408,7 @@ class Cadwyn(FastAPI):
407
408
 
408
409
  if version:
409
410
  root_path = self._extract_root_path(req)
410
- openapi_url = root_path + f"{self.openapi_url}?version={version}"
411
+ openapi_url = root_path + f"{self.openapi_url}?version={quote(version, safe='')}"
411
412
  return get_redoc_html(openapi_url=openapi_url, title=f"{self.title} - ReDoc")
412
413
 
413
414
  return self._render_docs_dashboard(req, docs_url=cast("str", self.redoc_url))
@@ -14,6 +14,7 @@ from functools import cache
14
14
  from typing import (
15
15
  TYPE_CHECKING,
16
16
  Annotated,
17
+ ClassVar,
17
18
  Generic,
18
19
  Union,
19
20
  _BaseGenericAlias, # pyright: ignore[reportAttributeAccessIssue]
@@ -41,6 +42,7 @@ from pydantic._internal._decorators import (
41
42
  from pydantic._internal._known_annotated_metadata import collect_known_metadata
42
43
  from pydantic._internal._typing_extra import try_eval_type as pydantic_try_eval_type
43
44
  from pydantic.fields import ComputedFieldInfo, FieldInfo
45
+ from pydantic_core import PydanticUndefined
44
46
  from typing_extensions import (
45
47
  Any,
46
48
  Doc,
@@ -316,7 +318,7 @@ def _wrap_pydantic_model(model: type[_T_PYDANTIC_MODEL]) -> "_PydanticModelWrapp
316
318
  field_name,
317
319
  )
318
320
  for field_name in model.__annotations__
319
- if field_name in defined_fields
321
+ if field_name in defined_fields and field_name in model.model_fields
320
322
  }
321
323
 
322
324
  main_attributes = fields | validators
@@ -592,7 +594,12 @@ class _AnnotationTransformer:
592
594
  from typing_inspection.typing_objects import is_any, is_newtype, is_typealiastype
593
595
 
594
596
  if isinstance(annotation, (types.GenericAlias, _BaseGenericAlias)):
595
- return get_origin(annotation)[tuple(self.change_version_of_annotation(arg) for arg in get_args(annotation))]
597
+ origin = get_origin(annotation)
598
+ args = get_args(annotation)
599
+ # Classvar does not support generic tuple arguments
600
+ if origin is ClassVar:
601
+ return ClassVar[self.change_version_of_annotation(args[0])]
602
+ return origin[tuple(self.change_version_of_annotation(arg) for arg in get_args(annotation))]
596
603
  elif is_typealiastype(annotation):
597
604
  if (
598
605
  annotation.__module__ is not None and (annotation.__module__.startswith("pydantic."))
@@ -948,11 +955,20 @@ def _add_field_to_model(
948
955
  f'in "{version_change_name}" but there is already a field with that name.',
949
956
  )
950
957
 
951
- field = PydanticFieldWrapper(
952
- alter_schema_instruction.field, alter_schema_instruction.field.annotation, alter_schema_instruction.name
953
- )
954
- model.fields[alter_schema_instruction.name] = field
955
- model.annotations[alter_schema_instruction.name] = alter_schema_instruction.field.annotation
958
+ # Special handling for ClassVar fields
959
+ if get_origin(alter_schema_instruction.field.annotation) is ClassVar:
960
+ # ClassVar fields should not be in model.fields, only in annotations and other_attributes
961
+ model.annotations[alter_schema_instruction.name] = alter_schema_instruction.field.annotation
962
+ # Set the actual ClassVar value in other_attributes
963
+ if alter_schema_instruction.field.default is not PydanticUndefined:
964
+ model.other_attributes[alter_schema_instruction.name] = alter_schema_instruction.field.default
965
+ else:
966
+ # Regular field handling
967
+ field = PydanticFieldWrapper(
968
+ alter_schema_instruction.field, alter_schema_instruction.field.annotation, alter_schema_instruction.name
969
+ )
970
+ model.fields[alter_schema_instruction.name] = field
971
+ model.annotations[alter_schema_instruction.name] = alter_schema_instruction.field.annotation
956
972
 
957
973
 
958
974
  def _change_field_in_model(
@@ -1085,6 +1101,11 @@ def _delete_field_from_model(model: _PydanticModelWrapper, field_name: str, vers
1085
1101
  validator = model.validators[field_name]
1086
1102
  model.validators[field_name].is_deleted = True
1087
1103
  model.annotations.pop(field_name, None)
1104
+ elif field_name in model.annotations and get_origin(model.annotations[field_name]) is ClassVar:
1105
+ # Handle ClassVar fields - they exist in annotations but not in model.fields
1106
+ model.annotations.pop(field_name)
1107
+ # Also remove the attribute from other_attributes if it exists there
1108
+ model.other_attributes.pop(field_name, None)
1088
1109
  else:
1089
1110
  raise InvalidGenerationInstructionError(
1090
1111
  f'You tried to delete a field "{field_name}" from "{model.name}" '
@@ -22,6 +22,7 @@ versions on your system.
22
22
  4. Make your changes
23
23
  5. Commit your changes to git.
24
24
  6. Open a [pull request](https://docs.github.com/en/pull-requests). Give the pull request a descriptive title indicating what it changes.
25
+ 7. Add yourself as a contributor using the [all-contributors bot](https://allcontributors.org/docs/en/bot/usage)
25
26
 
26
27
  ## Guidelines for writing code
27
28
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cadwyn"
3
- version = "5.4.2"
3
+ version = "5.4.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"
@@ -1,6 +1,6 @@
1
1
  import re
2
2
  from enum import Enum, auto
3
- from typing import Annotated, Any, Literal, Union
3
+ from typing import Annotated, Any, ClassVar, Literal, Union, get_origin
4
4
 
5
5
  import pytest
6
6
  from pydantic import BaseModel, Field, StringConstraints, ValidationError, computed_field, conint, constr
@@ -807,3 +807,81 @@ def test__schema_with_computed_field__remove_computed_field(create_runtime_schem
807
807
 
808
808
  assert not hasattr(old_instance, "image_url")
809
809
  assert "image_url" not in old_instance.model_dump()
810
+
811
+
812
+ def test__schema_with_classvar__should_be_recreated_in_older_version(create_runtime_schemas: CreateRuntimeSchemas):
813
+ class SchemaWithClassVar(BaseModel):
814
+ regular_field: str
815
+ class_level_config: ClassVar[str] = "default_config"
816
+
817
+ schemas = create_runtime_schemas(version_change())
818
+
819
+ latest_model = schemas["2001-01-01"][SchemaWithClassVar]
820
+ old_model = schemas["2000-01-01"][SchemaWithClassVar]
821
+
822
+ latest_instance = latest_model(regular_field="test")
823
+ old_instance = old_model(regular_field="test")
824
+
825
+ assert latest_model.class_level_config == "default_config"
826
+ assert old_model.class_level_config == "default_config"
827
+
828
+ assert "class_level_config" not in latest_instance.model_dump()
829
+ assert "class_level_config" not in old_instance.model_dump()
830
+
831
+ assert latest_instance.regular_field == "test"
832
+ assert old_instance.regular_field == "test"
833
+
834
+ assert "class_level_config" in latest_model.__annotations__
835
+ assert "class_level_config" in old_model.__annotations__
836
+
837
+ assert get_origin(latest_model.__annotations__["class_level_config"]) is ClassVar
838
+ assert get_origin(old_model.__annotations__["class_level_config"]) is ClassVar
839
+
840
+
841
+ def test__schema_with_classvar__remove_classvar(create_runtime_schemas: CreateRuntimeSchemas):
842
+ class SchemaWithClassVar(BaseModel):
843
+ regular_field: str
844
+ class_level_config: ClassVar[str] = "default_config"
845
+
846
+ schemas = create_runtime_schemas(version_change(schema(SchemaWithClassVar).field("class_level_config").didnt_exist))
847
+
848
+ latest_model = schemas["2001-01-01"][SchemaWithClassVar]
849
+ latest_instance = latest_model(regular_field="test")
850
+
851
+ assert latest_model.class_level_config == "default_config"
852
+ assert "class_level_config" not in latest_instance.model_dump()
853
+
854
+ old_model = schemas["2000-01-01"][SchemaWithClassVar]
855
+ old_instance = old_model(regular_field="test")
856
+
857
+ assert not hasattr(old_model, "class_level_config")
858
+ assert "class_level_config" not in old_instance.model_dump()
859
+
860
+ assert latest_instance.regular_field == "test"
861
+ assert old_instance.regular_field == "test"
862
+
863
+
864
+ def test__schema_with_classvar__add_classvar_field(create_runtime_schemas: CreateRuntimeSchemas):
865
+ class SchemaWithoutClassVar(BaseModel):
866
+ regular_field: str
867
+
868
+ schemas = create_runtime_schemas(
869
+ version_change(),
870
+ version_change(
871
+ schema(SchemaWithoutClassVar)
872
+ .field("new_config")
873
+ .existed_as(type=ClassVar[str], info=Field(default="added_config")),
874
+ schema(SchemaWithoutClassVar).field("new_config_without_value").existed_as(type=ClassVar[str]),
875
+ ),
876
+ )
877
+ latest_model = schemas["2002-01-01"][SchemaWithoutClassVar]
878
+ mid_model = schemas["2001-01-01"][SchemaWithoutClassVar]
879
+ old_model = schemas["2000-01-01"][SchemaWithoutClassVar]
880
+
881
+ assert not hasattr(latest_model, "new_config")
882
+ assert mid_model.new_config == "added_config" # pyright: ignore[reportAttributeAccessIssue]
883
+ assert old_model.new_config == "added_config" # pyright: ignore[reportAttributeAccessIssue]
884
+
885
+ assert not hasattr(latest_model, "new_config_without_value")
886
+ assert not hasattr(mid_model, "new_config_without_value")
887
+ assert not hasattr(old_model, "new_config_without_value")
@@ -98,7 +98,7 @@ wheels = [
98
98
 
99
99
  [[package]]
100
100
  name = "cadwyn"
101
- version = "5.4.2"
101
+ version = "5.4.4"
102
102
  source = { editable = "." }
103
103
  dependencies = [
104
104
  { name = "backports-strenum", marker = "python_full_version < '3.11'" },
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes