11import logging
22from base64 import b64decode
33from json import JSONDecodeError
4- from typing import TYPE_CHECKING , Any , ClassVar , cast
4+ from typing import TYPE_CHECKING , Any , ClassVar , List , Optional , cast
55
66from flask import Flask , json , jsonify , make_response , request
77from flask .views import View
1818
1919# TODO?: Switch to https://docs.python.org/3/library/http.html#http-methods
2020# for Python 3.11+
21- HTTP_METHODS = [
21+ ALL_HTTP_METHODS = [
2222 "GET" ,
2323 "HEAD" ,
2424 "POST" ,
@@ -144,17 +144,57 @@ def resp_record_to_flask_response(
144144 return resp
145145
146146
147- def _create_flask_app ( handler : "hints.Handler" ) -> Flask :
148- app = Flask ( f"serverless_local_ { handler . __name__ } " )
147+ class LocalFunctionServer :
148+ """LocalFunctionServer serves Scaleway FaaS handlers on a local http server."""
149149
150- # Create the view from the handler
151- view = HandlerWrapper ( handler ). as_view ( handler . __name__ , handler )
150+ def __init__ ( self ) -> None :
151+ self . app = Flask ( "serverless_local" )
152152
153- # By default, methods contains ["GET", "HEAD", "OPTIONS"]
154- app .add_url_rule ("/<path:path>" , methods = HTTP_METHODS , view_func = view )
155- app .add_url_rule ("/" , methods = HTTP_METHODS , defaults = {"path" : "" }, view_func = view )
153+ def add_handler (
154+ self ,
155+ handler : "hints.Handler" ,
156+ relative_url : Optional [str ] = None ,
157+ http_methods : Optional [List [str ]] = None ,
158+ ) -> "LocalFunctionServer" :
159+ """Add a handler to be served by the server.
156160
157- return app
161+ :param handler: serverless python handler
162+ :param relative_url: path to the handler, defaults to / + handler's name
163+ :param http_methods: HTTP methods for the handler, defaults to all methods
164+ """
165+ relative_url = relative_url if relative_url else "/" + handler .__name__
166+ if not relative_url .startswith ("/" ):
167+ relative_url = "/" + relative_url
168+
169+ http_methods = http_methods if http_methods else ALL_HTTP_METHODS
170+ http_methods = [method .upper () for method in http_methods ]
171+
172+ view = HandlerWrapper (handler ).as_view (handler .__name__ , handler )
173+
174+ # By default, methods contains ["GET", "HEAD", "OPTIONS"]
175+ self .app .add_url_rule (
176+ f"{ relative_url } /<path:path>" , methods = http_methods , view_func = view
177+ )
178+ self .app .add_url_rule (
179+ relative_url ,
180+ methods = http_methods ,
181+ defaults = {"path" : "" },
182+ view_func = view ,
183+ )
184+
185+ return self
186+
187+ def serve (
188+ self , * args : Any , port : int = 8080 , debug : bool = True , ** kwargs : Any
189+ ) -> None :
190+ """Serve the added FaaS handlers.
191+
192+ :param port: port that the server should listen on, defaults to 8080
193+ :param debug: run Flask in debug mode, enables hot-reloading and stack trace.
194+ """
195+ kwargs ["port" ] = port
196+ kwargs ["debug" ] = debug
197+ self .app .run (* args , ** kwargs )
158198
159199
160200def serve_handler (
@@ -175,7 +215,6 @@ def serve_handler(
175215 ... return {"body": event["httpMethod"]}
176216 >>> serve_handler_locally(handle, port=8080)
177217 """
178- app : Flask = _create_flask_app (handler )
179- kwargs ["port" ] = port
180- kwargs ["debug" ] = debug
181- app .run (* args , ** kwargs )
218+ server = LocalFunctionServer ()
219+ server .add_handler (handler = handler , relative_url = "/" )
220+ server .serve (* args , port = port , debug = debug , ** kwargs )
0 commit comments