Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
exclude_patterns = ["_build"]

nitpick_ignore = [
("py:class", "SomeLifespan"), # just a type alias
*[("py:class", f"svcs._core.T{i}") for i in range(1, 11)],
# This only fails in CI!?
*[("py:class", f"T{i}") for i in range(1, 11)],
Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,11 @@ exclude = [
"src/svcs/pyramid.py",
"tests/typing/mypy.py",
"tests/typing/pyramid.py",
# Starlette started failing since ty 0.0.0a26 and is nontrivial to fix due to
# the prevalence of private types:
# <https://github.com/Kludex/starlette/blob/49d4de92867cb38a781069701ad57cecab4a1a36/starlette/middleware/__init__.py#L9-L18>.
"src/svcs/starlette.py",
"tests/typing/starlette.py",
]


Expand Down
38 changes: 18 additions & 20 deletions src/svcs/fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import inspect

from collections.abc import AsyncGenerator, Callable
from typing import Annotated
from typing import Annotated, TypeAlias, cast

import attrs

Expand All @@ -19,6 +19,17 @@
from svcs._core import _KEY_REGISTRY


AsyncGenLifespan: TypeAlias = Callable[
[FastAPI, svcs.Registry],
AsyncGenerator[dict[str, object] | None, None],
]
AsyncCMLifespan: TypeAlias = Callable[
[FastAPI, svcs.Registry],
contextlib.AbstractAsyncContextManager[dict[str, object] | None],
]
SomeLifespan: TypeAlias = AsyncGenLifespan | AsyncCMLifespan


@attrs.define
class lifespan: # noqa: N801
"""
Expand All @@ -34,34 +45,21 @@ class lifespan: # noqa: N801
lifespan: The lifespan function to make *svcs*-aware.
"""

_lifespan: (
Callable[
[FastAPI, svcs.Registry],
contextlib.AbstractAsyncContextManager[dict[str, object]],
]
| Callable[
[FastAPI, svcs.Registry],
contextlib.AbstractAsyncContextManager[None],
]
| Callable[
[FastAPI, svcs.Registry], AsyncGenerator[dict[str, object], None]
]
| Callable[[FastAPI, svcs.Registry], AsyncGenerator[None, None]]
)
_lifespan: SomeLifespan
_state: dict[str, object] = attrs.field(factory=dict)
registry: svcs.Registry = attrs.field(factory=svcs.Registry)

@contextlib.asynccontextmanager
async def __call__(
self, app: FastAPI
) -> AsyncGenerator[dict[str, object], None]:
cm: Callable[
[FastAPI, svcs.Registry], contextlib.AbstractAsyncContextManager
]
cm: AsyncCMLifespan
if inspect.isasyncgenfunction(self._lifespan):
cm = contextlib.asynccontextmanager(self._lifespan)
cm = contextlib.asynccontextmanager(
cast(AsyncGenLifespan, self._lifespan)
)
else:
cm = self._lifespan # type: ignore[assignment]
cm = cast(AsyncCMLifespan, self._lifespan)

async with self.registry, cm(app, self.registry) as state:
self._state = state or {}
Expand Down
3 changes: 1 addition & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ dependency_groups = typing
deps =
pyrefly: pyrefly
pyright: pyright
# a26 is failing
ty: ty==0.0.1a25
ty: ty
commands =
# Mypy API checks need to happen per-Python version.
mypy: mypy src
Expand Down