Package pywbem :: Module cim_types |
[frames] | no frames]

# Source Code for Module pywbem.cim_types

      1  #
      2  # (C) Copyright 2003,2004 Hewlett-Packard Development Company, L.P.
      3  # (C) Copyright 2006,2007 Novell, Inc.
      4  #
      5  # This library is free software; you can redistribute it and/or
      6  # modify it under the terms of the GNU Lesser General Public
      7  # License as published by the Free Software Foundation; either
      8  # version 2.1 of the License, or (at your option) any later version.
      9  #
     10  # This program is distributed in the hope that it will be useful, but
     11  # WITHOUT ANY WARRANTY; without even the implied warranty of
     12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13  # Lesser General Public License for more details.
     14  #
     15  # You should have received a copy of the GNU Lesser General Public
     16  # License along with this program; if not, write to the Free Software
     17  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     18  #
     19  # Author: Tim Potter <tpot@hp.com>
     20  # Author: Bart Whiteley <bwhiteley@suse.de>
     21  #
     22
     23  """
     24  Types to represent CIM typed values, and related conversion functions.
     25
     26  The following table shows how CIM typed values are represented as Python
     27  objects:
     28
     29  =========================  ===========================
     30  CIM type                   Python type
     31  =========================  ===========================
     32  boolean                    `bool`
     33  char16                     `unicode` or `str`
     34  string                     `unicode` or `str`
     35  string (EmbeddedInstance)  `CIMInstance`
     36  string (EmbeddedObject)    `CIMInstance` or `CIMClass`
     37  datetime                   `CIMDateTime`
     38  reference                  `CIMInstanceName`
     39  uint8                      `Uint8`
     40  uint16                     `Uint16`
     41  uint32                     `Uint32`
     42  uint64                     `Uint64`
     43  sint8                      `Sint8`
     44  sint16                     `Sint16`
     45  sint32                     `Sint32`
     46  sint64                     `Sint64`
     47  real32                     `Real32`
     48  real64                     `Real64`
     49  [] (array)                 `list`
     50  =========================  ===========================
     51
     52  Note that constructors of PyWBEM classes that take CIM typed values as input
     53  may support Python types in addition to those shown above. For example, the
     54  `CIMProperty` class represents CIM datetime values internally as a
     55  `CIMDateTime` object, but its constructor accepts `datetime.timedelta`,
     56  `datetime.datetime`, `str`, and `unicode` objects in addition to
     57  `CIMDateTime` objects.
     58  """
     59
     60  # This module is meant to be safe for 'import *'.
     61
     62  from datetime import tzinfo, datetime, timedelta
     63  import re
     64
     65  __all__ = ['MinutesFromUTC', 'CIMType', 'CIMDateTime', 'CIMInt', 'Uint8',
     66             'Sint8', 'Uint16', 'Sint16', 'Uint32', 'Sint32', 'Uint64', 'Sint64',
     67             'CIMFloat', 'Real32', 'Real64', 'cimtype', 'type_from_name',
     68             'atomic_to_cim_xml']
     69
     70 -class MinutesFromUTC(tzinfo):
     71
     72      """
     73      A `datetime.tzinfo` implementation defined using a fixed offset in +/-
     74      minutes from UTC.
     75      """
     76
     77 -    def __init__(self, offset): # pylint: disable=super-init-not-called
     78          """
     79          Initialize the `MinutesFromUTC` object from a timezone offset.
     80
     81          :Parameters:
     82
     83            offset : `int`
     84              Timezone offset in +/- minutes from UTC, where a positive value
     85              indicates minutes east of UTC, and a negative value indicates
     86              minutes west of UTC.
     87          """
     88          self.__offset = timedelta(minutes=offset)
     89
     90 -    def utcoffset(self, dt): # pylint: disable=unused-argument
     91          """
     92          Implement the `datetime.tzinfo.utcoffset` method by returning
     93          the timezone offset as a `datetime.timedelta` object.
     94          """
     95          return self.__offset
     96
     97 -    def dst(self, dt): # pylint: disable=unused-argument
     98          """
     99          Implement the `datetime.tzinfo.dst` method by returning
    100          a DST value of 0 as a `datetime.timedelta` object.
    101          """
    102          return timedelta(0)
    103
    104 -class CIMType(object):
    105      """Base type for numeric and datetime CIM types."""
    106
    107 -class CIMDateTime(CIMType):
    108
    109      """
    110      A value of CIM type datetime.
    111
    112      The object represents either a timezone-aware point in time, or a time
    113      interval.
    114      """
    115
    116 -    def __init__(self, dtarg):
    117          """
    118          Initialize the `CIMDateTime` object from different types of input
    119          object.
    120
    121          :Parameters:
    122
    123            dtarg
    124              The input object, as one of the following types:
    125              An `str` or `unicode` object will be interpreted as CIM datetime
    126              format (see DSP0004) and will result in a point in time or a time
    127              interval.
    128              A `datetime.datetime` object must be timezone-aware and will
    129              result in a point in time.
    130              A `datetime.timedelta` object will result in a time interval.
    131              Another `CIMDateTime` object will be copied.
    132
    133          :Raises:
    134            :raise ValueError:
    135            :raise TypeError:
    136          """
    137          self.cimtype = 'datetime'
    138          self.__timedelta = None
    139          self.__datetime = None
    140          if isinstance(dtarg, basestring):
    141              date_pattern = re.compile(
    142                  r'^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.' \
    143                  r'(\d{6})([+|-])(\d{3})')
    144              s = date_pattern.search(dtarg)
    145              if s is not None:
    146                  g = s.groups()
    147                  offset = int(g[8])
    148                  if g[7] == '-':
    149                      offset = -offset
    150                  try:
    151                      self.__datetime = datetime(int(g[0]), int(g[1]),
    152                                                 int(g[2]), int(g[3]),
    153                                                 int(g[4]), int(g[5]),
    154                                                 int(g[6]),
    155                                                 MinutesFromUTC(offset))
    156                  except ValueError as exc:
    157                      raise ValueError('dtarg argument "%s" has invalid field '\
    158                                       'values for CIM datetime timestamp '\
    159                                       'format: %s' % (dtarg, exc))
    160              else:
    161                  tv_pattern = re.compile(
    162                      r'^(\d{8})(\d{2})(\d{2})(\d{2})\.(\d{6})(:)(000)')
    163                  s = tv_pattern.search(dtarg)
    164                  if s is not None:
    165                      g = s.groups()
    166                      # Because the input values are limited by the matched
    167                      # pattern, timedelta() never throws any exception.
    168                      self.__timedelta = timedelta(days=int(g[0]),
    169                                                   hours=int(g[1]),
    170                                                   minutes=int(g[2]),
    171                                                   seconds=int(g[3]),
    172                                                   microseconds=int(g[4]))
    173                  else:
    174                      raise ValueError('dtarg argument "%s" has an invalid CIM '\
    175                                       'datetime format' % dtarg)
    176          elif isinstance(dtarg, datetime):
    177              self.__datetime = dtarg
    178          elif isinstance(dtarg, timedelta):
    179              self.__timedelta = dtarg
    180          elif isinstance(dtarg, CIMDateTime):
    181              self.__datetime = dtarg.__datetime   # pylint: disable=protected-access
    182              self.__timedelta = dtarg.__timedelta # pylint: disable=protected-access
    183          else:
    184              raise TypeError('dtarg argument "%s" has an invalid type: %s '\
    185                              '(expected datetime, timedelta, string, or '\
    186                              'CIMDateTime)' % (dtarg, type(dtarg)))
    187
    188      @property
    189 -    def minutes_from_utc(self):
    190          """
    191          The timezone offset of a point in time object as +/- minutes from UTC.
    192
    193          A positive value of the timezone offset indicates minutes east of UTC,
    194          and a negative value indicates minutes west of UTC.
    195
    196          0, if the object represents a time interval.
    197          """
    198          offset = 0
    199          if self.__datetime is not None and \
    200                  self.__datetime.utcoffset() is not None:
    201              offset = self.__datetime.utcoffset().seconds / 60
    202              if self.__datetime.utcoffset().days == -1:
    203                  offset = -(60*24 - offset)
    204          return offset
    205
    206      @property
    207 -    def datetime(self):
    208          """
    209          The point in time represented by the object, as a `datetime.datetime`
    210          object.
    211
    212          `None` if the object represents a time interval.
    213          """
    214          return self.__datetime
    215
    216      @property
    217 -    def timedelta(self):
    218          """
    219          The time interval represented by the object, as a `datetime.timedelta`
    220          object.
    221
    222          `None` if the object represents a point in time.
    223          """
    224          return self.__timedelta
    225
    226      @property
    227 -    def is_interval(self):
    228          """
    229          A boolean indicating whether the object represents a time interval
    230          (`True`) or a point in time (`False`).
    231          """
    232          return self.__timedelta is not None
    233
    234      @staticmethod
    235 -    def get_local_utcoffset():
    236          """
    237          Return the timezone offset of the current local timezone from UTC.
    238
    239          A positive value indicates minutes east of UTC, and a negative
    240          value indicates minutes west of UTC.
    241          """
    242          utc = datetime.utcnow()
    243          local = datetime.now()
    244          if local < utc:
    245              return - int(float((utc - local).seconds) / 60 + .5)
    246          else:
    247              return int(float((local - utc).seconds) / 60 + .5)
    248
    249      @classmethod
    250 -    def now(cls, tzi=None):
    251          """
    252          Factory method that returns a new `CIMDateTime` object representing
    253          the current date and time.
    254
    255          The optional timezone information is used to convert the CIM datetime
    256          value into the desired timezone. That does not change the point in time
    257          that is represented by the value, but it changes the value of the
    258          `hhmmss` components of the CIM datetime value to compensate for changes
    259          in the timezone offset component.
    260
    261          :Parameters:
    262
    263            tzi : `datetime.tzinfo`
    264              Timezone information. `None` means that the current local timezone
    265              is used. The `datetime.tzinfo` object may be a `MinutesFromUTC`
    266              object.
    267
    268          :Returns:
    269
    270              A new `CIMDateTime` object representing the current date and time.
    271          """
    272          if tzi is None:
    273              tzi = MinutesFromUTC(cls.get_local_utcoffset())
    274          return cls(datetime.now(tzi))
    275
    276      @classmethod
    277 -    def fromtimestamp(cls, ts, tzi=None):
    278          """
    279          Factory method that returns a new `CIMDateTime` object from a POSIX
    280          timestamp value and optional timezone information.
    281
    282          A POSIX timestamp value is the number of seconds since 1970-01-01
    283          00:00:00 UTC. Thus, a POSIX timestamp value is unambiguous w.r.t. the
    284          timezone.
    285
    286          The optional timezone information is used to convert the CIM datetime
    287          value into the desired timezone. That does not change the point in time
    288          that is represented by the value, but it changes the value of the
    289          `hhmmss` components of the CIM datetime value to compensate for changes
    290          in the timezone offset component.
    291
    292          :Parameters:
    293
    294            ts : `int`
    295              POSIX timestamp value.
    296
    297            tzi : `datetime.tzinfo`
    298              Timezone information. `None` means that the current local timezone
    299              is used. The `datetime.tzinfo` object may be a `MinutesFromUTC`
    300              object.
    301
    302          :Returns:
    303
    304              A new `CIMDateTime` object representing the specified point in
    305              time.
    306          """
    307          if tzi is None:
    308              tzi = MinutesFromUTC(cls.get_local_utcoffset())
    309          return cls(datetime.fromtimestamp(ts, tzi))
    310
    311 -    def __str__(self):
    312          """
    313          Return a string representing the object in CIM datetime format.
    314          """
    315          if self.is_interval:
    316              hour = self.timedelta.seconds / 3600
    317              minute = (self.timedelta.seconds - hour * 3600) / 60
    318              second = self.timedelta.seconds - hour * 3600 - minute * 60
    319              return '%08d%02d%02d%02d.%06d:000' % \
    320                      (self.timedelta.days, hour, minute, second,
    321                       self.timedelta.microseconds)
    322          else:
    323              offset = self.minutes_from_utc
    324              sign = '+'
    325              if offset < 0:
    326                  sign = '-'
    327                  offset = -offset
    328              return '%d%02d%02d%02d%02d%02d.%06d%s%03d' % \
    329                     (self.datetime.year, self.datetime.month,
    330                      self.datetime.day, self.datetime.hour,
    331                      self.datetime.minute, self.datetime.second,
    332                      self.datetime.microsecond, sign, offset)
    333
    334 -    def __repr__(self):
    335          return '%s(\'%s\')' % (self.__class__.__name__, str(self))
    336
    337 -    def __getstate__(self):
    338          return str(self)
    339
    340 -    def __setstate__(self, arg):
    341          self.__init__(arg)
    342
    343 -    def __cmp__(self, other):
    344          if self is other:
    345              return 0
    346          elif not isinstance(other, CIMDateTime):
    347              return 1
    348          return cmp(self.datetime, other.datetime) or \
    349                 cmp(self.timedelta, other.timedelta)
    350
    351  # CIM integer types
    352
    353 -class CIMInt(CIMType, long):
    354      """Base type for integer CIM types."""
    355
    356 -class Uint8(CIMInt):
    357      """A value of CIM type uint8."""
    358      cimtype = 'uint8'
    359
    360 -class Sint8(CIMInt):
    361      """A value of CIM type sint8."""
    362      cimtype = 'sint8'
    363
    364 -class Uint16(CIMInt):
    365      """A value of CIM type uint16."""
    366      cimtype = 'uint16'
    367
    368 -class Sint16(CIMInt):
    369      """A value of CIM type sint16."""
    370      cimtype = 'sint16'
    371
    372 -class Uint32(CIMInt):
    373      """A value of CIM type uint32."""
    374      cimtype = 'uint32'
    375
    376 -class Sint32(CIMInt):
    377      """A value of CIM type sint32."""
    378      cimtype = 'sint32'
    379
    380 -class Uint64(CIMInt):
    381      """A value of CIM type uint64."""
    382      cimtype = 'uint64'
    383
    384 -class Sint64(CIMInt):
    385      """A value of CIM type sint64."""
    386      cimtype = 'sint64'
    387
    388  # CIM float types
    389
    390 -class CIMFloat(CIMType, float):
    391      """Base type for real (floating point) CIM types."""
    392
    393 -class Real32(CIMFloat):
    394      """A value of CIM type real32."""
    395      cimtype = 'real32'
    396
    397 -class Real64(CIMFloat):
    398      """A value of CIM type real64."""
    399      cimtype = 'real64'
    400
    401 -def cimtype(obj):
    402      """
    403      Return the CIM type name of a value, as a string.
    404
    405      For an array, the type is determined from the first array element because
    406      CIM arrays must be homogeneous. If the array is empty, ValueError is
    407      raised.
    408
    409      If the type of the value is not a CIM type, TypeError is raised.
    410
    411      :Parameters:
    412
    413        obj : CIM typed value
    414          The value whose CIM type name is returned.
    415
    416      :Returns:
    417
    418          The CIM type name of the value, as a string.
    419
    420      :Raises:
    421
    422          :raise TypeError:
    423              Type is not a CIM type.
    424
    425          :raise ValueError:
    426              Cannot determine CIM type from empty array.
    427      """
    428      if isinstance(obj, CIMType):
    429          return obj.cimtype
    430      if isinstance(obj, bool):
    431          return 'boolean'
    432      if isinstance(obj, (basestring)):
    433          return 'string'
    434      if isinstance(obj, list):
    435          if len(obj) == 0:
    436              raise ValueError("Cannot determine CIM type from empty array")
    437          return cimtype(obj[0])
    438      if isinstance(obj, (datetime, timedelta)):
    439          return 'datetime'
    440      raise TypeError("Type %s of this value is not a CIM type: %r" % \
    441                      (type(obj), obj))
    442
    443  _TYPE_FROM_NAME = {
    444      'boolean': bool,
    445      'string': unicode,
    446      'char16': unicode,
    447      'datetime': CIMDateTime,
    448      # 'reference' covered at run time
    449      'uint8': Uint8,
    450      'uint16': Uint16,
    451      'uint32': Uint32,
    452      'uint64': Uint64,
    453      'sint8': Sint8,
    454      'sint16': Sint16,
    455      'sint32': Sint32,
    456      'sint64': Sint64,
    457      'real32': Real32,
    458      'real64': Real64,
    459  }
    460
    461 -def type_from_name(type_name):
    462      """
    463      Return the Python type object for a given CIM type name.
    464
    465      For example, type name `'uint8'` will return type `Uint8`.
    466
    467      :Parameters:
    468
    469        type_name : `str` or `unicode`
    470          The simple (=non-array) CIM type name (e.g. `'uint8'` or
    471          `'reference'`).
    472
    473      :Returns:
    474
    475          The Python type object for the CIM type (e.g. `Uint8` or
    476          `CIMInstanceName`).
    477
    478      :Raises:
    479
    480        :raise ValueError:
    481          Unknown CIM type name.
    482      """
    483      if type_name == 'reference':
    484          # move import to run time to avoid circular imports
    485          from pywbem import cim_obj
    486          return cim_obj.CIMInstanceName
    487      try:
    488          type_obj = _TYPE_FROM_NAME[type_name]
    489      except KeyError:
    490          raise ValueError("Unknown CIM type name: %r" % type_name)
    491      return type_obj
    492
    493 -def atomic_to_cim_xml(obj):
    494      """
    495      Convert a value of an atomic scalar CIM type to a CIM-XML unicode string
    496      and return that string.
    497
    498      TODO: Verify whether we can change this function to raise a ValueError in
    499      case the value is not CIM typed.
    500
    501      :Parameters:
    502
    503        obj : CIM typed value.
    504          The CIM typed value`, including `None`. Must be a scalar. Must be an
    505          atomic type (i.e. not `CIMInstance` or `CIMClass`).
    506
    507      :Returns:
    508
    509          A unicode string in CIM-XML value format representing the CIM typed
    510          value. For a value of `None`, `None` is returned.
    511      """
    512      if isinstance(obj, bool):
    513          if obj:
    514              return u"true"
    515          else:
    516              return u"false"
    517      elif isinstance(obj, CIMDateTime):
    518          return unicode(obj)
    519      elif isinstance(obj, datetime):
    520          return unicode(CIMDateTime(obj))
    521      elif obj is None:
    522          return obj
    523      elif cimtype(obj) == 'real32':
    524          return u'%.8E' % obj
    525      elif cimtype(obj) == 'real64':
    526          return u'%.16E' % obj
    527      elif isinstance(obj, str):
    528          return obj.decode('utf-8')
    529      else: # e.g. unicode, int
    530          return unicode(obj)
    531