← pod_r5_v1i

fastapi_14372

resolved RESOLVED PASS · 30 tool calls · 287 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 (31)

#ToolArgumentsResult
1run_command
{
 "command": "( grep -rn 'class Depends' --include=*.py . || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.' ) | head -40\n( grep -rn 'class Security' --include=*.py . || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.' ) | head -40\n"
}
{"status": "ok", "stdout": "./fastapi/params.py:766:class Depends:\n./fastapi/params.py:773:class Security(Depends):\n./fastapi/security/oauth2.py:614:class Sec
full
{"status": "ok", "stdout": "./fastapi/params.py:766:class Depends:\n./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}
2run_command
{
 "command": "sed -n '760,790p' fastapi/params.py\n"
}
{"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}
3run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom fastapi.params import Depends, Security\n\nd1 = Depends(lambda x: x)\nd2 = Depends(lambda x: x)\ns1 = Security(lambda x: x)\ns2 = Security(lambda x: x)\n\nprint(f\"Depends hashable: {hash(d1) == hash(d1)}\")\ntry:\n    set([d1, d1])\n    print(\"Depends can be in a set\")\nexcept Exception as e:\n    print(f\"Depends set error: {e}\")\n\ntry:\n    set([d1, d2])\n    print(\"Depends set different instances\")\nexcept Exception as e:\n    print(f\"Depends set different instances error: {e}\")\n\ntry:\n    hash(d1)\n    print(\"hash(d1) worked\")\nexcept TypeError as e:\n    print(f\"hash(d1) failed: {e}\")\n\ntry:\n    hash(s1)\n    print(\"hash(s1) worked\")\nexcept TypeError as e:\n    print(f\"hash(s1) failed: {e}\")\nPY\n"
}
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"<stdin>\", line 8, in <module>\nTypeError: unhas
full
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n  File \"<stdin>\", line 8, in <module>\nTypeError: unhashable type: 'Depends'\n", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n  File \"<stdin>\", line 8, in <module>\nTypeError: unhashable type: 'Depends'\n", "exit_code": 1}}
4run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/params.py\")\ns = p.read_text()\nold = \"\"\"@dataclass\nclass Depends:\n    dependency: Optional[Callable[..., Any]] = None\n    use_cache: bool = True\n    scope: Union[Literal[\"function\", \"request\"], None] = None\"\"\"\nnew = \"\"\"@dataclass(frozen=True)\nclass Depends:\n    dependency: Optional[Callable[..., Any]] = None\n    use_cache: bool = True\n    scope: Union[Literal[\"function\", \"request\"], None] = None\"\"\"\nassert s.count(old) == 1, (\"old text found this many times:\", s.count(old))\nt = s.replace(old, new)\np.write_text(t)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
5run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom fastapi.params import Depends, Security\n\n# Mock callable for testing\ndef dep():\n    return 1\n\nd1 = Depends(dep)\nd2 = Depends(dep)\nd3 = Depends(lambda x: x)\ns1 = Security(dep)\ns2 = Security(dep)\ns3 = Security(dep, scopes=[\"admin\"])\n\nprint(f\"d1 hash: {hash(d1)}\")\nprint(f\"d2 hash: {hash(d2)}\")\nprint(f\"d3 hash: {hash(d3)}\")\nprint(f\"s1 hash: {hash(s1)}\")\nprint(f\"s2 hash: {hash(s2)}\")\nprint(f\"s3 hash: {hash(s3)}\")\n\nprint(f\"d1 == d2: {d1 == d2}\")\nprint(f\"d1 == d3: {d1 == d3}\")\nprint(f\"s1 == s2: {s1 == s2}\")\nprint(f\"s1 == s3: {s1 == s3}\")\n\n# Test set\nprint(f\"Set depends: {len({d1, d2, d3})}\")\nprint(f\"Set security: {len({s1, s2, s3})}\")\n\n# Test immutability\ntry:\n    d1.dependency = lambda x: x\nexcept Exception as e:\n    print(f\"d1 mutation failed as expected: {e}\")\n\ntry:\n    s1.scopes = [\"user\"]\nexcept Exception as e:\n    print(f\"s1 mutation failed as expected: {e}\")\nPY\n"
}
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"<stdin>\", line 1, in <module>\n File \"/private
full
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\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 772, in <module>\n    @dataclass\n     ^^^^^^^^^\n  File \"/Users/jp/.local/share/uv/python/cpython-3.13.15-macos-aarch64-none/lib/python3.13/dataclasses.py\", line 1354, in dataclass\n    return wrap(cls)\n  File \"/Users/jp/.local/share/uv/python/cpython-3.13.15-macos-aarch64-none/lib/python3.13/dataclasses.py\", line 1344, in wrap\n    return _process_class(cls, init, repr, eq, order, unsafe_hash,\n                          frozen, match_args, kw_only, slots,\n                          weakref_slot)\n  File \"/Users/jp/.local/share/uv/python/cpython-3.13.15-macos-aarch64-none/lib/python3.13/dataclasses.py\", line 1038, in _process_class\n    raise TypeError('cannot inherit non-frozen dataclass from a '\n                    'frozen one')\nTypeError: cannot inherit non-frozen dataclass from a frozen one\n", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\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 772, in <module>\n    @dataclass\n     ^^^^^^^^^\n  File \"/Users/jp/.local/share/uv/python
6run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/params.py\")\ns = p.read_text()\n\n# First, remove my previous incorrect change to Depends\nold_frozen = \"\"\"@dataclass(frozen=True)\nclass Depends:\n    dependency: Optional[Callable[..., Any]] = None\n    use_cache: bool = True\n    scope: Union[Literal[\"function\", \"request\"], None] = None\"\"\"\nold_normal = \"\"\"@dataclass\nclass Depends:\n    dependency: Optional[Callable[..., Any]] = None\n    use_cache: bool = True\n    scope: Union[Literal[\"function\", \"request\"], None] = None\"\"\"\n\nif old_frozen in s:\n    s = s.replace(old_frozen, old_normal)\n\n# Now make both frozen=True\ns = s.replace(old_normal, old_frozen)\n# Also need to make Security frozen\ns = s.replace(\"@dataclass\\nclass Security(Depends):\", \"@dataclass(frozen=True)\\nclass Security(Depends):\")\n\np.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
7run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom fastapi.params import Depends, Security\n\ndef dep():\n    return 1\n\nd1 = Depends(dep)\nd2 = Depends(dep)\nd3 = Depends(lambda x: x)\ns1 = Security(dep)\ns2 = Security(dep)\ns3 = Security(dep, scopes=[\"admin\"])\n\nprint(f\"d1 hash: {hash(d1)}\")\nprint(f\"d2 hash: {hash(d2)}\")\nprint(f\"d3 hash: {hash(d3)}\")\nprint(f\"s1 hash: {hash(s1)}\")\nprint(f\"s2 hash: {hash(s2)}\")\nprint(f\"s3 hash: {hash(s3)}\")\n\nprint(f\"d1 == d2: {d1 == d2}\")\nprint(f\"d1 == d3: {d1 == d3}\")\nprint(f\"s1 == s2: {s1 == s2}\")\nprint(f\"s1 == s3: {s1 == s3}\")\n\nprint(f\"Set depends: {len({d1, d2, d3})}\")\nprint(f\"Set security: {len({s1, s2, s3})}\")\n\ntry:\n    d1.dependency = lambda x: x\nexcept Exception as e:\n    print(f\"d1 mutation failed as expected: {e}\")\n\ntry:\n    s1.scopes = [\"user\"]\nexcept Exception as e:\n    print(f\"s1 mutation failed as expected: {e}\")\nPY\n"
}
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"<stdin>\", line 18, in <module>\n File \"<string
full
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n  File \"<stdin>\", line 18, in <module>\n  File \"<string>\", line 25, in __hash__\nTypeError: unhashable type: 'list'\n", "details": {"stdout": "d1 hash: -607709536567178759\nd2 hash: -607709536567178759\nd3 hash: -1596239253517067152\ns1 hash: 8577765045011194855\ns2 hash: 8577765045011194855\n", "stderr": "Traceback (most recent call last):\n  File \"<stdin>\", line 18, in <module>\n  File \"<string>\", line 25, in __hash__\nTypeError: unhashable type: 'list'\n", "exit_code": 1}}
8run_command
{
 "command": "sed -n '760,780p' fastapi/params.py\n"
}
{"status": "ok", "stdout": " json_schema_extra=json_schema_extra,\n **extra,\n )\n\n\n@dataclass(frozen=True)\nclass Depends:\n dependency: Optional[Callable[..
full
{"status": "ok", "stdout": "            json_schema_extra=json_schema_extra,\n            **extra,\n        )\n\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\n@dataclass(frozen=True)\nclass Security(Depends):\n    scopes: Optional[Sequence[str]] = None\n", "stderr": "", "exit_code": 0}
9run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/params.py\")\ns = p.read_text()\nold = \"\"\"@dataclass(frozen=True)\nclass Security(Depends):\n    scopes: Optional[Sequence[str]] = None\"\"\"\nnew = \"\"\"@dataclass(frozen=True)\nclass Security(Depends):\n    scopes: Optional[tuple[str, ...]] = None\"\"\"\ns = s.replace(old, new)\np.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
10run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom fastapi.params import Depends, Security\n\ndef dep():\n    return 1\n\nd1 = Depends(dep)\nd2 = Depends(dep)\nd3 = Depends(lambda x: x)\ns1 = Security(dep)\ns2 = Security(dep)\ns3 = Security(dep, scopes=[\"admin\"]) # Wait, if I use a list here, will it still fail?\n\ntry:\n    print(f\"s3 hash: {hash(s3)}\")\nexcept TypeError as e:\n    print(f\"s3 hash failed: {e}\")\n\n# If I use a tuple:\ns3_tuple = Security(dep, scopes=(\"admin\",))\ntry:\n    print(f\"s3_tuple hash: {hash(s3_tuple)}\")\nexcept TypeError as e:\n    print(f\"s3_tuple hash failed: {e}\")\n\nprint(f\"s1 == s2: {s1 == s2}\")\nprint(f\"s1 == s3_tuple: {s1 == s3_tuple}\")\nPY\n"
}
{"status": "ok", "stdout": "s3 hash failed: unhashable type: 'list'\ns3_tuple hash: 1628205305993413151\ns1 == s2: True\ns1 == s3_tuple: False\n", "stderr": "",
full
{"status": "ok", "stdout": "s3 hash failed: unhashable type: 'list'\ns3_tuple hash: 1628205305993413151\ns1 == s2: True\ns1 == s3_tuple: False\n", "stderr": "", "exit_code": 0}
11run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/params.py\")\ns = p.read_text()\n\n# Change scopes type back to Sequence[str] for compatibility, \n# but we need to handle the case where a list is passed in __post_init__.\n# Wait, dataclass(frozen=True) doesn't allow modifying fields in __post_init__ easily \n# unless we use object.__setattr__.\n\n# Let's see how to make it hashable even if scopes is a list.\n# The problem is that the dataclass hash depends on the fields.\n# If one of the fields is a list, it's unhashable.\n\n# We can implement __hash__ and __eq__ manually, or use __post_init__ to convert lists to tuples.\n\n# Let's try using __post_init__ to convert lists to tuples.\n# But since it's frozen=True, we must use object.__setattr__.\n\nold_security = \"\"\"@dataclass(frozen=True)\nclass Security(Depends):\n    scopes: Optional[tuple[str, ...]] = None\"\"\"\n\nnew_security = \"\"\"@dataclass(frozen=True)\nclass Security(Depends):\n    scopes: Optional[tuple[str, ...]] = None\n\n    def __post_init__(self):\n        if self.scopes is not None and not isinstance(self.scopes, tuple):\n            object.__setattr__(self, \"scopes\", tuple(self.scopes))\"\"\"\n\ns = s.replace(old_security, new_security)\np.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
12run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom fastapi.params import Depends, Security\n\ndef dep():\n    return 1\n\nd1 = Depends(dep)\nd2 = Depends(dep)\ns1 = Security(dep)\ns2 = Security(dep)\ns3 = Security(dep, scopes=[\"admin\"])\ns4 = Security(dep, scopes=(\"admin\",))\n\nprint(f\"d1 hash: {hash(d1)}\")\nprint(f\"d2 hash: {hash(d2)}\")\nprint(f\"s1 hash: {hash(s1)}\")\nprint(f\"s2 hash: {hash(s2)}\")\nprint(f\"s3 hash: {hash(s3)}\")\nprint(f\"s4 hash: {hash(s4)}\")\n\nprint(f\"d1 == d2: {d1 == d2}\")\nprint(f\"s1 == s2: {s1 == s2}\")\nprint(f\"s3 == s4: {s3 == s4}\")\n\nprint(f\"Set depends: {len({d1, d2})}\")\nprint(f\"Set security: {len({s1, s2, s3, s4})}\")\nPY\n"
}
{"status": "ok", "stdout": "d1 hash: -8630056160870762876\nd2 hash: -8630056160870762876\ns1 hash: -4648552809016419897\ns2 hash: -4648552809016419897\ns3 hash:
full
{"status": "ok", "stdout": "d1 hash: -8630056160870762876\nd2 hash: -8630056160870762876\ns1 hash: -4648552809016419897\ns2 hash: -4648552809016419897\ns3 hash: 6363703878490528142\ns4 hash: 6363703878490528142\nd1 == d2: True\ns1 == s2: True\ns3 == s4: True\nSet depends: 1\nSet security: 2\n", "stderr": "", "exit_code": 0}
13run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/params.py\")\ns = p.read_text()\n\n# I used tuple[str, ...] which might be incompatible with older python versions (3.8, 3.9).\n# FastAPI supports older versions. I should use Sequence[str] or Tuple[str, ...] from typing.\n\n# Let's check the imports in fastapi/params.py\nimport fastapi.params as params\n# Wait, I can't check imports easily without looking at the file.\n\n# Let's use the actual file content and make it more robust.\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
14run_command
{
 "command": "sed -n '1,100p' fastapi/params.py\n"
}
{"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            Optional[str],\n            deprecated(\n                \"Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead.\"\n            ),\n        ] = None,\n        discriminator: Union[str, None] = None,\n        strict: Union[bool, None] = _Unset,\n        multiple_of: Union[float, None] = _Unset,\n        allow_inf_nan: Union[bool, None] = _Unset,\n        max_digits: Union[int, None] = _Unset,\n        decimal_plac
15run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/params.py\")\ns = p.read_text()\n\n# Let's restore Sequence[str] and use __post_init__\n# First, find the current Security class and replace it\nimport re\n\n# Find the Security class and replace it with a version that uses Sequence[str]\n# and has __post_init__\npattern = r\"@dataclass\\(frozen=True\\)\\nclass Security\\(Depends\\):\\n    scopes: Optional\\[tuple\\[str, \\.\\.\\.\\]\\] = None\\n\\n    def __post_init__\\(self\\):\\n        if self\\.scopes is not None and not isinstance\\(self\\.scopes, tuple\\):\\n            object.\\_\\_setattr__\\(self, \"scopes\", tuple\\(self\\.scopes\\)\\)\"\n\n# The above pattern might be tricky because of the quotes. Let's use a simpler replace.\n\n# I will just rewrite the whole block.\nold_block = \"\"\"@dataclass(frozen=True)\nclass Security(Depends):\n    scopes: Optional[tuple[str, ...]] = None\n\n    def __post_init__(self):\n        if self.scopes is not None and not isinstance(self.scopes, tuple):\n            object.__setattr__(self, \"scopes\", tuple(self.scopes))\"\"\"\n\nnew_block = \"\"\"@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 and not isinstance(self.scopes, tuple):\n            object.__setattr__(self, \"scopes\", tuple(self.scopes))\"\"\"\n\ns = s.replace(old_block, new_block)\np.write_text(s)\nPY\n"
}
{"status": "error", "error_type": "CommandError", "error_message": " File \"<stdin>\", line 11\n pattern = r\"@dataclass\\(frozen=True\\)\\nclass Security\\(Dep
full
{"status": "error", "error_type": "CommandError", "error_message": "  File \"<stdin>\", line 11\n    pattern = r\"@dataclass\\(frozen=True\\)\\nclass Security\\(Depends\\):\\n    scopes: Optional\\[tuple\\[str, \\.\\.\\.\\]\\] = None\\n\\n    def __post_init__\\(self\\):\\n        if self\\.scopes is not None and not isinstance\\(self\\.scopes, tuple\\):\\n            object.\\_\\_setattr__\\(self, \"scopes\", tuple\\(self\\.scopes\\)\\)\"\n                                                                                                                                                                                                                                                                                       ^^^^^^\nSyntaxError: invalid syntax\n", "details": {"stdout": "", "stderr": "  File \"<stdin>\", line 11\n    pattern = r\"@dataclass\\(frozen=True\\)\\nclass Security\\(Depends\\):\\n    scopes: Optional\\[tuple\\[str, \\.\\.\\.\\]\\] = None\\n\\n    def __post_init__\\(self\\):\\n        if self\\.scopes is not None and not isinstance\\(self\\.scopes, tuple\\):\\n            object.\\_\\_setattr__\\(self, \"scopes\", tuple\\(self\\.scopes\\)\\)\"\n                                                                                                                                                                                                                                                                                       ^^^^^^\nSyntaxError: invalid syntax\n", "exit_code": 1}}
16run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/params.py\")\ns = p.read_text()\n\n# Find the start of class Security and replace it until the end of the class\n# We know it's a small class.\nimport re\n# Use a regex to replace the entire Security class definition\ns = re.sub(\n    r\"@dataclass\\(frozen=True\\)\\nclass Security\\(Depends\\):.*?Optional\\[tuple\\[str, \\.\\.\\.\\]\\] = None.*?def __post_init__\\(self\\):.*?object\\.__setattr__\\(self, \\\"scopes\\\", tuple\\(self\\.scopes\\)\\)\",\n    r\"@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 and not isinstance(self.scopes, tuple):\\n            object.__setattr__(self, \\\"scopes\\\", tuple(self.scopes))\",\n    s,\n    flags=re.DOTALL\n)\np.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
17run_command
{
 "command": "sed -n '760,800p' fastapi/params.py\n"
}
{"status": "ok", "stdout": " json_schema_extra=json_schema_extra,\n **extra,\n )\n\n\n@dataclass(frozen=True)\nclass Depends:\n dependency: Optional[Callable[..
full
{"status": "ok", "stdout": "            json_schema_extra=json_schema_extra,\n            **extra,\n        )\n\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\n@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 and not isinstance(self.scopes, tuple):\n            object.__setattr__(self, \\\"scopes\\\", tuple(self.scopes))\n", "stderr": "", "exit_code": 0}
18run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/params.py\")\ns = p.read_text()\n# Fix the triple backslashes in the previous re.sub\ns = s.replace('\\\\\\\\\"scopes\\\\\\\\\\\\\"', '\"scopes\"')\np.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
19run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom fastapi.params import Depends, Security\n\ndef dep():\n    return 1\n\nd1 = Depends(dep)\nd2 = Depends(dep)\ns1 = Security(dep)\ns2 = Security(dep)\ns3 = Security(dep, scopes=[\"admin\"])\ns4 = Security(dep, scopes=(\"admin\",))\n\nprint(f\"d1 hash: {hash(d1)}\")\nprint(f\"d2 hash: {hash(d2)}\")\nprint(f\"s1 hash: {hash(s1)}\")\nprint(f\"s2 hash: {hash(s2)}\")\nprint(f\"s3 hash: {hash(s3)}\")\nprint(f\"s4 hash: {hash(s4)}\")\n\nprint(f\"d1 == d2: {d1 == d2}\")\nprint(f\"s1 == s2: {s1 == s2}\")\nprint(f\"s3 == s4: {s3 == s4}\")\n\nprint(f\"Set depends: {len({d1, d2})}\")\nprint(f\"Set security: {len({s1, s2, s3, s4})}\")\nPY\n"
}
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"<stdin>\", line 1, in <module>\n File \"/private
full
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\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 778\n    object.__setattr__(self, \\\"scopes\\\", tuple(self.scopes))\n                              ^\nSyntaxError: unexpected character after line continuation character\n", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\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 778\n    object.__setattr__(self, \\\"scopes\\\", tuple(self.scopes))\n                              ^\nSyntaxError: unexpected character after line continuation character\n", "exit_code": 1}}
20run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/params.py\")\ns = p.read_text()\n# Let's just use a very simple replace for the broken line\ns = s.replace('\\\\\\\\\\\\\\\"scopes\\\\\\\\\\\\\"', '\"scopes\"')\np.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
21run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom fastapi.params import Depends, Security\n\ndef dep():\n    return 1\n\nd1 = Depends(dep)\nd2 = Depends(dep)\ns1 = Security(dep)\ns2 = Security(dep)\ns3 = Security(dep, scopes=[\"admin\"])\ns4 = Security(dep, scopes=(\"admin\",))\n\nprint(f\"d1 hash: {hash(d1)}\")\nprint(f\"d2 hash: {hash(d2)}\")\nprint(f\"s1 hash: {hash(s1)}\")\nprint(f\"s2 hash: {hash(s2)}\")\nprint(f\"s3 hash: {hash(s3)}\")\nprint(f\"s4 hash: {hash(s4)}\")\n\nprint(f\"d1 == d2: {d1 == d2}\")\nprint(f\"s1 == s2: {s1 == s2}\")\nprint(f\"s3 == s4: {s3 == s4}\")\n\nprint(f\"Set depends: {len({d1, d2})}\")\nprint(f\"Set security: {len({s1, s2, s3, s4})}\")\nPY\n"
}
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"<stdin>\", line 1, in <module>\n File \"/private
full
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\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 778\n    object.__setattr__(self, \\\"scopes\\\", tuple(self.scopes))\n                              ^\nSyntaxError: unexpected character after line continuation character\n", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\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 778\n    object.__setattr__(self, \\\"scopes\\\", tuple(self.scopes))\n                              ^\nSyntaxError: unexpected character after line continuation character\n", "exit_code": 1}}
22run_command
{
 "command": "sed -n '770,785p' fastapi/params.py\n"
}
{"status": "ok", "stdout": "\n\n@dataclass(frozen=True)\nclass Security(Depends):\n scopes: Optional[Sequence[str]] = None\n\n def __post_init__(self):\n if sel
full
{"status": "ok", "stdout": "\n\n@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 and not isinstance(self.scopes, tuple):\n            object.__setattr__(self, \\\"scopes\\\", tuple(self.scopes))\n", "stderr": "", "exit_code": 0}
23run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/params.py\")\ns = p.read_text()\ns = s.replace('\\\\\\\\\\\\\\\"scopes\\\\\\\\\\\\\"', '\"scopes\"')\np.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
24run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom fastapi.params import Depends, Security\n\ndef dep():\n    return 1\n\nd1 = Depends(dep)\nd2 = Depends(dep)\ns1 = Security(dep)\ns2 = Security(dep)\ns3 = Security(dep, scopes=[\"admin\"])\ns4 = Security(dep, scopes=(\"admin\",))\n\nprint(f\"d1 hash: {hash(d1)}\")\nprint(f\"d2 hash: {hash(d2)}\")\nprint(f\"s1 hash: {hash(s1)}\")\nprint(f\"s2 hash: {hash(s2)}\")\nprint(f\"s3 hash: {hash(s3)}\")\nprint(f\"s4 hash: {hash(s4)}\")\n\nprint(f\"d1 == d2: {d1 == d2}\")\nprint(f\"s1 == s2: {s1 == s2}\")\nprint(f\"s3 == s4: {s3 == s4}\")\n\nprint(f\"Set depends: {len({d1, d2})}\")\nprint(f\"Set security: {len({s1, s2, s3, s4})}\")\nPY\n"
}
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"<stdin>\", line 1, in <module>\n File \"/private
full
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\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 778\n    object.__setattr__(self, \\\"scopes\\\", tuple(self.scopes))\n                              ^\nSyntaxError: unexpected character after line continuation character\n", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\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 778\n    object.__setattr__(self, \\\"scopes\\\", tuple(self.scopes))\n                              ^\nSyntaxError: unexpected character after line continuation character\n", "exit_code": 1}}
25run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/params.py\")\ns = p.read_text()\n# The problem is that there are more backslashes than I thought.\n# Let's just replace the whole line.\nimport re\ns = re.sub(r'object\\.__setattr__\\(self, .*?tuple\\(self\\.scopes\\)\\)', '            object.__setattr__(self, \"scopes\", tuple(self.scopes))', s)\np.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
26run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom fastapi.params import Depends, Security\n\ndef dep():\n    return 1\n\nd1 = Depends(dep)\nd2 = Depends(dep)\ns1 = Security(dep)\ns2 = Security(dep)\ns3 = Security(dep, scopes=[\"admin\"])\ns4 = Security(dep, scopes=(\"admin\",))\n\nprint(f\"d1 hash: {hash(d1)}\")\nprint(f\"d2 hash: {hash(d2)}\")\nprint(f\"s1 hash: {hash(s1)}\")\nprint(f\"s2 hash: {hash(s2)}\")\nprint(f\"s3 hash: {hash(s3)}\")\nprint(f\"s4 hash: {hash(s4)}\")\n\nprint(f\"d1 == d2: {d1 == d2}\")\nprint(f\"s1 == s2: {s1 == s2}\")\nprint(f\"s3 == s4: {s3 == s4}\")\n\nprint(f\"Set depends: {len({d1, d2})}\")\nprint(f\"Set security: {len({s1, s2, s3, s4})}\")\nPY\n"
}
{"status": "ok", "stdout": "d1 hash: 5271028383955603241\nd2 hash: 5271028383955603241\ns1 hash: 6563828377808158944\ns2 hash: 6563828377808158944\ns3 hash: 613
full
{"status": "ok", "stdout": "d1 hash: 5271028383955603241\nd2 hash: 5271028383955603241\ns1 hash: 6563828377808158944\ns2 hash: 6563828377808158944\ns3 hash: 6132178290498045135\ns4 hash: 6132178290498045135\nd1 == d2: True\ns1 == s2: True\ns3 == s4: True\nSet depends: 1\nSet security: 2\n", "stderr": "", "exit_code": 0}
27run_command
{
 "command": "git diff\n"
}
{"status": "ok", "stdout": "diff --git a/fastapi/params.py b/fastapi/params.py\nindex 6a58d580..e1dbfa29 100644\n--- a/fastapi/params.py\n+++ b/fastapi/params.p
full
{"status": "ok", "stdout": "diff --git a/fastapi/params.py b/fastapi/params.py\nindex 6a58d580..e1dbfa29 100644\n--- a/fastapi/params.py\n+++ b/fastapi/params.py\n@@ -762,13 +762,17 @@ class File(Form):  # type: ignore[misc]\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+\n+    def __post_init__(self):\n+        if self.scopes is not None and not isinstance(self.scopes, tuple):\n+                        object.__setattr__(self, \"scopes\", tuple(self.scopes))\n", "stderr": "", "exit_code": 0}
28run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/params.py\")\ns = p.read_text()\n# Fix indentation of object.__setattr__\nimport re\ns = s.replace('                        object.__setattr__(self, \"scopes\", tuple(self.scopes))', '            object.__setattr__(self, \"scopes\", tuple(self.scopes))')\np.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
29run_command
{
 "command": "git diff\n"
}
{"status": "ok", "stdout": "diff --git a/fastapi/params.py b/fastapi/params.py\nindex 6a58d580..b7e4d10b 100644\n--- a/fastapi/params.py\n+++ b/fastapi/params.p
full
{"status": "ok", "stdout": "diff --git a/fastapi/params.py b/fastapi/params.py\nindex 6a58d580..b7e4d10b 100644\n--- a/fastapi/params.py\n+++ b/fastapi/params.py\n@@ -762,13 +762,17 @@ class File(Form):  # type: ignore[misc]\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+\n+    def __post_init__(self):\n+        if self.scopes is not None and not isinstance(self.scopes, tuple):\n+            object.__setattr__(self, \"scopes\", tuple(self.scopes))\n", "stderr": "", "exit_code": 0}
30run_command
{
 "command": "submit_patch"
}
{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/bas
full
{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/bash: submit_patch: command not found\n", "exit_code": 127}}
31submit_patch
{}
{"status": "ok", "patch_size": 692, "files_changed": 1}
full
{"status": "ok", "patch_size": 692, "files_changed": 1}

Patch

diff --git a/fastapi/params.py b/fastapi/params.py
index 6a58d580..b7e4d10b 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 and not isinstance(self.scopes, tuple):
+            object.__setattr__(self, "scopes", tuple(self.scopes))

Test output

show
.                                                                        [100%]
1 passed in 0.47s