moto/moto/emr/utils.py

221 lines
6.8 KiB
Python

from __future__ import unicode_literals
import random
import re
import string
from moto.core.utils import camelcase_to_underscores
import six
def random_id(size=13):
chars = list(range(10)) + list(string.ascii_uppercase)
return "".join(six.text_type(random.choice(chars)) for x in range(size))
def random_cluster_id(size=13):
return "j-{0}".format(random_id())
def random_step_id(size=13):
return "s-{0}".format(random_id())
def random_instance_group_id(size=13):
return "i-{0}".format(random_id())
def steps_from_query_string(querystring_dict):
steps = []
for step in querystring_dict:
step["jar"] = step.pop("hadoop_jar_step._jar")
step["properties"] = dict(
(o["Key"], o["Value"]) for o in step.get("properties", [])
)
step["args"] = []
idx = 1
keyfmt = "hadoop_jar_step._args.member.{0}"
while keyfmt.format(idx) in step:
step["args"].append(step.pop(keyfmt.format(idx)))
idx += 1
steps.append(step)
return steps
class Unflattener:
@staticmethod
def unflatten_complex_params(input_dict, param_name):
"""Function to unflatten (portions of) dicts with complex keys. The moto request parser flattens the incoming
request bodies, which is generally helpful, but for nested dicts/lists can result in a hard-to-manage
parameter exposion. This function allows one to selectively unflatten a set of dict keys, replacing them
with a deep dist/list structure named identically to the root component in the complex name.
Complex keys are composed of multiple components
separated by periods. Components may be prefixed with _, which is stripped. Lists indexes are represented
with two components, 'member' and the index number."""
items_to_process = {}
for k in input_dict.keys():
if k.startswith(param_name):
items_to_process[k] = input_dict[k]
if len(items_to_process) == 0:
return
for k in items_to_process.keys():
del input_dict[k]
for k in items_to_process.keys():
Unflattener._set_deep(k, input_dict, items_to_process[k])
@staticmethod
def _set_deep(complex_key, container, value):
keys = complex_key.split(".")
keys.reverse()
while len(keys) > 0:
if len(keys) == 1:
key = keys.pop().strip("_")
Unflattener._add_to_container(container, key, value)
else:
key = keys.pop().strip("_")
if keys[-1] == "member":
keys.pop()
if not Unflattener._key_in_container(container, key):
container = Unflattener._add_to_container(container, key, [])
else:
container = Unflattener._get_child(container, key)
else:
if not Unflattener._key_in_container(container, key):
container = Unflattener._add_to_container(container, key, {})
else:
container = Unflattener._get_child(container, key)
@staticmethod
def _add_to_container(container, key, value):
if type(container) is dict:
container[key] = value
elif type(container) is list:
i = int(key)
while len(container) < i:
container.append(None)
container[i - 1] = value
return value
@staticmethod
def _get_child(container, key):
if type(container) is dict:
return container[key]
elif type(container) is list:
i = int(key)
return container[i - 1]
@staticmethod
def _key_in_container(container, key):
if type(container) is dict:
return key in container
elif type(container) is list:
i = int(key)
return len(container) >= i
class CamelToUnderscoresWalker:
"""A class to convert the keys in dict/list hierarchical data structures from CamelCase to snake_case (underscores)"""
@staticmethod
def parse(x):
if isinstance(x, dict):
return CamelToUnderscoresWalker.parse_dict(x)
elif isinstance(x, list):
return CamelToUnderscoresWalker.parse_list(x)
else:
return CamelToUnderscoresWalker.parse_scalar(x)
@staticmethod
def parse_dict(x):
temp = {}
for key in x.keys():
temp[camelcase_to_underscores(key)] = CamelToUnderscoresWalker.parse(x[key])
return temp
@staticmethod
def parse_list(x):
temp = []
for i in x:
temp.append(CamelToUnderscoresWalker.parse(i))
return temp
@staticmethod
def parse_scalar(x):
return x
class ReleaseLabel(object):
version_re = re.compile(r"^emr-(\d+)\.(\d+)\.(\d+)$")
def __init__(self, release_label):
major, minor, patch = self.parse(release_label)
self.major = major
self.minor = minor
self.patch = patch
@classmethod
def parse(cls, release_label):
if not release_label:
raise ValueError("Invalid empty ReleaseLabel: %r" % release_label)
match = cls.version_re.match(release_label)
if not match:
raise ValueError("Invalid ReleaseLabel: %r" % release_label)
major, minor, patch = match.groups()
major = int(major)
minor = int(minor)
patch = int(patch)
return major, minor, patch
def __str__(self):
version = "emr-%d.%d.%d" % (self.major, self.minor, self.patch)
return version
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, str(self))
def __iter__(self):
return iter((self.major, self.minor, self.patch))
def __eq__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return (
self.major == other.major
and self.minor == other.minor
and self.patch == other.patch
)
def __ne__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return tuple(self) != tuple(other)
def __lt__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return tuple(self) < tuple(other)
def __le__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return tuple(self) <= tuple(other)
def __gt__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return tuple(self) > tuple(other)
def __ge__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return tuple(self) >= tuple(other)