Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix typing for async route methods #614

Open
wants to merge 3 commits into
base: trunk
Choose a base branch
from
Open
Changes from 1 commit
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
44 changes: 43 additions & 1 deletion src/klein/test/typing_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
This file contains code that should validate with type checking if type hints
are correct.

The code is not executed, it is here just to be checked by mypy.
The code is not executed. Instead, the code should simply validate when checked
with mypy, or fail and require a `type: ignore` comment.

Because those comments produce an error if mypy thinks they are unnecessary,
they service to confirm that code that should fail to verify does so.
"""

from twisted.internet.defer import succeed
Expand All @@ -16,6 +20,8 @@
class Application:
router = Klein()

# Ensure that various return object types for a route are valid.

@router.route("/object")
def returnsObject(self, request: IRequest) -> KleinRenderable:
return object() # type: ignore[return-value]
Expand Down Expand Up @@ -44,6 +50,9 @@ def returnsTag(self, request: IRequest) -> KleinRenderable:
def returnsNone(self, request: IRequest) -> KleinRenderable:
return None

# Ensure that same return object types for a route are valid when wrapped
# in a Deferred object.

@router.route("/deferred-object")
def returnsDeferredObject(self, request: IRequest) -> KleinRenderable:
return succeed(object()) # type: ignore[arg-type]
Expand Down Expand Up @@ -71,3 +80,36 @@ def returnsDeferredTag(self, request: IRequest) -> KleinRenderable:
@router.route("/deferred-none")
def returnsDeferredNone(self, request: IRequest) -> KleinRenderable:
return succeed(None)

# Ensure that same return object types for a route are valid in async
# methods.

@router.route("/async-object")
async def asyncReturnsObject(self, request: IRequest) -> KleinRenderable:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-> KleinRenderable isn't doing what you think here, because KleinRenderable includes both KleinSynchronousRenderable and Awaitable[KleinSynchronousRenderable], and you can't return both of those here; due to the implicit modification of async def, the actual return type of this function becomes (handwaving a bit over the Coroutine junk) Awaitable[Union[KleinSynchronousRenderable, Awaitable[KleinSynchronousRenderable]], which is gibberish.

What you actually want to do with these methods is to say, for example, "-> str", which will implicitly match against -> KleinRenderable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK that works for cases where I know the specific type I'm returning. What if I'm returning the result of something that gives me a KleinRenderable?

Presumably I will have awaited on it if appropriate, so I agree that maybe this should be KleinSynchronousRenderable, though that's currently not public.

That is, it should be possible to type this generically.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I use KleinSynchronousRenderable, I at least get passing for these, but it doesn't catch asyncReturnsObject as invalid… so not perfect, but better.

return succeed(object()) # type: ignore[arg-type]
wsanchez marked this conversation as resolved.
Show resolved Hide resolved

@router.route("/async-str")
async def asyncReturnsStr(self, request: IRequest) -> KleinRenderable:
return succeed("")

@router.route("/async-bytes")
async def asyncReturnsBytes(self, request: IRequest) -> KleinRenderable:
return succeed(b"")

@router.route("/async-iresource")
async def asyncReturnsIResource(self, request: IRequest) -> KleinRenderable:
return succeed(Resource())

@router.route("/async-irenderable")
async def asyncReturnsIRenderable(
self, request: IRequest
) -> KleinRenderable:
return succeed(Element())

@router.route("/async-tag")
async def asyncReturnsTag(self, request: IRequest) -> KleinRenderable:
return succeed(Tag(""))

@router.route("/async-none")
async def asyncReturnsNone(self, request: IRequest) -> KleinRenderable:
return succeed(None)