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

V2 of asyncjsonwrap #373

Merged
merged 12 commits into from Jun 29, 2021
2 changes: 1 addition & 1 deletion changelog.d/372.misc
@@ -1 +1 @@
Convert `inlineCallbacks` to async/await.
Convert `inlineCallbacks` to async/await.
1 change: 1 addition & 0 deletions changelog.d/373.misc
@@ -0,0 +1 @@
Convert `inlineCallbacks` to async/await.
54 changes: 15 additions & 39 deletions sydent/http/servlets/__init__.py
Expand Up @@ -19,7 +19,7 @@
from typing import Any, Dict, Iterable

from twisted.internet import defer
from twisted.python.failure import Failure
from twisted.python import failure
from twisted.web import server
from twisted.web.server import Request

Expand Down Expand Up @@ -163,38 +163,17 @@ def inner(self, request: Request, *args, **kwargs) -> bytes:


def asyncjsonwrap(f):
def reqDone(resp: Dict[str, Any], request: Request) -> None:
"""
Converts the given response content into JSON and encodes it to bytes, then
writes it as the response to the given request with the right headers.

:param resp: The response content to convert to JSON and encode.
:param request: The request to respond to.
"""
request.setHeader("Content-Type", "application/json")
request.write(dict_to_json_bytes(resp))
request.finish()

def reqErr(failure: Failure, request: Request) -> None:
"""
Logs the given failure. If the failure is a MatrixRestError, writes a response
using the info it contains, otherwise responds with 500 Internal Server Error.

:param failure: The failure to process.
:param request: The request to respond to.
"""
async def render(f, self, request: Request, **kwargs):
request.setHeader("Content-Type", "application/json")
if failure.check(MatrixRestError) is not None:
request.setResponseCode(failure.value.httpStatus)
request.write(
dict_to_json_bytes(
{"errcode": failure.value.errcode, "error": failure.value.error}
)
)
else:
logger.error(
"Request processing failed: %r, %s", failure, failure.getTraceback()
)
try:
result = await f(self, request, **kwargs)
request.write(dict_to_json_bytes(result))
except MatrixRestError as e:
request.setResponseCode(e.httpStatus)
request.write(dict_to_json_bytes({"errcode": e.errcode, "error": e.error}))
except Exception:
f = failure.Failure()
logger.error("Request processing failed: %r, %s", f, f.getTraceback())
request.setResponseCode(500)
request.write(
dict_to_json_bytes(
Expand All @@ -203,22 +182,19 @@ def reqErr(failure: Failure, request: Request) -> None:
)
request.finish()

@functools.wraps(f)
def inner(*args, **kwargs) -> int:
"""
Runs an asynchronous web handler function with the given arguments and add
reqDone and reqErr as the resulting Deferred's callbacks.
Runs an asynchronous web handler function with the given arguments.

:param f: The original function passed to the asyncjsonwrap decorator
Copy link
Contributor

Choose a reason for hiding this comment

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

f isn't a parameter to inner, so I think we should leave it out from here!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right it's a param to render. I wasn't paying attention there!

:param args: The arguments to pass to the function.
:param kwargs: The keyword arguments to pass to the function.

:return: A special code to tell the servlet that the response isn't ready yet
and will come later.
"""
request = args[1]

d = defer.ensureDeferred(f(*args, **kwargs))
d.addCallback(reqDone, request)
d.addErrback(reqErr, request)
defer.ensureDeferred(render(f, *args, **kwargs))
return server.NOT_DONE_YET

return inner
Expand Down