Skip to content
Draft
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
2 changes: 1 addition & 1 deletion requirements/mypy.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
mypy==1.15.0
mypy==1.16.1
mypy-zope==1.0.12
types-click==7.1.8
3 changes: 1 addition & 2 deletions src/klein/_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,8 +514,7 @@ def error_handler(request, failure):
f_or_exception, Exception
):
# f_or_exception is a KleinErrorHandler
f = cast(KleinErrorHandler, f_or_exception)
return self.handle_errors(Exception)(f)
return self.handle_errors(Exception)(f_or_exception)

# f_or_exception is an Exception class
exceptions = [f_or_exception] + list(additional_exceptions)
Expand Down
41 changes: 33 additions & 8 deletions src/klein/_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import TYPE_CHECKING, Any, Optional, Sequence, Tuple, Union, cast

from werkzeug.exceptions import HTTPException
from werkzeug.wrappers.response import Response as WerkResponse

from twisted.internet import defer
from twisted.internet.defer import Deferred, maybeDeferred, succeed
Expand All @@ -23,7 +24,6 @@
from ._app import (
ErrorMethods,
Klein,
KleinRenderable,
KleinRouteHandler,
RouteMetadata,
)
Expand Down Expand Up @@ -159,8 +159,27 @@ def __ne__(self, other: object) -> bool:
return result
return not result

def render(self, request: IRequest) -> KleinRenderable:
# Stuff we need to know for the mapper.
def render(self, request: IRequest) -> int | bytes:
"""
Render the request based on the underlying L{Klein} application, in a
multi-step process:

1. convert twisted input request information into something legible
to werkzeug

2. bind that info (URL, method, query args, etc) into a request
mapper, and look up a werkzeug endpoint (i.e.: klein
C{@route}-decorated method) to invoke

3. invoke that endpoint, getting something renderable

4. render that thing

5. handle any errors in lookup or rendering with declared error
handlers

6. render the thing that the error handler returned
"""
try:
(
url_scheme,
Expand Down Expand Up @@ -231,8 +250,9 @@ def _execute() -> Deferred:
def process(r: object) -> Any:
"""
Recursively go through r and any child Resources until something
returns an IRenderable, then render it and let the result of that
bubble back up.
returns something renderable (L{IResource}, L{IRenderable},
L{bytes}, L{str}, or L{Iterable} of same), then render it and let
the result of that bubble back up.
"""
# isinstance() is faster than providedBy(), so this speeds up the
# very common case of returning pre-rendered results, at the cost
Expand Down Expand Up @@ -279,14 +299,19 @@ def processing_failed(
he = failure.value
assert isinstance(he, HTTPException)
request.setResponseCode(he.code)
resp = he.get_response({})

# we need to call iter_encoded later so we need to include
# an explicit workaround for
# https://github.com/pallets/werkzeug/issues/3056
resp: WerkResponse
resp = he.get_response({}) # type:ignore[assignment]

for header, value in resp.headers:
request.setHeader(
ensure_utf8_bytes(header), ensure_utf8_bytes(value)
)

encoded = resp.iter_encoded() # type: ignore[attr-defined]
encoded = resp.iter_encoded()
return succeed(ensure_utf8_bytes(b"".join(encoded)))
else:
request.processingFailed( # type: ignore[attr-defined]
Expand Down Expand Up @@ -333,4 +358,4 @@ def write_response(
d.addCallback(write_response)
d.addErrback(log.err, _why="Unhandled Error writing response")

return server.NOT_DONE_YET # type: ignore[return-value]
return server.NOT_DONE_YET
Loading