20
20
21
21
logger = logging .getLogger (__name__ )
22
22
23
+ _DYNAMIC_ROUTE_PATTERN = r"(<\w+>)"
24
+ _NAMED_GROUP_BOUNDARY_PATTERN = r"(?P\1\\w+\\b)"
25
+
23
26
24
27
class ProxyEventType (Enum ):
25
28
"""An enumerations of the supported proxy event types."""
@@ -126,7 +129,11 @@ class Response:
126
129
"""Response data class that provides greater control over what is returned from the proxy event"""
127
130
128
131
def __init__ (
129
- self , status_code : int , content_type : Optional [str ], body : Union [str , bytes , None ], headers : Dict = None
132
+ self ,
133
+ status_code : int ,
134
+ content_type : Optional [str ],
135
+ body : Union [str , bytes , None ],
136
+ headers : Optional [Dict ] = None ,
130
137
):
131
138
"""
132
139
@@ -167,7 +174,7 @@ def __init__(
167
174
class ResponseBuilder :
168
175
"""Internally used Response builder"""
169
176
170
- def __init__ (self , response : Response , route : Route = None ):
177
+ def __init__ (self , response : Response , route : Optional [ Route ] = None ):
171
178
self .response = response
172
179
self .route = route
173
180
@@ -199,7 +206,7 @@ def _route(self, event: BaseProxyEvent, cors: Optional[CORSConfig]):
199
206
if self .route .compress and "gzip" in (event .get_header_value ("accept-encoding" , "" ) or "" ):
200
207
self ._compress ()
201
208
202
- def build (self , event : BaseProxyEvent , cors : CORSConfig = None ) -> Dict [str , Any ]:
209
+ def build (self , event : BaseProxyEvent , cors : Optional [ CORSConfig ] = None ) -> Dict [str , Any ]:
203
210
"""Build the full response dict to be returned by the lambda"""
204
211
self ._route (event , cors )
205
212
@@ -250,7 +257,7 @@ def lambda_handler(event, context):
250
257
def __init__ (
251
258
self ,
252
259
proxy_type : Enum = ProxyEventType .APIGatewayProxyEvent ,
253
- cors : CORSConfig = None ,
260
+ cors : Optional [ CORSConfig ] = None ,
254
261
debug : Optional [bool ] = None ,
255
262
):
256
263
"""
@@ -270,10 +277,10 @@ def __init__(
270
277
self ._cors_enabled : bool = cors is not None
271
278
self ._cors_methods : Set [str ] = {"OPTIONS" }
272
279
self ._debug = resolve_truthy_env_var_choice (
273
- choice = debug , env = os .getenv (constants .EVENT_HANDLER_DEBUG_ENV , "false" )
280
+ env = os .getenv (constants .EVENT_HANDLER_DEBUG_ENV , "false" ), choice = debug
274
281
)
275
282
276
- def get (self , rule : str , cors : bool = None , compress : bool = False , cache_control : str = None ):
283
+ def get (self , rule : str , cors : Optional [ bool ] = None , compress : bool = False , cache_control : Optional [ str ] = None ):
277
284
"""Get route decorator with GET `method`
278
285
279
286
Examples
@@ -298,7 +305,7 @@ def lambda_handler(event, context):
298
305
"""
299
306
return self .route (rule , "GET" , cors , compress , cache_control )
300
307
301
- def post (self , rule : str , cors : bool = None , compress : bool = False , cache_control : str = None ):
308
+ def post (self , rule : str , cors : Optional [ bool ] = None , compress : bool = False , cache_control : Optional [ str ] = None ):
302
309
"""Post route decorator with POST `method`
303
310
304
311
Examples
@@ -324,7 +331,7 @@ def lambda_handler(event, context):
324
331
"""
325
332
return self .route (rule , "POST" , cors , compress , cache_control )
326
333
327
- def put (self , rule : str , cors : bool = None , compress : bool = False , cache_control : str = None ):
334
+ def put (self , rule : str , cors : Optional [ bool ] = None , compress : bool = False , cache_control : Optional [ str ] = None ):
328
335
"""Put route decorator with PUT `method`
329
336
330
337
Examples
@@ -350,7 +357,9 @@ def lambda_handler(event, context):
350
357
"""
351
358
return self .route (rule , "PUT" , cors , compress , cache_control )
352
359
353
- def delete (self , rule : str , cors : bool = None , compress : bool = False , cache_control : str = None ):
360
+ def delete (
361
+ self , rule : str , cors : Optional [bool ] = None , compress : bool = False , cache_control : Optional [str ] = None
362
+ ):
354
363
"""Delete route decorator with DELETE `method`
355
364
356
365
Examples
@@ -375,7 +384,9 @@ def lambda_handler(event, context):
375
384
"""
376
385
return self .route (rule , "DELETE" , cors , compress , cache_control )
377
386
378
- def patch (self , rule : str , cors : bool = None , compress : bool = False , cache_control : str = None ):
387
+ def patch (
388
+ self , rule : str , cors : Optional [bool ] = None , compress : bool = False , cache_control : Optional [str ] = None
389
+ ):
379
390
"""Patch route decorator with PATCH `method`
380
391
381
392
Examples
@@ -403,7 +414,14 @@ def lambda_handler(event, context):
403
414
"""
404
415
return self .route (rule , "PATCH" , cors , compress , cache_control )
405
416
406
- def route (self , rule : str , method : str , cors : bool = None , compress : bool = False , cache_control : str = None ):
417
+ def route (
418
+ self ,
419
+ rule : str ,
420
+ method : str ,
421
+ cors : Optional [bool ] = None ,
422
+ compress : bool = False ,
423
+ cache_control : Optional [str ] = None ,
424
+ ):
407
425
"""Route decorator includes parameter `method`"""
408
426
409
427
def register_resolver (func : Callable ):
@@ -445,8 +463,35 @@ def __call__(self, event, context) -> Any:
445
463
446
464
@staticmethod
447
465
def _compile_regex (rule : str ):
448
- """Precompile regex pattern"""
449
- rule_regex : str = re .sub (r"(<\w+>)" , r"(?P\1.+)" , rule )
466
+ """Precompile regex pattern
467
+
468
+ Logic
469
+ -----
470
+
471
+ 1. Find any dynamic routes defined as <pattern>
472
+ e.g. @app.get("/accounts/<account_id>")
473
+ 2. Create a new regex by substituting every dynamic route found as a named group (?P<group>),
474
+ and match whole words only (word boundary) instead of a greedy match
475
+
476
+ non-greedy example with word boundary
477
+
478
+ rule: '/accounts/<account_id>'
479
+ regex: r'/accounts/(?P<account_id>\\ w+\\ b)'
480
+
481
+ value: /accounts/123/some_other_path
482
+ account_id: 123
483
+
484
+ greedy example without word boundary
485
+
486
+ regex: r'/accounts/(?P<account_id>.+)'
487
+
488
+ value: /accounts/123/some_other_path
489
+ account_id: 123/some_other_path
490
+ 3. Compiles a regex and include start (^) and end ($) in between for an exact match
491
+
492
+ NOTE: See #520 for context
493
+ """
494
+ rule_regex : str = re .sub (_DYNAMIC_ROUTE_PATTERN , _NAMED_GROUP_BOUNDARY_PATTERN , rule )
450
495
return re .compile ("^{}$" .format (rule_regex ))
451
496
452
497
def _to_proxy_event (self , event : Dict ) -> BaseProxyEvent :
@@ -470,7 +515,7 @@ def _resolve(self) -> ResponseBuilder:
470
515
match : Optional [re .Match ] = route .rule .match (path )
471
516
if match :
472
517
logger .debug ("Found a registered route. Calling function" )
473
- return self ._call_route (route , match .groupdict ())
518
+ return self ._call_route (route , match .groupdict ()) # pass fn args
474
519
475
520
logger .debug (f"No match found for path { path } and method { method } " )
476
521
return self ._not_found (method )
0 commit comments