Browse Source

Merge branch 'master' into master

pull/14249/head
Priyanshjain10 9 months ago
committed by GitHub
parent
commit
ab4112f99f
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 12
      docs/en/docs/release-notes.md
  2. 6
      docs/missing-translation.md
  3. 2
      fastapi/__init__.py
  4. 84
      fastapi/dependencies/utils.py
  5. 25
      fastapi/params.py
  6. 78
      tests/test_dependency_paramless.py
  7. 11
      tests/test_params_repr.py

12
docs/en/docs/release-notes.md

@ -7,6 +7,18 @@ hide:
## Latest Changes ## Latest Changes
## 0.120.3
### Refactors
* ♻️ Reduce internal cyclic recursion in dependencies, from 2 functions calling each other to 1 calling itself. PR [#14256](https://github.com/fastapi/fastapi/pull/14256) by [@tiangolo](https://github.com/tiangolo).
* ♻️ Refactor internals of dependencies, simplify code and remove `get_param_sub_dependant`. PR [#14255](https://github.com/fastapi/fastapi/pull/14255) by [@tiangolo](https://github.com/tiangolo).
* ♻️ Refactor internals of dependencies, simplify using dataclasses. PR [#14254](https://github.com/fastapi/fastapi/pull/14254) by [@tiangolo](https://github.com/tiangolo).
### Docs
* 📝 Update note for untranslated pages. PR [#14257](https://github.com/fastapi/fastapi/pull/14257) by [@YuriiMotov](https://github.com/YuriiMotov).
## 0.120.2 ## 0.120.2
### Fixes ### Fixes

6
docs/missing-translation.md

@ -1,7 +1,9 @@
/// warning /// warning
The current page still doesn't have a translation for this language. This page hasn’t been translated into your language yet. 🌍
But you can help translating it: [Contributing](https://fastapi.tiangolo.com/contributing/){.internal-link target=_blank}. We’re currently switching to an automated translation system 🤖, which will help keep all translations complete and up to date.
Learn more: [Contributing – Translations](https://fastapi.tiangolo.com/contributing/#translations){.internal-link target=_blank}
/// ///

2
fastapi/__init__.py

@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production""" """FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.120.2" __version__ = "0.120.3"
from starlette import status as status from starlette import status as status

84
fastapi/dependencies/utils.py

@ -125,60 +125,16 @@ def ensure_multipart_is_installed() -> None:
raise RuntimeError(multipart_not_installed_error) from None raise RuntimeError(multipart_not_installed_error) from None
def get_param_sub_dependant(
*,
param_name: str,
depends: params.Depends,
path: str,
security_scopes: Optional[List[str]] = None,
) -> Dependant:
assert depends.dependency
return get_sub_dependant(
depends=depends,
dependency=depends.dependency,
path=path,
name=param_name,
security_scopes=security_scopes,
)
def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant: def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:
assert callable(depends.dependency), ( assert callable(depends.dependency), (
"A parameter-less dependency must have a callable dependency" "A parameter-less dependency must have a callable dependency"
) )
return get_sub_dependant(depends=depends, dependency=depends.dependency, path=path) use_security_scopes: List[str] = []
if isinstance(depends, params.Security) and depends.scopes:
use_security_scopes.extend(depends.scopes)
def get_sub_dependant( return get_dependant(
*, path=path, call=depends.dependency, security_scopes=use_security_scopes
depends: params.Depends,
dependency: Callable[..., Any],
path: str,
name: Optional[str] = None,
security_scopes: Optional[List[str]] = None,
) -> Dependant:
security_requirement = None
security_scopes = security_scopes or []
if isinstance(depends, params.Security):
dependency_scopes = depends.scopes
security_scopes.extend(dependency_scopes)
if isinstance(dependency, SecurityBase):
use_scopes: List[str] = []
if isinstance(dependency, (OAuth2, OpenIdConnect)):
use_scopes = security_scopes
security_requirement = SecurityRequirement(
security_scheme=dependency, scopes=use_scopes
)
sub_dependant = get_dependant(
path=path,
call=dependency,
name=name,
security_scopes=security_scopes,
use_cache=depends.use_cache,
) )
if security_requirement:
sub_dependant.security_requirements.append(security_requirement)
return sub_dependant
CacheKey = Tuple[Optional[Callable[..., Any]], Tuple[str, ...]] CacheKey = Tuple[Optional[Callable[..., Any]], Tuple[str, ...]]
@ -282,9 +238,6 @@ def get_dependant(
security_scopes: Optional[List[str]] = None, security_scopes: Optional[List[str]] = None,
use_cache: bool = True, use_cache: bool = True,
) -> Dependant: ) -> Dependant:
path_param_names = get_path_param_names(path)
endpoint_signature = get_typed_signature(call)
signature_params = endpoint_signature.parameters
dependant = Dependant( dependant = Dependant(
call=call, call=call,
name=name, name=name,
@ -292,6 +245,9 @@ def get_dependant(
security_scopes=security_scopes, security_scopes=security_scopes,
use_cache=use_cache, use_cache=use_cache,
) )
path_param_names = get_path_param_names(path)
endpoint_signature = get_typed_signature(call)
signature_params = endpoint_signature.parameters
for param_name, param in signature_params.items(): for param_name, param in signature_params.items():
is_path_param = param_name in path_param_names is_path_param = param_name in path_param_names
param_details = analyze_param( param_details = analyze_param(
@ -301,12 +257,28 @@ def get_dependant(
is_path_param=is_path_param, is_path_param=is_path_param,
) )
if param_details.depends is not None: if param_details.depends is not None:
sub_dependant = get_param_sub_dependant( assert param_details.depends.dependency
param_name=param_name, use_security_scopes = security_scopes or []
depends=param_details.depends, if isinstance(param_details.depends, params.Security):
if param_details.depends.scopes:
use_security_scopes.extend(param_details.depends.scopes)
sub_dependant = get_dependant(
path=path, path=path,
security_scopes=security_scopes, call=param_details.depends.dependency,
name=param_name,
security_scopes=use_security_scopes,
use_cache=param_details.depends.use_cache,
) )
if isinstance(param_details.depends.dependency, SecurityBase):
use_scopes: List[str] = []
if isinstance(
param_details.depends.dependency, (OAuth2, OpenIdConnect)
):
use_scopes = use_security_scopes
security_requirement = SecurityRequirement(
security_scheme=param_details.depends.dependency, scopes=use_scopes
)
sub_dependant.security_requirements.append(security_requirement)
dependant.dependencies.append(sub_dependant) dependant.dependencies.append(sub_dependant)
continue continue
if add_non_field_param_to_dependency( if add_non_field_param_to_dependency(

25
fastapi/params.py

@ -1,4 +1,5 @@
import warnings import warnings
from dataclasses import dataclass
from enum import Enum from enum import Enum
from typing import Any, Callable, Dict, List, Optional, Sequence, Union from typing import Any, Callable, Dict, List, Optional, Sequence, Union
@ -761,26 +762,12 @@ class File(Form): # type: ignore[misc]
) )
@dataclass
class Depends: class Depends:
def __init__( dependency: Optional[Callable[..., Any]] = None
self, dependency: Optional[Callable[..., Any]] = None, *, use_cache: bool = True use_cache: bool = True
):
self.dependency = dependency
self.use_cache = use_cache
def __repr__(self) -> str:
attr = getattr(self.dependency, "__name__", type(self.dependency).__name__)
cache = "" if self.use_cache else ", use_cache=False"
return f"{self.__class__.__name__}({attr}{cache})"
@dataclass
class Security(Depends): class Security(Depends):
def __init__( scopes: Optional[Sequence[str]] = None
self,
dependency: Optional[Callable[..., Any]] = None,
*,
scopes: Optional[Sequence[str]] = None,
use_cache: bool = True,
):
super().__init__(dependency=dependency, use_cache=use_cache)
self.scopes = scopes or []

78
tests/test_dependency_paramless.py

@ -0,0 +1,78 @@
from typing import Union
from fastapi import FastAPI, HTTPException, Security
from fastapi.security import (
OAuth2PasswordBearer,
SecurityScopes,
)
from fastapi.testclient import TestClient
from typing_extensions import Annotated
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def process_auth(
credentials: Annotated[Union[str, None], Security(oauth2_scheme)],
security_scopes: SecurityScopes,
):
# This is an incorrect way of using it, this is not checking if the scopes are
# provided by the token, only if the endpoint is requesting them, but the test
# here is just to check if FastAPI is indeed registering and passing the scopes
# correctly when using Security with parameterless dependencies.
if "a" not in security_scopes.scopes or "b" not in security_scopes.scopes:
raise HTTPException(detail="a or b not in scopes", status_code=401)
return {"token": credentials, "scopes": security_scopes.scopes}
@app.get("/get-credentials")
def get_credentials(
credentials: Annotated[dict, Security(process_auth, scopes=["a", "b"])],
):
return credentials
@app.get(
"/parameterless-with-scopes",
dependencies=[Security(process_auth, scopes=["a", "b"])],
)
def get_parameterless_with_scopes():
return {"status": "ok"}
@app.get(
"/parameterless-without-scopes",
dependencies=[Security(process_auth)],
)
def get_parameterless_without_scopes():
return {"status": "ok"}
client = TestClient(app)
def test_get_credentials():
response = client.get("/get-credentials", headers={"authorization": "Bearer token"})
assert response.status_code == 200, response.text
assert response.json() == {"token": "token", "scopes": ["a", "b"]}
def test_parameterless_with_scopes():
response = client.get(
"/parameterless-with-scopes", headers={"authorization": "Bearer token"}
)
assert response.status_code == 200, response.text
assert response.json() == {"status": "ok"}
def test_parameterless_without_scopes():
response = client.get(
"/parameterless-without-scopes", headers={"authorization": "Bearer token"}
)
assert response.status_code == 401, response.text
assert response.json() == {"detail": "a or b not in scopes"}
def test_call_get_parameterless_without_scopes_for_coverage():
assert get_parameterless_without_scopes() == {"status": "ok"}

11
tests/test_params_repr.py

@ -1,7 +1,7 @@
from typing import Any, List from typing import Any, List
from dirty_equals import IsOneOf from dirty_equals import IsOneOf
from fastapi.params import Body, Cookie, Depends, Header, Param, Path, Query from fastapi.params import Body, Cookie, Header, Param, Path, Query
test_data: List[Any] = ["teststr", None, ..., 1, []] test_data: List[Any] = ["teststr", None, ..., 1, []]
@ -141,12 +141,3 @@ def test_body_repr_number():
def test_body_repr_list(): def test_body_repr_list():
assert repr(Body([])) == "Body([])" assert repr(Body([])) == "Body([])"
def test_depends_repr():
assert repr(Depends()) == "Depends(NoneType)"
assert repr(Depends(get_user)) == "Depends(get_user)"
assert repr(Depends(use_cache=False)) == "Depends(NoneType, use_cache=False)"
assert (
repr(Depends(get_user, use_cache=False)) == "Depends(get_user, use_cache=False)"
)

Loading…
Cancel
Save