Skip to content

Commit ed0f62a

Browse files
authored
Fix class-based dependant generation (#148)
1 parent 385b36a commit ed0f62a

5 files changed

Lines changed: 62 additions & 22 deletions

File tree

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ default_language_version:
22
python: python3.10
33
repos:
44
- repo: https://github.com/pre-commit/pre-commit-hooks
5-
rev: v4.4.0
5+
rev: v4.5.0
66
hooks:
77
- id: check-added-large-files
88
- id: check-yaml
@@ -18,7 +18,7 @@ repos:
1818
- id: python-check-blanket-noqa
1919

2020
- repo: https://github.com/astral-sh/ruff-pre-commit
21-
rev: v0.0.291
21+
rev: v0.2.2
2222
hooks:
2323
- id: ruff
2424
args: [--fix, --exit-non-zero-on-fix]

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.
55

66
## [Unreleased]
77

8+
## [3.6.5]
9+
10+
### Fixed
11+
12+
* When a class-based dependency was used, its dependant was incorrectly generated, causing all affected endpoints to completely stop functioning
13+
14+
## [3.6.4] <!-- Test release -->
15+
816
## [3.6.3]
917

1018
### Fixed

cadwyn/routing.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import functools
22
import inspect
33
import re
4+
import types
45
import typing
56
import warnings
67
from collections import defaultdict
@@ -541,20 +542,20 @@ def _modify_callable(
541542
modify_annotations: Callable[[dict[str, Any]], dict[str, Any]] = lambda a: a,
542543
modify_defaults: Callable[[tuple[Any, ...]], tuple[Any, ...]] = lambda a: a,
543544
):
544-
annotation_modifying_decorator = _copy_function(call)
545+
annotation_modifying_wrapper = _copy_function(call)
545546
old_params = inspect.signature(call).parameters
546-
callable_annotations = annotation_modifying_decorator.__annotations__
547+
callable_annotations = annotation_modifying_wrapper.__annotations__
547548

548-
annotation_modifying_decorator.__annotations__ = modify_annotations(callable_annotations)
549-
annotation_modifying_decorator.__defaults__ = modify_defaults(
549+
annotation_modifying_wrapper.__annotations__ = modify_annotations(callable_annotations)
550+
annotation_modifying_wrapper.__defaults__ = modify_defaults(
550551
tuple(p.default for p in old_params.values() if p.default is not inspect.Signature.empty),
551552
)
552-
annotation_modifying_decorator.__signature__ = _generate_signature(
553-
annotation_modifying_decorator,
553+
annotation_modifying_wrapper.__signature__ = _generate_signature(
554+
annotation_modifying_wrapper,
554555
old_params,
555556
)
556557

557-
return annotation_modifying_decorator
558+
return annotation_modifying_wrapper
558559

559560

560561
def _remake_endpoint_dependencies(route: fastapi.routing.APIRoute):
@@ -718,10 +719,13 @@ def _copy_function(function: _T) -> _T:
718719
while hasattr(function, "__alt_wrapped__"):
719720
function = function.__alt_wrapped__
720721

722+
if not isinstance(function, types.FunctionType):
723+
# This means that the callable is actually an instance of a regular class
724+
function = function.__call__
721725
if inspect.iscoroutinefunction(function):
722726

723727
@functools.wraps(function)
724-
async def annotation_modifying_decorator( # pyright: ignore[reportRedeclaration]
728+
async def annotation_modifying_wrapper( # pyright: ignore[reportRedeclaration]
725729
*args: Any,
726730
**kwargs: Any,
727731
) -> Any:
@@ -730,16 +734,16 @@ async def annotation_modifying_decorator( # pyright: ignore[reportRedeclaration
730734
else:
731735

732736
@functools.wraps(function)
733-
def annotation_modifying_decorator(
737+
def annotation_modifying_wrapper(
734738
*args: Any,
735739
**kwargs: Any,
736740
) -> Any:
737741
return function(*args, **kwargs)
738742

739743
# Otherwise it will have the same signature as __wrapped__ due to how inspect module works
740-
annotation_modifying_decorator.__alt_wrapped__ = ( # pyright: ignore[reportAttributeAccessIssue]
741-
annotation_modifying_decorator.__wrapped__
744+
annotation_modifying_wrapper.__alt_wrapped__ = ( # pyright: ignore[reportAttributeAccessIssue]
745+
annotation_modifying_wrapper.__wrapped__
742746
)
743-
del annotation_modifying_decorator.__wrapped__
747+
del annotation_modifying_wrapper.__wrapped__
744748

745-
return cast(_T, annotation_modifying_decorator)
749+
return cast(_T, annotation_modifying_wrapper)

pyproject.toml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "cadwyn"
3-
version = "3.6.4"
3+
version = "3.6.5"
44
description = "Production-ready community-driven modern Stripe-like API versioning in FastAPI"
55
authors = ["Stanislav Zmiev <zmievsa@gmail.com>"]
66
license = "MIT"
@@ -135,6 +135,8 @@ reportCircularImports = true
135135
[tool.ruff]
136136
target-version = "py310"
137137
line-length = 120
138+
139+
[tool.ruff.lint]
138140
select = [
139141
"F", # pyflakes
140142
"E", # pycodestyle errors
@@ -211,11 +213,7 @@ ignore = [
211213
"COM819", # Checks for the presence of prohibited trailing commas
212214
]
213215

214-
[tool.ruff.format]
215-
quote-style = "double"
216-
indent-style = "space"
217-
218-
[tool.ruff.per-file-ignores]
216+
[tool.ruff.lint.per-file-ignores]
219217
"tests/*" = [
220218
"S", # ignore bandit security issues in tests
221219
"B018", # ignore useless expressions in tests
@@ -231,9 +229,13 @@ indent-style = "space"
231229
"ERA001", # Found commented-out code (it's not actually commented out. It's just comments)
232230
]
233231

234-
[tool.ruff.mccabe]
232+
[tool.ruff.lint.mccabe]
235233
max-complexity = 14
236234

235+
[tool.ruff.format]
236+
quote-style = "double"
237+
indent-style = "space"
238+
237239
[build-system]
238240
requires = ["poetry-core>=1.0.0"]
239241
build-backend = "poetry.core.masonry.api"

tests/test_routing.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from dirty_equals import IsStr
1313
from fastapi import APIRouter, Body, Depends, UploadFile
1414
from fastapi.routing import APIRoute
15+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
1516
from fastapi.testclient import TestClient
1617
from pydantic import BaseModel
1718
from pytest_fixture_classes import fixture_class
@@ -30,6 +31,7 @@
3031
from tests.conftest import (
3132
CreateSimpleVersionedPackages,
3233
CreateVersionedApp,
34+
CreateVersionedClients,
3335
LatestModuleFor,
3436
RunSchemaCodegen,
3537
client,
@@ -1230,3 +1232,27 @@ class V2001(VersionChange):
12301232
}
12311233
assert endpoints_equal(routers[date(2000, 1, 1)].routes[0].endpoint, test_endpoint2) # pyright: ignore
12321234
assert endpoints_equal(routers[date(2000, 1, 1)].routes[0].endpoint, test_endpoint2) # pyright: ignore
1235+
1236+
1237+
def test__basic_router_generation__using_http_bearer(
1238+
router: VersionedAPIRouter,
1239+
create_versioned_clients: CreateVersionedClients,
1240+
):
1241+
auth_header_scheme = HTTPBearer(description="Bearer token for authentication")
1242+
1243+
def auth(
1244+
auth_header: Annotated[
1245+
HTTPAuthorizationCredentials | None,
1246+
Depends(auth_header_scheme),
1247+
],
1248+
):
1249+
raise NotImplementedError
1250+
1251+
@router.get("/test", dependencies=[Depends(auth)])
1252+
async def test():
1253+
raise NotImplementedError
1254+
1255+
client_2000, *_ = create_versioned_clients().values()
1256+
response = client_2000.get("/test")
1257+
assert response.status_code == 403
1258+
assert response.json() == {"detail": "Not authenticated"}

0 commit comments

Comments
 (0)