Ross Masters

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.

#codelog #python