TIL in Python typing
Simpler yield
return type
If a function returns a generator with the yield
keyword, it makes sense to type it with typing.Generator
. For example, in Pytest fixtures with cleanup logic:
from typing import Generator
@pytest.fixture
def httpclient() -> Generator[TestClient, None, None]:
app = create_app()
yield TestClient(app)
app.shutdown()
But I've always hated those superfluous Nones. After using cachew, a decorator-based persistent cache that uses iterators to retain the return-type, it turns out there's a simpler way:
from typing import Iterator
@pytest.fixture
def httpclient() -> Iterator[TestClient]:
app = create_app()
yield TestClient(app)
app.shutdown()
Generators implement the iterator-protocol, so it's technically correct. I won't be truly satisfied until it can be shortened to an import-less iter[T]
.
Annotating types
Something else that's hard to look at every day is a lengthy FastAPI Depends, particularly if it applies to a bunch of endpoints:
def get_authed_user() -> User:
...
@app.get("/dashboard")
def dashboard(*, user: User = Depends(get_authed_user)):
...
It turns out there's a Python type called Annotated
(Annotated[T, ...metadata]
) that lets you bind runtime logic to a type. For example, you could do this in FastAPI 0.95.0:
from typing import Annotated
def get_authed_user() -> User:
...
AuthenticatedUser = Annotated[User, Depends(get_authed_user)]
@app.get("/dashboard")
def dashboard(*, user: AuthenticatedUser):
...
I'm looking forward to black
spreading my functions over fewer pages.