From c0ad39e073bec6001c27670b7ca8af228c1dc62b Mon Sep 17 00:00:00 2001 From: Nir Schulman Date: Sat, 8 Nov 2025 11:26:12 +0200 Subject: [PATCH] Fixed some linting and python 3.8 compatibility issues. --- fastapi/dependencies/models.py | 3 +- fastapi/dependencies/utils.py | 87 ++++++++++++++++++++++------------ fastapi/exceptions.py | 8 ++++ fastapi/lifespan.py | 3 +- fastapi/params.py | 6 ++- fastapi/utils.py | 19 ++++---- 6 files changed, 83 insertions(+), 43 deletions(-) diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py index 4dde37aa5..9021190cc 100644 --- a/fastapi/dependencies/models.py +++ b/fastapi/dependencies/models.py @@ -11,7 +11,7 @@ from fastapi.types import ( LifespanDependencyCacheKey, DependencyScope, LifespanDependencyScope, - EndpointDependencyScope + EndpointDependencyScope, ) from typing_extensions import TypeAlias @@ -78,6 +78,7 @@ class LifespanDependant(_BaseDependant): ) return self.caller, self.index + @dataclass class EndpointDependant(_BaseDependant): scope: EndpointDependencyScope = None diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 3d51e38a8..e39d48ca4 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -53,13 +53,25 @@ from fastapi.concurrency import ( asynccontextmanager, contextmanager_in_threadpool, ) -from fastapi.dependencies.models import EndpointDependant, LifespanDependant, SecurityRequirement -from fastapi.exceptions import DependencyScopeError, InvalidDependencyScope, UninitializedLifespanDependency +from fastapi.dependencies.models import ( + EndpointDependant, + LifespanDependant, + SecurityRequirement, +) +from fastapi.exceptions import ( + DependencyScopeError, + InvalidDependencyScope, + UninitializedLifespanDependency, +) from fastapi.logger import logger from fastapi.security.base import SecurityBase from fastapi.security.oauth2 import OAuth2, SecurityScopes from fastapi.security.open_id_connect_url import OpenIdConnect -from fastapi.types import EndpointDependencyCacheKey, LifespanDependencyCacheKey, LifespanDependencyScope +from fastapi.types import ( + EndpointDependencyCacheKey, + LifespanDependencyCacheKey, + LifespanDependencyScope, +) from fastapi.utils import create_model_field, get_path_param_names from pydantic import BaseModel from pydantic.fields import FieldInfo @@ -122,7 +134,7 @@ def ensure_multipart_is_installed() -> None: def get_parameterless_sub_dependant( - *, depends: params.Depends, path: str, caller: Callable[..., Any], index: int + *, depends: params.Depends, path: str, caller: Callable[..., Any], index: int ) -> Union[EndpointDependant, LifespanDependant]: assert callable(depends.dependency), ( "A parameter-less dependency must have a callable dependency" @@ -135,7 +147,7 @@ def get_parameterless_sub_dependant( path=path, call=depends.dependency, security_scopes=use_security_scopes, - index=index + index=index, ) elif depends.scope == "lifespan": return get_lifespan_dependant( @@ -147,8 +159,7 @@ def get_parameterless_sub_dependant( ) else: raise InvalidDependencyScope( - f'Dependency "{index}" of {caller} has an invalid ' - f'scope: "{depends.scope}"' + f'Dependency "{index}" of {caller} has an invalid scope: "{depends.scope}"' ) @@ -256,7 +267,12 @@ def get_lifespan_dependant( dependency_signature = get_typed_signature(call) signature_params = dependency_signature.parameters dependant = LifespanDependant( - call=call, name=name, use_cache=use_cache, caller=caller, index=index, scope=scope + call=call, + name=name, + use_cache=use_cache, + caller=caller, + index=index, + scope=scope, ) for param_name, param in signature_params.items(): param_details = analyze_param( @@ -269,7 +285,7 @@ def get_lifespan_dependant( raise DependencyScopeError( f'Dependency "{dependant.name}" has "{scope}" scope, but was defined' f'with an invalid argument: "{param_name}" which is ' - f'not a valid sub-dependency. Lifespan scoped dependencies may only ' + f"not a valid sub-dependency. Lifespan scoped dependencies may only " f"use lifespan scoped sub-dependencies." ) @@ -277,7 +293,7 @@ def get_lifespan_dependant( raise DependencyScopeError( f'Dependency "{dependant.name}" has "{scope}" scope, but was defined with the ' f'sub-dependency "{param_name}" which has "{param_details.depends.scope}" scope. Lifespan scoped ' - f'dependencies may only use lifespan scoped sub-dependencies.' + f"dependencies may only use lifespan scoped sub-dependencies." ) assert param_details.depends.dependency is not None @@ -287,7 +303,7 @@ def get_lifespan_dependant( call=param_details.depends.dependency, use_cache=param_details.depends.use_cache, caller=call, - scope=scope + scope=scope, ) dependant.dependencies.append(sub_dependant) @@ -349,24 +365,28 @@ def get_endpoint_dependant( if param_details.depends.scopes: use_security_scopes.extend(param_details.depends.scopes) if param_details.depends.scope == "lifespan": - dependant.lifespan_dependencies.append(get_lifespan_dependant( - caller=call, - call=param_details.depends.dependency, - name=param_name, - use_cache=param_details.depends.use_cache, - index=index, - scope=param_details.depends.scope - )) + dependant.lifespan_dependencies.append( + get_lifespan_dependant( + caller=call, + call=param_details.depends.dependency, + name=param_name, + use_cache=param_details.depends.use_cache, + index=index, + scope=param_details.depends.scope, + ) + ) elif param_details.depends.scope in ("request", "function", None): - dependant.endpoint_dependencies.append(get_endpoint_dependant( - path=path, - call=param_details.depends.dependency, - name=param_name, - security_scopes=use_security_scopes, - use_cache=param_details.depends.use_cache, - index=index, - scope=param_details.depends.scope - )) + dependant.endpoint_dependencies.append( + get_endpoint_dependant( + path=path, + call=param_details.depends.dependency, + name=param_name, + security_scopes=use_security_scopes, + use_cache=param_details.depends.use_cache, + index=index, + scope=param_details.depends.scope, + ) + ) else: raise InvalidDependencyScope( f'Dependency "{name}" of {call} has an invalid ' @@ -628,7 +648,10 @@ def add_param_to_fields(*, field: ModelField, dependant: EndpointDependant) -> N async def _solve_generator( - *, dependant: Union[EndpointDependant, LifespanDependant], stack: AsyncExitStack, sub_values: Dict[str, Any] + *, + dependant: Union[EndpointDependant, LifespanDependant], + stack: AsyncExitStack, + sub_values: Dict[str, Any], ) -> Any: assert dependant.call if dependant.is_gen_callable: @@ -672,7 +695,7 @@ async def solve_lifespan_dependant( name=dependant.name, use_cache=dependant.use_cache, index=dependant.index, - scope=dependant.scope + scope=dependant.scope, ) dependency_arguments: Dict[str, Any] = {} @@ -691,7 +714,9 @@ async def solve_lifespan_dependant( if dependant_to_solve.is_gen_callable or dependant_to_solve.is_async_gen_callable: value = await _solve_generator( - dependant=dependant_to_solve, stack=async_exit_stack, sub_values=dependency_arguments + dependant=dependant_to_solve, + stack=async_exit_stack, + sub_values=dependency_arguments, ) elif dependant_to_solve.is_coroutine_callable: value = await call(**dependency_arguments) diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py index 01b9bbe44..1afc808d6 100644 --- a/fastapi/exceptions.py +++ b/fastapi/exceptions.py @@ -146,31 +146,39 @@ class FastAPIError(RuntimeError): A generic, FastAPI-specific error. """ + class DependencyError(FastAPIError): """ A generic error regarding to dependencies. """ + pass + class InvalidDependencyScope(DependencyError): """ A dependency was declared with an unsupported scope value. """ + pass + class DependencyScopeError(DependencyError): """ A dependency declared that it depends on another dependency with an invalid (narrower) scope. """ + class UninitializedLifespanDependency(DependencyError): """ A bug in FastAPI caused a lifespan-scoped dependency to not initialize properly at the point where we handled a request that depended on it. """ + pass + class ValidationException(Exception): def __init__(self, errors: Sequence[Any]) -> None: self._errors = errors diff --git a/fastapi/lifespan.py b/fastapi/lifespan.py index 4f5a20a4f..f3ad446b0 100644 --- a/fastapi/lifespan.py +++ b/fastapi/lifespan.py @@ -3,9 +3,10 @@ from __future__ import annotations from contextlib import AsyncExitStack from typing import TYPE_CHECKING, Any, Callable, Dict, List -from fastapi.dependencies.models import LifespanDependant, LifespanDependencyCacheKey +from fastapi.dependencies.models import LifespanDependant from fastapi.dependencies.utils import solve_lifespan_dependant from fastapi.routing import APIRoute, APIWebSocketRoute +from fastapi.types import LifespanDependencyCacheKey if TYPE_CHECKING: # pragma: nocover from fastapi import FastAPI diff --git a/fastapi/params.py b/fastapi/params.py index ae990c856..0f19122e3 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -770,9 +770,11 @@ class Depends: use_cache: bool = True scope: DependencyScope = None - def __post_init__(self): + def __post_init__(self) -> None: if self.scope not in ("lifespan", "request", "function", None): - raise InvalidDependencyScope(f"Dependency received an invalid scope: \"{self.scope}\"") + raise InvalidDependencyScope( + f'Dependency received an invalid scope: "{self.scope}"' + ) @dataclass diff --git a/fastapi/utils.py b/fastapi/utils.py index 8be10ff15..821f3c71b 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -2,7 +2,6 @@ import inspect import re import sys import warnings -from collections.abc import Callable from dataclasses import is_dataclass from typing import ( TYPE_CHECKING, @@ -13,7 +12,10 @@ from typing import ( Set, Type, Union, - cast, Awaitable, TypeVar, + cast, + Awaitable, + TypeVar, + Callable, ) from weakref import WeakKeyDictionary @@ -269,8 +271,8 @@ def get_value_or_default( def _is_coroutine_callable( - callable_: Callable[..., Any] -) -> TypeIs[Callable[..., Awaitable[Any]]]: + callable_: Union[Callable[..., _T], Callable[..., Awaitable[_T]]], +) -> TypeIs[Callable[..., Awaitable[_T]]]: if inspect.isroutine(callable_): return iscoroutinefunction(callable_) if inspect.isclass(callable_): @@ -278,12 +280,13 @@ def _is_coroutine_callable( dunder_call = getattr(callable_, "__call__", None) # noqa: B004 return iscoroutinefunction(dunder_call) + async def call_asynchronously( - callable_: Union[Callable[_P, _T], Callable[_P, Awaitable[_T]]], - *args: _P.args, - **kwargs: _P.kwargs + callable_: Union[Callable[_P, _T], Callable[_P, Awaitable[_T]]], + *args: _P.args, + **kwargs: _P.kwargs, ) -> _T: if _is_coroutine_callable(callable_): return await callable_(*args, **kwargs) else: - return await run_in_threadpool(callable_, *args, **kwargs) \ No newline at end of file + return await run_in_threadpool(callable_, *args, **kwargs)