From 3ac30c2809619de67414b66db436b1246156cb49 Mon Sep 17 00:00:00 2001 From: kerrirapes Date: Sat, 16 Feb 2019 09:18:48 -0300 Subject: [PATCH 1/5] Feature: Create feature load_json_queryStringParameters First commit for this feature --- lambda_decorators.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/lambda_decorators.py b/lambda_decorators.py index f98a9b7..c9b16df 100644 --- a/lambda_decorators.py +++ b/lambda_decorators.py @@ -489,6 +489,41 @@ def wrapper(event, context): return wrapper +def load_json_queryStringParameters(handler): + """ + Automatically deserialize event queryStringParameters with json.loads. + + Automatically returns a 400 BAD REQUEST if there is an error while parsing. + + Usage:: + + >>> from lambda_decorators import load_json_queryStringParameters + >>> @load_json_queryStringParameters + ... def handler(event, context): + ... return event['queryStringParameters']['foo'] + >>> handler({'queryStringParameters': '{"foo": "bar"}'}, object()) + 'bar' + + note that ``event['body']`` is already a dictionary and didn't have to + explicitly be parsed. + """ + @wraps(handler) + def wrapper(event, context): + if isinstance(event.get('queryStringParameters'), str): + try: + event['queryStringParameters'] = json.loads(event['queryStringParameters']) + if isinstance(event.get('queryStringParameters'), dict): + for key in event['queryStringParameters'].keys(): + if isinstance(event['queryStringParameters'].get(key), str): + event['queryStringParameters'][key] = json.loads(event['queryStringParameters'][key]) + + except: + return {'statusCode': 400, 'body': 'BAD REQUEST'} + return handler(event, context) + + return wrapper + + def json_schema_validator(request_schema=None, response_schema=None): """ Validate your request & response payloads against a JSONSchema. From 04f6fe05ea6d5383573b00c00c5654088526765e Mon Sep 17 00:00:00 2001 From: kerrirapes Date: Sat, 16 Feb 2019 11:42:49 -0300 Subject: [PATCH 2/5] Feature: Add functionality to evaluate sub-components --- .gitignore | 27 --------------------------- lambda_decorators.py | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 28 insertions(+), 35 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index a8ed8c9..0000000 --- a/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg -*.pyc - -# sphinx -_build - -# Serverless directories -.serverless - -# node js -node_modules diff --git a/lambda_decorators.py b/lambda_decorators.py index c9b16df..5cfb677 100644 --- a/lambda_decorators.py +++ b/lambda_decorators.py @@ -144,6 +144,7 @@ def handler(event, context): import json import logging +import ast import boto3 from functools import wraps, update_wrapper @@ -509,16 +510,35 @@ def load_json_queryStringParameters(handler): """ @wraps(handler) def wrapper(event, context): + isObject = lambda x: type(x) in [list, dict, tuple] + + def evaluate_items(obj): + def seq(obj): + if isinstance(obj, dict): + keys = obj.keys() + return zip([key for key in keys], [obj[key] for key in keys]) + else: + return enumerate(obj) + + for i, value in seq(obj): + try: + obj[i] = ast.literal_eval(value) + except ValueError as e: + print(e, obj[i]) + pass + if isObject(obj[i]): + obj[i] = evaluate_items(obj[i]) + return obj + if isinstance(event.get('queryStringParameters'), str): try: - event['queryStringParameters'] = json.loads(event['queryStringParameters']) - if isinstance(event.get('queryStringParameters'), dict): - for key in event['queryStringParameters'].keys(): - if isinstance(event['queryStringParameters'].get(key), str): - event['queryStringParameters'][key] = json.loads(event['queryStringParameters'][key]) - - except: - return {'statusCode': 400, 'body': 'BAD REQUEST'} + event['queryStringParameters'] = ast.literal_eval(event['queryStringParameters']) + if isObject(event['queryStringParameters']): + event['queryStringParameters'] = evaluate_items(event['queryStringParameters']) + + + except Exception as exception: + return {'statusCode': 400, 'body': str(exception) } return handler(event, context) return wrapper From 4ff81640e6cea621fb9c6576a08dea2b887c28f8 Mon Sep 17 00:00:00 2001 From: kerrirapes Date: Sat, 16 Feb 2019 12:23:21 -0300 Subject: [PATCH 3/5] Bug: Add support in evaluate_items for tuples Added support to assign values to tuples by converting to list and then returning to tuple --- lambda_decorators.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lambda_decorators.py b/lambda_decorators.py index 5cfb677..b6a9072 100644 --- a/lambda_decorators.py +++ b/lambda_decorators.py @@ -511,7 +511,7 @@ def load_json_queryStringParameters(handler): @wraps(handler) def wrapper(event, context): isObject = lambda x: type(x) in [list, dict, tuple] - + def evaluate_items(obj): def seq(obj): if isinstance(obj, dict): @@ -520,14 +520,17 @@ def seq(obj): else: return enumerate(obj) + tuple_flag = isinstance(obj, tuple) + obj = list(obj) if tuple_flag else obj for i, value in seq(obj): try: obj[i] = ast.literal_eval(value) except ValueError as e: - print(e, obj[i]) pass if isObject(obj[i]): obj[i] = evaluate_items(obj[i]) + obj = tuple(obj) if tuple_flag else obj + return obj if isinstance(event.get('queryStringParameters'), str): From 3445e087715ee90e1a940fc6f241ef8b81cab1a0 Mon Sep 17 00:00:00 2001 From: kerrirapes Date: Sat, 16 Feb 2019 12:24:32 -0300 Subject: [PATCH 4/5] Test: Add test cases --- tests/test_load_json_queryStringParameters.py | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 tests/test_load_json_queryStringParameters.py diff --git a/tests/test_load_json_queryStringParameters.py b/tests/test_load_json_queryStringParameters.py new file mode 100644 index 0000000..3a2cdfa --- /dev/null +++ b/tests/test_load_json_queryStringParameters.py @@ -0,0 +1,80 @@ +from mock import MagicMock + +from lambda_decorators import load_json_queryStringParameters + + +def test_queryStringParameters_empty(): + @load_json_queryStringParameters + def hello(example, context): + return example + assert hello({}, MagicMock()) == {} + + +def test_queryStringParameters_no_parameters(): + @load_json_queryStringParameters + def hello(example, context): + return example + + query_parameters = '{}' + result = {} + assert hello({'queryStringParameters': query_parameters}, + MagicMock()) == {'queryStringParameters': result} + + +def test_queryStringParameters_1level_str(): + @load_json_queryStringParameters + def hello(example, context): + return example + + query_parameters = '{"foo": "bar"}' + result = {"foo": "bar"} + assert hello({'queryStringParameters': query_parameters}, + MagicMock()) == {'queryStringParameters': result} + + + +def test_queryStringParameters_1level_list(): + @load_json_queryStringParameters + def hello(example, context): + return example + + query_parameters = '{"foo": ' + str(["bar1", "bar2"]) + '}' + result = {"foo": ["bar1", "bar2"]} + assert hello({'queryStringParameters': query_parameters}, + MagicMock()) == {'queryStringParameters': result} + + +def test_queryStringParameters_2level_dict(): + @load_json_queryStringParameters + def hello(example, context): + return example + + query_parameters = '{"foo": {"bar1": "baz1", "bar2": "baz2"}}' + result = {"foo": {"bar1": "baz1", "bar2": "baz2"}} + assert hello({'queryStringParameters': query_parameters}, + MagicMock()) == {'queryStringParameters': result} + +def test_queryStringParameters_2level_list(): + @load_json_queryStringParameters + def hello(example, context): + return example + + query_parameters = '{"foo": {"bar1": ' + str(["(1.0)", "(1, 2)"]) + ', "bar2": "baz2"}}' + result = {"foo": {"bar1": [(1.0), (1, 2)], "bar2": "baz2"}} + assert hello({'queryStringParameters': query_parameters}, + MagicMock()) == {'queryStringParameters': result} + + +def test_queryStringParameters_2level_None_bool(): + @load_json_queryStringParameters + def hello(example, context): + return example + + query_parameters = '{"foo": {"bar1": ' + str(["(1.0)", "(1, 2)"]) + ', "bar2": ' + str(("None", "True")) + '}}' + result = {"foo": {"bar1": [(1.0), (1, 2)], "bar2": (None, True)}} + assert hello({'queryStringParameters': query_parameters}, + MagicMock()) == {'queryStringParameters': result} + + + + From 3a37647e4bc0c20a19bda4a9aa7917ab4c24c9c1 Mon Sep 17 00:00:00 2001 From: kerrirapes Date: Sat, 16 Feb 2019 12:39:56 -0300 Subject: [PATCH 5/5] Documentation: Add documention to the .py and README --- README.rst | 1 + lambda_decorators.py | 15 +++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 2efe285..33f3f33 100644 --- a/README.rst +++ b/README.rst @@ -57,6 +57,7 @@ for common usecases when using AWS Lambda with Python. * `cors_headers `_ - automatic injection of CORS headers * `dump_json_body `_ - auto-serialization of http body to JSON * `load_json_body `_ - auto-deserialize of http body from JSON +* `load_json_queryStringParameters ` - auto-deserialize of http queryStringParameters from JSON * `json_http_resp `_ - automatic serialization of python object to HTTP JSON response * `json_schema_validator `_ - use JSONSchema to validate request&response payloads * `load_urlencoded_body `_ - auto-deserialize of http body from a querystring encoded body diff --git a/lambda_decorators.py b/lambda_decorators.py index b6a9072..4fef047 100644 --- a/lambda_decorators.py +++ b/lambda_decorators.py @@ -58,6 +58,7 @@ def handler(event, context): * :func:`cors_headers` - automatic injection of CORS headers * :func:`dump_json_body` - auto-serialization of http body to JSON * :func:`load_json_body` - auto-deserialize of http body from JSON +* :func:`load_json_queryStringParameters` - auto-deserialize of http queryStringParameters from JSON * :func:`json_http_resp` - automatic serialization of python object to HTTP JSON response * :func:`json_schema_validator` - use JSONSchema to validate request&response payloads * :func:`load_urlencoded_body` - auto-deserialize of http body from a querystring encoded body @@ -492,21 +493,19 @@ def wrapper(event, context): def load_json_queryStringParameters(handler): """ - Automatically deserialize event queryStringParameters with json.loads. + Automatically deserialize event queryStringParameters with ast.literal_eval - Automatically returns a 400 BAD REQUEST if there is an error while parsing. + Automatically returns a 400 if there is an error while parsing. Usage:: >>> from lambda_decorators import load_json_queryStringParameters >>> @load_json_queryStringParameters ... def handler(event, context): - ... return event['queryStringParameters']['foo'] - >>> handler({'queryStringParameters': '{"foo": "bar"}'}, object()) - 'bar' + ... return event['queryStringParameters'] + >>> handler({'queryStringParameters': '{"foo": ["bar1", "None"]'}, object()) + {"foo": ["bar1", None]} - note that ``event['body']`` is already a dictionary and didn't have to - explicitly be parsed. """ @wraps(handler) def wrapper(event, context): @@ -530,7 +529,7 @@ def seq(obj): if isObject(obj[i]): obj[i] = evaluate_items(obj[i]) obj = tuple(obj) if tuple_flag else obj - + return obj if isinstance(event.get('queryStringParameters'), str):