cadwyn 4.3.0__tar.gz → 4.4.0__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 (133) hide show
  1. {cadwyn-4.3.0 → cadwyn-4.4.0}/.github/actions/setup-python-uv/action.yaml +4 -4
  2. {cadwyn-4.3.0 → cadwyn-4.4.0}/.github/workflows/ci.yaml +35 -10
  3. cadwyn-4.4.0/.github/workflows/daily_tests.yaml +33 -0
  4. {cadwyn-4.3.0 → cadwyn-4.4.0}/.github/workflows/validate_links.yaml +9 -4
  5. {cadwyn-4.3.0 → cadwyn-4.4.0}/CHANGELOG.md +13 -0
  6. {cadwyn-4.3.0 → cadwyn-4.4.0}/Makefile +1 -1
  7. {cadwyn-4.3.0 → cadwyn-4.4.0}/PKG-INFO +2 -2
  8. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/applications.py +30 -6
  9. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/route_generation.py +36 -12
  10. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/quickstart/setup.md +1 -1
  11. cadwyn-4.4.0/docs_src/quickstart/setup/block001.sh +1 -0
  12. {cadwyn-4.3.0 → cadwyn-4.4.0}/pyproject.toml +2 -3
  13. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_resources/versioned_app/app.py +3 -5
  14. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_resources/versioned_app/webhooks.py +1 -1
  15. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/conftest.py +1 -0
  16. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/test_applications.py +67 -7
  17. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/test_router_generation.py +17 -17
  18. {cadwyn-4.3.0 → cadwyn-4.4.0}/uv.lock +6 -6
  19. {cadwyn-4.3.0 → cadwyn-4.4.0}/.github/CODE_OF_CONDUCT.md +0 -0
  20. {cadwyn-4.3.0 → cadwyn-4.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  21. {cadwyn-4.3.0 → cadwyn-4.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  22. {cadwyn-4.3.0 → cadwyn-4.4.0}/.github/workflows/publish_docs.yaml +0 -0
  23. {cadwyn-4.3.0 → cadwyn-4.4.0}/.github/workflows/release.yaml +0 -0
  24. {cadwyn-4.3.0 → cadwyn-4.4.0}/.gitignore +0 -0
  25. {cadwyn-4.3.0 → cadwyn-4.4.0}/.pre-commit-config.yaml +0 -0
  26. {cadwyn-4.3.0 → cadwyn-4.4.0}/LICENSE +0 -0
  27. {cadwyn-4.3.0 → cadwyn-4.4.0}/README.md +0 -0
  28. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/__init__.py +0 -0
  29. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/__main__.py +0 -0
  30. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/_asts.py +0 -0
  31. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/_importer.py +0 -0
  32. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/_render.py +0 -0
  33. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/_utils.py +0 -0
  34. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/changelogs.py +0 -0
  35. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/exceptions.py +0 -0
  36. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/middleware.py +0 -0
  37. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/py.typed +0 -0
  38. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/routing.py +0 -0
  39. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/schema_generation.py +0 -0
  40. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/static/__init__.py +0 -0
  41. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/static/docs.html +0 -0
  42. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/structure/__init__.py +0 -0
  43. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/structure/common.py +0 -0
  44. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/structure/data.py +0 -0
  45. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/structure/endpoints.py +0 -0
  46. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/structure/enums.py +0 -0
  47. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/structure/schemas.py +0 -0
  48. {cadwyn-4.3.0 → cadwyn-4.4.0}/cadwyn/structure/versions.py +0 -0
  49. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/CNAME +0 -0
  50. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/__init__.py +0 -0
  51. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/concepts/api_version_header_and_context_variables.md +0 -0
  52. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/concepts/beware_of_data_versioning.md +0 -0
  53. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/concepts/changelogs.md +0 -0
  54. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/concepts/cli.md +0 -0
  55. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/concepts/endpoint_migrations.md +0 -0
  56. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/concepts/enum_migrations.md +0 -0
  57. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/concepts/index.md +0 -0
  58. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/concepts/main_app.md +0 -0
  59. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/concepts/methodology.md +0 -0
  60. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/concepts/schema_generation.md +0 -0
  61. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/concepts/schema_migrations.md +0 -0
  62. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/concepts/testing.md +0 -0
  63. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/concepts/version_changes.md +0 -0
  64. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/home/CONTRIBUTING.md +0 -0
  65. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/how_to/change_business_logic/index.md +0 -0
  66. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/how_to/change_endpoints/index.md +0 -0
  67. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/how_to/change_openapi_schemas/add_field.md +0 -0
  68. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/how_to/change_openapi_schemas/change_field_type.md +0 -0
  69. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/how_to/change_openapi_schemas/changing_constraints.md +0 -0
  70. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/how_to/change_openapi_schemas/remove_field.md +0 -0
  71. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/how_to/change_openapi_schemas/rename_a_field_in_schema.md +0 -0
  72. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/how_to/index.md +0 -0
  73. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/img/dashboard_with_one_version.png +0 -0
  74. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/img/dashboard_with_two_versions.png +0 -0
  75. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/img/get_users_endpoint_from_prior_version.png +0 -0
  76. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/img/simplified_migration_model.png +0 -0
  77. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/img/sponsor_logos/monite.png +0 -0
  78. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/img/unversioned_dashboard.png +0 -0
  79. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/index.md +0 -0
  80. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/plugin.py +0 -0
  81. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/quickstart/tutorial.md +0 -0
  82. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/theory/how_to_build_versioning_framework.md +0 -0
  83. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/theory/how_we_got_here.md +0 -0
  84. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs/theory/literature.md +0 -0
  85. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs_src/__init__.py +0 -0
  86. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs_src/quickstart/__init__.py +0 -0
  87. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs_src/quickstart/setup/__init__.py +0 -0
  88. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs_src/quickstart/setup/block002.py +0 -0
  89. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs_src/quickstart/setup/tests/__init__.py +0 -0
  90. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs_src/quickstart/setup/tests/test_block002.py +0 -0
  91. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs_src/quickstart/tutorial/__init__.py +0 -0
  92. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs_src/quickstart/tutorial/block001.py +0 -0
  93. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs_src/quickstart/tutorial/block002.py +0 -0
  94. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs_src/quickstart/tutorial/block003.py +0 -0
  95. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs_src/quickstart/tutorial/tests/__init__.py +0 -0
  96. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs_src/quickstart/tutorial/tests/test_block001.py +0 -0
  97. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs_src/quickstart/tutorial/tests/test_block002.py +0 -0
  98. {cadwyn-4.3.0 → cadwyn-4.4.0}/docs_src/quickstart/tutorial/tests/test_block003.py +0 -0
  99. {cadwyn-4.3.0 → cadwyn-4.4.0}/mkdocs.yml +0 -0
  100. {cadwyn-4.3.0 → cadwyn-4.4.0}/ruff.toml +0 -0
  101. {cadwyn-4.3.0 → cadwyn-4.4.0}/scripts/fix_links.py +0 -0
  102. {cadwyn-4.3.0 → cadwyn-4.4.0}/scripts/split_md.py +0 -0
  103. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/__init__.py +0 -0
  104. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_data/__init__.py +0 -0
  105. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_data/unversioned_schema_dir/__init__.py +0 -0
  106. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_data/unversioned_schema_dir/unversioned_schemas.py +0 -0
  107. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_data/unversioned_schemas.py +0 -0
  108. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_resources/__init__.py +0 -0
  109. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_resources/app_for_testing_routing.py +0 -0
  110. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_resources/render/__init__.py +0 -0
  111. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_resources/render/classes.py +0 -0
  112. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_resources/render/complex/__init__.py +0 -0
  113. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_resources/render/complex/classes.py +0 -0
  114. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_resources/render/complex/versions.py +0 -0
  115. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_resources/render/versions.py +0 -0
  116. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_resources/utils.py +0 -0
  117. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_resources/versioned_app/__init__.py +0 -0
  118. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_resources/versioned_app/v2021_01_01.py +0 -0
  119. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/_resources/versioned_app/v2022_01_02.py +0 -0
  120. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/test_changelog.py +0 -0
  121. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/test_cli.py +0 -0
  122. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/test_data_migrations.py +0 -0
  123. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/test_render.py +0 -0
  124. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/test_routing.py +0 -0
  125. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/test_schema_generation/__init__.py +0 -0
  126. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/test_schema_generation/test_enum.py +0 -0
  127. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/test_schema_generation/test_schema.py +0 -0
  128. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/test_schema_generation/test_schema_field.py +0 -0
  129. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/test_schema_generation/test_schema_validator.py +0 -0
  130. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/test_structure.py +0 -0
  131. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/tutorial/__init__.py +0 -0
  132. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/tutorial/main.py +0 -0
  133. {cadwyn-4.3.0 → cadwyn-4.4.0}/tests/tutorial/test_example.py +0 -0
@@ -13,15 +13,15 @@ inputs:
13
13
  runs:
14
14
  using: "composite"
15
15
  steps:
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ inputs.python-version }}
16
19
  - uses: astral-sh/setup-uv@v3
17
20
  with:
18
21
  version: ${{ inputs.uv-version }}
19
22
  enable-cache: true
20
23
  cache-dependency-glob: "uv.lock"
21
- - uses: actions/setup-python@v5
22
- with:
23
- python-version: ${{ inputs.python-version }}
24
24
  - run: |
25
- uv sync --inexact --frozen --all-extras --dev
25
+ uv sync --frozen --all-extras --dev
26
26
  echo "$(pwd)/.venv/bin" >> $GITHUB_PATH
27
27
  shell: bash
@@ -38,32 +38,57 @@ jobs:
38
38
  - uses: ./.github/actions/setup-python-uv
39
39
  with:
40
40
  python-version: ${{ matrix.python-version }}
41
- - run: uv run coverage run --source=. -m pytest .
41
+ - run: uv run coverage run --source=. --parallel-mode -m pytest tests
42
42
  - name: Upload coverage results
43
43
  uses: actions/upload-artifact@v3
44
44
  if: matrix.os == 'ubuntu-latest' # Cross-platform coverage combination doesn't work
45
45
  with:
46
- name: coverage-results
46
+ name: main-tests-coverage-results
47
+ path: coverage/
48
+ Tutorial-tests:
49
+ runs-on: ubuntu-latest
50
+ name: Tutorial tests
51
+ steps:
52
+ - uses: actions/checkout@v4
53
+ - uses: actions/setup-python@v5
54
+ with:
55
+ python-version: "3.10"
56
+ - name: Install cadwyn with instructions from docs
57
+ run: sh docs_src/quickstart/setup/block001.sh
58
+ - run: pip install pytest coverage dirty-equals
59
+ - run: coverage run --source=. --parallel-mode -m pytest docs_src
60
+ - name: Upload coverage results
61
+ uses: actions/upload-artifact@v3
62
+ with:
63
+ name: docs-tests-coverage-results
47
64
  path: coverage/
48
65
  Coverage:
49
- needs: Tests
66
+ needs: [Tests, Tutorial-tests]
50
67
  runs-on: ubuntu-latest
51
68
  steps:
52
69
  - uses: actions/checkout@v4
53
- - uses: actions/download-artifact@v3
70
+ - name: Download main tests coverage info
71
+ uses: actions/download-artifact@v3
54
72
  with:
55
- name: coverage-results
73
+ name: main-tests-coverage-results
56
74
  path: coverage/
57
- - uses: ./.github/actions/setup-python-uv
58
- - run: ls -all
59
- - run: uv run coverage combine
60
- - run: uv run coverage xml
75
+ - name: Download docs tests coverage info
76
+ uses: actions/download-artifact@v3
77
+ with:
78
+ name: docs-tests-coverage-results
79
+ path: coverage/
80
+ - uses: actions/setup-python@v5
81
+ with:
82
+ python-version: "3.10"
83
+ - run: pip install 'coverage[toml]'
84
+ - run: coverage combine
85
+ - run: coverage xml
61
86
  - name: Upload to Codecov
62
87
  uses: codecov/codecov-action@v4
63
88
  env:
64
89
  fail_ci_if_error: true
65
90
  CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
66
- - run: uv run coverage report --fail-under=100
91
+ - run: coverage report --fail-under=100
67
92
 
68
93
  Lint:
69
94
  runs-on: ubuntu-latest
@@ -0,0 +1,33 @@
1
+ name: Twice Daily Package Update Testing
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "0 0 * * *"
6
+ - cron: "0 12 * * *"
7
+ workflow_dispatch: # Allows manual triggering of the workflow
8
+
9
+ jobs:
10
+ update-dependencies-and-test:
11
+ name: Update dependencies and run tests
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: ./.github/actions/setup-python-uv
17
+ - run: uv sync --refresh --all-extras --dev --upgrade
18
+ - run: pytest .
19
+
20
+ notify-on-failure:
21
+ name: Notify on failure
22
+ runs-on: ubuntu-latest
23
+ needs: update-dependencies-and-test
24
+ if: failure()
25
+ steps:
26
+ - name: Send Telegram Notification on Failure
27
+ uses: appleboy/telegram-action@master
28
+ with:
29
+ to: 438153389
30
+ token: ${{ secrets.TELEGRAM_TOKEN }}
31
+ message: |
32
+ New version of something broke Cadwyn!
33
+ ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
@@ -21,10 +21,15 @@ jobs:
21
21
  runs-on: ubuntu-latest
22
22
  steps:
23
23
  - uses: actions/checkout@v4
24
- - uses:
25
- ./.github/actions/setup-python-uv
26
- # TODO: Validate that the command exits with code 0 and has no ERROR in logs (both)
27
- - run: uv run mkdocs build
24
+ - uses: ./.github/actions/setup-python-uv
25
+ - run: |
26
+ OUTPUT=$(uv run mkdocs build 2>&1)
27
+ echo "$OUTPUT"
28
+
29
+ if echo "$OUTPUT" | grep -q "ERROR"; then
30
+ exit 1
31
+ fi
32
+
28
33
  markdown-link-check:
29
34
  runs-on: ubuntu-latest
30
35
  steps:
@@ -5,6 +5,19 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [4.4.0]
9
+
10
+ ### Added
11
+
12
+ * Support for [webhooks](https://fastapi.tiangolo.com/advanced/openapi-webhooks/) in swagger
13
+ * Automatic generation of versioned routes and webhooks upon the first request to Cadwyn. Notice that if you were using some of cadwyn's internal interfaces, this might break your code. If it did, make an issue and let's discuss your use case
14
+
15
+ ## [4.3.1]
16
+
17
+ ### Fixed
18
+
19
+ * Removed typer from main dependencies
20
+
8
21
  ## [4.3.0]
9
22
 
10
23
  ### Changed
@@ -2,7 +2,7 @@ SHELL := /bin/bash
2
2
  py_warn = PYTHONDEVMODE=1
3
3
 
4
4
  install:
5
- uv sync --all-extras
5
+ uv sync --all-extras --dev
6
6
 
7
7
  lint:
8
8
  pre-commit run --all-files
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: cadwyn
3
- Version: 4.3.0
3
+ Version: 4.4.0
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
@@ -38,10 +38,10 @@ Requires-Dist: issubclass>=0.1.2
38
38
  Requires-Dist: jinja2>=3.1.2
39
39
  Requires-Dist: pydantic>=2.0.0
40
40
  Requires-Dist: starlette>=0.30.0
41
- Requires-Dist: typer>=0.7.0
42
41
  Requires-Dist: typing-extensions>=4.8.0
43
42
  Provides-Extra: standard
44
43
  Requires-Dist: fastapi[standard]>=0.112.3; extra == 'standard'
44
+ Requires-Dist: typer>=0.7.0; extra == 'standard'
45
45
  Description-Content-Type: text/markdown
46
46
 
47
47
  # Cadwyn
@@ -25,6 +25,7 @@ from starlette.routing import BaseRoute, Route
25
25
  from starlette.types import Lifespan
26
26
  from typing_extensions import Self
27
27
 
28
+ from cadwyn._utils import same_definition_as_in
28
29
  from cadwyn.changelogs import CadwynChangelogResource, _generate_changelog
29
30
  from cadwyn.middleware import HeaderVersioningMiddleware, _get_api_version_dependency
30
31
  from cadwyn.route_generation import generate_versioned_routers
@@ -96,8 +97,8 @@ class Cadwyn(FastAPI):
96
97
  **extra: Any,
97
98
  ) -> None:
98
99
  self.versions = versions
99
- # TODO: Remove argument entirely in any major version.
100
100
  self._dependency_overrides_provider = FakeDependencyOverridesProvider({})
101
+ self._cadwyn_initialized = False
101
102
 
102
103
  super().__init__(
103
104
  debug=debug,
@@ -156,6 +157,8 @@ class Cadwyn(FastAPI):
156
157
  api_version_header_name=api_version_header_name,
157
158
  api_version_var=self.versions.api_version_var,
158
159
  )
160
+ self._versioned_webhook_routers: dict[date, APIRouter] = {}
161
+ self._latest_version_router = APIRouter(dependency_overrides_provider=self._dependency_overrides_provider)
159
162
 
160
163
  self.changelog_url = changelog_url
161
164
  self.include_changelog_url_in_schema = include_changelog_url_in_schema
@@ -176,6 +179,26 @@ class Cadwyn(FastAPI):
176
179
  default_response_class=default_response_class,
177
180
  )
178
181
 
182
+ @same_definition_as_in(FastAPI.__call__)
183
+ async def __call__(self, scope: Any, receive: Any, send: Any) -> None:
184
+ if not self._cadwyn_initialized:
185
+ self._cadwyn_initialize()
186
+ self.__call__ = super().__call__
187
+ await self.__call__(scope, receive, send)
188
+
189
+ def _cadwyn_initialize(self) -> None:
190
+ generated_routers = generate_versioned_routers(
191
+ self._latest_version_router,
192
+ webhooks=self.webhooks,
193
+ versions=self.versions,
194
+ )
195
+ for version, router in generated_routers.endpoints.items():
196
+ self.add_header_versioned_routers(router, header_value=version.isoformat())
197
+
198
+ for version, router in generated_routers.webhooks.items():
199
+ self._versioned_webhook_routers[version] = router
200
+ self._cadwyn_initialized = True
201
+
179
202
  def _add_default_versioned_routers(self) -> None:
180
203
  for version in self.versions:
181
204
  self.router.versioned_routers[version.value] = APIRouter(**self._kwargs_to_router)
@@ -240,12 +263,8 @@ class Cadwyn(FastAPI):
240
263
  )
241
264
 
242
265
  def generate_and_include_versioned_routers(self, *routers: APIRouter) -> None:
243
- root_router = APIRouter(dependency_overrides_provider=self._dependency_overrides_provider)
244
266
  for router in routers:
245
- root_router.include_router(router)
246
- router_versions = generate_versioned_routers(root_router, versions=self.versions)
247
- for version, router in router_versions.items():
248
- self.add_header_versioned_routers(router, header_value=version.isoformat())
267
+ self._latest_version_router.include_router(router)
249
268
 
250
269
  async def openapi_jsons(self, req: Request) -> JSONResponse:
251
270
  raw_version = req.query_params.get("version") or req.headers.get(self.router.api_version_header_name)
@@ -276,6 +295,10 @@ class Cadwyn(FastAPI):
276
295
  if root_path and root_path not in server_urls and self.root_path_in_servers:
277
296
  self.servers.insert(0, {"url": root_path})
278
297
 
298
+ webhook_routes = None
299
+ if version in self._versioned_webhook_routers:
300
+ webhook_routes = self._versioned_webhook_routers[version].routes
301
+
279
302
  return JSONResponse(
280
303
  get_openapi(
281
304
  title=self.title,
@@ -287,6 +310,7 @@ class Cadwyn(FastAPI):
287
310
  contact=self.contact,
288
311
  license_info=self.license_info,
289
312
  routes=routes,
313
+ webhooks=webhook_routes,
290
314
  tags=self.openapi_tags,
291
315
  servers=self.servers,
292
316
  )
@@ -7,7 +7,6 @@ from typing import (
7
7
  TYPE_CHECKING,
8
8
  Any,
9
9
  Generic,
10
- TypeVar,
11
10
  cast,
12
11
  )
13
12
 
@@ -20,7 +19,7 @@ from fastapi.routing import APIRoute
20
19
  from issubclass import issubclass as lenient_issubclass
21
20
  from pydantic import BaseModel
22
21
  from starlette.routing import BaseRoute
23
- from typing_extensions import assert_never
22
+ from typing_extensions import TypeVar, assert_never
24
23
 
25
24
  from cadwyn._utils import Sentinel
26
25
  from cadwyn.exceptions import (
@@ -48,7 +47,8 @@ if TYPE_CHECKING:
48
47
  from fastapi.dependencies.models import Dependant
49
48
 
50
49
  _Call = TypeVar("_Call", bound=Callable[..., Any])
51
- _R = TypeVar("_R", bound=fastapi.routing.APIRouter)
50
+ _R = TypeVar("_R", bound=APIRouter)
51
+ _WR = TypeVar("_WR", bound=APIRouter, default=APIRouter)
52
52
  # This is a hack we do because we can't guarantee how the user will use the router.
53
53
  _DELETED_ROUTE_TAG = "_CADWYN_DELETED_ROUTE"
54
54
 
@@ -59,8 +59,21 @@ class _EndpointInfo:
59
59
  endpoint_methods: frozenset[str]
60
60
 
61
61
 
62
- def generate_versioned_routers(router: _R, versions: VersionBundle) -> dict[VersionDate, _R]:
63
- return _EndpointTransformer(router, versions).transform()
62
+ @dataclass(slots=True, frozen=True)
63
+ class GeneratedRouters(Generic[_R, _WR]):
64
+ endpoints: dict[VersionDate, _R]
65
+ webhooks: dict[VersionDate, _WR]
66
+
67
+
68
+ def generate_versioned_routers(
69
+ router: _R,
70
+ versions: VersionBundle,
71
+ *,
72
+ webhooks: _WR | None = None,
73
+ ) -> GeneratedRouters[_R, _WR]:
74
+ if webhooks is None:
75
+ webhooks = cast(_WR, APIRouter())
76
+ return _EndpointTransformer(router, versions, webhooks).transform()
64
77
 
65
78
 
66
79
  class VersionedAPIRouter(fastapi.routing.APIRouter):
@@ -77,30 +90,36 @@ class VersionedAPIRouter(fastapi.routing.APIRouter):
77
90
  return endpoint
78
91
 
79
92
 
80
- class _EndpointTransformer(Generic[_R]):
81
- def __init__(self, parent_router: _R, versions: VersionBundle) -> None:
93
+ class _EndpointTransformer(Generic[_R, _WR]):
94
+ def __init__(self, parent_router: _R, versions: VersionBundle, webhooks: _WR) -> None:
82
95
  super().__init__()
83
96
  self.parent_router = parent_router
84
97
  self.versions = versions
98
+ self.parent_webhooks_router = webhooks
85
99
  self.schema_generators = generate_versioned_models(versions)
86
100
 
87
101
  self.routes_that_never_existed = [
88
102
  route for route in parent_router.routes if isinstance(route, APIRoute) and _DELETED_ROUTE_TAG in route.tags
89
103
  ]
90
104
 
91
- def transform(self) -> dict[VersionDate, _R]:
105
+ def transform(self) -> GeneratedRouters[_R, _WR]:
92
106
  router = deepcopy(self.parent_router)
107
+ webhook_router = deepcopy(self.parent_webhooks_router)
93
108
  routers: dict[VersionDate, _R] = {}
109
+ webhook_routers: dict[VersionDate, _WR] = {}
94
110
 
95
111
  for version in self.versions:
96
112
  self.schema_generators[str(version.value)].annotation_transformer.migrate_router_to_version(router)
113
+ self.schema_generators[str(version.value)].annotation_transformer.migrate_router_to_version(webhook_router)
97
114
 
98
115
  self._validate_all_data_converters_are_applied(router, version)
99
116
 
100
117
  routers[version.value] = router
118
+ webhook_routers[version.value] = webhook_router
101
119
  # Applying changes for the next version
102
120
  router = deepcopy(router)
103
- self._apply_endpoint_changes_to_router(router, version)
121
+ webhook_router = deepcopy(webhook_router)
122
+ self._apply_endpoint_changes_to_router(router.routes + webhook_router.routes, version)
104
123
 
105
124
  if self.routes_that_never_existed:
106
125
  raise RouterGenerationError(
@@ -146,7 +165,13 @@ class _EndpointTransformer(Generic[_R]):
146
165
  for route in router.routes
147
166
  if not (isinstance(route, fastapi.routing.APIRoute) and _DELETED_ROUTE_TAG in route.tags)
148
167
  ]
149
- return routers
168
+ for _, webhook_router in webhook_routers.items():
169
+ webhook_router.routes = [
170
+ route
171
+ for route in webhook_router.routes
172
+ if not (isinstance(route, fastapi.routing.APIRoute) and _DELETED_ROUTE_TAG in route.tags)
173
+ ]
174
+ return GeneratedRouters(routers, webhook_routers)
150
175
 
151
176
  def _validate_all_data_converters_are_applied(self, router: APIRouter, version: Version):
152
177
  path_to_route_methods_mapping, head_response_models, head_request_bodies = self._extract_all_routes_identifiers(
@@ -223,10 +248,9 @@ class _EndpointTransformer(Generic[_R]):
223
248
  # TODO (https://github.com/zmievsa/cadwyn/issues/28): Simplify
224
249
  def _apply_endpoint_changes_to_router( # noqa: C901
225
250
  self,
226
- router: fastapi.routing.APIRouter,
251
+ routes: list[BaseRoute] | list[APIRoute],
227
252
  version: Version,
228
253
  ):
229
- routes = router.routes
230
254
  for version_change in version.changes:
231
255
  for instruction in version_change.alter_endpoint_instructions:
232
256
  original_routes = _get_routes(
@@ -6,7 +6,7 @@ Cadwyn is built around FastAPI and supports all of its functionality out of the
6
6
  ## Installation
7
7
 
8
8
  ```bash
9
- pip install 'cadwyn[standard]'
9
+ {!> ../docs_src/quickstart/setup/block001.sh !}
10
10
  ```
11
11
 
12
12
  ## The basics
@@ -0,0 +1 @@
1
+ pip install 'cadwyn[standard]'
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cadwyn"
3
- version = "4.3.0"
3
+ version = "4.4.0"
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"
@@ -50,7 +50,6 @@ dependencies = [
50
50
  "fastapi >=0.112.3",
51
51
  "starlette >=0.30.0",
52
52
  "pydantic >=2.0.0",
53
- "typer >=0.7.0",
54
53
  "jinja2 >=3.1.2",
55
54
  "issubclass >=0.1.2",
56
55
  "backports-strenum >=1.3.1,<2; python_version < '3.11'",
@@ -58,7 +57,7 @@ dependencies = [
58
57
  ]
59
58
 
60
59
  [project.optional-dependencies]
61
- standard = ["fastapi[standard]>=0.112.3"]
60
+ standard = ["fastapi[standard]>=0.112.3", "typer>=0.7.0"]
62
61
 
63
62
  [project.urls]
64
63
  "Source code" = "https://github.com/zmievsa/cadwyn"
@@ -8,10 +8,9 @@ from fastapi.testclient import TestClient
8
8
 
9
9
  from cadwyn import Cadwyn
10
10
  from cadwyn.structure.versions import Version, VersionBundle
11
- from tests._resources.utils import BASIC_HEADERS
12
11
  from tests._resources.versioned_app.v2021_01_01 import router as v2021_01_01_router
13
12
  from tests._resources.versioned_app.v2022_01_02 import router as v2022_01_02_router
14
- from tests._resources.versioned_app.webhooks import router as webhooks_router
13
+ from tests._resources.versioned_app.webhooks import router as unversioned_router
15
14
 
16
15
 
17
16
  # TODO: Add better tests for covering lifespan
@@ -23,17 +22,16 @@ async def lifespan(app: FastAPI):
23
22
  versioned_app = Cadwyn(versions=VersionBundle(Version(date(2021, 1, 1))), lifespan=lifespan)
24
23
  versioned_app.add_header_versioned_routers(v2021_01_01_router, header_value="2021-01-01")
25
24
  versioned_app.add_header_versioned_routers(v2022_01_02_router, header_value="2022-02-02")
26
- versioned_app.include_router(webhooks_router)
25
+ versioned_app.include_router(unversioned_router)
27
26
 
28
27
  versioned_app_with_custom_api_version_var = Cadwyn(
29
28
  versions=VersionBundle(Version(date(2021, 1, 1))), lifespan=lifespan, api_version_var=ContextVar("My api version")
30
29
  )
31
30
  versioned_app_with_custom_api_version_var.add_header_versioned_routers(v2021_01_01_router, header_value="2021-01-01")
32
31
  versioned_app_with_custom_api_version_var.add_header_versioned_routers(v2022_01_02_router, header_value="2022-02-02")
33
- versioned_app_with_custom_api_version_var.include_router(webhooks_router)
32
+ versioned_app_with_custom_api_version_var.include_router(unversioned_router)
34
33
 
35
34
  # TODO: We should not have any clients that are run like this. Instead, all of them must run using "with"
36
- client = TestClient(versioned_app, raise_server_exceptions=False, headers=BASIC_HEADERS)
37
35
  client_without_headers = TestClient(versioned_app)
38
36
  client_without_headers_and_with_custom_api_version_var = TestClient(versioned_app_with_custom_api_version_var)
39
37
 
@@ -3,6 +3,6 @@ from fastapi.routing import APIRouter
3
3
  router = APIRouter(prefix="/v1")
4
4
 
5
5
 
6
- @router.post("/webhooks", response_model=dict)
6
+ @router.post("/unversioned", response_model=dict)
7
7
  def read_root():
8
8
  return {"saved": True}
@@ -105,6 +105,7 @@ class CreateVersionedApp:
105
105
  )
106
106
  )
107
107
  app.generate_and_include_versioned_routers(router)
108
+ app._cadwyn_initialize()
108
109
  return app
109
110
 
110
111
 
@@ -6,10 +6,13 @@ import pytest
6
6
  from fastapi import APIRouter, BackgroundTasks, Depends, FastAPI
7
7
  from fastapi.routing import APIRoute
8
8
  from fastapi.testclient import TestClient
9
+ from pydantic import BaseModel
9
10
 
10
11
  from cadwyn import Cadwyn
11
12
  from cadwyn.route_generation import VersionedAPIRouter
12
- from cadwyn.structure.versions import HeadVersion, Version, VersionBundle
13
+ from cadwyn.structure.endpoints import endpoint
14
+ from cadwyn.structure.schemas import schema
15
+ from cadwyn.structure.versions import HeadVersion, Version, VersionBundle, VersionChange
13
16
  from tests._resources.utils import BASIC_HEADERS, DEFAULT_API_VERSION
14
17
  from tests._resources.versioned_app.app import (
15
18
  client_without_headers,
@@ -162,8 +165,8 @@ def test__header_based_versioning__invalid_version_header_format__should_raise_4
162
165
  assert resp.json()[0]["loc"] == ["header", "x-api-version"]
163
166
 
164
167
 
165
- def test__get_webhooks_router():
166
- resp = client_without_headers.post("/v1/webhooks")
168
+ def test__get_unversioned_router():
169
+ resp = client_without_headers.post("/v1/unversioned")
167
170
  assert resp.status_code == 200
168
171
  assert resp.json() == {"saved": True}
169
172
 
@@ -254,14 +257,14 @@ def test__get_docs__specific_version():
254
257
  assert resp.status_code == 200
255
258
 
256
259
 
257
- def test__get_webhooks_with_redirect():
258
- resp = client_without_headers.post("/v1/webhooks/")
260
+ def test__get_unversioned_with_redirect():
261
+ resp = client_without_headers.post("/v1/unversioned/")
259
262
  assert resp.status_code == 200
260
263
  assert resp.json() == {"saved": True}
261
264
 
262
265
 
263
- def test__get_webhooks_as_partial_because_of_method():
264
- resp = client_without_headers.patch("/v1/webhooks")
266
+ def test__get_unversioned_as_partial_because_of_method():
267
+ resp = client_without_headers.patch("/v1/unversioned")
265
268
  assert resp.status_code == 405
266
269
 
267
270
 
@@ -291,3 +294,60 @@ def test__background_tasks():
291
294
  resp = client.post("/send-notification/test@example.com", headers=BASIC_HEADERS)
292
295
  assert resp.status_code == 200, resp.json()
293
296
  assert background_task_data == ("test@example.com", "some notification")
297
+
298
+
299
+ def test__webhooks():
300
+ webhooks = VersionedAPIRouter()
301
+
302
+ class Subscription(BaseModel):
303
+ username: str
304
+ monthly_fee: float
305
+ start_date: str
306
+
307
+ @webhooks.post("new-subscription")
308
+ def new_subscription(body: Subscription): # pragma: no cover
309
+ """
310
+ When a new user subscribes to your service we'll send you a POST request with this
311
+ data to the URL that you register for the event `new-subscription` in the dashboard.
312
+ """
313
+
314
+ class MyVersionChange(VersionChange):
315
+ description = "Mess with webhooks"
316
+ instructions_to_migrate_to_previous_version = [
317
+ endpoint("new-subscription", ["POST"]).didnt_exist,
318
+ schema(Subscription).field("monthly_fee").didnt_exist,
319
+ ]
320
+
321
+ app = Cadwyn(
322
+ versions=VersionBundle(HeadVersion(), Version("2023-04-12", MyVersionChange), Version("2022-11-16")),
323
+ webhooks=webhooks,
324
+ )
325
+
326
+ @app.webhooks.post("post-subscription") # pragma: no cover
327
+ def post_subscription(body: Subscription): # pragma: no cover
328
+ """This should also be there"""
329
+
330
+ with TestClient(app) as client:
331
+ resp = client.get("/openapi.json?version=2023-04-12")
332
+ openapi_dict = resp.json()
333
+
334
+ assert "webhooks" in openapi_dict, "'webhooks' section is missing"
335
+ assert "new-subscription" in openapi_dict["webhooks"], "'new-subscription' webhook is missing"
336
+ assert "post-subscription" in openapi_dict["webhooks"], "'post-subscription' webhook is missing"
337
+ assert "post" in openapi_dict["webhooks"]["post-subscription"], "POST method for 'post-subscription' is missing"
338
+ assert "Subscription" in openapi_dict["components"]["schemas"], "'Subscription' component is missing"
339
+ assert (
340
+ "monthly_fee" in openapi_dict["components"]["schemas"]["Subscription"]["properties"]
341
+ ), "monthly_fee field is missing"
342
+
343
+ resp = client.get("/openapi.json?version=2022-11-16")
344
+ openapi_dict = resp.json()
345
+
346
+ assert "webhooks" in openapi_dict, "'webhooks' section is missing"
347
+ assert "new-subscription" not in openapi_dict["webhooks"], "'new-subscription' webhook is missing"
348
+ assert "post-subscription" in openapi_dict["webhooks"], "'post-subscription' webhook is present"
349
+ assert "post" in openapi_dict["webhooks"]["post-subscription"], "POST method for 'post-subscription' is missing"
350
+ assert "Subscription" in openapi_dict["components"]["schemas"], "'Subscription' component is missing"
351
+ assert (
352
+ "monthly_fee" not in openapi_dict["components"]["schemas"]["Subscription"]["properties"]
353
+ ), "monthly_fee field is present yet it must be deleted"
@@ -190,10 +190,10 @@ def test__endpoint_existed__deleting_restoring_deleting_restoring_an_endpoint(
190
190
  )
191
191
  routers = generate_versioned_routers(router, versions=versions)
192
192
 
193
- assert len(routers[date(2003, 1, 1)].routes) == 0
194
- assert len(routers[date(2002, 1, 1)].routes) == 1
195
- assert len(routers[date(2001, 1, 1)].routes) == 0
196
- assert len(routers[date(2000, 1, 1)].routes) == 1
193
+ assert len(routers.endpoints[date(2003, 1, 1)].routes) == 0
194
+ assert len(routers.endpoints[date(2002, 1, 1)].routes) == 1
195
+ assert len(routers.endpoints[date(2001, 1, 1)].routes) == 0
196
+ assert len(routers.endpoints[date(2000, 1, 1)].routes) == 1
197
197
 
198
198
 
199
199
  @pytest.mark.parametrize(
@@ -599,14 +599,14 @@ def test__endpoint_existed__deleting_and_restoring_two_routes_for_the_same_endpo
599
599
  )
600
600
  routers = generate_versioned_routers(router, versions=versions)
601
601
 
602
- assert len(routers[date(2002, 1, 1)].routes) == 0
603
- assert len(routers[date(2001, 1, 1)].routes) == 1
604
- assert len(routers[date(2000, 1, 1)].routes) == 2
602
+ assert len(routers.endpoints[date(2002, 1, 1)].routes) == 0
603
+ assert len(routers.endpoints[date(2001, 1, 1)].routes) == 1
604
+ assert len(routers.endpoints[date(2000, 1, 1)].routes) == 2
605
605
 
606
- assert endpoints_equal(routers[date(2001, 1, 1)].routes[0].endpoint, route_to_restore_first) # pyright: ignore
606
+ assert endpoints_equal(routers.endpoints[date(2001, 1, 1)].routes[0].endpoint, route_to_restore_first) # pyright: ignore
607
607
  assert {
608
- get_wrapped_endpoint(routers[date(2000, 1, 1)].routes[0].endpoint), # pyright: ignore
609
- get_wrapped_endpoint(routers[date(2000, 1, 1)].routes[1].endpoint), # pyright: ignore
608
+ get_wrapped_endpoint(routers.endpoints[date(2000, 1, 1)].routes[0].endpoint), # pyright: ignore
609
+ get_wrapped_endpoint(routers.endpoints[date(2000, 1, 1)].routes[1].endpoint), # pyright: ignore
610
610
  } == {
611
611
  route_to_restore_first,
612
612
  route_to_restore_second,
@@ -1014,9 +1014,9 @@ def test__cascading_router_exists(router: VersionedAPIRouter, api_version_var: C
1014
1014
  )
1015
1015
  routers = generate_versioned_routers(router, versions=versions)
1016
1016
 
1017
- assert client(routers[date(2002, 1, 1)]).get("/test").json() == {"detail": "Not Found"}
1018
- assert client(routers[date(2001, 1, 1)]).get("/test").json() == 83
1019
- assert client(routers[date(2000, 1, 1)]).get("/test").json() == 83
1017
+ assert client(routers.endpoints[date(2002, 1, 1)]).get("/test").json() == {"detail": "Not Found"}
1018
+ assert client(routers.endpoints[date(2001, 1, 1)]).get("/test").json() == 83
1019
+ assert client(routers.endpoints[date(2000, 1, 1)]).get("/test").json() == 83
1020
1020
 
1021
1021
 
1022
1022
  def test__cascading_router_didnt_exist(
@@ -1041,13 +1041,13 @@ def test__cascading_router_didnt_exist(
1041
1041
  )
1042
1042
  routers = generate_versioned_routers(router, versions=versions)
1043
1043
 
1044
- assert client(routers[date(2002, 1, 1)]).get("/test").json() == 83
1044
+ assert client(routers.endpoints[date(2002, 1, 1)]).get("/test").json() == 83
1045
1045
 
1046
- assert client(routers[date(2001, 1, 1)]).get("/test").json() == {
1046
+ assert client(routers.endpoints[date(2001, 1, 1)]).get("/test").json() == {
1047
1047
  "detail": "Not Found",
1048
1048
  }
1049
1049
 
1050
- assert client(routers[date(2000, 1, 1)]).get("/test").json() == {
1050
+ assert client(routers.endpoints[date(2000, 1, 1)]).get("/test").json() == {
1051
1051
  "detail": "Not Found",
1052
1052
  }
1053
1053
 
@@ -1081,7 +1081,7 @@ def test__generate_versioned_routers__two_routers(
1081
1081
  root_router.include_router(router)
1082
1082
  root_router.include_router(router2)
1083
1083
 
1084
- routers = generate_versioned_routers(root_router, versions=versions)
1084
+ routers = generate_versioned_routers(root_router, versions=versions).endpoints
1085
1085
  assert all(type(r) is APIRouter for r in routers.values())
1086
1086
  assert len(routers[date(2001, 1, 1)].routes) == 2
1087
1087
  assert len(routers[date(2000, 1, 1)].routes) == 1
@@ -84,7 +84,7 @@ wheels = [
84
84
 
85
85
  [[package]]
86
86
  name = "cadwyn"
87
- version = "4.2.4"
87
+ version = "4.3.1"
88
88
  source = { editable = "." }
89
89
  dependencies = [
90
90
  { name = "backports-strenum", marker = "python_full_version < '3.11'" },
@@ -93,13 +93,13 @@ dependencies = [
93
93
  { name = "jinja2" },
94
94
  { name = "pydantic" },
95
95
  { name = "starlette" },
96
- { name = "typer" },
97
96
  { name = "typing-extensions" },
98
97
  ]
99
98
 
100
99
  [package.optional-dependencies]
101
100
  standard = [
102
101
  { name = "fastapi", extra = ["standard"] },
102
+ { name = "typer" },
103
103
  ]
104
104
 
105
105
  [package.dev-dependencies]
@@ -131,7 +131,7 @@ requires-dist = [
131
131
  { name = "jinja2", specifier = ">=3.1.2" },
132
132
  { name = "pydantic", specifier = ">=2.0.0" },
133
133
  { name = "starlette", specifier = ">=0.30.0" },
134
- { name = "typer", specifier = ">=0.7.0" },
134
+ { name = "typer", marker = "extra == 'standard'", specifier = ">=0.7.0" },
135
135
  { name = "typing-extensions", specifier = ">=4.8.0" },
136
136
  ]
137
137
 
@@ -1167,16 +1167,16 @@ wheels = [
1167
1167
 
1168
1168
  [[package]]
1169
1169
  name = "rich"
1170
- version = "13.9.1"
1170
+ version = "13.9.2"
1171
1171
  source = { registry = "https://pypi.org/simple" }
1172
1172
  dependencies = [
1173
1173
  { name = "markdown-it-py" },
1174
1174
  { name = "pygments" },
1175
1175
  { name = "typing-extensions", marker = "python_full_version < '3.11'" },
1176
1176
  ]
1177
- sdist = { url = "https://files.pythonhosted.org/packages/b3/78/87d00a1df7c457ad9aa0139f01b8a11c67209f27f927c503b0109bf2ed6c/rich-13.9.1.tar.gz", hash = "sha256:097cffdf85db1babe30cc7deba5ab3a29e1b9885047dab24c57e9a7f8a9c1466", size = 222907 }
1177
+ sdist = { url = "https://files.pythonhosted.org/packages/aa/9e/1784d15b057b0075e5136445aaea92d23955aad2c93eaede673718a40d95/rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c", size = 222843 }
1178
1178
  wheels = [
1179
- { url = "https://files.pythonhosted.org/packages/ab/71/cd9549551f1aa11cf7e5f92bae5817979e8b3a19e31e8810c15f3f45c311/rich-13.9.1-py3-none-any.whl", hash = "sha256:b340e739f30aa58921dc477b8adaa9ecdb7cecc217be01d93730ee1bc8aa83be", size = 242147 },
1179
+ { url = "https://files.pythonhosted.org/packages/67/91/5474b84e505a6ccc295b2d322d90ff6aa0746745717839ee0c5fb4fdcceb/rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1", size = 242117 },
1180
1180
  ]
1181
1181
 
1182
1182
  [[package]]
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