from collections.abc import AsyncGenerator, Callable, Generator from typing import Any from fastapi.dependencies.models import ( Dependant, _get_cache_key, _get_computed_scope, _get_oauth_scopes, _get_security_scheme, _is_async_gen_callable, _is_async_gen_callable_cached, _is_coroutine_callable, _is_coroutine_callable_cached, _is_gen_callable, _is_gen_callable_cached, _is_security_scheme, _uses_scopes, ) from fastapi.security import APIKeyHeader def sync_dependency() -> None: pass # pragma: no cover async def async_dependency() -> None: pass # pragma: no cover def generator_dependency() -> Generator[None, None, None]: yield # pragma: no cover async def async_generator_dependency() -> AsyncGenerator[None, None]: yield # pragma: no cover class UnhashableCallable: __hash__ = None async def __call__(self) -> None: pass # pragma: no cover class UnhashableGeneratorCallable: __hash__ = None def __call__(self) -> Generator[None, None, None]: yield # pragma: no cover class UnhashableAsyncGeneratorCallable: __hash__ = None async def __call__(self) -> AsyncGenerator[None, None]: yield # pragma: no cover class EqualCallable: def __eq__(self, other: object) -> bool: return isinstance(other, EqualCallable) def __hash__(self) -> int: return 1 # pragma: no cover class EqualAsyncCallable(EqualCallable): async def __call__(self) -> None: pass # pragma: no cover class EqualSyncCallable(EqualCallable): def __call__(self) -> None: pass # pragma: no cover def test_callable_classification_is_shared_by_call() -> None: _is_gen_callable_cached.cache_clear() _is_async_gen_callable_cached.cache_clear() _is_coroutine_callable_cached.cache_clear() for _ in range(2): assert not _is_gen_callable(async_dependency) assert not _is_async_gen_callable(async_dependency) assert _is_coroutine_callable(async_dependency) for cached_function in ( _is_gen_callable_cached, _is_async_gen_callable_cached, _is_coroutine_callable_cached, ): cache_info = cached_function.cache_info() assert cache_info.hits == 1 assert cache_info.misses == 1 assert cache_info.maxsize == 4096 def test_callable_classification_cache_supports_large_apps() -> None: callables: list[Callable[[], None]] = [lambda: None for _ in range(3000)] for classifier, cached_classifier in ( (_is_gen_callable, _is_gen_callable_cached), (_is_async_gen_callable, _is_async_gen_callable_cached), (_is_coroutine_callable, _is_coroutine_callable_cached), ): cached_classifier.cache_clear() for _ in range(2): assert all(not classifier(call) for call in callables) cache_info = cached_classifier.cache_info() assert cache_info.hits == len(callables) assert cache_info.misses == len(callables) assert cache_info.maxsize == 4096 cached_classifier.cache_clear() def test_unhashable_callable_classification() -> None: assert _is_coroutine_callable(UnhashableCallable()) assert _is_gen_callable(UnhashableGeneratorCallable()) assert _is_async_gen_callable(UnhashableAsyncGeneratorCallable()) def test_equal_callable_instances_are_cached_by_identity() -> None: async_callable = EqualAsyncCallable() sync_callable = EqualSyncCallable() assert async_callable == sync_callable assert _is_coroutine_callable(async_callable) assert not _is_coroutine_callable(sync_callable) def test_callable_classification() -> None: assert not _is_gen_callable(sync_dependency) assert not _is_async_gen_callable(sync_dependency) assert not _is_coroutine_callable(sync_dependency) assert _is_gen_callable(generator_dependency) assert _is_async_gen_callable(async_generator_dependency) def test_derived_values_are_not_stored_on_dependant() -> None: dependant = Dependant(call=async_dependency) uses_scopes_cache = {} assert _get_oauth_scopes(dependant=dependant) == [] assert not _uses_scopes(dependant=dependant, cache=uses_scopes_cache) assert not _uses_scopes(dependant=dependant, cache=uses_scopes_cache) assert _get_computed_scope(dependant=dependant) is None assert _get_cache_key(dependant=dependant) == (async_dependency, (), "") assert not hasattr(dependant, "__dict__") def test_security_scheme_helpers() -> None: security_scheme = APIKeyHeader(name="key") security_dependant = Dependant(call=security_scheme) dependant = Dependant(dependencies=[security_dependant]) assert _is_security_scheme(dependant=security_dependant) assert _get_security_scheme(dependant=security_dependant) is security_scheme assert _uses_scopes(dependant=dependant) def test_derived_values_follow_dependency_state() -> None: child = Dependant(call=sync_dependency) dependant = Dependant( call=sync_dependency, dependencies=[child], own_oauth_scopes=[], parent_oauth_scopes=["parent"], ) assert _get_cache_key(dependant=dependant) == (sync_dependency, (), "") child.security_scopes_param_name = "scopes" dependant.own_oauth_scopes = ["own", "parent"] assert _uses_scopes(dependant=dependant) assert _get_oauth_scopes(dependant=dependant) == ["parent", "own"] assert _get_cache_key(dependant=dependant) == ( sync_dependency, ("own", "parent"), "", ) def test_explicit_and_generator_scopes() -> None: assert ( _get_computed_scope(dependant=Dependant(call=sync_dependency, scope="function")) == "function" ) assert ( _get_computed_scope(dependant=Dependant(call=generator_dependency)) == "request" ) def test_callable_return_annotations_are_not_used() -> None: class CallableWithUnhashableReturn: def __call__(self) -> Any: return None # pragma: no cover __hash__ = None assert not _is_coroutine_callable(CallableWithUnhashableReturn())