resolved RESOLVED PASS · 21 tool calls · 136 s · fastapi/fastapi
♻️ 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 | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn \"def Depends\" fastapi | head -20"
} | {"status": "ok", "stdout": "fastapi/param_functions.py:2221:def Depends( # noqa: N802\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "fastapi/param_functions.py:2221:def Depends( # noqa: N802\n", "stderr": "", "exit_code": 0} |
| 2 | run_command | {
"command": "grep -rn \"def Security\" fastapi | head -20"
} | {"status": "ok", "stdout": "fastapi/param_functions.py:2301:def Security( # noqa: N802\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "fastapi/param_functions.py:2301:def Security( # noqa: N802\n", "stderr": "", "exit_code": 0} |
| 3 | run_command | {
"command": "sed -n '2200,2320p' fastapi/param_functions.py"
} | {"status": "ok", "stdout": " le=le,\n min_length=min_length,\n max_length=max_length,\n pattern=pattern,\n regex=regex,\n discriminator=discriminator,\n strict=full{"status": "ok", "stdout": " le=le,\n min_length=min_length,\n max_length=max_length,\n pattern=pattern,\n regex=regex,\n discriminator=discriminator,\n strict=strict,\n multiple_of=multiple_of,\n allow_inf_nan=allow_inf_nan,\n max_digits=max_digits,\n decimal_places=decimal_places,\n example=example,\n examples=examples,\n openapi_examples=openapi_examples,\n deprecated=deprecated,\n include_in_schema=include_in_schema,\n json_schema_extra=json_schema_extra,\n **extra,\n )\n\n\ndef Depends( # noqa: N802\n dependency: Annotated[\n Optional[Callable[..., Any]],\n Doc(\n \"\"\"\n A \"dependable\" callable (like a function).\n\n Don't call it directly, FastAPI will call it for you, just pass the object\n directly.\n \"\"\"\n ),\n ] = None,\n *,\n use_cache: Annotated[\n bool,\n Doc(\n \"\"\"\n By default, after a dependency is called the first time in a request, if\n the dependency is declared again for the rest of the request (for example\n if the dependency is needed by several dependencies), the value will be\n re-used for the rest of the request.\n\n Set `use_cache` to `False` to disable this behavior and ensure the\n dependency is called again (if declared more than once) in the same request.\n \"\"\"\n ),\n ] = True,\n scope: Annotated[\n Union[Literal[\"function\", \"request\"], None],\n Doc(\n \"\"\"\n Mainly for dependencies with `yield`, define when the dependency function\n should start (the code before `yield`) and when it should end (the code\n after `yield`).\n\n * `\"function\"`: start the dependency before the *path operation function*\n that h |
| 4 | run_command | {
"command": "grep -rn \"class Depends\" fastapi | 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} |
| 5 | run_command | {
"command": "grep -rn \"class Security\" fastapi | head -20"
} | {"status": "ok", "stdout": "fastapi/dependencies/models.py:19:class SecurityRequirement:\nfastapi/security/oauth2.py:614:class SecurityScopes:\nfastapi/securityfull{"status": "ok", "stdout": "fastapi/dependencies/models.py:19:class SecurityRequirement:\nfastapi/security/oauth2.py:614:class SecurityScopes:\nfastapi/security/base.py:4:class SecurityBase:\nfastapi/params.py:773:class Security(Depends):\nfastapi/openapi/models.py:337:class SecuritySchemeType(Enum):\nfastapi/openapi/models.py:344:class SecurityBase(BaseModelWithConfig):\n", "stderr": "", "exit_code": 0} |
| 6 | run_command | {
"command": "sed -n '760,785p' 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]] = Nofull{"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} |
| 7 | run_command | {
"command": "python3 -c \"from dataclasses import dataclass; from typing import Optional, Callable, Any, Union, Literal; import collections; @dataclass\\nclass Depends:\\n dependency: Optional[Callable[..., Any]] = None\\n use_cache: bool = True\\n scope: Union[Literal['function', 'request'], None] = None\\n\\n@dataclass\\nclass Security(Depends):\\n scopes: Optional[collections.abc.Sequence[str]] = None\\n\\nd1 = Depends(dependency=lambda: 1)\\nd2 = Depends(dependency=lambda: 1)\\nprint(f'Depends hashable: {hash(d1) == hash(d2) if hash(d1) else \\\"No\\\"}')\\nprint(f'Depends in set: {len({d1, d1}) == 1}')\\n\""
} | {"status": "error", "error_type": "CommandError", "error_message": " File \"<string>\", line 1\n from dataclasses import dataclass; from typing import Optional,full{"status": "error", "error_type": "CommandError", "error_message": " File \"<string>\", line 1\n from dataclasses import dataclass; from typing import Optional, Callable, Any, Union, Literal; import collections; @dataclass\\nclass Depends:\\n dependency: Optional[Callable[..., Any]] = None\\n use_cache: bool = True\\n scope: Union[Literal['function', 'request'], None] = None\\n\\n@dataclass\\nclass Security(Depends):\\n scopes: Optional[collections.abc.Sequence[str]] = None\\n\\nd1 = Depends(dependency=lambda: 1)\\nd2 = Depends(dependency=lambda: 1)\\nprint(f'Depends hashable: {hash(d1) == hash(d2) if hash(d1) else \"No\"}')\\nprint(f'Depends in set: {len({d1, d1}) == 1}')\\n\n ^\nSyntaxError: invalid syntax\n", "details": {"stdout": "", "stderr": " File \"<string>\", line 1\n from dataclasses import dataclass; from typing import Optional, Callable, Any, Union, Literal; import collections; @dataclass\\nclass Depends:\\n dependency: Optional[Callable[..., Any]] = None\\n use_cache: bool = True\\n scope: Union[Literal['function', 'request'], None] = None\\n\\n@dataclass\\nclass Security(Depends):\\n scopes: Optional[collections.abc.Sequence[str]] = None\\n\\nd1 = Depends(dependency=lambda: 1)\\nd2 = Depends(dependency=lambda: 1)\\nprint(f'Depends hashable: {hash(d1) == hash(d2) if hash(d1) else \"No\"}')\\nprint(f'Depends in set: {len({d1, d1}) == 1}')\\n\n ^\nSyntaxError: invalid syntax\n", "exit_code": 1}} |
| 8 | run_command | {
"command": "python3 -c \"from dataclasses import dataclass; from typing import Optional, Callable, Any, Union, Literal, Sequence; \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@dataclass\nclass Security(Depends):\n scopes: Optional[Sequence[str]] = None\n\nd1 = Depends(dependency=lambda: 1)\ntry:\n hash(d1)\n print('Hashable')\nexcept TypeError as e:\n print(f'Not hashable: {e}')\n\""
} | {"status": "ok", "stdout": "Not hashable: unhashable type: 'Depends'\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Not hashable: unhashable type: 'Depends'\n", "stderr": "", "exit_code": 0} |
| 9 | run_command | {
"command": "python3 -c \"from dataclasses import dataclass; from typing import Optional, Callable, Any, Union, Literal, Sequence; \n\n@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@dataclass(frozen=True)\nclass Security(Depends):\n scopes: Optional[Sequence[str]] = None\n\nd1 = Depends(dependency=lambda: 1)\ntry:\n hash(d1)\n print('Hashable')\nexcept TypeError as e:\n print(f'Not hashable: {e}')\n\""
} | {"status": "ok", "stdout": "Hashable\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Hashable\n", "stderr": "", "exit_code": 0} |
| 10 | run_command | {
"command": "python3 -c \"from dataclasses import dataclass; from typing import Optional, Callable, Any, Union, Literal, Sequence; \n\n@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@dataclass(frozen=True)\nclass Security(Depends):\n scopes: Optional[Sequence[str]] = None\n\nd1 = Depends(dependency=lambda: 1)\nd2 = Depends(dependency=lambda: 1)\nprint(f'd1 == d2: {d1 == d2}')\nprint(f'hash(d1) == hash(d2): {hash(d1) == hash(d2)}')\n\""
} | {"status": "ok", "stdout": "d1 == d2: False\nhash(d1) == hash(d2): False\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "d1 == d2: False\nhash(d1) == hash(d2): False\n", "stderr": "", "exit_code": 0} |
| 11 | run_command | {
"command": "python3 -c \"from dataclasses import dataclass; from typing import Optional, Callable, Any, Union, Literal, Sequence; \n\n@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@dataclass(frozen=True)\nclass Security(Depends):\n scopes: Optional[Sequence[str]] = None\n\ndef my_dep(): pass\nd1 = Depends(dependency=my_dep)\nd2 = Depends(dependency=my_dep)\nprint(f'd1 == d2: {d1 == d2}')\nprint(f'hash(d1) == hash(d2): {hash(d1) == hash(d2)}')\n\""
} | {"status": "ok", "stdout": "d1 == d2: True\nhash(d1) == hash(d2): True\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "d1 == d2: True\nhash(d1) == hash(d2): True\n", "stderr": "", "exit_code": 0} |
| 12 | edit_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} |
| 13 | run_command | {
"command": "python3 -c \"from fastapi.params import Depends, Security; def d(): pass; d1 = Depends(dependency=d); d2 = Depends(dependency=d); print(f'Depends hashable: {hash(d1) == hash(d2)}'); s1 = Security(dependency=d, scopes=['a']); s2 = Security(dependency=d, scopes=['a']); print(f'Security hashable: {hash(s1) == hash(s2)}')\""
} | {"status": "error", "error_type": "CommandError", "error_message": " File \"<string>\", line 1\n from fastapi.params import Depends, Security; def d(): pass; d1full{"status": "error", "error_type": "CommandError", "error_message": " File \"<string>\", line 1\n from fastapi.params import Depends, Security; def d(): pass; d1 = Depends(dependency=d); d2 = Depends(dependency=d); print(f'Depends hashable: {hash(d1) == hash(d2)}'); s1 = Security(dependency=d, scopes=['a']); s2 = Security(dependency=d, scopes=['a']); print(f'Security hashable: {hash(s1) == hash(s2)}')\n ^^^\nSyntaxError: invalid syntax\n", "details": {"stdout": "", "stderr": " File \"<string>\", line 1\n from fastapi.params import Depends, Security; def d(): pass; d1 = Depends(dependency=d); d2 = Depends(dependency=d); print(f'Depends hashable: {hash(d1) == hash(d2)}'); s1 = Security(dependency=d, scopes=['a']); s2 = Security(dependency=d, scopes=['a']); print(f'Security hashable: {hash(s1) == hash(s2)}')\n ^^^\nSyntaxError: invalid syntax\n", "exit_code": 1}} |
| 14 | run_command | {
"command": "python3 -c \"\nfrom fastapi.params import Depends, Security\ndef d(): pass\nd1 = Depends(dependency=d)\nd2 = Depends(dependency=d)\nprint(f'Depends hashable: {hash(d1) == hash(d2)}')\n# Security scopes is a list, which is not hashable. \n# Let's see if it fails.\ntry:\n s1 = Security(dependency=d, scopes=['a'])\n print(f'Security created')\n hash(s1)\n print('Security hashable')\nexcept TypeError as e:\n print(f'Security not hashable: {e}')\n\""
} | {"status": "ok", "stdout": "Depends hashable: True\nSecurity created\nSecurity not hashable: unhashable type: 'list'\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Depends hashable: True\nSecurity created\nSecurity not hashable: unhashable type: 'list'\n", "stderr": "", "exit_code": 0} |
| 15 | run_command | {
"command": "python3 -c \"\nfrom fastapi.params import Depends, Security\ndef d(): pass\ns1 = Security(dependency=d, scopes=('a',))\ntry:\n hash(s1)\n print('Security hashable with tuple')\nexcept TypeError as e:\n print(f'Security not hashable with tuple: {e}')\n\""
} | {"status": "ok", "stdout": "Security hashable with tuple\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Security hashable with tuple\n", "stderr": "", "exit_code": 0} |
| 16 | run_command | {
"command": "python3 -c \"\nfrom fastapi.params import Depends, Security\ndef d(): pass\ns1 = Security(dependency=d, scopes=['a'])\ntry:\n hash(s1)\n print('Security hashable')\nexcept TypeError as e:\n print(f'Security not hashable: {e}')\n\""
} | {"status": "ok", "stdout": "Security not hashable: unhashable type: 'list'\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Security not hashable: unhashable type: 'list'\n", "stderr": "", "exit_code": 0} |
| 17 | run_command | {
"command": "grep -rn \"Security(\" fastapi/param_functions.py"
} | {"status": "ok", "stdout": "2301:def Security( # noqa: N802\n2376: current_user: Annotated[User, Security(get_current_active_user, scopes=[\"items\"])]\n2381: rfull{"status": "ok", "stdout": "2301:def Security( # noqa: N802\n2376: current_user: Annotated[User, Security(get_current_active_user, scopes=[\"items\"])]\n2381: return params.Security(dependency=dependency, scopes=scopes, use_cache=use_cache)\n", "stderr": "", "exit_code": 0} |
| 18 | run_command | {
"command": "sed -n '2300,2385p' fastapi/param_functions.py"
} | {"status": "ok", "stdout": "\ndef Security( # noqa: N802\n dependency: Annotated[\n Optional[Callable[..., Any]],\n Doc(\n \"\"\"\n A \"dependable\" callable (lfull{"status": "ok", "stdout": "\ndef Security( # noqa: N802\n dependency: Annotated[\n Optional[Callable[..., Any]],\n Doc(\n \"\"\"\n A \"dependable\" callable (like a function).\n\n Don't call it directly, FastAPI will call it for you, just pass the object\n directly.\n \"\"\"\n ),\n ] = None,\n *,\n scopes: Annotated[\n Optional[Sequence[str]],\n Doc(\n \"\"\"\n OAuth2 scopes required for the *path operation* that uses this Security\n dependency.\n\n The term \"scope\" comes from the OAuth2 specification, it seems to be\n intentionally vague and interpretable. It normally refers to permissions,\n in cases to roles.\n\n These scopes are integrated with OpenAPI (and the API docs at `/docs`).\n So they are visible in the OpenAPI specification.\n )\n \"\"\"\n ),\n ] = None,\n use_cache: Annotated[\n bool,\n Doc(\n \"\"\"\n By default, after a dependency is called the first time in a request, if\n the dependency is declared again for the rest of the request (for example\n if the dependency is needed by several dependencies), the value will be\n re-used for the rest of the request.\n\n Set `use_cache` to `False` to disable this behavior and ensure the\n dependency is called again (if declared more than once) in the same request.\n \"\"\"\n ),\n ] = True,\n) -> Any:\n \"\"\"\n Declare a FastAPI Security dependency.\n\n The only difference with a regular dependency is that it can declare OAuth2\n scopes that will be integrated with OpenAPI and the automatic UI docs (by default\n at `/docs`).\n\n It takes a single \"dependable\" callable (like a function).\n\n Don't call it directly, FastAPI will call it for you.\n\n Re |
| 19 | run_command | {
"command": "python3 -c \"\nfrom dataclasses import dataclass\nfrom typing import Optional, Callable, Any, Union, Literal, Sequence\n\n@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@dataclass(frozen=True)\nclass Security(Depends):\n scopes: Optional[Sequence[str]] = None\n\ndef d(): pass\ns1 = Security(dependency=d, scopes=['a'])\ntry:\n hash(s1)\n print('Hashable')\nexcept TypeError as e:\n print(f'Not hashable: {e}')\n\n# If we convert scopes to tuple in __post_init__\n@dataclass(frozen=True)\nclass SecurityFixed(Depends):\n scopes: Optional[Sequence[str]] = None\n def __post_init__(self):\n # Since it's frozen, we must use object.__setattr__\n if self.scopes is not None:\n object.__setattr__(self, 'scopes', tuple(self.scopes))\n\ns2 = SecurityFixed(dependency=d, scopes=['a'])\ntry:\n hash(s2)\n print('SecurityFixed hashable')\nexcept TypeError as e:\n print(f'SecurityFixed not hashable: {e}')\n\""
} | {"status": "ok", "stdout": "Not hashable: unhashable type: 'list'\nSecurityFixed hashable\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Not hashable: unhashable type: 'list'\nSecurityFixed hashable\n", "stderr": "", "exit_code": 0} |
| 20 | edit_file | {
"filepath": "fastapi/params.py",
"new_string": "@dataclass(frozen=True)\nclass Security(Depends):\n scopes: Optional[Sequence[str]] = None\n\n def __post_init__(self):\n if self.scopes is not None:\n object.__setattr__(self, \"scopes\", tuple(self.scopes))\n",
"old_string": "@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@@ -772,3 +7full{"status": "ok", "filepath": "fastapi/params.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/params.py\n+++ b/fastapi/params.py\n@@ -772,3 +772,7 @@\n @dataclass(frozen=True)\n class Security(Depends):\n scopes: Optional[Sequence[str]] = None\n+\n+ def __post_init__(self):\n+ if self.scopes is not None:\n+ object.__setattr__(self, \"scopes\", tuple(self.scopes))\n", "is_truncated": false} |
| 21 | run_command | {
"command": "python3 -c \"\nfrom fastapi.params import Depends, Security\ndef d(): pass\nd1 = Depends(dependency=d)\nd2 = Depends(dependency=d)\nprint(f'Depends hashable: {hash(d1) == hash(d2)}')\ns1 = Security(dependency=d, scopes=['a'])\ns2 = Security(dependency=d, scopes=['a'])\nprint(f'Security hashable: {hash(s1) == hash(s2)}')\n\""
} | {"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} |
| 22 | submit_patch | {} | {"status": "ok", "patch_size": 653, "files_changed": 1}full{"status": "ok", "patch_size": 653, "files_changed": 1} |
diff --git a/fastapi/params.py b/fastapi/params.py
index 6a58d580..2792beb8 100644
--- a/fastapi/params.py
+++ b/fastapi/params.py
@@ -762,13 +762,17 @@ 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 __post_init__(self):
+ if self.scopes is not None:
+ object.__setattr__(self, "scopes", tuple(self.scopes))
. [100%] 1 passed in 0.50s