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/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 e6ddce8..8dd472e 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 @@ -13,7 +13,7 @@ __validate_kwargs = {"format_checker": FormatChecker()} -__required_keys = ["httpMethod", "path"] +__required_keys = ["httpMethod", "resource"] class Response(object): @@ -104,7 +104,13 @@ 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() + + # 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 kwargs = {} @@ -117,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( @@ -173,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: @@ -182,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()]) 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", diff --git a/tests/test_lambdarest.py b/tests/test_lambdarest.py index 2d223af..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,20 +257,60 @@ 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"', "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_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())) @@ -285,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"', @@ -297,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