[cloud] Improve snake <-> camel conversion for AWS utils (#31400)
Allow CamelCase version of snake_dict_to_camel_dict (currently only dromedaryCase is supported) Add reversible option to camel_dict_to_snake_dict Add tests for both of these options
This commit is contained in:
parent
2425374fdd
commit
15a58d498d
2 changed files with 69 additions and 19 deletions
|
@ -343,19 +343,26 @@ def ec2_connect(module):
|
|||
return ec2
|
||||
|
||||
|
||||
def _camel_to_snake(name):
|
||||
def _camel_to_snake(name, reversible=False):
|
||||
|
||||
def prepend_underscore_and_lower(m):
|
||||
return '_' + m.group(0).lower()
|
||||
|
||||
import re
|
||||
# Cope with pluralized abbreviations such as TargetGroupARNs
|
||||
# that would otherwise be rendered target_group_ar_ns
|
||||
plural_pattern = r'[A-Z]{3,}s$'
|
||||
s1 = re.sub(plural_pattern, prepend_underscore_and_lower, name)
|
||||
if reversible:
|
||||
upper_pattern = r'[A-Z]'
|
||||
else:
|
||||
# Cope with pluralized abbreviations such as TargetGroupARNs
|
||||
# that would otherwise be rendered target_group_ar_ns
|
||||
upper_pattern = r'[A-Z]{3,}s$'
|
||||
|
||||
s1 = re.sub(upper_pattern, prepend_underscore_and_lower, name)
|
||||
# Handle when there was nothing before the plural_pattern
|
||||
if s1.startswith("_") and not name.startswith("_"):
|
||||
s1 = s1[1:]
|
||||
if reversible:
|
||||
return s1
|
||||
|
||||
# Remainder of solution seems to be https://stackoverflow.com/a/1176023
|
||||
first_cap_pattern = r'(.)([A-Z][a-z]+)'
|
||||
all_cap_pattern = r'([a-z0-9])([A-Z]+)'
|
||||
|
@ -363,14 +370,22 @@ def _camel_to_snake(name):
|
|||
return re.sub(all_cap_pattern, r'\1_\2', s2).lower()
|
||||
|
||||
|
||||
def camel_dict_to_snake_dict(camel_dict):
|
||||
def camel_dict_to_snake_dict(camel_dict, reversible=False):
|
||||
"""
|
||||
reversible allows two way conversion of a camelized dict
|
||||
such that snake_dict_to_camel_dict(camel_dict_to_snake_dict(x)) == x
|
||||
|
||||
This is achieved through mapping e.g. HTTPEndpoint to h_t_t_p_endpoint
|
||||
where the default would be simply http_endpoint, which gets turned into
|
||||
HttpEndpoint if recamelized.
|
||||
"""
|
||||
|
||||
def value_is_list(camel_list):
|
||||
|
||||
checked_list = []
|
||||
for item in camel_list:
|
||||
if isinstance(item, dict):
|
||||
checked_list.append(camel_dict_to_snake_dict(item))
|
||||
checked_list.append(camel_dict_to_snake_dict(item, reversible))
|
||||
elif isinstance(item, list):
|
||||
checked_list.append(value_is_list(item))
|
||||
else:
|
||||
|
@ -381,35 +396,44 @@ def camel_dict_to_snake_dict(camel_dict):
|
|||
snake_dict = {}
|
||||
for k, v in camel_dict.items():
|
||||
if isinstance(v, dict):
|
||||
snake_dict[_camel_to_snake(k)] = camel_dict_to_snake_dict(v)
|
||||
snake_dict[_camel_to_snake(k, reversible=reversible)] = camel_dict_to_snake_dict(v, reversible)
|
||||
elif isinstance(v, list):
|
||||
snake_dict[_camel_to_snake(k)] = value_is_list(v)
|
||||
snake_dict[_camel_to_snake(k, reversible=reversible)] = value_is_list(v)
|
||||
else:
|
||||
snake_dict[_camel_to_snake(k)] = v
|
||||
snake_dict[_camel_to_snake(k, reversible=reversible)] = v
|
||||
|
||||
return snake_dict
|
||||
|
||||
|
||||
def snake_dict_to_camel_dict(snake_dict):
|
||||
def _snake_to_camel(snake, capitalize_first=False):
|
||||
if capitalize_first:
|
||||
return ''.join(x.capitalize() or '_' for x in snake.split('_'))
|
||||
else:
|
||||
return snake.split('_')[0] + ''.join(x.capitalize() or '_' for x in snake.split('_')[1:])
|
||||
|
||||
def camelize(complex_type):
|
||||
|
||||
def snake_dict_to_camel_dict(snake_dict, capitalize_first=False):
|
||||
"""
|
||||
Perhaps unexpectedly, snake_dict_to_camel_dict returns dromedaryCase
|
||||
rather than true CamelCase. Passing capitalize_first=True returns
|
||||
CamelCase. The default remains False as that was the original implementation
|
||||
"""
|
||||
|
||||
def camelize(complex_type, capitalize_first=False):
|
||||
if complex_type is None:
|
||||
return
|
||||
new_type = type(complex_type)()
|
||||
if isinstance(complex_type, dict):
|
||||
for key in complex_type:
|
||||
new_type[camel(key)] = camelize(complex_type[key])
|
||||
new_type[_snake_to_camel(key, capitalize_first)] = camelize(complex_type[key], capitalize_first)
|
||||
elif isinstance(complex_type, list):
|
||||
for i in range(len(complex_type)):
|
||||
new_type.append(camelize(complex_type[i]))
|
||||
new_type.append(camelize(complex_type[i], capitalize_first))
|
||||
else:
|
||||
return complex_type
|
||||
return new_type
|
||||
|
||||
def camel(words):
|
||||
return words.split('_')[0] + ''.join(x.capitalize() or '_' for x in words.split('_')[1:])
|
||||
|
||||
return camelize(snake_dict)
|
||||
return camelize(snake_dict, capitalize_first)
|
||||
|
||||
|
||||
def ansible_dict_to_boto3_filter_list(filters_dict):
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ansible.compat.tests import unittest
|
||||
from ansible.module_utils.ec2 import _camel_to_snake
|
||||
from ansible.module_utils.ec2 import _camel_to_snake, _snake_to_camel
|
||||
|
||||
EXPECTED_SNAKIFICATION = {
|
||||
'alllower': 'alllower',
|
||||
|
@ -29,9 +29,35 @@ EXPECTED_SNAKIFICATION = {
|
|||
'PLURALs': 'plurals'
|
||||
}
|
||||
|
||||
EXPECTED_REVERSIBLE = {
|
||||
'TwoWords': 'two_words',
|
||||
'AllUpperAtEND': 'all_upper_at_e_n_d',
|
||||
'AllUpperButPLURALs': 'all_upper_but_p_l_u_r_a_ls',
|
||||
'TargetGroupARNs': 'target_group_a_r_ns',
|
||||
'HTTPEndpoints': 'h_t_t_p_endpoints',
|
||||
'PLURALs': 'p_l_u_r_a_ls'
|
||||
}
|
||||
|
||||
|
||||
class CamelToSnakeTestCase(unittest.TestCase):
|
||||
|
||||
def test_camel_to_snake(self):
|
||||
for (k, v) in EXPECTED_SNAKIFICATION.items():
|
||||
self.assertEqual(_camel_to_snake(k), v)
|
||||
|
||||
def test_reversible_camel_to_snake(self):
|
||||
for (k, v) in EXPECTED_REVERSIBLE.items():
|
||||
self.assertEqual(_camel_to_snake(k, reversible=True), v)
|
||||
|
||||
|
||||
class SnakeToCamelTestCase(unittest.TestCase):
|
||||
|
||||
def test_snake_to_camel_reversed(self):
|
||||
for (k, v) in EXPECTED_REVERSIBLE.items():
|
||||
self.assertEqual(_snake_to_camel(v, capitalize_first=True), k)
|
||||
|
||||
|
||||
class CamelToSnakeAndBackTestCase(unittest.TestCase):
|
||||
def test_camel_to_snake_and_back(self):
|
||||
for (k, v) in EXPECTED_REVERSIBLE.items():
|
||||
self.assertEqual(_snake_to_camel(_camel_to_snake(k, reversible=True), capitalize_first=True), k)
|
||||
|
|
Loading…
Reference in a new issue