Skip to content

serve() parameter type annotations gets in the way #353

@nresare

Description

@nresare

Even though it is nice to have very specific type annotations on the serve() function in hypercorn.asyncio for users that write their own apps and use hypercorn to run them, I would assume that many users of hypercorn use it to drive ASGI applications that are implemented by some existing code such as a framework or app that already runs with a different ASGI server.

This leads to a situation where popular type checkers will create difficult-to-fix warnings a lot of valid ASGI apps.

Consider for example the attached trivial hello world app. It will run fine on my system, invoked with uv run another.py but type checkers such as mypy or ty will tell you something like:

% uv run ty check src/hypercorn_lab/another.py 
error[invalid-argument-type]: Argument to function `serve` is incorrect
  --> src/hypercorn_lab/another.py:35:17
   |
35 |     await serve(get_app(), config)
   |                 ^^^^^^^^^ Expected `((hypercorn.typing.HTTPScope | WebsocketScope | hypercorn.typing.LifespanScope, () -> Awaitable[hypercorn.typing.HTTPRequestEvent | hypercorn.typing.HTTPDisconnectEvent | WebsocketConnectEvent | ... omitted 4 union elements], (hypercorn.typing.HTTPResponseStartEvent | hypercorn.typing.HTTPResponseBodyEvent | hypercorn.typing.HTTPResponseTrailersEvent | ... omitted 12 union elements, /) -> Awaitable[None], /) -> Awaitable[None]) | ((dict[Unknown, Unknown], (...) -> Unknown, /) -> Iterable[bytes])`, found `(asgiref.typing.HTTPScope | WebSocketScope | asgiref.typing.LifespanScope, () -> Awaitable[asgiref.typing.HTTPRequestEvent | asgiref.typing.HTTPDisconnectEvent | WebSocketConnectEvent | ... omitted 4 union elements], (asgiref.typing.HTTPResponseStartEvent | asgiref.typing.HTTPResponseBodyEvent | asgiref.typing.HTTPResponseTrailersEvent | ... omitted 11 union elements, /) -> Awaitable[None], /) -> Awaitable[None]`
   |
info: type `(HTTPScope | WebSocketScope | LifespanScope, () -> Awaitable[HTTPRequestEvent | HTTPDisconnectEvent | WebSocketConnectEvent | ... omitted 4 union elements], (HTTPResponseStartEvent | HTTPResponseBodyEvent | HTTPResponseTrailersEvent | ... omitted 11 union elements, /) -> Awaitable[None], /) -> Awaitable[None]` is not assignable to any element of the union `((HTTPScope | WebsocketScope | LifespanScope, () -> Awaitable[HTTPRequestEvent | HTTPDisconnectEvent | WebsocketConnectEvent | ... omitted 4 union elements], (HTTPResponseStartEvent | HTTPResponseBodyEvent | HTTPResponseTrailersEvent | ... omitted 12 union elements, /) -> Awaitable[None], /) -> Awaitable[None]) | ((dict[Unknown, Unknown], (...) -> Unknown, /) -> Iterable[bytes])`
info: ├── the first parameter has an incompatible type: `HTTPScope | WebsocketScope | LifespanScope` is not assignable to `HTTPScope | WebSocketScope | LifespanScope`
info: │   └── element `HTTPScope` of union `HTTPScope | WebsocketScope | LifespanScope` is not assignable to `HTTPScope | WebSocketScope | LifespanScope`
info: │       └── type `HTTPScope` is not assignable to any element of the union `HTTPScope | WebSocketScope | LifespanScope`
info: │           ├── field "asgi" on TypedDict `HTTPScope` has type `ASGIVersions` which is not assignable to type `ASGIVersions` expected by TypedDict `HTTPScope`
info: │           │   └── field "spec_version" is required in TypedDict `ASGIVersions` but not required in TypedDict `ASGIVersions`
info: │           ├── field "asgi" on TypedDict `HTTPScope` has type `ASGIVersions` which is not assignable to type `ASGIVersions` expected by TypedDict `WebSocketScope`
info: │           └── field "asgi" on TypedDict `HTTPScope` has type `ASGIVersions` which is not assignable to type `ASGIVersions` expected by TypedDict `LifespanScope`
info: └── incompatible return types: `Awaitable[None]` is not assignable to `Iterable[bytes]`
info:     └── protocol `Awaitable[None]` is not assignable to protocol `Iterable[bytes]`
info:         └── protocol member `__iter__` is not defined on type `Awaitable[None]`
info: Function defined here
  --> .venv/lib/python3.14/site-packages/hypercorn/asyncio/__init__.py:13:11
   |
13 | async def serve(
   |           ^^^^^
14 |     app: Framework,
   |     -------------- Parameter declared here
   |

I picked the asgiref types in my example because they are commonly used and to the best of my understanding seem to reflect the ASGI specification, but it seems to me like working with regular dicts would be equally valid.

One way of addressing this would be to create an alternative variant to serve() with a more relaxed type signature for the ASGI app. I'm happy to volunteer implementing and verify it is with some commonly used frameworks and type checkers if there is interest

another.py

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions