From 42a8efe35948d611977b8717ab0cdc55e7d8b865 Mon Sep 17 00:00:00 2001 From: Luke Kingland Date: Mon, 9 Sep 2024 14:16:36 +0900 Subject: [PATCH] feat: make function itself ASGI callable --- cmd/fhttp/main.py | 26 ++++++++++++++++++++------ src/func_python/http.py | 8 ++++++-- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/cmd/fhttp/main.py b/cmd/fhttp/main.py index f014c15a..314dc05b 100644 --- a/cmd/fhttp/main.py +++ b/cmd/fhttp/main.py @@ -2,8 +2,12 @@ import logging from func_python.http import serve +# Set the default logging level to INFO logging.basicConfig(level=logging.INFO) +# Allow this test to be either instanced (default) or --static +# to test the two different primary method signatures supported in the +# final Function. parser = argparse.ArgumentParser(description='Serve a Test Function') parser.add_argument('--static', action='store_true', help='Serve the example static handler (default is to ' @@ -11,12 +15,15 @@ args = parser.parse_args() +# Example static handler. +# Enable with --static +# Must be named exactly "handle" async def handle(scope, receive, send): """ handle is an example of a static handler which can be sent to the middleware as a funciton. It will be wrapped in a default Funciton instance before being served as an ASGI application. """ - logging.info("OK: static") + logging.info("OK: static!!") await send({ 'type': 'http.response.start', @@ -29,17 +36,19 @@ async def handle(scope, receive, send): }) -class Function: +# Example instanced handler +# This is the default expected by this test. +# The class can be named anything. See "new" below. +class MyFunction: """ Function is an example of a functioon instance. The structure implements the function which will be deployed as a network service. The class name can be changed. The only required method is "handle". """ - - async def handle(self, scope, receive, send): + async def __call__(self, scope, receive, send): """ handle is the only method which must be implemented by the function instance for it to be served as an ASGI handler. """ - logging.info("OK: instanced") + logging.info("OK: instanced!!") await send({ 'type': 'http.response.start', @@ -52,6 +61,9 @@ async def handle(self, scope, receive, send): }) +# Funciton instance constructor +# expected to be named exactly "new" +# Must return a callable which conforms to the ASGI spec. def new(): """ new is the factory function (or constructor) which will create a new function instance when invoked. This must be named "new", and the @@ -59,9 +71,11 @@ def new(): the ASGI specification's method signature. The name of the class itself can be changed. """ - return Function() + return MyFunction() +# Run the example. +# Start either the static or instanced handler depending on flag --static if __name__ == "__main__": if args.static: logging.info("Starting static handler") diff --git a/src/func_python/http.py b/src/func_python/http.py index f79898ee..167d5883 100644 --- a/src/func_python/http.py +++ b/src/func_python/http.py @@ -34,6 +34,10 @@ class DefaultFunction: def __init__(self, handler): self.handle = handler + async def __call__(self, scope, receive, send): + # delegate to the handler implementation provided during construction. + await self.handle (scope, receive, send) + class ASGIApplication(): def __init__(self, f): @@ -105,8 +109,8 @@ async def __call__(self, scope, receive, send): elif scope['path'] == '/health/readiness': await self.handle_readiness(scope, receive, send) else: - if hasattr(self.f, "handle"): - await self.f.handle(scope, receive, send) + if callable(self.f): + await self.f(scope, receive, send) else: raise Exception("function does not implement handle") except Exception as e: