Skip to content

Commit e280590

Browse files
authored
Merge pull request #105 from gridsmartercities/transform
Added ability to transform an extracted value before validating it.
2 parents cf6899e + 88960b6 commit e280590

File tree

4 files changed

+135
-2
lines changed

4 files changed

+135
-2
lines changed

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,49 @@ def extract_dictionary_example(a_dictionary, **kwargs):
242242
"""
243243
return kwargs # returns {'my_param_1': 'Hello!', 'my_param_2': 'Bye!'}
244244

245+
```
246+
247+
You can apply a transformation to an extracted value. The transformation will happen before validation.
248+
249+
Example:
250+
```python
251+
@extract(parameters=[
252+
Parameter(path='/params/my_param', func_param_name='a_dictionary', transform=int) # extracts a non mandatory my_param from a_dictionary
253+
])
254+
def extract_with_transform_example(a_dictionary, my_param=None):
255+
"""
256+
a_dictionary = {
257+
'params': {
258+
'my_param_1': '2' # the original value is the string '2'
259+
}
260+
}
261+
"""
262+
return my_param # returns the int value 2
263+
264+
```
265+
266+
The transform function can be any function, with its own error handling.
267+
268+
Example:
269+
```python
270+
271+
def to_int(arg):
272+
try:
273+
return int(arg)
274+
except Exception:
275+
raise Exception("My custom error message")
276+
277+
@extract(parameters=[
278+
Parameter(path='/params/my_param', func_param_name='a_dictionary', transform=to_int) # extracts a non mandatory my_param from a_dictionary
279+
])
280+
def extract_with_custom_transform_example(a_dictionary, my_param=None):
281+
return {}
282+
283+
response = extract_with_custom_transform_example({'params': {'my_param': 'abc'}})
284+
285+
print(response) # prints {'statusCode': 400, 'body': '{"message": "Error extracting parameters"}'}, and the logs will contain the "My custom error message" message.
286+
287+
245288
```
246289

247290
### extract_from_event

aws_lambda_decorators/classes.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def validate(self, value, group_errors):
130130
class Parameter(ValidatedParameter, BaseParameter):
131131
"""Class used to encapsulate the extract methods parameter data."""
132132

133-
def __init__(self, path="", func_param_name=None, validators=None, var_name=None, default=None): # noqa: pylint - too-many-arguments
133+
def __init__(self, path="", func_param_name=None, validators=None, var_name=None, default=None, transform=None): # noqa: pylint - too-many-arguments
134134
"""
135135
Sets the private variables of the Parameter object.
136136
@@ -154,9 +154,11 @@ def fun(event, context). To extract from context func_param_name has to be "cont
154154
value is the last element of the path (e.g. "c" in the case above)
155155
default (any): Optional, a default value if the value is missing and not mandatory.
156156
The default value is None
157+
transform (function): Optional, a function to apply to the extracted value before checking validation rules.
157158
"""
158159
self._path = path
159160
self._default = default
161+
self._transform = transform
160162
ValidatedParameter.__init__(self, func_param_name, validators)
161163
BaseParameter.__init__(self, var_name)
162164

@@ -187,6 +189,9 @@ def extract_value(self, dict_value):
187189
if not self._name:
188190
self._name = real_key
189191

192+
if dict_value and self._transform:
193+
dict_value = self._transform(dict_value)
194+
190195
return dict_value
191196

192197
def validate_path(self, value, group_errors=False):

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
LONG_DESCRIPTION = open("README.md").read()
44

55
setup(name="aws-lambda-decorators",
6-
version="0.44",
6+
version="0.45",
77
description="A set of python decorators to simplify aws python lambda development",
88
long_description=LONG_DESCRIPTION,
99
long_description_content_type="text/markdown",

tests/test_decorators.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1679,6 +1679,91 @@ def handler(event, a=None): # noqa: pylint - unused-argument
16791679
response = handler(event)
16801680
self.assertEqual(None, response)
16811681

1682+
def test_can_apply_transformation(self):
1683+
event = {
1684+
"a": "2"
1685+
}
1686+
1687+
@extract([Parameter("/a", "event", transform=float)])
1688+
def handler(event, a=None): # noqa: pylint - unused-argument
1689+
return a
1690+
1691+
response = handler(event)
1692+
self.assertEqual(2, response)
1693+
1694+
def test_apply_transformation_on_none_value(self):
1695+
event = {
1696+
"a": None
1697+
}
1698+
1699+
@extract([Parameter("/a", "event", transform=float)])
1700+
def handler(event, a=None): # noqa: pylint - unused-argument
1701+
return a
1702+
1703+
response = handler(event)
1704+
self.assertEqual(None, response)
1705+
1706+
def test_apply_custom_transformation(self):
1707+
event = {
1708+
"a": "2"
1709+
}
1710+
1711+
def to_float(arg):
1712+
return float(arg)
1713+
1714+
@extract([Parameter("/a", "event", transform=to_float)])
1715+
def handler(event, a=None): # noqa: pylint - unused-argument
1716+
return a
1717+
1718+
response = handler(event)
1719+
self.assertEqual(2, response)
1720+
1721+
@patch("aws_lambda_decorators.decorators.LOGGER")
1722+
def test_apply_custom_transformation_with_error_handling(self, mock_logger):
1723+
event = {
1724+
"a": "abc"
1725+
}
1726+
1727+
def to_float(arg):
1728+
try:
1729+
return float(arg)
1730+
except Exception:
1731+
raise Exception(f"Custom error message: value '{arg}' cannot be converted to float")
1732+
1733+
@extract([Parameter("/a", "event", transform=to_float)])
1734+
def handler(event, a=None): # noqa: pylint - unused-argument
1735+
return {}
1736+
1737+
response = handler(event)
1738+
self.assertEqual(400, response["statusCode"])
1739+
self.assertEqual("{\"message\": \"Error extracting parameters\"}", response["body"])
1740+
1741+
mock_logger.error.assert_called_once_with("%s: %s in argument %s for path %s",
1742+
"Exception",
1743+
"Custom error message: value 'abc' cannot be converted to float",
1744+
"event",
1745+
"/a")
1746+
1747+
@patch("aws_lambda_decorators.decorators.LOGGER")
1748+
def test_apply_invalid_transformation_raises_error(self, mock_logger):
1749+
event = {
1750+
"a": "abc"
1751+
}
1752+
1753+
@extract([Parameter("/a", "event", transform=float)])
1754+
def handler(event, a=None): # noqa: pylint - unused-argument
1755+
return {}
1756+
1757+
response = handler(event)
1758+
self.assertEqual(400, response["statusCode"])
1759+
self.assertEqual("{\"message\": \"Error extracting parameters\"}", response["body"])
1760+
1761+
mock_logger.error.assert_called_once_with("%s: %s in argument %s for path %s",
1762+
"ValueError",
1763+
"could not convert string to float: 'abc'",
1764+
"event",
1765+
"/a")
1766+
16821767

16831768
class IsolatedDecoderTests(unittest.TestCase):
16841769
# Tests have been named so they run in a specific order

0 commit comments

Comments
 (0)