Skip to content

Commit 9917a93

Browse files
committed
Functions now support optional arguments.
1 parent bbe7300 commit 9917a93

File tree

2 files changed

+87
-5
lines changed

2 files changed

+87
-5
lines changed

jmespath/functions.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,22 +81,28 @@ def call_function(self, function_name, resolved_args):
8181
return function(self, *resolved_args)
8282

8383
def _validate_arguments(self, args, signature, function_name):
84-
if signature and signature[-1].get('variadic'):
84+
required_arguments_count = len([param for param in signature if not param.get('optional') or not param['optional']])
85+
optional_arguments_count = len([param for param in signature if param.get('optional') and param['optional']])
86+
has_variadic = signature[-1].get('variadic') if signature != None else False
87+
if has_variadic:
8588
if len(args) < len(signature):
8689
raise exceptions.VariadictArityError(
8790
len(signature), len(args), function_name)
88-
elif len(args) != len(signature):
91+
elif optional_arguments_count > 0:
92+
if len(args) < required_arguments_count or len(args) > (required_arguments_count + optional_arguments_count):
93+
raise exceptions.ArityError(
94+
len(signature), len(args), function_name)
95+
elif len(args) != required_arguments_count:
8996
raise exceptions.ArityError(
9097
len(signature), len(args), function_name)
9198
return self._type_check(args, signature, function_name)
9299

93100
def _type_check(self, actual, signature, function_name):
94-
for i in range(len(signature)):
95-
allowed_types = signature[i]['types']
101+
for i in range(min(len(signature), len(actual))):
102+
allowed_types = self._get_allowed_types_from_signature(signature[i])
96103
if allowed_types:
97104
self._type_check_single(actual[i], allowed_types,
98105
function_name)
99-
100106
def _type_check_single(self, current, types, function_name):
101107
# Type checking involves checking the top level type,
102108
# and in the case of arrays, potentially checking the types
@@ -120,6 +126,13 @@ def _type_check_single(self, current, types, function_name):
120126
self._subtype_check(current, allowed_subtypes,
121127
types, function_name)
122128

129+
## signature supports monotype {'type': 'type-name'}
130+
## or multiple types {'types': ['type1-name', 'type2-name']}
131+
def _get_allowed_types_from_signature(self, spec):
132+
if spec.get('type'):
133+
spec.update({'types': [spec.get('type')]})
134+
return spec.get('types')
135+
123136
def _get_allowed_pytypes(self, types):
124137
allowed_types = []
125138
allowed_subtypes = []

tests/test_signatures.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from tests import unittest
2+
3+
from jmespath import compat
4+
from jmespath import exceptions
5+
from jmespath import functions
6+
7+
class TestFunctionSignatures(unittest.TestCase):
8+
9+
def setUp(self):
10+
self._functions = functions.Functions()
11+
12+
def test_signature_with_monotype_argument(self):
13+
(function_name, signature) = self._make_test("_function_with_monotyped_arguments")
14+
self._functions._validate_arguments(['string'], signature, function_name)
15+
self.assertRaises(
16+
exceptions.ArityError, lambda :
17+
self._functions._validate_arguments([], signature, function_name)
18+
)
19+
20+
def test_signature_with_optional_arguments(self):
21+
(function_name, signature) = self._make_test("_function_with_optional_arguments")
22+
self._functions._validate_arguments(['string'], signature, function_name)
23+
self._functions._validate_arguments(['string', 42], signature, function_name)
24+
self._functions._validate_arguments(['string', 43], signature, function_name)
25+
self.assertRaises(
26+
exceptions.ArityError, lambda :
27+
self._functions._validate_arguments([], signature, function_name)
28+
)
29+
self.assertRaises(
30+
exceptions.ArityError, lambda :
31+
self._functions._validate_arguments(['string', 42, 43, 44], signature, function_name)
32+
)
33+
34+
def test_signature_with_variadic_arguments(self):
35+
(function_name, signature) = self._make_test("_function_with_variadic_arguments")
36+
self._functions._validate_arguments(['string', 'text1'], signature, function_name)
37+
self._functions._validate_arguments(['string', 'text1', 'text2'], signature, function_name)
38+
self.assertRaises(
39+
exceptions.VariadictArityError, lambda :
40+
self._functions._validate_arguments(['string'], signature, function_name)
41+
)
42+
43+
def _make_test(self, funcName):
44+
for name, method in compat.get_methods(TestFunctionSignatures):
45+
print(name)
46+
if name != funcName:
47+
continue
48+
signature = getattr(method, 'signature', None)
49+
return (funcName, signature)
50+
return None
51+
52+
## arg1 can only be of type 'string'
53+
## this signature supports testing simplified syntax
54+
## where 'type' is a string instead of an array of strings
55+
@functions.signature({'type': 'string'})
56+
def _function_with_monotyped_arguments(self, arg1):
57+
return None
58+
59+
@functions.signature({'type': 'string'}, {'type': 'string', 'variadic': True})
60+
def _function_with_variadic_arguments(self, arg1, *arguments):
61+
return None
62+
63+
@functions.signature({'type': 'string'}, {'type': 'number', 'optional': True}, {'type': 'number', 'optional': True})
64+
def _function_with_optional_arguments(self, arg1, opt1, opt2):
65+
return None
66+
67+
68+
if __name__ == '__main__':
69+
unittest.main()

0 commit comments

Comments
 (0)