-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjmespath_custom_functions.py
More file actions
117 lines (82 loc) · 3.67 KB
/
jmespath_custom_functions.py
File metadata and controls
117 lines (82 loc) · 3.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import re
from typing import Dict, List, Union
from jmespath import exceptions, functions
class CustomFunctions(functions.Functions):
"""JMESPath custom functions.
Along with standard `Built-in JMESPath Functions<https://jmespath.org/specification.html#built-in-functions>`_
the following custom functions are added.
- ``pyregex(expression: str, string: str) -> Union[str, None]``
- Uses python's built-in ``re.fullmatch`` on the given ``string`` with the regex ``expression``
- Returns the string if a full match is found or else None.
- ``pyregex_group(expression: str, string: str, group: int) -> Union[str, None]``
- Uses python's built-in ``re.fullmatch`` on the given set of ``strings`` with the regex ``expression``
- Then pulls the given regex ``group`` number from the expression
- Returns the matching regex group if found, or else None.
- ``lower(string: str) -> str``
- Convert string to lowercase.
- ``upper(string: str) -> str``
- Convert string to uppercase.
There is also a self regulating regex cache that is added to this class.
Because of this, **instances of this class are not thread safe** .
Parameters
----------
regex_cache_size : int, optional
Max number of compiled regex patterns to cache, by default 10000
"""
def __init__(self, regex_cache_size: int = 10000):
super().__init__()
self._regex_cache_size = regex_cache_size
self._regex_cache_count = 0
self._regex_cache: Dict[str, re.Pattern]= {}
def _get_regex(self, expression: str) -> re.Pattern:
if expression not in self._regex_cache:
self._regex_cache[expression] = re.compile(expression)
self._regex_cache_count += 1
if self._regex_cache_count > self._regex_cache_size:
# if cache is full, remove the oldest entries
for _ in range(self._regex_cache_count - self._regex_cache_size):
self._regex_cache.pop(next(iter(self._regex_cache)))
self._regex_cache_count -= 1
return self._regex_cache[expression]
@functions.signature(
{"types": ["string"]},
{"types": ["string"]}
)
def _func_pyregex(self, expression: str, string: str) -> Union[str, None]:
if self._get_regex(expression).fullmatch(string) is not None:
return string
return None
@functions.signature(
{"types": ["string"]},
{"types": ["string"]},
{"types": ["number"]}
)
def _func_pyregex_group(
self,
expression: str,
string: str,
group: int
) -> Union[str, None]:
if type(group) != int:
raise exceptions.JMESPathError(
f"In function pyregex_group, type of 'group' was {type(group)} but the input must translate to an integer."
)
if group < 1:
raise exceptions.JMESPathError("In function pyregex_group, value of 'group' was {} but the input must be greater than 0.")
group_match = None
re_match = self._get_regex(expression).fullmatch(string)
if re_match is not None:
re_groups = re_match.groups()
if group <= len(re_groups):
group_match = re_groups[group - 1]
return group_match
@functions.signature(
{"types": ["string"]}
)
def _func_lower(self, string: str) -> str:
return string.lower()
@functions.signature(
{"types": ["string"]}
)
def _func_upper(self, string: str) -> str:
return string.upper()