diff --git a/requirements/mypy.txt b/requirements/mypy.txt index 419674e9..b2f3410c 100644 --- a/requirements/mypy.txt +++ b/requirements/mypy.txt @@ -1,3 +1,3 @@ -mypy==1.15.0 +mypy==1.16.1 mypy-zope==1.0.12 types-click==7.1.8 diff --git a/src/klein/_app.py b/src/klein/_app.py index b1175d20..d02267fc 100644 --- a/src/klein/_app.py +++ b/src/klein/_app.py @@ -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) diff --git a/src/klein/_resource.py b/src/klein/_resource.py index a42c0afd..48e33697 100644 --- a/src/klein/_resource.py +++ b/src/klein/_resource.py @@ -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 @@ -23,7 +24,6 @@ from ._app import ( ErrorMethods, Klein, - KleinRenderable, KleinRouteHandler, RouteMetadata, ) @@ -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, @@ -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 @@ -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] @@ -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