← pod_r1_v1n

fastapi_14372

resolved RESOLVED PASS · 19 tool calls · 207 s · fastapi/fastapi

Task input

♻️ Make the result of `Depends()` and `Security()` hashable, as a workaround for other tools interacting with these internal parts

♻️ Make the result of `Depends()` and `Security()` hashable, as a workaround for other tools interacting with these internal parts

---

This comes from https://github.com/fastapi/fastapi/pull/14320

As a note, FastAPI doesn't really have official support for extending its internals in ways that are not documented in the docs.

So this is not a long-term commitment to keep some specific behavior, but a quick workaround to make things easier for current tools that interact with FastAPI internals in some way.

---

At some point in the future, I'll refactor the internals of FastAPI to make them more explicitly private, and then also figure out the right ways to expose any points of connection with external components that currently need to interact with the internals in some way, and formalize that better.

---

Before that, I need to handle a few issues, bugs, features, refactors, deprecations, etc. And then I'll be able to come back to this idea of figuring out how and what to expose. :nerd_face: 

Tool calls (20)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rn \"class Depends\" . | head -20"
}
{"status": "ok", "stdout": "./fastapi/params.py:766:class Depends:\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "./fastapi/params.py:766:class Depends:\n", "stderr": "", "exit_code": 0}
2run_command
{
 "command": "grep -rn \"class Security\" . | head -20"
}
{"status": "ok", "stdout": "./fastapi/params.py:773:class Security(Depends):\n./fastapi/security/oauth2.py:614:class SecurityScopes:\n./fastapi/security/base.py
full
{"status": "ok", "stdout": "./fastapi/params.py:773:class Security(Depends):\n./fastapi/security/oauth2.py:614:class SecurityScopes:\n./fastapi/security/base.py:4:class SecurityBase:\n./fastapi/dependencies/models.py:19:class SecurityRequirement:\n./fastapi/openapi/models.py:337:class SecuritySchemeType(Enum):\n./fastapi/openapi/models.py:344:class SecurityBase(BaseModelWithConfig):\n", "stderr": "", "exit_code": 0}
3run_command
{
 "command": "sed -n '760,780p' fastapi/params.py"
}
{"status": "ok", "stdout": " json_schema_extra=json_schema_extra,\n **extra,\n )\n\n\n@dataclass\nclass Depends:\n dependency: Optional[Callable[..., Any]] = No
full
{"status": "ok", "stdout": "            json_schema_extra=json_schema_extra,\n            **extra,\n        )\n\n\n@dataclass\nclass Depends:\n    dependency: Optional[Callable[..., Any]] = None\n    use_cache: bool = True\n    scope: Union[Literal[\"function\", \"request\"], None] = None\n\n\n@dataclass\nclass Security(Depends):\n    scopes: Optional[Sequence[str]] = None\n", "stderr": "", "exit_code": 0}
4run_command
{
 "command": "python3 -c \"from dataclasses import dataclass; @dataclass\\nclass A:\\n    x: int\\na1 = A(1)\\na2 = A(1)\\nprint(f'Hashable: {hash(a1) == hash(a2)}')\\nprint(f'Equal: {a1 == a2}')\""
}
{"status": "error", "error_type": "CommandError", "error_message": " File \"<string>\", line 1\n from dataclasses import dataclass; @dataclass\\nclass A:\\n x:
full
{"status": "error", "error_type": "CommandError", "error_message": "  File \"<string>\", line 1\n    from dataclasses import dataclass; @dataclass\\nclass A:\\n    x: int\\na1 = A(1)\\na2 = A(1)\\nprint(f'Hashable: {hash(a1) == hash(a2)}')\\nprint(f'Equal: {a1 == a2}')\n                                       ^\nSyntaxError: invalid syntax\n", "details": {"stdout": "", "stderr": "  File \"<string>\", line 1\n    from dataclasses import dataclass; @dataclass\\nclass A:\\n    x: int\\na1 = A(1)\\na2 = A(1)\\nprint(f'Hashable: {hash(a1) == hash(a2)}')\\nprint(f'Equal: {a1 == a2}')\n                                       ^\nSyntaxError: invalid syntax\n", "exit_code": 1}}
5run_command
{
 "command": "python3 -c \"from dataclasses import dataclass; \n@dataclass\nclass A:\n    x: int\na1 = A(1)\na2 = A(1)\ntry:\n    print(f'Hashable: {hash(a1)}')\nexcept TypeError as e:\n    print(f'Error: {e}')\n\""
}
{"status": "ok", "stdout": "Error: unhashable type: 'A'\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Error: unhashable type: 'A'\n", "stderr": "", "exit_code": 0}
6run_command
{
 "command": "python3 -c \"from dataclasses import dataclass; \n@dataclass(frozen=True)\nclass A:\n    x: int\na1 = A(1)\na2 = A(1)\nprint(f'Hashable: {hash(a1) == hash(a2)}')\nprint(f'Equal: {a1 == a2}')\n\""
}
{"status": "ok", "stdout": "Hashable: True\nEqual: True\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Hashable: True\nEqual: True\n", "stderr": "", "exit_code": 0}
7edit_file
{
 "filepath": "fastapi/params.py",
 "new_string": "@dataclass(frozen=True)\nclass Depends:\n    dependency: Optional[Callable[..., Any]] = None\n    use_cache: bool = True\n    scope: Union[Literal[\"function\", \"request\"], None] = None\n\n\n@dataclass(frozen=True)\nclass Security(Depends):\n    scopes: Optional[Sequence[str]] = None\n",
 "old_string": "@dataclass\nclass Depends:\n    dependency: Optional[Callable[..., Any]] = None\n    use_cache: bool = True\n    scope: Union[Literal[\"function\", \"request\"], None] = None\n\n\n@dataclass\nclass Security(Depends):\n    scopes: Optional[Sequence[str]] = None\n"
}
{"status": "ok", "filepath": "fastapi/params.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/params.py\n+++ b/fastapi/params.py\n@@ -762,13 +
full
{"status": "ok", "filepath": "fastapi/params.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/params.py\n+++ b/fastapi/params.py\n@@ -762,13 +762,13 @@\n         )\n \n \n-@dataclass\n+@dataclass(frozen=True)\n class Depends:\n     dependency: Optional[Callable[..., Any]] = None\n     use_cache: bool = True\n     scope: Union[Literal[\"function\", \"request\"], None] = None\n \n \n-@dataclass\n+@dataclass(frozen=True)\n class Security(Depends):\n     scopes: Optional[Sequence[str]] = None\n", "is_truncated": false}
8run_command
{
 "command": "python3 -c \"from fastapi.params import Depends, Security; d1 = Depends(use_cache=True); d2 = Depends(use_cache=True); s1 = Security(scopes=['a']); s2 = Security(scopes=['a']); print(f'Depends hashable: {hash(d1) == hash(d2)}'); print(f'Security hashable: {hash(s1) == hash(s2)}')\""
}
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"<string>\", line 1, in <module>\n from fastapi.p
full
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n  File \"<string>\", line 1, in <module>\n    from fastapi.params import Depends, Security; d1 = Depends(use_cache=True); d2 = Depends(use_cache=True); s1 = Security(scopes=['a']); s2 = Security(scopes=['a']); print(f'Depends hashable: {hash(d1) == hash(d2)}'); print(f'Security hashable: {hash(s1) == hash(s2)}')\n                                                                                                                                                                                                                                                        ~~~~^^^^\n  File \"<string>\", line 25, in __hash__\nTypeError: unhashable type: 'list'\n", "details": {"stdout": "Depends hashable: True\n", "stderr": "Traceback (most recent call last):\n  File \"<string>\", line 1, in <module>\n    from fastapi.params import Depends, Security; d1 = Depends(use_cache=True); d2 = Depends(use_cache=True); s1 = Security(scopes=['a']); s2 = Security(scopes=['a']); print(f'Depends hashable: {hash(d1) == hash(d2)}'); print(f'Security hashable: {hash(s1) == hash(s2)}')\n                                                                                                                                                                                                                                                        ~~~~^^^^\n  File \"<string>\", line 25, in __hash__\nTypeError: unhashable type: 'list'\n", "exit_code": 1}}
9run_command
{
 "command": "python3 -c \"from fastapi.params import Depends, Security; d1 = Depends(use_cache=True); d2 = Depends(use_cache=True); s1 = Security(scopes=('a',)); s2 = Security(scopes=('a',)); print(f'Depends hashable: {hash(d1) == hash(d2)}'); print(f'Security hashable: {hash(s1) == hash(s2)}')\""
}
{"status": "ok", "stdout": "Depends hashable: True\nSecurity hashable: True\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Depends hashable: True\nSecurity hashable: True\n", "stderr": "", "exit_code": 0}
10run_command
{
 "command": "grep -rn \"Security(\" . | head -20"
}
{"status": "ok", "stdout": "./tests/test_security_http_digest_description.py:11:def read_current_user(credentials: HTTPAuthorizationCredentials = Security(secur
full
{"status": "ok", "stdout": "./tests/test_security_http_digest_description.py:11:def read_current_user(credentials: HTTPAuthorizationCredentials = Security(security)):\n./tests/test_security_oauth2.py:25:def get_current_user(oauth_header: \"str\" = Security(reusable_oauth2)):\n./tests/test_security_http_basic_optional.py:14:def read_current_user(credentials: Optional[HTTPBasicCredentials] = Security(security)):\n./tests/test_dependency_security_overrides.py:28:    user_data: Tuple[str, List[str]] = Security(get_user, scopes=[\"foo\", \"bar\"]),\n./tests/test_security_openid_connect_description.py:17:def get_current_user(oauth_header: str = Security(oid)):\n./tests/test_dependency_paramless.py:17:    credentials: Annotated[Union[str, None], Security(oauth2_scheme)],\n./tests/test_dependency_paramless.py:31:    credentials: Annotated[dict, Security(process_auth, scopes=[\"a\", \"b\"])],\n./tests/test_dependency_paramless.py:38:    dependencies=[Security(process_auth, scopes=[\"a\", \"b\"])],\n./tests/test_dependency_paramless.py:46:    dependencies=[Security(process_auth)],\n./tests/test_security_http_base_description.py:11:def read_current_user(credentials: HTTPAuthorizationCredentials = Security(security)):\n./tests/test_security_api_key_cookie_optional.py:17:def get_current_user(oauth_header: Optional[str] = Security(api_key)):\n./tests/test_security_api_key_header.py:15:def get_current_user(oauth_header: str = Security(api_key)):\n./tests/test_security_oauth2_password_bearer_optional_description.py:17:async def read_items(token: Optional[str] = Security(oauth2_scheme)):\n./tests/test_security_openid_connect_optional.py:17:def get_current_user(oauth_header: Optional[str] = Security(oid)):\n./tests/test_security_http_base_optional.py:14:    credentials: Optional[HTTPAuthorizationCredentials] = Security(security),\n./tests/test_security_api_key_query.py:15:def get_current_user(oauth_header: str = Security(api_key)):\n./tests/test_security_http_bearer.py:11:def read_cu
11edit_file
{
 "filepath": "fastapi/params.py",
 "new_string": "@dataclass(frozen=True)\nclass Depends:\n    dependency: Optional[Callable[..., Any]] = None\n    use_cache: bool = True\n    scope: Union[Literal[\"function\", \"request\"], None] = None\n\n\n@dataclass(frozen=True)\nclass Security(Depends):\n    scopes: Optional[Union[Sequence[str], Tuple[str, ...]]] = None\n",
 "old_string": "@dataclass(frozen=True)\nclass Depends:\n    dependency: Optional[Callable[..., Any]] = None\n    use_cache: bool = True\n    scope: Union[Literal[\"function\", \"request\"], None] = None\n\n\n@dataclass(frozen=True)\nclass Security(Depends):\n    scopes: Optional[Sequence[str]] = None\n"
}
{"status": "ok", "filepath": "fastapi/params.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/params.py\n+++ b/fastapi/params.py\n@@ -771,4 +7
full
{"status": "ok", "filepath": "fastapi/params.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/params.py\n+++ b/fastapi/params.py\n@@ -771,4 +771,4 @@\n \n @dataclass(frozen=True)\n class Security(Depends):\n-    scopes: Optional[Sequence[str]] = None\n+    scopes: Optional[Union[Sequence[str], Tuple[str, ...]]] = None\n", "is_truncated": false}
12run_command
{
 "command": "python3 -c \"from fastapi.params import Depends, Security; d1 = Depends(use_cache=True); d2 = Depends(use_cache=True); s1 = Security(scopes=['a']); s2 = Security(scopes=['a']); print(f'Depends hashable: {hash(d1) == hash(d2)}'); print(f'Security hashable: {hash(s1) == hash(s2)}')\""
}
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"<string>\", line 1, in <module>\n from fastapi.p
full
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n  File \"<string>\", line 1, in <module>\n    from fastapi.params import Depends, Security; d1 = Depends(use_cache=True); d2 = Depends(use_cache=True); s1 = Security(scopes=['a']); s2 = Security(scopes=['a']); print(f'Depends hashable: {hash(d1) == hash(d2)}'); print(f'Security hashable: {hash(s1) == hash(s2)}')\n    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/private/workspace/fastapi/__init__.py\", line 7, in <module>\n    from .applications import FastAPI as FastAPI\n  File \"/private/workspace/fastapi/applications.py\", line 17, in <module>\n    from fastapi import routing\n  File \"/private/workspace/fastapi/routing.py\", line 28, in <module>\n    from fastapi import params, temp_pydantic_v1_params\n  File \"/private/workspace/fastapi/params.py\", line 773, in <module>\n    class Security(Depends):\n        scopes: Optional[Union[Sequence[str], Tuple[str, ...]]] = None\n  File \"/private/workspace/fastapi/params.py\", line 774, in Security\n    scopes: Optional[Union[Sequence[str], Tuple[str, ...]]] = None\n                                          ^^^^^\nNameError: name 'Tuple' is not defined. Did you mean: 'tuple'?\n", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n  File \"<string>\", line 1, in <module>\n    from fastapi.params import Depends, Security; d1 = Depends(use_cache=True); d2 = Depends(use_cache=True); s1 = Security(scopes=['a']); s2 = Security(scopes=['a']); print(f'Depends hashable: {hash(d1) == hash(d2)}'); print(f'Security hashable: {hash(s1) == hash(s2)}')\n    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/private/workspace/fastapi/__init__.py\", line 7, in <module>\n    from .applications import FastAPI as FastAPI\n  File \"/private/workspace/fastapi/applications.py\", line 17, in <module>\n    from fastapi import routing\n  File \"/private/workspace/fastapi/routing.py\", line 28, in <module>
13run_command
{
 "command": "sed -n '1,50p' fastapi/params.py"
}
{"status": "ok", "stdout": "import warnings\nfrom dataclasses import dataclass\nfrom enum import Enum\nfrom typing import Any, Callable, Dict, List, Optional, S
full
{"status": "ok", "stdout": "import warnings\nfrom dataclasses import dataclass\nfrom enum import Enum\nfrom typing import Any, Callable, Dict, List, Optional, Sequence, Union\n\nfrom fastapi.openapi.models import Example\nfrom pydantic.fields import FieldInfo\nfrom typing_extensions import Annotated, Literal, deprecated\n\nfrom ._compat import (\n    PYDANTIC_V2,\n    PYDANTIC_VERSION_MINOR_TUPLE,\n    Undefined,\n)\n\n_Unset: Any = Undefined\n\n\nclass ParamTypes(Enum):\n    query = \"query\"\n    header = \"header\"\n    path = \"path\"\n    cookie = \"cookie\"\n\n\nclass Param(FieldInfo):  # type: ignore[misc]\n    in_: ParamTypes\n\n    def __init__(\n        self,\n        default: Any = Undefined,\n        *,\n        default_factory: Union[Callable[[], Any], None] = _Unset,\n        annotation: Optional[Any] = None,\n        alias: Optional[str] = None,\n        alias_priority: Union[int, None] = _Unset,\n        # TODO: update when deprecating Pydantic v1, import these types\n        # validation_alias: str | AliasPath | AliasChoices | None\n        validation_alias: Union[str, None] = None,\n        serialization_alias: Union[str, None] = None,\n        title: Optional[str] = None,\n        description: Optional[str] = None,\n        gt: Optional[float] = None,\n        ge: Optional[float] = None,\n        lt: Optional[float] = None,\n        le: Optional[float] = None,\n        min_length: Optional[int] = None,\n        max_length: Optional[int] = None,\n        pattern: Optional[str] = None,\n        regex: Annotated[\n", "stderr": "", "exit_code": 0}
14edit_file
{
 "filepath": "fastapi/params.py",
 "new_string": "from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union\n",
 "old_string": "from typing import Any, Callable, Dict, List, Optional, Sequence, Union\n"
}
{"status": "ok", "filepath": "fastapi/params.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/params.py\n+++ b/fastapi/params.py\n@@ -1,7 +1,7
full
{"status": "ok", "filepath": "fastapi/params.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/params.py\n+++ b/fastapi/params.py\n@@ -1,7 +1,7 @@\n import warnings\n from dataclasses import dataclass\n from enum import Enum\n-from typing import Any, Callable, Dict, List, Optional, Sequence, Union\n+from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union\n \n from fastapi.openapi.models import Example\n from pydantic.fields import FieldInfo\n", "is_truncated": false}
15run_command
{
 "command": "python3 -c \"from fastapi.params import Depends, Security; d1 = Depends(use_cache=True); d2 = Depends(use_cache=True); s1 = Security(scopes=['a']); s2 = Security(scopes=['a']); print(f'Depends hashable: {hash(d1) == hash(d2)}'); print(f'Security hashable: {hash(s1) == hash(s2)}')\""
}
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"<string>\", line 1, in <module>\n from fastapi.p
full
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n  File \"<string>\", line 1, in <module>\n    from fastapi.params import Depends, Security; d1 = Depends(use_cache=True); d2 = Depends(use_cache=True); s1 = Security(scopes=['a']); s2 = Security(scopes=['a']); print(f'Depends hashable: {hash(d1) == hash(d2)}'); print(f'Security hashable: {hash(s1) == hash(s2)}')\n                                                                                                                                                                                                                                                        ~~~~^^^^\n  File \"<string>\", line 25, in __hash__\nTypeError: unhashable type: 'list'\n", "details": {"stdout": "Depends hashable: True\n", "stderr": "Traceback (most recent call last):\n  File \"<string>\", line 1, in <module>\n    from fastapi.params import Depends, Security; d1 = Depends(use_cache=True); d2 = Depends(use_cache=True); s1 = Security(scopes=['a']); s2 = Security(scopes=['a']); print(f'Depends hashable: {hash(d1) == hash(d2)}'); print(f'Security hashable: {hash(s1) == hash(s2)}')\n                                                                                                                                                                                                                                                        ~~~~^^^^\n  File \"<string>\", line 25, in __hash__\nTypeError: unhashable type: 'list'\n", "exit_code": 1}}
16run_command
{
 "command": "python3 -c \"from dataclasses import dataclass; \n@dataclass(frozen=True)\nclass Security:\n    scopes: list = None\ns = Security(scopes=['a'])\ntry:\n    hash(s)\nexcept TypeError as e:\n    print(e)\""
}
{"status": "ok", "stdout": "unhashable type: 'list'\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "unhashable type: 'list'\n", "stderr": "", "exit_code": 0}
17run_command
{
 "command": "python3 -c \"from dataclasses import dataclass; \n@dataclass(frozen=True)\nclass Security:\n    scopes: list = None\n    def __hash__(self):\n        return hash((tuple(self.scopes) if self.scopes is not None else None,))\ns1 = Security(scopes=['a'])\ns2 = Security(scopes=['a'])\nprint(hash(s1) == hash(s2))\""
}
{"status": "ok", "stdout": "True\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "True\n", "stderr": "", "exit_code": 0}
18edit_file
{
 "filepath": "fastapi/params.py",
 "new_string": "@dataclass(frozen=True)\nclass Depends:\n    dependency: Optional[Callable[..., Any]] = None\n    use_cache: bool = True\n    scope: Union[Literal[\"function\", \"request\"], None] = None\n\n\n@dataclass(frozen=True)\nclass Security(Depends):\n    scopes: Optional[Sequence[str]] = None\n\n    def __hash__(self):\n        return hash((\n            self.dependency,\n            self.use_cache,\n            self.scope,\n            tuple(self.scopes) if self.scopes is not None else None,\n        ))\n",
 "old_string": "@dataclass(frozen=True)\nclass Depends:\n    dependency: Optional[Callable[..., Any]] = None\n    use_cache: bool = True\n    scope: Union[Literal[\"function\", \"request\"], None] = None\n\n\n@dataclass(frozen=True)\nclass Security(Depends):\n    scopes: Optional[Union[Sequence[str], Tuple[str, ...]]] = None\n"
}
{"status": "ok", "filepath": "fastapi/params.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/params.py\n+++ b/fastapi/params.py\n@@ -771,4 +7
full
{"status": "ok", "filepath": "fastapi/params.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/params.py\n+++ b/fastapi/params.py\n@@ -771,4 +771,12 @@\n \n @dataclass(frozen=True)\n class Security(Depends):\n-    scopes: Optional[Union[Sequence[str], Tuple[str, ...]]] = None\n+    scopes: Optional[Sequence[str]] = None\n+\n+    def __hash__(self):\n+        return hash((\n+            self.dependency,\n+            self.use_cache,\n+            self.scope,\n+            tuple(self.scopes) if self.scopes is not None else None,\n+        ))\n", "is_truncated": false}
19run_command
{
 "command": "python3 -c \"from fastapi.params import Depends, Security; d1 = Depends(use_cache=True); d2 = Depends(use_cache=True); s1 = Security(scopes=['a']); s2 = Security(scopes=['a']); print(f'Depends hashable: {hash(d1) == hash(d2)}'); print(f'Security hashable: {hash(s1) == hash(s2)}')\""
}
{"status": "ok", "stdout": "Depends hashable: True\nSecurity hashable: True\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Depends hashable: True\nSecurity hashable: True\n", "stderr": "", "exit_code": 0}
20submit_patch
{}
{"status": "ok", "patch_size": 1061, "files_changed": 1}
full
{"status": "ok", "patch_size": 1061, "files_changed": 1}

Patch

diff --git a/fastapi/params.py b/fastapi/params.py
index 6a58d580..d6d69c62 100644
--- a/fastapi/params.py
+++ b/fastapi/params.py
@@ -1,7 +1,7 @@
 import warnings
 from dataclasses import dataclass
 from enum import Enum
-from typing import Any, Callable, Dict, List, Optional, Sequence, Union
+from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union
 
 from fastapi.openapi.models import Example
 from pydantic.fields import FieldInfo
@@ -762,13 +762,21 @@ 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
+
+    def __hash__(self):
+        return hash((
+            self.dependency,
+            self.use_cache,
+            self.scope,
+            tuple(self.scopes) if self.scopes is not None else None,
+        ))

Test output

show
.                                                                        [100%]
1 passed in 0.29s