From: Sebastián Ramírez Date: Wed, 19 Nov 2025 16:50:18 +0000 (+0100) Subject: ♻️ Make the result of `Depends()` and `Security()` hashable, as a workaround for... X-Git-Tag: 0.121.3~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=85701631a0241c5f02b4940734a5428f66abe167;p=thirdparty%2Ffastapi%2Ffastapi.git ♻️ Make the result of `Depends()` and `Security()` hashable, as a workaround for other tools interacting with these internal parts (#14372) --- diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 4b69e39a15..1e92c1ba2a 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -1,3 +1,4 @@ +import dataclasses import inspect from contextlib import AsyncExitStack, contextmanager from copy import copy, deepcopy @@ -428,7 +429,7 @@ def analyze_param( if depends is not None and depends.dependency is None: # Copy `depends` before mutating it depends = copy(depends) - depends.dependency = type_annotation + depends = dataclasses.replace(depends, dependency=type_annotation) # Handle non-param type annotations like Request if lenient_issubclass( diff --git a/fastapi/params.py b/fastapi/params.py index 6a58d5808e..6d07df35e1 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -762,13 +762,13 @@ class File(Form): # type: ignore[misc] ) -@dataclass +@dataclass(frozen=True) class Depends: dependency: Optional[Callable[..., Any]] = None use_cache: bool = True scope: Union[Literal["function", "request"], None] = None -@dataclass +@dataclass(frozen=True) class Security(Depends): scopes: Optional[Sequence[str]] = None diff --git a/tests/test_depends_hashable.py b/tests/test_depends_hashable.py new file mode 100644 index 0000000000..d57f2726ec --- /dev/null +++ b/tests/test_depends_hashable.py @@ -0,0 +1,25 @@ +# This is more or less a workaround to make Depends and Security hashable +# as other tools that use them depend on that +# Ref: https://github.com/fastapi/fastapi/pull/14320 + +from fastapi import Depends, Security + + +def dep(): + pass + + +def test_depends_hashable(): + dep() # just for coverage + d1 = Depends(dep) + d2 = Depends(dep) + d3 = Depends(dep, scope="function") + d4 = Depends(dep, scope="function") + + s1 = Security(dep) + s2 = Security(dep) + + assert hash(d1) == hash(d2) + assert hash(s1) == hash(s2) + assert hash(d1) != hash(d3) + assert hash(d3) == hash(d4)