From f51ef956e54489fd768e73e35a0308bb9fc3d143 Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Fri, 6 Apr 2018 16:32:57 +0200 Subject: [PATCH 1/6] if resource is set in request, use that instead of path --- lambdarest/__init__.py | 7 +++++++ tests/test_lambdarest.py | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/lambdarest/__init__.py b/lambdarest/__init__.py index e6ddce8..05fa20b 100644 --- a/lambdarest/__init__.py +++ b/lambdarest/__init__.py @@ -105,6 +105,13 @@ def inner_lambda_handler(event, context=None): # Save context within event for easy access event["context"] = context path = event["path"].lower() + + # if APIGW is used, and a custom domain is setup, the actual path will + # differ and will have the basepath included. 'Resource' will always + # contain the actual path to the resource + if 'resource' in event: + path = event['resource'].lower() + method_name = event["httpMethod"].lower() func = None kwargs = {} diff --git a/tests/test_lambdarest.py b/tests/test_lambdarest.py index 2d223af..89b599d 100644 --- a/tests/test_lambdarest.py +++ b/tests/test_lambdarest.py @@ -271,6 +271,24 @@ def test_that_specified_path_works(self): "statusCode": 200, "headers": {}} + def test_that_apigw_with_basepath_works(self): + json_body = {} + + self.event["body"] = json.dumps(json_body) + self.event["httpMethod"] = "GET" + + get_mock1 = mock.Mock(return_value="foo") + + self.lambda_handler.handle("get", path="/foo/bar")(get_mock1) # decorate mock + + self.event["path"] = "/v1/foo/bar" + self.event["resource"] = "/foo/bar" + result1 = self.lambda_handler(self.event, self.context) + assert result1 == { + "body": '"foo"', + "statusCode": 200, + "headers": {}} + def test_that_no_path_specified_match_all(self): random.seed(time.mktime(datetime.now().timetuple())) From 6b9aaddd3fe6e4eee5a41fdf4e1ba13c0588dea2 Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Wed, 11 Apr 2018 14:57:58 +0200 Subject: [PATCH 2/6] Added support for the {proxy+} endpoint. Default to "resource" event key --- README.md | 36 ++++++++++++++++++++++++++++++------ lambdarest/__init__.py | 13 ++++++------- tests/test_lambdarest.py | 34 ++++++++++++++++++++++++++++------ 3 files changed, 64 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index e939f13..634af21 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ def my_own_get(event): input_event = { "body": '{}', "httpMethod": "GET", - "path": "/" + "resource": "/" } result = lambda_handler(event=input_event) assert result == {"body": '{"this": "will be json dumped"}', "statusCode": 200, "headers":{}} @@ -75,7 +75,7 @@ def my_own_get(event): valid_input_event = { "body": '{"foo":"bar"}', "httpMethod": "GET", - "path": "/with-schema/" + "resource": "/with-schema/" } result = lambda_handler(event=valid_input_event) assert result == {"body": '{"this": "will be json dumped"}', "statusCode": 200, "headers":{}} @@ -84,7 +84,7 @@ assert result == {"body": '{"this": "will be json dumped"}', "statusCode": 200, invalid_input_event = { "body": '{"foo":666}', "httpMethod": "GET", - "path": "/with-schema/" + "resource": "/with-schema/" } result = lambda_handler(event=invalid_input_event) assert result == {"body": '"Validation Error"', "statusCode": 400, "headers":{}} @@ -129,7 +129,7 @@ valid_input_event = { "foo": "1, 2.2, 3" }, "httpMethod": "GET", - "path": "/with-params/" + "resource": "/with-params/" } result = lambda_handler(event=valid_input_event) assert result == {"body": '{"foo": [1.0, 2.2, 3.0]}', "statusCode": 200, "headers":{}} @@ -153,7 +153,7 @@ def my_own_get(event): input_event = { "body": '{}', "httpMethod": "GET", - "path": "/foo/bar/baz" + "resource": "/foo/bar/baz" } result = lambda_handler(event=input_event) assert result == {"body": '{"this": "will be json dumped"}', "statusCode": 200, "headers":{}} @@ -175,12 +175,36 @@ def my_own_get(event, id): input_event = { "body": '{}', "httpMethod": "GET", - "path": "/foo/1234/" + "resource": "/foo/1234/" } result = lambda_handler(event=input_event) assert result == {"body": '{"my-id": 1234}', "statusCode": 200, "headers":{}} ``` +Or use the Proxy APIGateway magic endpoint: +```python +from lambdarest import lambda_handler + +@lambda_handler.handle("get", path="/bar/") +def my_own_get(event, path): + return {"path": path} + + +##### TEST ##### + +input_event = { + "body": '{}', + "httpMethod": "GET", + "path": "/foo/bar/baz", + "resource": "/bar/{proxy+}", + "pathParameters": { + "proxy": "bar/baz" + } +} +result = lambda_handler(event=input_event) +assert result == {"body": '{"path": "bar/baz"}', "statusCode": 200, "headers":{}} +``` + ## Anormal unittest behaviour with `lambda_handler` singleton diff --git a/lambdarest/__init__.py b/lambdarest/__init__.py index 05fa20b..95b4b40 100644 --- a/lambdarest/__init__.py +++ b/lambdarest/__init__.py @@ -13,7 +13,7 @@ __validate_kwargs = {"format_checker": FormatChecker()} -__required_keys = ["httpMethod", "path"] +__required_keys = ["httpMethod", "resource"] class Response(object): @@ -104,13 +104,12 @@ def inner_lambda_handler(event, context=None): # Save context within event for easy access event["context"] = context - path = event["path"].lower() + path = event['resource'].lower() - # if APIGW is used, and a custom domain is setup, the actual path will - # differ and will have the basepath included. 'Resource' will always - # contain the actual path to the resource - if 'resource' in event: - path = event['resource'].lower() + # proxy is a bit weird. We just replace the value in the uri with the + # actual value provided by apigw, and use that + if '{proxy+}' in path: + path = path.replace('{proxy+}', event['pathParameters']['proxy']) method_name = event["httpMethod"].lower() func = None diff --git a/tests/test_lambdarest.py b/tests/test_lambdarest.py index 89b599d..3bb41b0 100644 --- a/tests/test_lambdarest.py +++ b/tests/test_lambdarest.py @@ -22,8 +22,8 @@ def assert_called_once(mock): class TestLambdarestFunctions(unittest.TestCase): def setUp(self): self.event = { - "resource": "/test", - "path": "/", + "resource": "/", + "path": "/v1/", "httpMethod": "POST", "headers": None, "queryStringParameters": None, @@ -257,14 +257,14 @@ def test_that_specified_path_works(self): self.lambda_handler.handle("get", path="/foo/bar")(get_mock1) # decorate mock self.lambda_handler.handle("get", path="/bar/foo")(get_mock2) # decorate mock - self.event["path"] = "/foo/bar" + self.event["resource"] = "/foo/bar" result1 = self.lambda_handler(self.event, self.context) assert result1 == { "body": '"foo"', "statusCode": 200, "headers": {}} - self.event["path"] = "/bar/foo" + self.event["resource"] = "/bar/foo" result2 = self.lambda_handler(self.event, self.context) assert result2 == { "body": '"bar"', @@ -289,6 +289,28 @@ def test_that_apigw_with_basepath_works(self): "statusCode": 200, "headers": {}} + + def test_that_apigw_with_proxy_param_works(self): + json_body = {} + + self.event["body"] = json.dumps(json_body) + self.event["httpMethod"] = "GET" + + get_mock1 = mock.Mock(return_value="foo") + + self.lambda_handler.handle("get", path="/foo/")(get_mock1) # decorate mock + + self.event["path"] = "/v1/foo/" + self.event["pathParameters"] = { + "proxy": "foobar" + } + self.event["resource"] = "/foo/{proxy+}" + result1 = self.lambda_handler(self.event, self.context) + assert result1 == { + "body": '"foo"', + "statusCode": 200, + "headers": {}} + def test_that_no_path_specified_match_all(self): random.seed(time.mktime(datetime.now().timetuple())) @@ -303,7 +325,7 @@ def test_that_no_path_specified_match_all(self): r = range(1000) for i in range(10): # test with a non-deterministic path - self.event["path"] = "/foo/{}/".format(random.choice(r)) + self.event["resource"] = "/foo/{}/".format(random.choice(r)) result = self.lambda_handler(self.event, self.context) assert result == { "body": '"foo"', @@ -315,7 +337,7 @@ def test_exception_in_handler_should_be_reraised(self): json_body = {} self.event["body"] = json.dumps(json_body) self.event["httpMethod"] = "GET" - self.event["path"] = "/foo/bar" + self.event["resource"] = "/foo/bar" def divide_by_zero(_): return 1/0 From f7c6d95f6956031a731eaf45dd44defabfac9eae Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Wed, 11 Apr 2018 14:58:40 +0200 Subject: [PATCH 3/6] Cleanup, and added check for start of / in path. --- lambdarest/__init__.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lambdarest/__init__.py b/lambdarest/__init__.py index 95b4b40..bc77ad4 100644 --- a/lambdarest/__init__.py +++ b/lambdarest/__init__.py @@ -179,7 +179,8 @@ def inner(event, *args, **kwargs): json_data = { "body": json.loads(event.get("body") or "{}"), "query": __json_load_query( - event.get("queryStringParameters")) + event.get("queryStringParameters") + ) } event["json"] = json_data if schema: @@ -188,9 +189,18 @@ def inner(event, *args, **kwargs): return func(event, *args, **kwargs) # if this is a catch all url, make sure that it's setup correctly - target_path = path if path == '*': - target_path = "//" + target_path = "/*" + else: + target_path = path + + # replace the * with the werkzeug catch all path + if '*' in target_path: + target_path = target_path.replace('*', '') + + # make sure the path starts with / + if not target_path.startswith('/'): + raise ValueError("Please configure path with starting slash") # register http handler function rule = Rule(target_path, endpoint=inner, methods=[method_name.lower()]) From 3583f7167773bfc2cba3a88d325b43b379c85a89 Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Wed, 11 Apr 2018 15:23:23 +0200 Subject: [PATCH 4/6] bump version --- HISTORY.rst | 7 +++++++ lambdarest/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 60dd10e..5c2c437 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -50,3 +50,10 @@ First OSS release **features** - added Werkzeug path parameters + +4.1.0 (2018-04-11) ++++++++++++++++++++ + +**features** +- Added support for custom Api Gateway hostnames and basepaths +- Added support for {proxy+} paths diff --git a/lambdarest/__init__.py b/lambdarest/__init__.py index bc77ad4..7328878 100644 --- a/lambdarest/__init__.py +++ b/lambdarest/__init__.py @@ -3,7 +3,7 @@ __author__ = """sloev""" __email__ = 'jgv@trustpilot.com' -__version__ = '4.0.0' +__version__ = '4.1.0' import json diff --git a/setup.py b/setup.py index 06b6e1d..f208cf2 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ setup( name='lambdarest', - version='4.0.0', + version='4.1.0', description="pico framework for aws lambda with optional json schema validation", long_description=readme + '\n\n' + history, author="jgv", From d70e87b9fed585ca34209900d9e711b82e2673b2 Mon Sep 17 00:00:00 2001 From: Johannes Valbjorn Date: Thu, 12 Apr 2018 08:16:29 +0200 Subject: [PATCH 5/6] bump version write history --- HISTORY.rst | 6 ++++++ lambdarest/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 60dd10e..4d59816 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -50,3 +50,9 @@ First OSS release **features** - added Werkzeug path parameters + +4.1.0 (2018-04-12) ++++++++++++++++++++ + +**features** +- added support for custom domains diff --git a/lambdarest/__init__.py b/lambdarest/__init__.py index bc77ad4..7328878 100644 --- a/lambdarest/__init__.py +++ b/lambdarest/__init__.py @@ -3,7 +3,7 @@ __author__ = """sloev""" __email__ = 'jgv@trustpilot.com' -__version__ = '4.0.0' +__version__ = '4.1.0' import json diff --git a/setup.py b/setup.py index 06b6e1d..f208cf2 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ setup( name='lambdarest', - version='4.0.0', + version='4.1.0', description="pico framework for aws lambda with optional json schema validation", long_description=readme + '\n\n' + history, author="jgv", From 2d5342ea7b637b99e9a40009fd27dfa3228dc59e Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Thu, 12 Apr 2018 10:42:45 +0200 Subject: [PATCH 6/6] Fixed bad * path --- lambdarest/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambdarest/__init__.py b/lambdarest/__init__.py index 7328878..8dd472e 100644 --- a/lambdarest/__init__.py +++ b/lambdarest/__init__.py @@ -123,7 +123,7 @@ def inner_lambda_handler(event, context=None): func = rule.endpoint # if this is a catch-all rule, don't send any kwargs - if rule.rule == "//": + if rule.rule == "/": kwargs = {} except NotFound as e: logging.warning(logging_message.format(