utils.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. # Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
  2. #
  3. # Modifications made by Cloudera are:
  4. # Copyright (c) 2016 Cloudera, Inc. All rights reserved.
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License"). You
  7. # may not use this file except in compliance with the License. A copy of
  8. # the License is located at
  9. #
  10. # http://aws.amazon.com/apache2.0/
  11. #
  12. # or in the "license" file accompanying this file. This file is
  13. # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
  14. # ANY KIND, either express or implied. See the License for the specific
  15. # language governing permissions and limitations under the License.
  16. import contextlib
  17. import datetime
  18. import functools
  19. import re
  20. import signal
  21. from altuscli import LIST_TYPE
  22. from altuscli import OBJECT_TYPE
  23. from altuscli.compat import OrderedDict
  24. import dateutil.parser
  25. from dateutil.tz import tzlocal
  26. from dateutil.tz import tzutc
  27. # These are chars that do not need to be urlencoded based on rfc2986, section 2.3.
  28. SAFE_CHARS = '-._~'
  29. def get_service_module_name(service_model):
  30. name = service_model.service_name
  31. name = name.replace('Cloudera', '')
  32. name = name.replace('Altus', '')
  33. name = re.sub('\W+', '', name)
  34. return name
  35. def json_encoder(obj):
  36. """JSON encoder that formats datetimes as ISO8601 format."""
  37. if isinstance(obj, datetime.datetime):
  38. return obj.isoformat()
  39. else:
  40. return obj
  41. class CachedProperty(object):
  42. def __init__(self, fget):
  43. self._fget = fget
  44. def __get__(self, obj, cls):
  45. if obj is None:
  46. return self
  47. else:
  48. computed_value = self._fget(obj)
  49. obj.__dict__[self._fget.__name__] = computed_value
  50. return computed_value
  51. def instance_cache(func):
  52. """Method decorator for caching method calls to a single instance.
  53. **This is not a general purpose caching decorator.**
  54. In order to use this, you *must* provide an ``_instance_cache``
  55. attribute on the instance.
  56. This decorator is used to cache method calls. The cache is only
  57. scoped to a single instance though such that multiple instances
  58. will maintain their own cache. In order to keep things simple,
  59. this decorator requires that you provide an ``_instance_cache``
  60. attribute on your instance.
  61. """
  62. func_name = func.__name__
  63. @functools.wraps(func)
  64. def _cache_guard(self, *args, **kwargs):
  65. cache_key = (func_name, args)
  66. if kwargs:
  67. kwarg_items = tuple(sorted(kwargs.items()))
  68. cache_key = (func_name, args, kwarg_items)
  69. result = self._instance_cache.get(cache_key)
  70. if result is not None:
  71. return result
  72. result = func(self, *args, **kwargs)
  73. self._instance_cache[cache_key] = result
  74. return result
  75. return _cache_guard
  76. def parse_timestamp(value):
  77. """Parse a timestamp into a datetime object.
  78. Supported formats:
  79. * iso8601
  80. * rfc822
  81. * epoch (value is an integer)
  82. This will return a ``datetime.datetime`` object.
  83. """
  84. if isinstance(value, (int, float)):
  85. # Possibly an epoch time.
  86. return datetime.datetime.fromtimestamp(value, tzlocal())
  87. else:
  88. try:
  89. return datetime.datetime.fromtimestamp(float(value), tzlocal())
  90. except (TypeError, ValueError):
  91. pass
  92. try:
  93. return dateutil.parser.parse(value)
  94. except (TypeError, ValueError) as e:
  95. raise ValueError('Invalid timestamp "%s": %s' % (value, e))
  96. def parse_to_aware_datetime(value):
  97. """Converted the passed in value to a datetime object with tzinfo.
  98. This function can be used to normalize all timestamp inputs. This
  99. function accepts a number of different types of inputs, but
  100. will always return a datetime.datetime object with time zone
  101. information.
  102. The input param ``value`` can be one of several types:
  103. * A datetime object (both naive and aware)
  104. * An integer representing the epoch time (can also be a string
  105. of the integer, i.e '0', instead of 0). The epoch time is
  106. considered to be UTC.
  107. * An iso8601 formatted timestamp. This does not need to be
  108. a complete timestamp, it can contain just the date portion
  109. without the time component.
  110. The returned value will be a datetime object that will have tzinfo.
  111. If no timezone info was provided in the input value, then UTC is
  112. assumed, not local time.
  113. """
  114. # This is a general purpose method that handles several cases of
  115. # converting the provided value to a string timestamp suitable to be
  116. # serialized to an http request. It can handle:
  117. # 1) A datetime.datetime object.
  118. if isinstance(value, datetime.datetime):
  119. datetime_obj = value
  120. else:
  121. # 2) A string object that's formatted as a timestamp.
  122. # We document this as being an iso8601 timestamp, although
  123. # parse_timestamp is a bit more flexible.
  124. datetime_obj = parse_timestamp(value)
  125. if datetime_obj.tzinfo is None:
  126. # I think a case would be made that if no time zone is provided,
  127. # we should use the local time. However, to restore backwards
  128. # compat, the previous behavior was to assume UTC, which is
  129. # what we're going to do here.
  130. datetime_obj = datetime_obj.replace(tzinfo=tzutc())
  131. else:
  132. datetime_obj = datetime_obj.astimezone(tzutc())
  133. return datetime_obj
  134. @contextlib.contextmanager
  135. def ignore_ctrl_c():
  136. original = signal.signal(signal.SIGINT, signal.SIG_IGN)
  137. try:
  138. yield
  139. finally:
  140. signal.signal(signal.SIGINT, original)
  141. def datetime2timestamp(dt, default_timezone=None):
  142. """Calculate the timestamp based on the given datetime instance.
  143. :type dt: datetime
  144. :param dt: A datetime object to be converted into timestamp
  145. :type default_timezone: tzinfo
  146. :param default_timezone: If it is provided as None, we treat it as tzutc().
  147. But it is only used when dt is a naive datetime.
  148. :returns: The timestamp
  149. """
  150. epoch = datetime.datetime(1970, 1, 1)
  151. if dt.tzinfo is None:
  152. if default_timezone is None:
  153. default_timezone = tzutc()
  154. dt = dt.replace(tzinfo=default_timezone)
  155. d = dt.replace(tzinfo=None) - dt.utcoffset() - epoch
  156. if hasattr(d, "total_seconds"):
  157. return d.total_seconds() # Works in Python 2.7+
  158. return (d.microseconds + (d.seconds + d.days * 24 * 3600) * 10**6) / 10**6
  159. class ArgumentGenerator(object):
  160. """Generate sample input based on a shape model.
  161. This class contains a ``generate_skeleton`` method that will take
  162. an input shape (created from ``altuscli.model``) and generate
  163. a sample dictionary corresponding to the input shape.
  164. The specific values used are place holder values. For strings an
  165. empty string is used, for numbers 0 or 0.0 is used. For datetime a date in
  166. RFC822 is used. The intended usage of this class is to generate the *shape*
  167. of the input object. In the future we might take the defaults from the
  168. model.
  169. This can be useful for operations that have complex input shapes.
  170. This allows a user to just fill in the necessary data instead of
  171. worrying about the specific object of the input arguments.
  172. Example usage::
  173. clidriver = CLIDriver
  174. ddb = clidriver.get_service_model('dataeng')
  175. arg_gen = ArgumentGenerator()
  176. sample_input = arg_gen.generate_skeleton(
  177. ddb.operation_model('createCluster').input_shape)
  178. print("Sample input for dataeng.createCluster: %s" % sample_input)
  179. """
  180. def __init__(self):
  181. pass
  182. def generate_skeleton(self, shape):
  183. if shape.type_name == OBJECT_TYPE:
  184. return self._generate_type_object(shape)
  185. elif shape.type_name == LIST_TYPE:
  186. return self._generate_type_array(shape)
  187. elif shape.type_name == 'string':
  188. return ''
  189. elif shape.type_name in ['integer']:
  190. return 0
  191. elif shape.type_name == 'number':
  192. return 0.0
  193. elif shape.type_name == 'boolean':
  194. return True
  195. elif shape.type_name == 'datetime':
  196. return 'Wed, 02 Oct 2002 13:00:00 GMT'
  197. else:
  198. raise Exception("Unknown shape type: %s" % shape.type_name)
  199. def _generate_type_object(self, shape):
  200. skeleton = OrderedDict()
  201. for member_name, member_shape in shape.members.items():
  202. skeleton[member_name] = self.generate_skeleton(member_shape)
  203. return skeleton
  204. def _generate_type_array(self, shape):
  205. # For list elements we've arbitrarily decided to return the first
  206. # element for the skeleton list.
  207. return [
  208. self.generate_skeleton(shape.member),
  209. ]