failed WRONG_FIX wrong_fix ยท 19 tool calls ยท 120 s ยท fastapi/fastapi
๐ Fix OpenAPI security scheme OAuth2 scopes declaration, deduplicate security schemes with different scopes ๐ Fix OpenAPI security scheme OAuth2 scopes declaration, deduplicate security schemes with different scopes This should fix https://github.com/fastapi/fastapi/issues/14454
| # | Tool | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn \"securitySchemes\" fastapi/openapi | head -20"
} | {"status": "ok", "stdout": "fastapi/openapi/models.py:422: securitySchemes: Optional[Dict[str, Union[SecurityScheme, Reference]]] = None\nfastapi/openapi/utils.full{"status": "ok", "stdout": "fastapi/openapi/models.py:422: securitySchemes: Optional[Dict[str, Union[SecurityScheme, Reference]]] = None\nfastapi/openapi/utils.py:523: components.setdefault(\"securitySchemes\", {}).update(\nfastapi/openapi/utils.py:542: components.setdefault(\"securitySchemes\", {}).update(\n", "exit_code": 0} |
| 2 | read_file | {
"filepath": "fastapi/openapi/utils.py",
"start_line": 500,
"end_line": 560
} | {"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": " webhook_paths: Dict[str, Dict[str, Any]] = {}\n operation_ids: Set[str] = set()\n all_fielfull{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": " webhook_paths: Dict[str, Dict[str, Any]] = {}\n operation_ids: Set[str] = set()\n all_fields = get_fields_from_routes(list(routes or []) + list(webhooks or []))\n model_name_map = get_compat_model_name_map(all_fields)\n field_mapping, definitions = get_definitions(\n fields=all_fields,\n model_name_map=model_name_map,\n separate_input_output_schemas=separate_input_output_schemas,\n )\n for route in routes or []:\n if isinstance(route, routing.APIRoute):\n result = get_openapi_path(\n route=route,\n operation_ids=operation_ids,\n model_name_map=model_name_map,\n field_mapping=field_mapping,\n separate_input_output_schemas=separate_input_output_schemas,\n )\n if result:\n path, security_schemes, path_definitions = result\n if path:\n paths.setdefault(route.path_format, {}).update(path)\n if security_schemes:\n components.setdefault(\"securitySchemes\", {}).update(\n security_schemes\n )\n if path_definitions:\n definitions.update(path_definitions)\n for webhook in webhooks or []:\n if isinstance(webhook, routing.APIRoute):\n result = get_openapi_path(\n route=webhook,\n operation_ids=operation_ids,\n model_name_map=model_name_map,\n field_mapping=field_mapping,\n separate_input_output_schemas=separate_input_output_schemas,\n )\n if result:\n path, security_schemes, path_definitions = result\n if path:\n webhook_paths.setdefault(webhook.path_format, {}).update(path)\n if security_schemes:\n co |
| 3 | run_command | {
"command": "grep -rn \"get_openapi_path\" fastapi/openapi | head -20"
} | {"status": "ok", "stdout": "fastapi/openapi/utils.py:250:def get_openapi_path(\nfastapi/openapi/utils.py:321: ) = get_openapi_path(\nfastapi/openapi/utils.py:51full{"status": "ok", "stdout": "fastapi/openapi/utils.py:250:def get_openapi_path(\nfastapi/openapi/utils.py:321: ) = get_openapi_path(\nfastapi/openapi/utils.py:511: result = get_openapi_path(\nfastapi/openapi/utils.py:530: result = get_openapi_path(\n", "exit_code": 0} |
| 4 | read_file | {
"end_line": 350,
"filepath": "fastapi/openapi/utils.py",
"start_line": 250
} | {"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": "def get_openapi_path(\n *,\n route: routing.APIRoute,\n operation_ids: Set[str],\n model_nafull{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": "def get_openapi_path(\n *,\n route: routing.APIRoute,\n operation_ids: Set[str],\n model_name_map: ModelNameMap,\n field_mapping: Dict[\n Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue\n ],\n separate_input_output_schemas: bool = True,\n) -> Tuple[Dict[str, Any], Dict[str, Any], Dict[str, Any]]:\n path = {}\n security_schemes: Dict[str, Any] = {}\n definitions: Dict[str, Any] = {}\n assert route.methods is not None, \"Methods must be a list\"\n if isinstance(route.response_class, DefaultPlaceholder):\n current_response_class: Type[Response] = route.response_class.value\n else:\n current_response_class = route.response_class\n assert current_response_class, \"A response class is needed to generate OpenAPI\"\n route_response_media_type: Optional[str] = current_response_class.media_type\n if route.include_in_schema:\n for method in route.methods:\n operation = get_openapi_operation_metadata(\n route=route, method=method, operation_ids=operation_ids\n )\n parameters: List[Dict[str, Any]] = []\n flat_dependant = get_flat_dependant(route.dependant, skip_repeats=True)\n security_definitions, operation_security = get_openapi_security_definitions(\n flat_dependant=flat_dependant\n )\n if operation_security:\n operation.setdefault(\"security\", []).extend(operation_security)\n if security_definitions:\n security_schemes.update(security_definitions)\n operation_parameters = _get_openapi_operation_parameters(\n dependant=route.dependant,\n model_name_map=model_name_map,\n field_mapping=field_mapping,\n separate_input_output_schemas=separate_input_output_schemas,\n )\n |
| 5 | run_command | {
"command": "grep -rn \"get_openapi_security_definitions\" fastapi/openapi | head -20"
} | {"status": "ok", "stdout": "fastapi/openapi/utils.py:78:def get_openapi_security_definitions(\nfastapi/openapi/utils.py:277: security_definitions, operation_secfull{"status": "ok", "stdout": "fastapi/openapi/utils.py:78:def get_openapi_security_definitions(\nfastapi/openapi/utils.py:277: security_definitions, operation_security = get_openapi_security_definitions(\n", "exit_code": 0} |
| 6 | read_file | {
"end_line": 120,
"filepath": "fastapi/openapi/utils.py",
"start_line": 70
} | {"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": " \"2XX\": \"Success\",\n \"3XX\": \"Redirection\",\n \"4XX\": \"Client Error\",\n \"5XX\": full{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": " \"2XX\": \"Success\",\n \"3XX\": \"Redirection\",\n \"4XX\": \"Client Error\",\n \"5XX\": \"Server Error\",\n \"DEFAULT\": \"Default Response\",\n}\n\n\ndef get_openapi_security_definitions(\n flat_dependant: Dependant,\n) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:\n security_definitions = {}\n operation_security = []\n for security_requirement in flat_dependant.security_requirements:\n security_definition = jsonable_encoder(\n security_requirement.security_scheme.model,\n by_alias=True,\n exclude_none=True,\n )\n security_name = security_requirement.security_scheme.scheme_name\n security_definitions[security_name] = security_definition\n operation_security.append({security_name: security_requirement.scopes})\n return security_definitions, operation_security\n\n\ndef _get_openapi_operation_parameters(\n *,\n dependant: Dependant,\n model_name_map: ModelNameMap,\n field_mapping: Dict[\n Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue\n ],\n separate_input_output_schemas: bool = True,\n) -> List[Dict[str, Any]]:\n parameters = []\n flat_dependant = get_flat_dependant(dependant, skip_repeats=True)\n path_params = _get_flat_fields_from_params(flat_dependant.path_params)\n query_params = _get_flat_fields_from_params(flat_dependant.query_params)\n header_params = _get_flat_fields_from_params(flat_dependant.header_params)\n cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params)\n parameter_groups = [\n (ParamTypes.path, path_params),\n (ParamTypes.query, query_params),\n (ParamTypes.header, header_params),\n (ParamTypes.cookie, cookie_params),\n ]\n default_convert_underscores = True\n if len(flat_dependant.header_params) == 1:\n first_field = flat_dependant.header |
| 7 | run_command | {
"command": "grep -rn \"OAuth2\" fastapi/security/oauth2.py | head -20"
} | {"status": "ok", "stdout": "5:from fastapi.openapi.models import OAuth2 as OAuth2Model\n17:class OAuth2PasswordRequestForm:\n20: for an OAuth2 password flow.\n2full{"status": "ok", "stdout": "5:from fastapi.openapi.models import OAuth2 as OAuth2Model\n17:class OAuth2PasswordRequestForm:\n20: for an OAuth2 password flow.\n22: The OAuth2 specification dictates that for a password flow the data should be\n29: [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).\n37: from fastapi.security import OAuth2PasswordRequestForm\n43: def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):\n55: Note that for OAuth2 the scope `items:read` is a single scope in an opaque string.\n70: The OAuth2 spec says it is required and MUST be the fixed string\n73: `OAuth2PasswordRequestFormStrict` dependency.\n82: `username` string. The OAuth2 spec requires the exact field name\n92: `password` string. The OAuth2 spec requires the exact field name\n127: But the OAuth2 specification recommends sending the `client_id` and\n138: as part of the form fields. But the OAuth2 specification recommends\n153:class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):\n156: for an OAuth2 password flow.\n158: The OAuth2 specification dictates that for a password flow the data should be\n164: The only difference between `OAuth2PasswordRequestFormStrict` and\n165: `OAuth2PasswordRequestForm` is that `OAuth2PasswordRequestFormStrict` requires the\n167: is required in the OAuth2 specification (it seems that for no particular reason),\n", "exit_code": 0} |
| 8 | read_file | {
"end_line": 200,
"filepath": "fastapi/security/oauth2.py",
"start_line": 1
} | {"status": "ok", "filepath": "fastapi/security/oauth2.py", "content": "from typing import Any, Dict, List, Optional, Union, cast\n\nfrom annotated_doc import Dofull{"status": "ok", "filepath": "fastapi/security/oauth2.py", "content": "from typing import Any, Dict, List, Optional, Union, cast\n\nfrom annotated_doc import Doc\nfrom fastapi.exceptions import HTTPException\nfrom fastapi.openapi.models import OAuth2 as OAuth2Model\nfrom fastapi.openapi.models import OAuthFlows as OAuthFlowsModel\nfrom fastapi.param_functions import Form\nfrom fastapi.security.base import SecurityBase\nfrom fastapi.security.utils import get_authorization_scheme_param\nfrom starlette.requests import Request\nfrom starlette.status import HTTP_401_UNAUTHORIZED\n\n# TODO: import from typing when deprecating Python 3.9\nfrom typing_extensions import Annotated\n\n\nclass OAuth2PasswordRequestForm:\n \"\"\"\n This is a dependency class to collect the `username` and `password` as form data\n for an OAuth2 password flow.\n\n The OAuth2 specification dictates that for a password flow the data should be\n collected using form data (instead of JSON) and that it should have the specific\n fields `username` and `password`.\n\n All the initialization parameters are extracted from the request.\n\n Read more about it in the\n [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).\n\n ## Example\n\n ```python\n from typing import Annotated\n\n from fastapi import Depends, FastAPI\n from fastapi.security import OAuth2PasswordRequestForm\n\n app = FastAPI()\n\n\n @app.post(\"/login\")\n def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):\n data = {}\n data[\"scopes\"] = []\n for scope in form_data.scopes:\n data[\"scopes\"].append(scope)\n if form_data.client_id:\n data[\"client_id\"] = form_data.client_id\n if form_data.client_secret:\n data[\"client_secret\"] = form_data.client_secret\n return data\n ```\n\n Note that for OAuth2 the scope `items:read` i |
| 9 | read_file | {
"end_line": 400,
"filepath": "fastapi/security/oauth2.py",
"start_line": 201
} | {"status": "ok", "filepath": "fastapi/security/oauth2.py", "content": " know that that it is application specific, it's not part of the specification.\n\n\n grafull{"status": "ok", "filepath": "fastapi/security/oauth2.py", "content": " know that that it is application specific, it's not part of the specification.\n\n\n grant_type: the OAuth2 spec says it is required and MUST be the fixed string \"password\".\n This dependency is strict about it. If you want to be permissive, use instead the\n OAuth2PasswordRequestForm dependency class.\n username: username string. The OAuth2 spec requires the exact field name \"username\".\n password: password string. The OAuth2 spec requires the exact field name \"password\".\n scope: Optional string. Several scopes (each one a string) separated by spaces. E.g.\n \"items:read items:write users:read profile openid\"\n client_id: optional string. OAuth2 recommends sending the client_id and client_secret (if any)\n using HTTP Basic auth, as: client_id:client_secret\n client_secret: optional string. OAuth2 recommends sending the client_id and client_secret (if any)\n using HTTP Basic auth, as: client_id:client_secret\n \"\"\"\n\n def __init__(\n self,\n grant_type: Annotated[\n str,\n Form(pattern=\"^password$\"),\n Doc(\n \"\"\"\n The OAuth2 spec says it is required and MUST be the fixed string\n \"password\". This dependency is strict about it. If you want to be\n permissive, use instead the `OAuth2PasswordRequestForm` dependency\n class.\n \"\"\"\n ),\n ],\n username: Annotated[\n str,\n Form(),\n Doc(\n \"\"\"\n `username` string. The OAuth2 spec requires the exact field name\n `username`.\n \"\"\"\n ),\n ],\n password: Annotated[\n str,\n Form(),\n Doc(\n \"\"\"\n `password` string. The OAuth2 |
| 10 | read_file | {
"end_line": 500,
"filepath": "fastapi/security/oauth2.py",
"start_line": 351
} | {"status": "ok", "filepath": "fastapi/security/oauth2.py", "content": " ),\n ] = None,\n auto_error: Annotated[\n bool,\n Doc(\n \"\"\"\n By default, if no HTTPfull{"status": "ok", "filepath": "fastapi/security/oauth2.py", "content": " ),\n ] = None,\n auto_error: Annotated[\n bool,\n Doc(\n \"\"\"\n By default, if no HTTP Authorization header is provided, required for\n OAuth2 authentication, it will automatically cancel the request and\n send the client an error.\n\n If `auto_error` is set to `False`, when the HTTP Authorization header\n is not available, instead of erroring out, the dependency result will\n be `None`.\n\n This is useful when you want to have optional authentication.\n\n It is also useful when you want to have authentication that can be\n provided in one of multiple optional ways (for example, with OAuth2\n or in a cookie).\n \"\"\"\n ),\n ] = True,\n ):\n self.model = OAuth2Model(\n flows=cast(OAuthFlowsModel, flows), description=description\n )\n self.scheme_name = scheme_name or self.__class__.__name__\n self.auto_error = auto_error\n\n def make_not_authenticated_error(self) -> HTTPException:\n \"\"\"\n The OAuth 2 specification doesn't define the challenge that should be used,\n because a `Bearer` token is not really the only option to authenticate.\n\n But declaring any other authentication challenge would be application-specific\n as it's not defined in the specification.\n\n For practical reasons, this method uses the `Bearer` challenge by default, as\n it's probably the most common one.\n\n If you are implementing an OAuth2 authentication scheme other than the provided\n ones in FastAPI (based on bearer tokens), you might want to override this.\n\n Ref: https://datatracker.ietf.org/doc/html/rfc6749\n \"\"\"\n return HTTPException(\n |
| 11 | read_file | {
"end_line": 666,
"filepath": "fastapi/security/oauth2.py",
"start_line": 501
} | {"status": "ok", "filepath": "fastapi/security/oauth2.py", "content": " )\n super().__init__(\n flows=flows,\n scheme_name=scheme_name,\n description=descriptiofull{"status": "ok", "filepath": "fastapi/security/oauth2.py", "content": " )\n super().__init__(\n flows=flows,\n scheme_name=scheme_name,\n description=description,\n auto_error=auto_error,\n )\n\n async def __call__(self, request: Request) -> Optional[str]:\n authorization = request.headers.get(\"Authorization\")\n scheme, param = get_authorization_scheme_param(authorization)\n if not authorization or scheme.lower() != \"bearer\":\n if self.auto_error:\n raise self.make_not_authenticated_error()\n else:\n return None\n return param\n\n\nclass OAuth2AuthorizationCodeBearer(OAuth2):\n \"\"\"\n OAuth2 flow for authentication using a bearer token obtained with an OAuth2 code\n flow. An instance of it would be used as a dependency.\n \"\"\"\n\n def __init__(\n self,\n authorizationUrl: str,\n tokenUrl: Annotated[\n str,\n Doc(\n \"\"\"\n The URL to obtain the OAuth2 token.\n \"\"\"\n ),\n ],\n refreshUrl: Annotated[\n Optional[str],\n Doc(\n \"\"\"\n The URL to refresh the token and obtain a new one.\n \"\"\"\n ),\n ] = None,\n scheme_name: Annotated[\n Optional[str],\n Doc(\n \"\"\"\n Security scheme name.\n\n It will be included in the generated OpenAPI (e.g. visible at `/docs`).\n \"\"\"\n ),\n ] = None,\n scopes: Annotated[\n Optional[Dict[str, str]],\n Doc(\n \"\"\"\n The OAuth2 scopes that would be required by the *path operations* that\n use this dependency.\n \"\"\"\n ),\n ] = None,\n descript |
| 12 | read_file | {
"end_line": 666,
"filepath": "fastapi/security/oauth2.py",
"start_line": 651
} | {"status": "ok", "filepath": "fastapi/security/oauth2.py", "content": " List[str],\n Doc(\n \"\"\"\n The list of all the scopes required by dependencies.\n \"\"full{"status": "ok", "filepath": "fastapi/security/oauth2.py", "content": " List[str],\n Doc(\n \"\"\"\n The list of all the scopes required by dependencies.\n \"\"\"\n ),\n ] = scopes or []\n self.scope_str: Annotated[\n str,\n Doc(\n \"\"\"\n All the scopes required by all the dependencies in a single string\n separated by spaces, as defined in the OAuth2 specification.\n \"\"\"\n ),\n ] = \" \".join(self.scopes)\n", "start_line": 651, "end_line": 666, "total_lines": 666, "is_truncated": false} |
| 13 | read_file | {
"end_line": 100,
"filepath": "fastapi/openapi/models.py",
"start_line": 1
} | {"status": "ok", "filepath": "fastapi/openapi/models.py", "content": "from enum import Enum\nfrom typing import Any, Callable, Dict, Iterable, List, Optional, Sfull{"status": "ok", "filepath": "fastapi/openapi/models.py", "content": "from enum import Enum\nfrom typing import Any, Callable, Dict, Iterable, List, Optional, Set, Type, Union\n\nfrom fastapi._compat import (\n PYDANTIC_V2,\n CoreSchema,\n GetJsonSchemaHandler,\n JsonSchemaValue,\n _model_rebuild,\n with_info_plain_validator_function,\n)\nfrom fastapi.logger import logger\nfrom pydantic import AnyUrl, BaseModel, Field\nfrom typing_extensions import Annotated, Literal, TypedDict\nfrom typing_extensions import deprecated as typing_deprecated\n\ntry:\n import email_validator\n\n assert email_validator # make autoflake ignore the unused import\n from pydantic import EmailStr\nexcept ImportError: # pragma: no cover\n\n class EmailStr(str): # type: ignore\n @classmethod\n def __get_validators__(cls) -> Iterable[Callable[..., Any]]:\n yield cls.validate\n\n @classmethod\n def validate(cls, v: Any) -> str:\n logger.warning(\n \"email-validator not installed, email fields will be treated as str.\\n\"\n \"To install, run: pip install email-validator\"\n )\n return str(v)\n\n @classmethod\n def _validate(cls, __input_value: Any, _: Any) -> str:\n logger.warning(\n \"email-validator not installed, email fields will be treated as str.\\n\"\n \"To install, run: pip install email-validator\"\n )\n return str(__input_value)\n\n @classmethod\n def __get_pydantic_json_schema__(\n cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler\n ) -> JsonSchemaValue:\n return {\"type\": \"string\", \"format\": \"email\"}\n\n @classmethod\n def __get_pydantic_core_schema__(\n cls, source: Type[Any], handler: Callable[[Any], CoreSchema]\n ) -> CoreSchema:\n return with_info_plain_validator_function |
| 14 | read_file | {
"end_line": 451,
"filepath": "fastapi/openapi/models.py",
"start_line": 400
} | {"status": "ok", "filepath": "fastapi/openapi/models.py", "content": "class OAuth2(SecurityBase):\n type_: SecuritySchemeType = Field(default=SecuritySchemeTypefull{"status": "ok", "filepath": "fastapi/openapi/models.py", "content": "class OAuth2(SecurityBase):\n type_: SecuritySchemeType = Field(default=SecuritySchemeType.oauth2, alias=\"type\")\n flows: OAuthFlows\n\n\nclass OpenIdConnect(SecurityBase):\n type_: SecuritySchemeType = Field(\n default=SecuritySchemeType.openIdConnect, alias=\"type\"\n )\n openIdConnectUrl: str\n\n\nSecurityScheme = Union[APIKey, HTTPBase, OAuth2, OpenIdConnect, HTTPBearer]\n\n\nclass Components(BaseModelWithConfig):\n schemas: Optional[Dict[str, Union[Schema, Reference]]] = None\n responses: Optional[Dict[str, Union[Response, Reference]]] = None\n parameters: Optional[Dict[str, Union[Parameter, Reference]]] = None\n examples: Optional[Dict[str, Union[Example, Reference]]] = None\n requestBodies: Optional[Dict[str, Union[RequestBody, Reference]]] = None\n headers: Optional[Dict[str, Union[Header, Reference]]] = None\n securitySchemes: Optional[Dict[str, Union[SecurityScheme, Reference]]] = None\n links: Optional[Dict[str, Union[Link, Reference]]] = None\n # Using Any for Specification Extensions\n callbacks: Optional[Dict[str, Union[Dict[str, PathItem], Reference, Any]]] = None\n pathItems: Optional[Dict[str, Union[PathItem, Reference]]] = None\n\n\nclass Tag(BaseModelWithConfig):\n name: str\n description: Optional[str] = None\n externalDocs: Optional[ExternalDocumentation] = None\n\n\nclass OpenAPI(BaseModelWithConfig):\n openapi: str\n info: Info\n jsonSchemaDialect: Optional[str] = None\n servers: Optional[List[Server]] = None\n # Using Any for Specification Extensions\n paths: Optional[Dict[str, Union[PathItem, Any]]] = None\n webhooks: Optional[Dict[str, Union[PathItem, Reference]]] = None\n components: Optional[Components] = None\n security: Optional[List[Dict[str, List[str]]]] = None\n tags: Optional[List[Tag]] = None\n externalDocs: Optional[ExternalDocumentation] = None\n\n\n_model_rebuild |
| 15 | read_file | {
"end_line": 399,
"filepath": "fastapi/openapi/models.py",
"start_line": 350
} | {"status": "ok", "filepath": "fastapi/openapi/models.py", "content": " query = \"query\"\n header = \"header\"\n cookie = \"cookie\"\n\n\nclass APIKey(SecurityBfull{"status": "ok", "filepath": "fastapi/openapi/models.py", "content": " query = \"query\"\n header = \"header\"\n cookie = \"cookie\"\n\n\nclass APIKey(SecurityBase):\n type_: SecuritySchemeType = Field(default=SecuritySchemeType.apiKey, alias=\"type\")\n in_: APIKeyIn = Field(alias=\"in\")\n name: str\n\n\nclass HTTPBase(SecurityBase):\n type_: SecuritySchemeType = Field(default=SecuritySchemeType.http, alias=\"type\")\n scheme: str\n\n\nclass HTTPBearer(HTTPBase):\n scheme: Literal[\"bearer\"] = \"bearer\"\n bearerFormat: Optional[str] = None\n\n\nclass OAuthFlow(BaseModelWithConfig):\n refreshUrl: Optional[str] = None\n scopes: Dict[str, str] = {}\n\n\nclass OAuthFlowImplicit(OAuthFlow):\n authorizationUrl: str\n\n\nclass OAuthFlowPassword(OAuthFlow):\n tokenUrl: str\n\n\nclass OAuthFlowClientCredentials(OAuthFlow):\n tokenUrl: str\n\n\nclass OAuthFlowAuthorizationCode(OAuthFlow):\n authorizationUrl: str\n tokenUrl: str\n\n\nclass OAuthFlows(BaseModelWithConfig):\n implicit: Optional[OAuthFlowImplicit] = None\n password: Optional[OAuthFlowPassword] = None\n clientCredentials: Optional[OAuthFlowClientCredentials] = None\n authorizationCode: Optional[OAuthFlowAuthorizationCode] = None\n\n\n", "start_line": 350, "end_line": 399, "total_lines": 451, "is_truncated": false} |
| 16 | read_file | {
"end_line": 100,
"filepath": "fastapi/openapi/utils.py",
"start_line": 1
} | {"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": "import http.client\nimport inspect\nimport warnings\nfrom typing import Any, Dict, List, Opfull{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": "import http.client\nimport inspect\nimport warnings\nfrom typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Type, Union, cast\n\nfrom fastapi import routing\nfrom fastapi._compat import (\n JsonSchemaValue,\n ModelField,\n Undefined,\n get_compat_model_name_map,\n get_definitions,\n get_schema_from_model_field,\n lenient_issubclass,\n)\nfrom fastapi.datastructures import DefaultPlaceholder\nfrom fastapi.dependencies.models import Dependant\nfrom fastapi.dependencies.utils import (\n _get_flat_fields_from_params,\n get_flat_dependant,\n get_flat_params,\n)\nfrom fastapi.encoders import jsonable_encoder\nfrom fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX\nfrom fastapi.openapi.models import OpenAPI\nfrom fastapi.params import Body, ParamTypes\nfrom fastapi.responses import Response\nfrom fastapi.types import ModelNameMap\nfrom fastapi.utils import (\n deep_dict_update,\n generate_operation_id_for_path,\n is_body_allowed_for_status_code,\n)\nfrom pydantic import BaseModel\nfrom starlette.responses import JSONResponse\nfrom starlette.routing import BaseRoute\nfrom typing_extensions import Literal\n\nfrom .._compat import _is_model_field\n\nvalidation_error_definition = {\n \"title\": \"ValidationError\",\n \"type\": \"object\",\n \"properties\": {\n \"loc\": {\n \"title\": \"Location\",\n \"type\": \"array\",\n \"items\": {\"anyOf\": [{\"type\": \"string\"}, {\"type\": \"integer\"}]},\n },\n \"msg\": {\"title\": \"Message\", \"type\": \"string\"},\n \"type\": {\"title\": \"Error Type\", \"type\": \"string\"},\n },\n \"required\": [\"loc\", \"msg\", \"type\"],\n}\n\nvalidation_error_response_definition = {\n \"title\": \"HTTPValidationError\",\n \"type\": \"object\",\n \"properties\": {\n \"detail\": {\n \"title\": \"Detail\",\n |
| 17 | edit_file | {
"filepath": "fastapi/openapi/utils.py",
"new_string": " for security_requirement in flat_dependant.security_requirements:\n security_definition = jsonable_encoder(\n security_requirement.security_scheme.model,\n by_alias=True,\n exclude_none=True,\n )\n security_name = security_requirement.security_scheme.scheme_name\n if security_name in security_definitions:\n # If the security scheme is already defined, we should check if it's an OAuth2 scheme\n # and if the scopes are different. If they are different, we should create a new\n # security scheme name to avoid deduplicating schemes with different scopes.\n # However, the current implementation just overwrites it.\n # To fix this, we can check if the current definition is the same as the new one.\n if security_definitions[security_name] == security_definition:\n operation_security.append({security_name: security_requirement.scopes})\n continue\n\n security_definitions[security_name] = security_definition\n operation_security.append({security_name: security_requirement.scopes})",
"old_string": " for security_requirement in flat_dependant.security_requirements:\n security_definition = jsonable_encoder(\n security_requirement.security_scheme.model,\n by_alias=True,\n exclude_none=True,\n )\n security_name = security_requirement.security_scheme.scheme_name\n security_definitions[security_name] = security_definition\n operation_security.append({security_name: security_requirement.scopes})"
} | {"status": "ok", "filepath": "fastapi/openapi/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/openapi/utils.py\n+++ b/fastapi/openapi/ufull{"status": "ok", "filepath": "fastapi/openapi/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/openapi/utils.py\n+++ b/fastapi/openapi/utils.py\n@@ -87,6 +87,16 @@\n exclude_none=True,\n )\n security_name = security_requirement.security_scheme.scheme_name\n+ if security_name in security_definitions:\n+ # If the security scheme is already defined, we should check if it's an OAuth2 scheme\n+ # and if the scopes are different. If they are different, we should create a new\n+ # security scheme name to avoid deduplicating schemes with different scopes.\n+ # However, the current implementation just overwrites it.\n+ # To fix this, we can check if the current definition is the same as the new one.\n+ if security_definitions[security_name] == security_definition:\n+ operation_security.append({security_name: security_requirement.scopes})\n+ continue\n+\n security_definitions[security_name] = security_definition\n operation_security.append({security_name: security_requirement.scopes})\n return security_definitions, operation_security\n", "is_truncated": false} |
| 18 | read_file | {
"end_line": 100,
"filepath": "fastapi/openapi/utils.py",
"start_line": 70
} | {"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": " \"2XX\": \"Success\",\n \"3XX\": \"Redirection\",\n \"4XX\": \"Client Error\",\n \"5XX\": full{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": " \"2XX\": \"Success\",\n \"3XX\": \"Redirection\",\n \"4XX\": \"Client Error\",\n \"5XX\": \"Server Error\",\n \"DEFAULT\": \"Default Response\",\n}\n\n\ndef get_openapi_security_definitions(\n flat_dependant: Dependant,\n) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:\n security_definitions = {}\n operation_security = []\n for security_requirement in flat_dependant.security_requirements:\n security_definition = jsonable_encoder(\n security_requirement.security_scheme.model,\n by_alias=True,\n exclude_none=True,\n )\n security_name = security_requirement.security_scheme.scheme_name\n if security_name in security_definitions:\n # If the security scheme is already defined, we should check if it's an OAuth2 scheme\n # and if the scopes are different. If they are different, we should create a new\n # security scheme name to avoid deduplicating schemes with different scopes.\n # However, the current implementation just overwrites it.\n # To fix this, we can check if the current definition is the same as the new one.\n if security_definitions[security_name] == security_definition:\n operation_security.append({security_name: security_requirement.scopes})\n continue\n\n security_definitions[security_name] = security_definition\n", "start_line": 70, "end_line": 100, "total_lines": 568, "is_truncated": false} |
| 19 | edit_file | {
"filepath": "fastapi/openapi/utils.py",
"new_string": " for security_requirement in flat_dependant.security_requirements:\n security_definition = jsonable_encoder(\n security_requirement.security_scheme.model,\n by_alias=True,\n exclude_none=True,\n )\n security_name = security_requirement.security_scheme.scheme_name\n if security_name in security_definitions:\n if security_definitions[security_name] == security_definition:\n operation_security.append({security_name: security_requirement.scopes})\n continue\n \n # If the security scheme name is the same but the definition is different,\n # we need to create a unique name for this security scheme.\n # This is important for OAuth2 schemes with different scopes.\n # We can append a suffix to the security name.\n # We use a simple counter for this.\n suffix = 1\n original_security_name = security_name\n while security_name in security_definitions:\n security_name = f\"{original_security_name}_{suffix}\"\n suffix += 1\n\n security_definitions[security_name] = security_definition\n operation_security.append({security_name: security_requirement.scopes})",
"old_string": " for security_requirement in flat_dependant.security_requirements:\n security_definition = jsonable_encoder(\n security_requirement.security_scheme.model,\n by_alias=True,\n exclude_none=True,\n )\n security_name = security_requirement.security_scheme.scheme_name\n if security_name in security_definitions:\n # If the security scheme is already defined, we should check if it's an OAuth2 scheme\n # and if the scopes are different. If they are different, we should create a new\n # security scheme name to avoid deduplicating schemes with different scopes.\n # However, the current implementation just overwrites it.\n # To fix this, we can check if the current definition is the same as the new one.\n if security_definitions[security_name] == security_definition:\n operation_security.append({security_name: security_requirement.scopes})\n continue\n\n security_definitions[security_name] = security_definition\n operation_security.append({security_name: security_requirement.scopes})"
} | {"status": "ok", "filepath": "fastapi/openapi/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/openapi/utils.py\n+++ b/fastapi/openapi/ufull{"status": "ok", "filepath": "fastapi/openapi/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/openapi/utils.py\n+++ b/fastapi/openapi/utils.py\n@@ -88,14 +88,20 @@\n )\n security_name = security_requirement.security_scheme.scheme_name\n if security_name in security_definitions:\n- # If the security scheme is already defined, we should check if it's an OAuth2 scheme\n- # and if the scopes are different. If they are different, we should create a new\n- # security scheme name to avoid deduplicating schemes with different scopes.\n- # However, the current implementation just overwrites it.\n- # To fix this, we can check if the current definition is the same as the new one.\n if security_definitions[security_name] == security_definition:\n operation_security.append({security_name: security_requirement.scopes})\n continue\n+ \n+ # If the security scheme name is the same but the definition is different,\n+ # we need to create a unique name for this security scheme.\n+ # This is important for OAuth2 schemes with different scopes.\n+ # We can append a suffix to the security name.\n+ # We use a simple counter for this.\n+ suffix = 1\n+ original_security_name = security_name\n+ while security_name in security_definitions:\n+ security_name = f\"{original_security_name}_{suffix}\"\n+ suffix += 1\n \n security_definitions[security_name] = security_definition\n operation_security.append({security_name: security_requirement.scopes})\n", "is_truncated": false} |
| 20 | submit_patch | [] | {"status": "ok", "patch_size": 1370, "files_changed": 1}full{"status": "ok", "patch_size": 1370, "files_changed": 1} |
diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py
index dbc93d28..5079a369 100644
--- a/fastapi/openapi/utils.py
+++ b/fastapi/openapi/utils.py
@@ -87,6 +87,22 @@ def get_openapi_security_definitions(
exclude_none=True,
)
security_name = security_requirement.security_scheme.scheme_name
+ if security_name in security_definitions:
+ if security_definitions[security_name] == security_definition:
+ operation_security.append({security_name: security_requirement.scopes})
+ continue
+
+ # If the security scheme name is the same but the definition is different,
+ # we need to create a unique name for this security scheme.
+ # This is important for OAuth2 schemes with different scopes.
+ # We can append a suffix to the security name.
+ # We use a simple counter for this.
+ suffix = 1
+ original_security_name = security_name
+ while security_name in security_definitions:
+ security_name = f"{original_security_name}_{suffix}"
+ suffix += 1
+
security_definitions[security_name] = security_definition
operation_security.append({security_name: security_requirement.scopes})
return security_definitions, operation_security
โ
โ {"OAuth2AuthorizationCodeBearer": ["read", "write"]}], โ
โ }, โ
โ }, โ
โ }, โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
These changes are not applied.
Use --inline-snapshot=fix to apply them, or use the interactive mode with
--inline-snapshot=review
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Problems โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
inline-snapshot is not able to format your code.
This issue can be solved by:
* installing inline-snapshot[black] which gives you the same formatting like in
older versions
* adding a `format-command` to your pyproject.toml (see
https://15r10nk.github.io/inline-snapshot/latest/configuration/#format-command
for more information).
==================================== ERRORS ====================================
___________________ ERROR at teardown of test_openapi_schema ___________________
some snapshots in this test have incorrect values.
If you just created this value with --inline-snapshot=create, the value is now created and you can ignore this message.
=================================== FAILURES ===================================
_____________________________ test_openapi_schema ______________________________
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
> assert response.json() == snapshot(
{
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/": {
"get": {
"summary": "Root",
"operationId": "root__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"security": [{"OAuth2AuthorizationCodeBearer": []}],
}
},
"/items/": {
"get": {
"summary": "Read Items",
"operationId": "read_items_items__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"security": [
{"OAuth2AuthorizationCodeBearer": ["read"]},
],
},
"post": {
"summary": "Create Item",
"operationId": "create_item_items__post",
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"security": [
{"OAuth2AuthorizationCodeBearer": ["read", "write"]},
],
},
},
},
"components": {
"securitySchemes": {
"OAuth2AuthorizationCodeBearer": {
"type": "oauth2",
"flows": {
"authorizationCode": {
"scopes": {
"read": "Read access",
"write": "Write access",
},
"authorizationUrl": "authorize",
"tokenUrl": "token",
}
},
}
}
},
}
)
E AssertionError: assert {'openapi': '... 'token'}}}}}} == {'openapi': '... 'token'}}}}}}
E
E Omitting 3 identical items, use -vv to show
E Differing items:
E {'paths': {'/': {'get': {'summary': 'Root', 'operationId': 'root__get', 'responses': {'200': {'description': 'Successf...2AuthorizationCodeBearer': []}, {'OAuth2AuthorizationCodeBearer': [...]}, {'OAuth2AuthorizationCodeBearer': [...]}]}}}} != {'paths': {'/': {'get': {'summary': 'Root', 'operationId': 'root__get', 'responses': {'200': {'description': 'Successf...: {'description': 'Successful Response', 'content': {...}}}, 'security': [{'OAuth2AuthorizationCodeBearer': [...]}]}}}}
E Use -v to get more diff
tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py:66: AssertionError
=============================== warnings summary ===============================
../../../../../../kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45: DeprecationWarning: The anyio.abc.BlockingPortal alias is deprecated, use anyio.from_thread.BlockingPortal instead.
_PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]]
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 2 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 3 passed, 1 warning, 1 error in 0.82s