1 # Licensed to the Apache Software Foundation (ASF) under one or more
2 # contributor license agreements. See the NOTICE file distributed with
3 # this work for additional information regarding copyright ownership.
4 # The ASF licenses this file to You under the Apache License, Version 2.0
5 # (the "License"); you may not use this file except in compliance with
6 # the License. You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
17 from datetime import (datetime, tzinfo, timedelta)
19 from functools import total_ordering
21 from total_ordering import total_ordering
23 from aria.parser import implements_specification
24 from aria.utils.collections import (StrictDict, OrderedDict)
25 from aria.utils.formatting import safe_repr
27 from .modeling.data_types import (coerce_to_data_type_class, report_issue_for_bad_format,
31 class Timezone(tzinfo):
33 Timezone as fixed offset in hours and minutes east of UTC.
36 def __init__(self, hours=0, minutes=0):
37 super(Timezone, self).__init__()
38 self._offset = timedelta(hours=hours, minutes=minutes)
40 def utcoffset(self, dt): # pylint: disable=unused-argument
43 def tzname(self, dt): # pylint: disable=unused-argument
44 return str(self._offset)
46 def dst(self, dt): # pylint: disable=unused-argument
56 @implements_specification('timestamp', 'yaml-1.1')
57 class Timestamp(object):
59 TOSCA timestamps follow the YAML specification, which in turn is a variant of ISO8601.
61 Long forms and short forms (without time of day and assuming UTC timezone) are supported for
62 parsing. The canonical form (for rendering) matches the long form at the UTC timezone.
64 See the `Timestamp Language-Independent Type for YAML Version 1.1 (Working Draft 2005-01-18)
65 <http://yaml.org/type/timestamp.html>`__
68 REGULAR_SHORT = r'^(?P<year>[0-9][0-9][0-9][0-9])-(?P<month>[0-9][0-9])-(?P<day>[0-9][0-9])$'
70 r'^(?P<year>[0-9][0-9][0-9][0-9])-(?P<month>[0-9][0-9]?)-(?P<day>[0-9][0-9]?)' + \
72 r'(?P<hour>[0-9][0-9]?):(?P<minute>[0-9][0-9]):(?P<second>[0-9][0-9])' + \
73 r'(?P<fraction>\.[0-9]*)?' + \
74 r'(([ \t]*)Z|(?P<tzhour>[-+][0-9][0-9])?(:(?P<tzminute>[0-9][0-9])?)?)?$'
75 CANONICAL = '%Y-%m-%dT%H:%M:%S'
77 def __init__(self, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
79 match = re.match(Timestamp.REGULAR_SHORT, value)
82 year = int(match.group('year'))
83 month = int(match.group('month'))
84 day = int(match.group('day'))
85 self.value = datetime(year, month, day, tzinfo=UTC)
87 match = re.match(Timestamp.REGULAR_LONG, value)
90 year = int(match.group('year'))
91 month = int(match.group('month'))
92 day = int(match.group('day'))
93 hour = match.group('hour')
96 minute = match.group('minute')
97 if minute is not None:
99 second = match.group('second')
100 if second is not None:
102 fraction = match.group('fraction')
103 if fraction is not None:
104 fraction = int(float(fraction) * 1000000.0) # convert to microseconds
105 tzhour = match.group('tzhour')
106 if tzhour is not None:
110 tzminute = match.group('tzminute')
111 if tzminute is not None:
112 tzminute = int(tzminute)
115 self.value = datetime(year, month, day, hour, minute, second, fraction,
116 Timezone(tzhour, tzminute))
119 'timestamp must be formatted as YAML ISO8601 variant or "YYYY-MM-DD": %s'
123 def as_datetime_utc(self):
124 return self.value.astimezone(UTC)
128 return self.__str__()
131 the_datetime = self.as_datetime_utc
133 % (the_datetime.strftime(Timestamp.CANONICAL), Timestamp._fraction_as_str(the_datetime))
136 return repr(self.__str__())
138 def __eq__(self, timestamp):
139 if not isinstance(timestamp, Timestamp):
141 return self.value == timestamp.value
143 def __lt__(self, timestamp):
144 return self.value < timestamp.value
147 def _fraction_as_str(the_datetime):
148 return '{0:g}'.format(the_datetime.microsecond / 1000000.0).lstrip('0')
152 @implements_specification('3.2.2', 'tosca-simple-1.0')
153 class Version(object):
155 TOSCA supports the concept of "reuse" of type definitions, as well as template definitions which
156 could be version and change over time. It is important to provide a reliable, normative means to
157 represent a version string which enables the comparison and management of types and templates
158 over time. Therefore, the TOSCA TC intends to provide a normative version type (string) for this
159 purpose in future Working Drafts of this specification.
161 See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
162 /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
163 #TYPE_TOSCA_VERSION>`__
167 r'^(?P<major>\d+)\.(?P<minor>\d+)(\.(?P<fix>\d+)' + \
168 r'((\.(?P<qualifier>\d+))(\-(?P<build>\d+))?)?)?$'
173 Key method for fast sorting.
175 return (version.major, version.minor, version.fix, version.qualifier, version.build)
177 def __init__(self, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
178 str_value = str(value)
179 match = re.match(Version.REGEX, str_value)
182 'version must be formatted as <major_version>.<minor_version>'
183 '[.<fix_version>[.<qualifier>[-<build_version]]]: %s'
186 self.value = str_value
188 self.major = match.group('major')
189 self.major = int(self.major)
190 self.minor = match.group('minor')
191 self.minor = int(self.minor)
192 self.fix = match.group('fix')
193 if self.fix is not None:
194 self.fix = int(self.fix)
195 self.qualifier = match.group('qualifier')
196 if self.qualifier is not None:
197 self.qualifier = int(self.qualifier)
198 self.build = match.group('build')
199 if self.build is not None:
200 self.build = int(self.build)
210 return repr(self.__str__())
212 def __eq__(self, version):
213 if not isinstance(version, Version):
215 return (self.major, self.minor, self.fix, self.qualifier, self.build) == \
216 (version.major, version.minor, version.fix, version.qualifier, version.build)
218 def __lt__(self, version):
219 if self.major < version.major:
221 elif self.major == version.major:
222 if self.minor < version.minor:
224 elif self.minor == version.minor:
225 if self.fix < version.fix:
227 elif self.fix == version.fix:
228 if self.qualifier < version.qualifier:
230 elif self.qualifier == version.qualifier:
231 if self.build < version.build:
236 @implements_specification('3.2.3', 'tosca-simple-1.0')
239 The range type can be used to define numeric ranges with a lower and upper boundary. For
240 example, this allows for specifying a range of ports to be opened in a firewall.
242 See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
243 /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
244 #TYPE_TOSCA_RANGE>`__
247 def __init__(self, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
248 if not isinstance(value, list):
249 raise ValueError('range value is not a list: %s' % safe_repr(value))
251 raise ValueError('range value does not have exactly 2 elements: %s' % safe_repr(value))
254 return isinstance(v, int) and (not isinstance(v, bool)) # In Python bool is an int
256 if not is_int(value[0]):
257 raise ValueError('lower bound of range is not a valid integer: %s'
258 % safe_repr(value[0]))
260 if value[1] != 'UNBOUNDED':
261 if not is_int(value[1]):
262 raise ValueError('upper bound of range is not a valid integer or "UNBOUNDED": %s'
263 % safe_repr(value[0]))
265 if value[0] >= value[1]:
267 'upper bound of range is not greater than the lower bound: %s >= %s'
268 % (safe_repr(value[0]), safe_repr(value[1])))
272 def is_in(self, value):
273 if value < self.value[0]:
275 if (self.value[1] != 'UNBOUNDED') and (value > self.value[1]):
281 return list(self.value)
284 @implements_specification('3.2.4', 'tosca-simple-1.0')
287 The list type allows for specifying multiple values for a parameter of property. For example, if
288 an application allows for being configured to listen on multiple ports, a list of ports could be
289 configured using the list data type.
291 See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
292 /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
297 def _create(context, presentation, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
298 if not isinstance(value, list):
299 raise ValueError('"list" data type value is not a list: %s' % safe_repr(value))
301 entry_schema_type = entry_schema._get_type(context)
302 entry_schema_constraints = entry_schema.constraints
306 v = coerce_value(context, presentation, entry_schema_type, None,
307 entry_schema_constraints, v, aspect)
313 # Can't define as property because it's old-style Python class
318 @implements_specification('3.2.5', 'tosca-simple-1.0')
319 class Map(StrictDict):
321 The map type allows for specifying multiple values for a parameter of property as a map. In
322 contrast to the list type, where each entry can only be addressed by its index in the list,
323 entries in a map are named elements that can be addressed by their keys.
325 See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
326 /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
331 def _create(context, presentation, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
332 if not isinstance(value, dict):
333 raise ValueError('"map" data type value is not a dict: %s' % safe_repr(value))
335 if entry_schema is None:
336 raise ValueError('"map" data type does not define "entry_schema"')
338 entry_schema_type = entry_schema._get_type(context)
339 entry_schema_constraints = entry_schema.constraints
342 for k, v in value.iteritems():
343 v = coerce_value(context, presentation, entry_schema_type, None,
344 entry_schema_constraints, v, aspect)
350 def __init__(self, items=None):
351 super(Map, self).__init__(items, key_class=str)
353 # Can't define as property because it's old-style Python class
355 return OrderedDict(self)
359 @implements_specification('3.2.6', 'tosca-simple-1.0')
360 class Scalar(object):
362 The scalar-unit type can be used to define scalar values along with a unit from the list of
365 See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
366 /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
367 #TYPE_TOSCA_SCALAR_UNIT>`__
373 Key method for fast sorting.
377 def __init__(self, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
378 str_value = str(value)
379 match = re.match(self.REGEX, str_value) # pylint: disable=no-member
381 raise ValueError('scalar must be formatted as <scalar> <unit>: %s' % safe_repr(value))
383 self.factor = float(match.group('scalar'))
385 raise ValueError('scalar is negative: %s' % safe_repr(self.factor))
387 self.unit = match.group('unit')
389 unit_lower = self.unit.lower()
391 for k, v in self.UNITS.iteritems(): # pylint: disable=no-member
392 if k.lower() == unit_lower:
396 if unit_size is None:
397 raise ValueError('scalar specified with unsupported unit: %s' % safe_repr(self.unit))
399 self.value = self.TYPE(self.factor * unit_size) # pylint: disable=no-member
404 ('value', self.value),
405 ('factor', self.factor),
407 ('unit_size', self.UNITS[self.unit]))) # pylint: disable=no-member
410 return '%s %s' % (self.value, self.UNIT) # pylint: disable=no-member
413 return repr(self.__str__())
415 def __eq__(self, scalar):
416 if isinstance(scalar, Scalar):
419 value = self.TYPE(scalar) # pylint: disable=no-member
420 return self.value == value
422 def __lt__(self, scalar):
423 if isinstance(scalar, Scalar):
426 value = self.TYPE(scalar) # pylint: disable=no-member
427 return self.value < value
430 @implements_specification('3.2.6.4', 'tosca-simple-1.0')
431 class ScalarSize(Scalar):
433 Integer scalar for counting bytes.
435 See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
436 /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
437 #TYPE_TOSCA_SCALAR_UNIT_SIZE>`__
440 # See: http://www.regular-expressions.info/floatingpoint.html
442 r'^(?P<scalar>[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)\s*(?P<unit>B|kB|KiB|MB|MiB|GB|GiB|TB|TiB)$'
453 'TiB': 1099511627776}
459 @implements_specification('3.2.6.5', 'tosca-simple-1.0')
460 class ScalarTime(Scalar):
462 Floating point scalar for counting seconds.
464 See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
465 /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
466 #TYPE_TOSCA_SCALAR_UNIT_TIME>`__
469 # See: http://www.regular-expressions.info/floatingpoint.html
470 REGEX = r'^(?P<scalar>[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)\s*(?P<unit>ns|us|ms|s|m|h|d)$'
485 @implements_specification('3.2.6.6', 'tosca-simple-1.0')
486 class ScalarFrequency(Scalar):
488 Floating point scalar for counting cycles per second (Hz).
490 See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
491 /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
492 #TYPE_TOSCA_SCALAR_UNIT_FREQUENCY>`__
495 # See: http://www.regular-expressions.info/floatingpoint.html
496 REGEX = r'^(?P<scalar>[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)\s*(?P<unit>Hz|kHz|MHz|GHz)$'
509 # The following are hooked in the YAML as 'coerce_value' extensions
512 def coerce_timestamp(context, presentation, the_type, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
513 return coerce_to_data_type_class(context, presentation, Timestamp, entry_schema, constraints,
517 def coerce_version(context, presentation, the_type, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
518 return coerce_to_data_type_class(context, presentation, Version, entry_schema, constraints,
522 def coerce_range(context, presentation, the_type, entry_schema, constraints, value, aspect):
523 if aspect == 'in_range':
524 # When we're in a "in_range" constraint, the values are *not* themselves ranges, but numbers
527 except ValueError as e:
528 report_issue_for_bad_format(context, presentation, the_type, value, aspect, e)
529 except TypeError as e:
530 report_issue_for_bad_format(context, presentation, the_type, value, aspect, e)
532 return coerce_to_data_type_class(context, presentation, Range, entry_schema, constraints,
536 def coerce_list(context, presentation, the_type, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
537 return coerce_to_data_type_class(context, presentation, List, entry_schema, constraints,
541 def coerce_map_value(context, presentation, the_type, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument
542 return coerce_to_data_type_class(context, presentation, Map, entry_schema, constraints, value,
546 def coerce_scalar_unit_size(context, presentation, the_type, entry_schema, constraints, value, # pylint: disable=unused-argument
548 return coerce_to_data_type_class(context, presentation, ScalarSize, entry_schema, constraints,
552 def coerce_scalar_unit_time(context, presentation, the_type, entry_schema, constraints, value, # pylint: disable=unused-argument
554 return coerce_to_data_type_class(context, presentation, ScalarTime, entry_schema, constraints,
558 def coerce_scalar_unit_frequency(context, presentation, the_type, entry_schema, constraints, value, # pylint: disable=unused-argument
560 return coerce_to_data_type_class(context, presentation, ScalarFrequency, entry_schema,
561 constraints, value, aspect)