Skip to content

Commit

Permalink
Remove async_generator and async_exit_stack deps, as starlette uses c…
Browse files Browse the repository at this point in the history
…ontextlib2
  • Loading branch information
AlonMenczer committed Jul 24, 2021
1 parent d084a3d commit 60982db
Show file tree
Hide file tree
Showing 6 changed files with 26 additions and 100 deletions.
9 changes: 0 additions & 9 deletions docs/en/docs/tutorial/dependencies/dependencies-with-yield.md
Expand Up @@ -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 <a href="https://github.com/sorcio/async_exit_stack" class="external-link" target="_blank">async-exit-stack</a> and <a href="https://github.com/python-trio/async_generator" class="external-link" target="_blank">async-generator</a>.

!!! note "Technical Details"
Any function that is valid to use with:

Expand Down
11 changes: 0 additions & 11 deletions docs/en/docs/tutorial/sql-databases.md
Expand Up @@ -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 <a href="https://github.com/sorcio/async_exit_stack" class="external-link" target="_blank">async-exit-stack</a> and <a href="https://github.com/python-trio/async_generator" class="external-link" target="_blank">async-generator</a>.

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.
Expand Down
62 changes: 25 additions & 37 deletions 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:
Expand Down
28 changes: 1 addition & 27 deletions fastapi/dependencies/utils.py
Expand Up @@ -20,7 +20,6 @@
from fastapi import params
from fastapi.concurrency import (
AsyncExitStack,
_fake_asynccontextmanager,
asynccontextmanager,
contextmanager_in_threadpool,
)
Expand Down Expand Up @@ -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,
Expand All @@ -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):
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
)
Expand Down
4 changes: 0 additions & 4 deletions pyproject.toml
Expand Up @@ -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"
]
Expand Down Expand Up @@ -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]
Expand Down
12 changes: 0 additions & 12 deletions tests/test_fakeasync.py

This file was deleted.

0 comments on commit 60982db

Please sign in to comment.