1
1
import logging
2
2
from base64 import b64decode
3
3
from json import JSONDecodeError
4
- from typing import TYPE_CHECKING , Any , ClassVar , cast
4
+ from typing import TYPE_CHECKING , Any , ClassVar , List , Optional , cast
5
5
6
6
from flask import Flask , json , jsonify , make_response , request
7
7
from flask .views import View
18
18
19
19
# TODO?: Switch to https://docs.python.org/3/library/http.html#http-methods
20
20
# for Python 3.11+
21
- HTTP_METHODS = [
21
+ ALL_HTTP_METHODS = [
22
22
"GET" ,
23
23
"HEAD" ,
24
24
"POST" ,
@@ -144,17 +144,57 @@ def resp_record_to_flask_response(
144
144
return resp
145
145
146
146
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."""
149
149
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" )
152
152
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.
156
160
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 )
158
198
159
199
160
200
def serve_handler (
@@ -175,7 +215,6 @@ def serve_handler(
175
215
... return {"body": event["httpMethod"]}
176
216
>>> serve_handler_locally(handle, port=8080)
177
217
"""
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