diff --git a/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md index 3388a0828c193..82553afae52dd 100644 --- a/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md @@ -7,15 +7,6 @@ To do this, use `yield` instead of `return`, and write the extra steps after. !!! tip Make sure to use `yield` one single time. -!!! info - For this to work, you need to use **Python 3.7** or above, or in **Python 3.6**, install the "backports": - - ``` - pip install async-exit-stack async-generator - ``` - - This installs async-exit-stack and async-generator. - !!! note "Technical Details" Any function that is valid to use with: diff --git a/docs/en/docs/tutorial/sql-databases.md b/docs/en/docs/tutorial/sql-databases.md index c623fad298380..e8ebb29c8c776 100644 --- a/docs/en/docs/tutorial/sql-databases.md +++ b/docs/en/docs/tutorial/sql-databases.md @@ -441,17 +441,6 @@ You can find an example of Alembic in a FastAPI project in the templates from [P ### Create a dependency -!!! info - For this to work, you need to use **Python 3.7** or above, or in **Python 3.6**, install the "backports": - - ```console - $ pip install async-exit-stack async-generator - ``` - - This installs async-exit-stack and async-generator. - - You can also use the alternative method with a "middleware" explained at the end. - Now use the `SessionLocal` class we created in the `sql_app/databases.py` file to create a dependency. We need to have an independent database session/connection (`SessionLocal`) per request, use the same session through all the request and then close it after the request is finished. diff --git a/fastapi/concurrency.py b/fastapi/concurrency.py index d1fdfe5f60647..7ac82cfe80180 100644 --- a/fastapi/concurrency.py +++ b/fastapi/concurrency.py @@ -1,46 +1,34 @@ -from typing import Any, Callable - -from starlette.concurrency import iterate_in_threadpool as iterate_in_threadpool # noqa -from starlette.concurrency import run_in_threadpool as run_in_threadpool # noqa -from starlette.concurrency import ( # noqa - run_until_first_complete as run_until_first_complete, +import sys +from typing import AsyncGenerator, ContextManager, TypeVar + +__all__ = [ + "iterate_in_threadpool", + "run_in_threadpool", + "run_until_first_complete", + "contextmanager_in_threadpool", + "asynccontextmanager", + "AsyncExitStack", +] + +from starlette.concurrency import ( + iterate_in_threadpool, + run_in_threadpool, + run_until_first_complete, ) -asynccontextmanager_error_message = """ -FastAPI's contextmanager_in_threadpool require Python 3.7 or above, -or the backport for Python 3.6, installed with: - pip install async-generator -""" - - -def _fake_asynccontextmanager(func: Callable[..., Any]) -> Callable[..., Any]: - def raiser(*args: Any, **kwargs: Any) -> Any: - raise RuntimeError(asynccontextmanager_error_message) - - return raiser +if sys.version_info >= (3, 7): + from contextlib import AsyncExitStack, asynccontextmanager +else: + from contextlib2 import AsyncExitStack, asynccontextmanager -try: - from contextlib import asynccontextmanager as asynccontextmanager # type: ignore -except ImportError: - try: - from async_generator import ( # type: ignore # isort: skip - asynccontextmanager as asynccontextmanager, - ) - except ImportError: # pragma: no cover - asynccontextmanager = _fake_asynccontextmanager - -try: - from contextlib import AsyncExitStack as AsyncExitStack # type: ignore -except ImportError: - try: - from async_exit_stack import AsyncExitStack as AsyncExitStack # type: ignore - except ImportError: # pragma: no cover - AsyncExitStack = None # type: ignore +_T = TypeVar("_T") -@asynccontextmanager # type: ignore -async def contextmanager_in_threadpool(cm: Any) -> Any: +@asynccontextmanager +async def contextmanager_in_threadpool( + cm: ContextManager[_T], +) -> AsyncGenerator[_T, None]: try: yield await run_in_threadpool(cm.__enter__) except Exception as e: diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 95049d40ea82b..04dc2aec29981 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -20,7 +20,6 @@ from fastapi import params from fastapi.concurrency import ( AsyncExitStack, - _fake_asynccontextmanager, asynccontextmanager, contextmanager_in_threadpool, ) @@ -266,18 +265,6 @@ def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) -> return annotation -async_contextmanager_dependencies_error = """ -FastAPI dependencies with yield require Python 3.7 or above, -or the backports for Python 3.6, installed with: - pip install async-exit-stack async-generator -""" - - -def check_dependency_contextmanagers() -> None: - if AsyncExitStack is None or asynccontextmanager == _fake_asynccontextmanager: - raise RuntimeError(async_contextmanager_dependencies_error) # pragma: no cover - - def get_dependant( *, path: str, @@ -289,8 +276,6 @@ def get_dependant( path_param_names = get_path_param_names(path) endpoint_signature = get_typed_signature(call) signature_params = endpoint_signature.parameters - if is_gen_callable(call) or is_async_gen_callable(call): - check_dependency_contextmanagers() dependant = Dependant(call=call, name=name, path=path, use_cache=use_cache) for param_name, param in signature_params.items(): if isinstance(param.default, params.Depends): @@ -452,14 +437,6 @@ async def solve_generator( if is_gen_callable(call): cm = contextmanager_in_threadpool(contextmanager(call)(**sub_values)) elif is_async_gen_callable(call): - if not inspect.isasyncgenfunction(call): - # asynccontextmanager from the async_generator backfill pre python3.7 - # does not support callables that are not functions or methods. - # See https://github.com/python-trio/async_generator/issues/32 - # - # Expand the callable class into its __call__ method before decorating it. - # This approach will work on newer python versions as well. - call = getattr(call, "__call__", None) cm = asynccontextmanager(call)(**sub_values) return await stack.enter_async_context(cm) @@ -539,10 +516,7 @@ async def solve_dependencies( solved = dependency_cache[sub_dependant.cache_key] elif is_gen_callable(call) or is_async_gen_callable(call): stack = request.scope.get("fastapi_astack") - if stack is None: - raise RuntimeError( - async_contextmanager_dependencies_error - ) # pragma: no cover + assert isinstance(stack, AsyncExitStack) solved = await solve_generator( call=call, stack=stack, sub_values=sub_values ) diff --git a/pyproject.toml b/pyproject.toml index 58382690b7512..77a1032006021 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,8 +58,6 @@ test = [ "databases[sqlite] >=0.3.2,<0.4.0", "orjson >=3.2.1,<4.0.0", "ujson >=4.0.1,<5.0.0", - "async_exit_stack >=1.0.1,<2.0.0", - "async_generator >=1.10,<2.0.0", "python-multipart >=0.0.5,<0.0.6", "flask >=1.1.2,<2.0.0" ] @@ -90,8 +88,6 @@ all = [ "orjson >=3.2.1,<4.0.0", "email_validator >=1.1.1,<2.0.0", "uvicorn[standard] >=0.12.0,<0.14.0", - "async_exit_stack >=1.0.1,<2.0.0", - "async_generator >=1.10,<2.0.0" ] [tool.isort] diff --git a/tests/test_fakeasync.py b/tests/test_fakeasync.py deleted file mode 100644 index 4e146b0ff2328..0000000000000 --- a/tests/test_fakeasync.py +++ /dev/null @@ -1,12 +0,0 @@ -import pytest -from fastapi.concurrency import _fake_asynccontextmanager - - -@_fake_asynccontextmanager -def never_run(): - pass # pragma: no cover - - -def test_fake_async(): - with pytest.raises(RuntimeError): - never_run()