thirdparty/google_appengine/google/appengine/api/datastore_types.py
changeset 109 620f9b141567
child 149 f2e327a7c5de
equal deleted inserted replaced
108:261778de26ff 109:620f9b141567
       
     1 #!/usr/bin/env python
       
     2 #
       
     3 # Copyright 2007 Google Inc.
       
     4 #
       
     5 # Licensed under the Apache License, Version 2.0 (the "License");
       
     6 # you may not use this file except in compliance with the License.
       
     7 # You may obtain a copy of the License at
       
     8 #
       
     9 #     http://www.apache.org/licenses/LICENSE-2.0
       
    10 #
       
    11 # Unless required by applicable law or agreed to in writing, software
       
    12 # distributed under the License is distributed on an "AS IS" BASIS,
       
    13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       
    14 # See the License for the specific language governing permissions and
       
    15 # limitations under the License.
       
    16 #
       
    17 
       
    18 """Higher-level, semantic data types for the datastore. These types
       
    19 are expected to be set as attributes of Entities.  See "Supported Data Types"
       
    20 in the API Guide.
       
    21 
       
    22 Most of these types are based on XML elements from Atom and GData elements
       
    23 from the atom and gd namespaces. For more information, see:
       
    24 
       
    25   http://www.atomenabled.org/developers/syndication/
       
    26   http://code.google.com/apis/gdata/common-elements.html
       
    27 
       
    28 The namespace schemas are:
       
    29 
       
    30   http://www.w3.org/2005/Atom
       
    31   http://schemas.google.com/g/2005
       
    32 """
       
    33 
       
    34 
       
    35 
       
    36 
       
    37 
       
    38 import base64
       
    39 import calendar
       
    40 import datetime
       
    41 import os
       
    42 import re
       
    43 import string
       
    44 import time
       
    45 import urlparse
       
    46 from xml.sax import saxutils
       
    47 from google.appengine.datastore import datastore_pb
       
    48 from google.appengine.api import datastore_errors
       
    49 from google.appengine.api import users
       
    50 from google.net.proto import ProtocolBuffer
       
    51 from google.appengine.datastore import entity_pb
       
    52 
       
    53 _LOCAL_APP_ID = u':self'
       
    54 
       
    55 _MAX_STRING_LENGTH = 500
       
    56 
       
    57 _MAX_LINK_PROPERTY_LENGTH = 2083
       
    58 
       
    59 RESERVED_PROPERTY_NAME = re.compile('^__.*__$');
       
    60 
       
    61 class UtcTzinfo(datetime.tzinfo):
       
    62   def utcoffset(self, dt): return datetime.timedelta(0)
       
    63   def dst(self, dt): return datetime.timedelta(0)
       
    64   def tzname(self, dt): return 'UTC'
       
    65   def __repr__(self): return 'datastore_types.UTC'
       
    66 
       
    67 UTC = UtcTzinfo()
       
    68 
       
    69 
       
    70 def typename(obj):
       
    71   """Returns the type of obj as a string. More descriptive and specific than
       
    72   type(obj), and safe for any object, unlike __class__."""
       
    73   if hasattr(obj, '__class__'):
       
    74     return getattr(obj, '__class__').__name__
       
    75   else:
       
    76     return type(obj).__name__
       
    77 
       
    78 
       
    79 def ValidateString(value, name='Value',
       
    80                    exception=datastore_errors.BadValueError,
       
    81                    max_len=_MAX_STRING_LENGTH):
       
    82   """Raises an exception if value is not a valid string or a subclass thereof.
       
    83 
       
    84   A string is valid if it's not empty, no more than _MAX_STRING_LENGTH bytes,
       
    85   and not a Blob. The exception type can be specified with the exception
       
    86   argument; it defaults to BadValueError.
       
    87 
       
    88   Args:
       
    89     value: the value to validate.
       
    90     name: the name of this value; used in the exception message.
       
    91     exception: the type of exception to raise.
       
    92     max_len: the maximum allowed length, in bytes
       
    93   """
       
    94   if not isinstance(value, basestring) or isinstance(value, Blob):
       
    95     raise exception('%s should be a string; received %s (a %s):' %
       
    96                     (name, value, typename(value)))
       
    97   elif value == '':
       
    98     raise exception('%s must not be empty.' % name)
       
    99   elif len(value.encode('utf-8')) > max_len:
       
   100     raise exception('%s must be under %d bytes.' % (name, max_len))
       
   101 
       
   102 
       
   103 class Key(object):
       
   104   """The primary key for a datastore entity.
       
   105 
       
   106   A datastore GUID. A Key instance uniquely identifies an entity across all
       
   107   apps, and includes all information necessary to fetch the entity from the
       
   108   datastore with Get().
       
   109 
       
   110   Key implements __hash__, and key instances are immutable, so Keys may be
       
   111   used in sets and as dictionary keys.
       
   112   """
       
   113   __reference = None
       
   114 
       
   115   def __init__(self, encoded=None):
       
   116     """Constructor. Creates a Key from a string.
       
   117 
       
   118     Args:
       
   119       # a base64-encoded primary key, generated by Key.__str__
       
   120       encoded: str
       
   121     """
       
   122     if encoded is not None:
       
   123       if not isinstance(encoded, basestring):
       
   124         try:
       
   125           repr_encoded = repr(encoded)
       
   126         except:
       
   127           repr_encoded = "<couldn't encode>"
       
   128         raise datastore_errors.BadArgumentError(
       
   129           'Key() expects a string; received %s (a %s).' %
       
   130           (repr_encoded, typename(encoded)))
       
   131       try:
       
   132         modulo = len(encoded) % 4
       
   133         if modulo != 0:
       
   134           encoded += ('=' * (4 - modulo))
       
   135 
       
   136         encoded_pb = base64.urlsafe_b64decode(str(encoded))
       
   137         self.__reference = entity_pb.Reference(encoded_pb)
       
   138         assert self.__reference.IsInitialized()
       
   139 
       
   140       except (AssertionError, TypeError), e:
       
   141         raise datastore_errors.BadKeyError(
       
   142           'Invalid string key %s. Details: %s' % (encoded, e))
       
   143       except Exception, e:
       
   144         if e.__class__.__name__ == 'ProtocolBufferDecodeError':
       
   145           raise datastore_errors.BadKeyError('Invalid string key %s.' % encoded)
       
   146         else:
       
   147           raise
       
   148     else:
       
   149       self.__reference = entity_pb.Reference()
       
   150 
       
   151   @staticmethod
       
   152   def from_path(*args, **kwds):
       
   153     """Static method to construct a Key out of a "path" (kind, id or name, ...).
       
   154 
       
   155     This is useful when an application wants to use just the id or name portion
       
   156     of a key in e.g. a URL, where the rest of the URL provides enough context to
       
   157     fill in the rest, i.e. the app id (always implicit), the entity kind, and
       
   158     possibly an ancestor key. Since ids and names are usually small, they're
       
   159     more attractive for use in end-user-visible URLs than the full string
       
   160     representation of a key.
       
   161 
       
   162     Args:
       
   163       kind: the entity kind (a str or unicode instance)
       
   164       id_or_name: the id (an int or long) or name (a str or unicode instance)
       
   165 
       
   166     Additional positional arguments are allowed and should be
       
   167     alternating kind and id/name.
       
   168 
       
   169     Keyword args:
       
   170       parent: optional parent Key; default None.
       
   171 
       
   172     Returns:
       
   173       A new Key instance whose .kind() and .id() or .name() methods return
       
   174       the *last* kind and id or name positional arguments passed.
       
   175 
       
   176     Raises:
       
   177       BadArgumentError for invalid arguments.
       
   178       BadKeyError if the parent key is incomplete.
       
   179     """
       
   180     parent = kwds.pop('parent', None)
       
   181     _app = kwds.pop('_app', None)
       
   182 
       
   183     if kwds:
       
   184       raise datastore_errors.BadArgumentError(
       
   185           'Excess keyword arguments ' + repr(kwds))
       
   186 
       
   187     if not args or len(args) % 2:
       
   188       raise datastore_errors.BadArgumentError(
       
   189           'A non-zero even number of positional arguments is required '
       
   190           '(kind, id or name, kind, id or name, ...); received %s' % repr(args))
       
   191 
       
   192     if _app is not None:
       
   193       if not isinstance(_app, basestring):
       
   194         raise datastore_errors.BadArgumentError(
       
   195           'Expected a string _app; received %r (a %s).' %
       
   196           (_app, typename(_app)))
       
   197 
       
   198     if parent is not None:
       
   199       if not isinstance(parent, Key):
       
   200         raise datastore_errors.BadArgumentError(
       
   201             'Expected None or a Key as parent; received %r (a %s).' %
       
   202             (parent, typename(parent)))
       
   203       if not parent.has_id_or_name():
       
   204         raise datastore_errors.BadKeyError(
       
   205             'The parent Key is incomplete.')
       
   206       if _app is not None and _app != parent.app():
       
   207         raise datastore_errors.BadArgumentError(
       
   208             'The _app argument (%r) should match parent.app() (%s)' %
       
   209             (_app, parent.app()))
       
   210 
       
   211     key = Key()
       
   212     ref = key.__reference
       
   213     if parent is not None:
       
   214       ref.CopyFrom(parent.__reference)
       
   215     elif _app is not None:
       
   216       ref.set_app(_app)
       
   217     else:
       
   218       ref.set_app(_LOCAL_APP_ID)
       
   219 
       
   220     path = ref.mutable_path()
       
   221     for i in xrange(0, len(args), 2):
       
   222       kind, id_or_name = args[i:i+2]
       
   223       if isinstance(kind, basestring):
       
   224         kind = kind.encode('utf-8')
       
   225       else:
       
   226         raise datastore_errors.BadArgumentError(
       
   227             'Expected a string kind as argument %d; received %r (a %s).' %
       
   228             (i + 1, kind, typename(kind)))
       
   229       elem = path.add_element()
       
   230       elem.set_type(kind)
       
   231       if isinstance(id_or_name, (int, long)):
       
   232         elem.set_id(id_or_name)
       
   233       elif isinstance(id_or_name, basestring):
       
   234         ValidateString(id_or_name, 'name')
       
   235         if id_or_name and id_or_name[0] in string.digits:
       
   236           raise datastore_errors.BadArgumentError(
       
   237             'Names may not begin with a digit; received %s.' % id_or_name)
       
   238         elem.set_name(id_or_name.encode('utf-8'))
       
   239       else:
       
   240         raise datastore_errors.BadArgumentError(
       
   241             'Expected an integer id or string name as argument %d; '
       
   242             'received %r (a %s).' % (i + 2, id_or_name, typename(id_or_name)))
       
   243 
       
   244     assert ref.IsInitialized()
       
   245     return key
       
   246 
       
   247   def app(self):
       
   248     """Returns this entity's app id, a string."""
       
   249     if self.__reference.app():
       
   250       return self.__reference.app().decode('utf-8')
       
   251     else:
       
   252       return None
       
   253 
       
   254   def kind(self):
       
   255     """Returns this entity's kind, as a string."""
       
   256     if self.__reference.path().element_size() > 0:
       
   257       encoded = self.__reference.path().element_list()[-1].type()
       
   258       return unicode(encoded.decode('utf-8'))
       
   259     else:
       
   260       return None
       
   261 
       
   262   def id(self):
       
   263     """Returns this entity's id, or None if it doesn't have one."""
       
   264     elems = self.__reference.path().element_list()
       
   265     if elems and elems[-1].has_id() and elems[-1].id():
       
   266       return elems[-1].id()
       
   267     else:
       
   268       return None
       
   269 
       
   270   def name(self):
       
   271     """Returns this entity's name, or None if it doesn't have one."""
       
   272     elems = self.__reference.path().element_list()
       
   273     if elems and elems[-1].has_name() and elems[-1].name():
       
   274       return elems[-1].name().decode('utf-8')
       
   275     else:
       
   276       return None
       
   277 
       
   278   def id_or_name(self):
       
   279     """Returns this entity's id or name, whichever it has, or None."""
       
   280     if self.id() is not None:
       
   281       return self.id()
       
   282     else:
       
   283       return self.name()
       
   284 
       
   285   def has_id_or_name(self):
       
   286     """Returns True if this entity has an id or name, False otherwise.
       
   287     """
       
   288     return self.id_or_name() is not None
       
   289 
       
   290   def parent(self):
       
   291     """Returns this entity's parent, as a Key. If this entity has no parent,
       
   292     returns None."""
       
   293     if self.__reference.path().element_size() > 1:
       
   294       parent = Key()
       
   295       parent.__reference.CopyFrom(self.__reference)
       
   296       parent.__reference.path().element_list().pop()
       
   297       return parent
       
   298     else:
       
   299       return None
       
   300 
       
   301   def ToTagUri(self):
       
   302     """Returns a tag: URI for this entity for use in XML output.
       
   303 
       
   304     Foreign keys for entities may be represented in XML output as tag URIs.
       
   305     RFC 4151 describes the tag URI scheme. From http://taguri.org/:
       
   306 
       
   307       The tag algorithm lets people mint - create - identifiers that no one
       
   308       else using the same algorithm could ever mint. It is simple enough to do
       
   309       in your head, and the resulting identifiers can be easy to read, write,
       
   310       and remember. The identifiers conform to the URI (URL) Syntax.
       
   311 
       
   312     Tag URIs for entities use the app's auth domain and the date that the URI
       
   313      is generated. The namespace-specific part is <kind>[<key>].
       
   314 
       
   315     For example, here is the tag URI for a Kitten with the key "Fluffy" in the
       
   316     catsinsinks app:
       
   317 
       
   318       tag:catsinsinks.googleapps.com,2006-08-29:Kitten[Fluffy]
       
   319 
       
   320     Raises a BadKeyError if this entity's key is incomplete.
       
   321     """
       
   322     if not self.has_id_or_name():
       
   323       raise datastore_errors.BadKeyError(
       
   324         'ToTagUri() called for an entity with an incomplete key.')
       
   325 
       
   326     return u'tag:%s.%s,%s:%s[%s]' % (saxutils.escape(self.app()),
       
   327                                      os.environ['AUTH_DOMAIN'],
       
   328                                      datetime.date.today().isoformat(),
       
   329                                      saxutils.escape(self.kind()),
       
   330                                      saxutils.escape(str(self)))
       
   331   ToXml = ToTagUri
       
   332 
       
   333   def entity_group(self):
       
   334     """Returns this key's entity group as a Key.
       
   335 
       
   336     Note that the returned Key will be incomplete if this Key is for a root
       
   337     entity and it is incomplete.
       
   338     """
       
   339     group = Key._FromPb(self.__reference)
       
   340     del group.__reference.path().element_list()[1:]
       
   341     return group
       
   342 
       
   343   @staticmethod
       
   344   def _FromPb(pb):
       
   345     """Static factory method. Creates a Key from an entity_pb.Reference.
       
   346 
       
   347     Not intended to be used by application developers. Enforced by hiding the
       
   348     entity_pb classes.
       
   349 
       
   350     Args:
       
   351       pb: entity_pb.Reference
       
   352     """
       
   353     if not isinstance(pb, entity_pb.Reference):
       
   354       raise datastore_errors.BadArgumentError(
       
   355         'Key constructor takes an entity_pb.Reference; received %s (a %s).' %
       
   356         (pb, typename(pb)))
       
   357 
       
   358     key = Key()
       
   359     key.__reference = entity_pb.Reference()
       
   360     key.__reference.CopyFrom(pb)
       
   361     return key
       
   362 
       
   363   def _ToPb(self):
       
   364     """Converts this Key to its protocol buffer representation.
       
   365 
       
   366     Not intended to be used by application developers. Enforced by hiding the
       
   367     entity_pb classes.
       
   368 
       
   369     Returns:
       
   370       # the Reference PB representation of this Key
       
   371       entity_pb.Reference
       
   372     """
       
   373     pb = entity_pb.Reference()
       
   374     pb.CopyFrom(self.__reference)
       
   375     if not self.has_id_or_name():
       
   376       pb.mutable_path().element_list()[-1].set_id(0)
       
   377 
       
   378     pb.app().decode('utf-8')
       
   379     for pathelem in pb.path().element_list():
       
   380       pathelem.type().decode('utf-8')
       
   381 
       
   382     return pb
       
   383 
       
   384   def __str__(self):
       
   385     """Encodes this Key as an opaque string.
       
   386 
       
   387     Returns a string representation of this key, suitable for use in HTML,
       
   388     URLs, and other similar use cases. If the entity's key is incomplete,
       
   389     raises a BadKeyError.
       
   390 
       
   391     Unfortunately, this string encoding isn't particularly compact, and its
       
   392     length varies with the length of the path. If you want a shorter identifier
       
   393     and you know the kind and parent (if any) ahead of time, consider using just
       
   394     the entity's id or name.
       
   395 
       
   396     Returns:
       
   397       string
       
   398     """
       
   399     if (self.has_id_or_name()):
       
   400       encoded = base64.urlsafe_b64encode(self.__reference.Encode())
       
   401       return encoded.replace('=', '')
       
   402     else:
       
   403       raise datastore_errors.BadKeyError(
       
   404         'Cannot string encode an incomplete key!\n%s' % self.__reference)
       
   405 
       
   406   def __repr__(self):
       
   407     """Returns an eval()able string representation of this key.
       
   408 
       
   409     Returns a Python string of the form 'datastore_types.Key.from_path(...)'
       
   410     that can be used to recreate this key.
       
   411 
       
   412     Returns:
       
   413       string
       
   414     """
       
   415     args = []
       
   416     for elem in self.__reference.path().element_list():
       
   417       args.append(repr(elem.type()))
       
   418       if elem.has_name():
       
   419         args.append(repr(elem.name().decode('utf-8')))
       
   420       else:
       
   421         args.append(repr(elem.id()))
       
   422 
       
   423     args.append('_app=%r' % self.__reference.app().decode('utf-8'))
       
   424     return u'datastore_types.Key.from_path(%s)' % ', '.join(args)
       
   425 
       
   426   def __cmp__(self, other):
       
   427     """Returns negative, zero, or positive when comparing two keys.
       
   428 
       
   429     TODO(ryanb): for API v2, we should change this to make incomplete keys, ie
       
   430     keys without an id or name, not equal to any other keys.
       
   431 
       
   432     Args:
       
   433       other: Key to compare to.
       
   434 
       
   435     Returns:
       
   436       Negative if self is less than "other"
       
   437       Zero if "other" is equal to self
       
   438       Positive if self is greater than "other"
       
   439     """
       
   440     if not isinstance(other, Key):
       
   441       return -2
       
   442 
       
   443     self_args = []
       
   444     other_args = []
       
   445 
       
   446     if (self.app() in (_LOCAL_APP_ID, None) or
       
   447         other.app() in (_LOCAL_APP_ID, None)):
       
   448       pass
       
   449     else:
       
   450       self_args.append(self.__reference.app().decode('utf-8'))
       
   451       other_args.append(other.__reference.app().decode('utf-8'))
       
   452 
       
   453     for elem in self.__reference.path().element_list():
       
   454       self_args.append(repr(elem.type()))
       
   455       if elem.has_name():
       
   456         self_args.append(repr(elem.name().decode('utf-8')))
       
   457       else:
       
   458         self_args.append(elem.id())
       
   459 
       
   460     for elem in other.__reference.path().element_list():
       
   461       other_args.append(repr(elem.type()))
       
   462       if elem.has_name():
       
   463         other_args.append(repr(elem.name().decode('utf-8')))
       
   464       else:
       
   465         other_args.append(elem.id())
       
   466 
       
   467     result = cmp(self_args, other_args)
       
   468     return result
       
   469 
       
   470   def __hash__(self):
       
   471     """Returns a 32-bit integer hash of this key.
       
   472 
       
   473     Implements Python's hash protocol so that Keys may be used in sets and as
       
   474     dictionary keys.
       
   475 
       
   476     Returns:
       
   477       int
       
   478     """
       
   479     return hash(self.__str__())
       
   480 
       
   481 
       
   482 class Category(unicode):
       
   483   """A tag, ie a descriptive word or phrase. Entities may be tagged by users,
       
   484   and later returned by a queries for that tag. Tags can also be used for
       
   485   ranking results (frequency), photo captions, clustering, activity, etc.
       
   486 
       
   487   Here's a more in-depth description:  http://www.zeldman.com/daily/0405d.shtml
       
   488 
       
   489   This is the Atom "category" element. In XML output, the tag is provided as
       
   490   the term attribute. See:
       
   491   http://www.atomenabled.org/developers/syndication/#category
       
   492 
       
   493   Raises BadValueError if tag is not a string or subtype.
       
   494   """
       
   495   TERM = 'user-tag'
       
   496 
       
   497   def __init__(self, tag):
       
   498     super(Category, self).__init__(self, tag)
       
   499     ValidateString(tag, 'tag')
       
   500 
       
   501   def ToXml(self):
       
   502     return u'<category term="%s" label=%s />' % (Category.TERM,
       
   503                                                  saxutils.quoteattr(self))
       
   504 
       
   505 
       
   506 class Link(unicode):
       
   507   """A fully qualified URL. Usually http: scheme, but may also be file:, ftp:,
       
   508   news:, among others.
       
   509 
       
   510   If you have email (mailto:) or instant messaging (aim:, xmpp:) links,
       
   511   consider using the Email or IM classes instead.
       
   512 
       
   513   This is the Atom "link" element. In XML output, the link is provided as the
       
   514   href attribute. See:
       
   515   http://www.atomenabled.org/developers/syndication/#link
       
   516 
       
   517   Raises BadValueError if link is not a fully qualified, well-formed URL.
       
   518   """
       
   519   def __init__(self, link):
       
   520     super(Link, self).__init__(self, link)
       
   521     ValidateString(link, 'link', max_len=_MAX_LINK_PROPERTY_LENGTH)
       
   522 
       
   523     scheme, domain, path, params, query, fragment = urlparse.urlparse(link)
       
   524     if (not scheme or (scheme != 'file' and not domain) or
       
   525                       (scheme == 'file' and not path)):
       
   526       raise datastore_errors.BadValueError('Invalid URL: %s' % link)
       
   527 
       
   528   def ToXml(self):
       
   529     return u'<link href=%s />' % saxutils.quoteattr(self)
       
   530 
       
   531 
       
   532 class Email(unicode):
       
   533   """An RFC2822 email address. Makes no attempt at validation; apart from
       
   534   checking MX records, email address validation is a rathole.
       
   535 
       
   536   This is the gd:email element. In XML output, the email address is provided as
       
   537   the address attribute. See:
       
   538   http://code.google.com/apis/gdata/common-elements.html#gdEmail
       
   539 
       
   540   Raises BadValueError if email is not a valid email address.
       
   541   """
       
   542   def __init__(self, email):
       
   543     super(Email, self).__init__(self, email)
       
   544     ValidateString(email, 'email')
       
   545 
       
   546   def ToXml(self):
       
   547     return u'<gd:email address=%s />' % saxutils.quoteattr(self)
       
   548 
       
   549 
       
   550 class GeoPt(object):
       
   551   """A geographical point, specified by floating-point latitude and longitude
       
   552   coordinates. Often used to integrate with mapping sites like Google Maps.
       
   553   May also be used as ICBM coordinates.
       
   554 
       
   555   This is the georss:point element. In XML output, the coordinates are
       
   556   provided as the lat and lon attributes. See: http://georss.org/
       
   557 
       
   558   Serializes to '<lat>,<lon>'. Raises BadValueError if it's passed an invalid
       
   559   serialized string, or if lat and lon are not valid floating points in the
       
   560   ranges [-90, 90] and [-180, 180], respectively.
       
   561   """
       
   562   lat = None
       
   563   lon = None
       
   564 
       
   565   def __init__(self, lat, lon=None):
       
   566     if lon is None:
       
   567       try:
       
   568         split = lat.split(',')
       
   569         lat, lon = split
       
   570       except (AttributeError, ValueError):
       
   571         raise datastore_errors.BadValueError(
       
   572           'Expected a "lat,long" formatted string; received %s (a %s).' %
       
   573           (lat, typename(lat)))
       
   574 
       
   575     try:
       
   576       lat = float(lat)
       
   577       lon = float(lon)
       
   578       if abs(lat) > 90:
       
   579         raise datastore_errors.BadValueError(
       
   580           'Latitude must be between -90 and 90; received %f' % lat)
       
   581       if abs(lon) > 180:
       
   582         raise datastore_errors.BadValueError(
       
   583           'Longitude must be between -180 and 180; received %f' % lon)
       
   584     except (TypeError, ValueError):
       
   585       raise datastore_errors.BadValueError(
       
   586         'Expected floats for lat and long; received %s (a %s) and %s (a %s).' %
       
   587         (lat, typename(lat), lon, typename(lon)))
       
   588 
       
   589     self.lat = lat
       
   590     self.lon = lon
       
   591 
       
   592   def __cmp__(self, other):
       
   593     if not isinstance(other, GeoPt):
       
   594       try:
       
   595         other = GeoPt(other)
       
   596       except datastore_errors.BadValueError:
       
   597         return NotImplemented
       
   598 
       
   599     lat_cmp = cmp(self.lat, other.lat)
       
   600     if lat_cmp != 0:
       
   601       return lat_cmp
       
   602     else:
       
   603       return cmp(self.lon, other.lon)
       
   604 
       
   605   def __hash__(self):
       
   606     """Returns a 32-bit integer hash of this point.
       
   607 
       
   608     Implements Python's hash protocol so that GeoPts may be used in sets and
       
   609     as dictionary keys.
       
   610 
       
   611     Returns:
       
   612       int
       
   613     """
       
   614     return hash((self.lat, self.lon))
       
   615 
       
   616   def __repr__(self):
       
   617     """Returns an eval()able string representation of this GeoPt.
       
   618 
       
   619     The returned string is of the form 'datastore_types.GeoPt([lat], [lon])'.
       
   620 
       
   621     Returns:
       
   622       string
       
   623     """
       
   624     return 'datastore_types.GeoPt(%r, %r)' % (self.lat, self.lon)
       
   625 
       
   626   def __unicode__(self):
       
   627     return u'%s,%s' % (unicode(self.lat), unicode(self.lon))
       
   628 
       
   629   __str__ = __unicode__
       
   630 
       
   631   def ToXml(self):
       
   632     return u'<georss:point>%s %s</georss:point>' % (unicode(self.lat),
       
   633                                                     unicode(self.lon))
       
   634 
       
   635 class IM(object):
       
   636   """An instant messaging handle. Includes both an address and its protocol.
       
   637   The protocol value is either a standard IM scheme or a URL identifying the
       
   638   IM network for the protocol. Possible values include:
       
   639 
       
   640     Value                           Description
       
   641     sip                             SIP/SIMPLE
       
   642     unknown                         Unknown or unspecified
       
   643     xmpp                            XMPP/Jabber
       
   644     http://aim.com/                 AIM
       
   645     http://icq.com/                 ICQ
       
   646     http://talk.google.com/         Google Talk
       
   647     http://messenger.msn.com/       MSN Messenger
       
   648     http://messenger.yahoo.com/     Yahoo Messenger
       
   649     http://sametime.com/            Lotus Sametime
       
   650     http://gadu-gadu.pl/            Gadu-Gadu
       
   651 
       
   652   This is the gd:im element. In XML output, the address and protocol are
       
   653   provided as the address and protocol attributes, respectively. See:
       
   654   http://code.google.com/apis/gdata/common-elements.html#gdIm
       
   655 
       
   656   Serializes to '<protocol> <address>'. Raises BadValueError if tag is not a
       
   657   standard IM scheme or a URL.
       
   658   """
       
   659   PROTOCOLS = [ 'sip', 'unknown', 'xmpp' ]
       
   660 
       
   661   protocol = None
       
   662   address = None
       
   663 
       
   664   def __init__(self, protocol, address=None):
       
   665     if address is None:
       
   666       try:
       
   667         split = protocol.split(' ')
       
   668         protocol, address = split
       
   669       except (AttributeError, ValueError):
       
   670         raise datastore_errors.BadValueError(
       
   671           'Expected string of format "protocol address"; received %s' %
       
   672           str(protocol))
       
   673 
       
   674     ValidateString(address, 'address')
       
   675     if protocol not in self.PROTOCOLS:
       
   676       Link(protocol)
       
   677 
       
   678     self.address = address
       
   679     self.protocol = protocol
       
   680 
       
   681   def __cmp__(self, other):
       
   682     if not isinstance(other, IM):
       
   683       try:
       
   684         other = IM(other)
       
   685       except datastore_errors.BadValueError:
       
   686         return NotImplemented
       
   687 
       
   688     return cmp((self.address, self.protocol),
       
   689                (other.address, other.protocol))
       
   690 
       
   691   def __repr__(self):
       
   692     """Returns an eval()able string representation of this IM.
       
   693 
       
   694     The returned string is of the form:
       
   695 
       
   696       datastore_types.IM('address', 'protocol')
       
   697 
       
   698     Returns:
       
   699       string
       
   700     """
       
   701     return 'datastore_types.IM(%r, %r)' % (self.protocol, self.address)
       
   702 
       
   703   def __unicode__(self):
       
   704     return u'%s %s' % (self.protocol, self.address)
       
   705 
       
   706   __str__ = __unicode__
       
   707 
       
   708   def ToXml(self):
       
   709     return (u'<gd:im protocol=%s address=%s />' %
       
   710             (saxutils.quoteattr(self.protocol),
       
   711              saxutils.quoteattr(self.address)))
       
   712 
       
   713   def __len__(self):
       
   714     return len(unicode(self))
       
   715 
       
   716 class PhoneNumber(unicode):
       
   717   """A human-readable phone number or address.
       
   718 
       
   719   No validation is performed. Phone numbers have many different formats -
       
   720   local, long distance, domestic, international, internal extension, TTY,
       
   721   VOIP, SMS, and alternative networks like Skype, XFire and Roger Wilco. They
       
   722   all have their own numbering and addressing formats.
       
   723 
       
   724   This is the gd:phoneNumber element. In XML output, the phone number is
       
   725   provided as the text of the element. See:
       
   726   http://code.google.com/apis/gdata/common-elements.html#gdPhoneNumber
       
   727 
       
   728   Raises BadValueError if phone is not a string or subtype.
       
   729   """
       
   730   def __init__(self, phone):
       
   731     super(PhoneNumber, self).__init__(self, phone)
       
   732     ValidateString(phone, 'phone')
       
   733 
       
   734   def ToXml(self):
       
   735     return u'<gd:phoneNumber>%s</gd:phoneNumber>' % saxutils.escape(self)
       
   736 
       
   737 
       
   738 class PostalAddress(unicode):
       
   739   """A human-readable mailing address. Again, mailing address formats vary
       
   740   widely, so no validation is performed.
       
   741 
       
   742   This is the gd:postalAddress element. In XML output, the address is provided
       
   743   as the text of the element. See:
       
   744   http://code.google.com/apis/gdata/common-elements.html#gdPostalAddress
       
   745 
       
   746   Raises BadValueError if address is not a string or subtype.
       
   747   """
       
   748   def __init__(self, address):
       
   749     super(PostalAddress, self).__init__(self, address)
       
   750     ValidateString(address, 'address')
       
   751 
       
   752   def ToXml(self):
       
   753     return u'<gd:postalAddress>%s</gd:postalAddress>' % saxutils.escape(self)
       
   754 
       
   755 
       
   756 class Rating(long):
       
   757   """A user-provided integer rating for a piece of content. Normalized to a
       
   758   0-100 scale.
       
   759 
       
   760   This is the gd:rating element. In XML output, the address is provided
       
   761   as the text of the element. See:
       
   762   http://code.google.com/apis/gdata/common-elements.html#gdRating
       
   763 
       
   764   Serializes to the decimal string representation of the rating. Raises
       
   765   BadValueError if the rating is not an integer in the range [0, 100].
       
   766   """
       
   767   MIN = 0
       
   768   MAX = 100
       
   769 
       
   770   def __init__(self, rating):
       
   771     super(Rating, self).__init__(self, rating)
       
   772     if isinstance(rating, float) or isinstance(rating, complex):
       
   773       raise datastore_errors.BadValueError(
       
   774         'Expected int or long; received %s (a %s).' %
       
   775         (rating, typename(rating)))
       
   776 
       
   777     try:
       
   778       if long(rating) < Rating.MIN or long(rating) > Rating.MAX:
       
   779         raise datastore_errors.BadValueError()
       
   780     except ValueError:
       
   781       raise datastore_errors.BadValueError(
       
   782         'Expected int or long; received %s (a %s).' %
       
   783         (rating, typename(rating)))
       
   784 
       
   785   def ToXml(self):
       
   786     return (u'<gd:rating value="%d" min="%d" max="%d" />' %
       
   787             (self, Rating.MIN, Rating.MAX))
       
   788 
       
   789 
       
   790 class Text(unicode):
       
   791   """A long string type.
       
   792 
       
   793   Strings of any length can be stored in the datastore using this
       
   794   type. It behaves identically to the Python unicode type, except for
       
   795   the constructor, which only accepts str and unicode arguments.
       
   796   """
       
   797 
       
   798   def __new__(cls, arg=None, encoding=None):
       
   799     """Constructor.
       
   800 
       
   801     We only accept unicode and str instances, the latter with encoding.
       
   802 
       
   803     Args:
       
   804       arg: optional unicode or str instance; default u''
       
   805       encoding: optional encoding; disallowed when isinstance(arg, unicode),
       
   806                 defaults to 'ascii' when isinstance(arg, str);
       
   807     """
       
   808     if arg is None:
       
   809       arg = u''
       
   810     if isinstance(arg, unicode):
       
   811       if encoding is not None:
       
   812         raise TypeError('Text() with a unicode argument '
       
   813                         'should not specify an encoding')
       
   814       return super(Text, cls).__new__(cls, arg)
       
   815 
       
   816     if isinstance(arg, str):
       
   817       if encoding is None:
       
   818         encoding = 'ascii'
       
   819       return super(Text, cls).__new__(cls, arg, encoding)
       
   820 
       
   821     raise TypeError('Text() argument should be str or unicode, not %s' %
       
   822                     type(arg).__name__)
       
   823 
       
   824 class Blob(str):
       
   825   """A blob type, appropriate for storing binary data of any length.
       
   826 
       
   827   This behaves identically to the Python str type, except for the
       
   828   constructor, which only accepts str arguments.
       
   829   """
       
   830 
       
   831   def __new__(cls, arg=None):
       
   832     """Constructor.
       
   833 
       
   834     We only accept str instances.
       
   835 
       
   836     Args:
       
   837       arg: optional str instance (default '')
       
   838     """
       
   839     if arg is None:
       
   840       arg = ''
       
   841     if isinstance(arg, str):
       
   842       return super(Blob, cls).__new__(cls, arg)
       
   843 
       
   844     raise TypeError('Blob() argument should be str instance, not %s' %
       
   845                     type(arg).__name__)
       
   846 
       
   847 
       
   848 _PROPERTY_TYPES = [
       
   849   str,
       
   850   unicode,
       
   851   bool,
       
   852   int,
       
   853   long,
       
   854   type(None),
       
   855   float,
       
   856   Key,
       
   857   datetime.datetime,
       
   858   Blob,
       
   859   Text,
       
   860   users.User,
       
   861   Category,
       
   862   Link,
       
   863   Email,
       
   864   GeoPt,
       
   865   IM,
       
   866   PhoneNumber,
       
   867   PostalAddress,
       
   868   Rating,
       
   869   ]
       
   870 
       
   871 _PROPERTY_MEANINGS = {
       
   872 
       
   873 
       
   874 
       
   875   Blob:              entity_pb.Property.BLOB,
       
   876   Text:              entity_pb.Property.TEXT,
       
   877   datetime.datetime: entity_pb.Property.GD_WHEN,
       
   878   Category:          entity_pb.Property.ATOM_CATEGORY,
       
   879   Link:              entity_pb.Property.ATOM_LINK,
       
   880   Email:             entity_pb.Property.GD_EMAIL,
       
   881   GeoPt:             entity_pb.Property.GEORSS_POINT,
       
   882   IM:                entity_pb.Property.GD_IM,
       
   883   PhoneNumber:       entity_pb.Property.GD_PHONENUMBER,
       
   884   PostalAddress:     entity_pb.Property.GD_POSTALADDRESS,
       
   885   Rating:            entity_pb.Property.GD_RATING,
       
   886   }
       
   887 
       
   888 _RAW_PROPERTY_TYPES = (
       
   889   Blob,
       
   890   Text,
       
   891 )
       
   892 
       
   893 def ToPropertyPb(name, values):
       
   894   """Creates a type-specific onestore property PB from a property name and a
       
   895   value or list of values. Determines the type of property based on the type
       
   896   of the value(s).
       
   897 
       
   898   If name is invalid, Serialize throws a BadPropertyError. If values is
       
   899   an unsupported type, or an empty list, or a list with elements of different
       
   900   types, Serialize throws a BadValueError.
       
   901 
       
   902   Args:
       
   903     # the property name
       
   904     name: string
       
   905     # either a supported type or a list of them. if a list, all
       
   906     # of the list's elements should be of the same type
       
   907     values: string, int, long, float, datetime, Key, or list
       
   908 
       
   909   Returns:
       
   910     # a list of or single StringProperty, Int64Property, BoolProperty,
       
   911     # DoubleProperty, PointProperty, UserProperty, or ReferenceProperty.
       
   912     [entity_pb.*Property, ...]
       
   913   """
       
   914   ValidateString(name, 'property name', datastore_errors.BadPropertyError)
       
   915   if RESERVED_PROPERTY_NAME.match(name):
       
   916     raise datastore_errors.BadPropertyError('%s is a reserved property name.' %
       
   917                                             name)
       
   918 
       
   919   if isinstance(values, tuple):
       
   920     raise datastore_errors.BadValueError(
       
   921         'May not use tuple property value; property %s is %s.' %
       
   922         (name, repr(values)))
       
   923 
       
   924   if isinstance(values, list):
       
   925     multiple = True
       
   926   else:
       
   927     multiple = False
       
   928     values = [values]
       
   929 
       
   930   if not values:
       
   931     raise datastore_errors.BadValueError(
       
   932         'May not use the empty list as a property value; property %s is %s.' %
       
   933         (name, repr(values)))
       
   934 
       
   935   def long_if_int(val):
       
   936     if isinstance(val, int) and not isinstance(val, bool):
       
   937       return long(val)
       
   938     else:
       
   939       return val
       
   940 
       
   941   values = [long_if_int(v) for v in values]
       
   942 
       
   943   try:
       
   944     proptype = values[0].__class__
       
   945     for v in values:
       
   946       if v is not None:
       
   947         if (v.__class__ is not proptype and not
       
   948             (v.__class__ in (str, unicode) and proptype in (str, unicode))):
       
   949           raise datastore_errors.BadValueError(
       
   950               'Values for property %s have mismatched types: %s (a %s) and '
       
   951               '%s (a %s).' % (name, values[0], proptype, v, typename(v)))
       
   952         elif (isinstance(v, Key) and not v.has_id_or_name()):
       
   953           raise datastore_errors.BadValueError(
       
   954               'Incomplete key found for reference property %s.' % name)
       
   955   except (KeyError, ValueError, TypeError, IndexError, AttributeError), msg:
       
   956     raise datastore_errors.BadValueError(
       
   957       'Error type checking values for property %s: %s' % (name, msg))
       
   958 
       
   959   if proptype not in _PROPERTY_TYPES:
       
   960     raise datastore_errors.BadValueError(
       
   961       'Unsupported type for property %s: %s' % (name, proptype))
       
   962 
       
   963   pbs = []
       
   964   for v in values:
       
   965     pb = entity_pb.Property()
       
   966     pb.set_name(name.encode('utf-8'))
       
   967     pb.set_multiple(multiple)
       
   968     if _PROPERTY_MEANINGS.has_key(proptype):
       
   969       pb.set_meaning(_PROPERTY_MEANINGS[proptype])
       
   970 
       
   971     pbvalue = pb.mutable_value()
       
   972     if v is None:
       
   973       pass
       
   974     elif isinstance(v, Blob):
       
   975       pbvalue.set_stringvalue(v)
       
   976     elif isinstance(v, (basestring, IM)):
       
   977       if not isinstance(v, Text):
       
   978         if isinstance(v, Link):
       
   979           max_len = _MAX_LINK_PROPERTY_LENGTH
       
   980         else:
       
   981           max_len = _MAX_STRING_LENGTH
       
   982         if len(v) > max_len:
       
   983           raise datastore_errors.BadValueError(
       
   984             'Property %s is %d bytes long; it must be %d or less. '
       
   985             'Consider Text instead, which can store strings of any length.' %
       
   986             (name, len(v), max_len))
       
   987       pbvalue.set_stringvalue(unicode(v).encode('utf-8'))
       
   988     elif isinstance(v, datetime.datetime):
       
   989       if v.tzinfo:
       
   990         v = v.astimezone(UTC)
       
   991       pbvalue.set_int64value(
       
   992         long(calendar.timegm(v.timetuple()) * 1000000L) + v.microsecond)
       
   993     elif isinstance(v, GeoPt):
       
   994       pbvalue.mutable_pointvalue().set_x(v.lat)
       
   995       pbvalue.mutable_pointvalue().set_y(v.lon)
       
   996     elif isinstance(v, users.User):
       
   997       pbvalue.mutable_uservalue().set_email(v.email().encode('utf-8'))
       
   998       pbvalue.mutable_uservalue().set_auth_domain(
       
   999         v.auth_domain().encode('utf-8'))
       
  1000       pbvalue.mutable_uservalue().set_gaiaid(0)
       
  1001     elif isinstance(v, Key):
       
  1002       ref = v._Key__reference
       
  1003       pbvalue.mutable_referencevalue().set_app(ref.app())
       
  1004       for elem in ref.path().element_list():
       
  1005         pbvalue.mutable_referencevalue().add_pathelement().CopyFrom(elem)
       
  1006     elif isinstance(v, bool):
       
  1007       pbvalue.set_booleanvalue(v)
       
  1008     elif isinstance(v, long):
       
  1009       pbvalue.set_int64value(v)
       
  1010       try:
       
  1011         pbvalue.Encode()
       
  1012       except ProtocolBuffer.ProtocolBufferEncodeError, e:
       
  1013         pbvalue.clear_int64value()
       
  1014         raise OverflowError(e)
       
  1015     elif isinstance(v, float):
       
  1016       pbvalue.set_doublevalue(v)
       
  1017     else:
       
  1018       assert False, "Shouldn't reach here; property type was validated above."
       
  1019 
       
  1020     pbs.append(pb)
       
  1021 
       
  1022   if multiple:
       
  1023     return pbs
       
  1024   else:
       
  1025     return pbs[0]
       
  1026 
       
  1027 
       
  1028 def FromReferenceProperty(value):
       
  1029   """Converts a reference PropertyValue to a Key. Raises BadValueError is prop
       
  1030   is not a PropertyValue.
       
  1031 
       
  1032   Args:
       
  1033     value: entity_pb.PropertyValue
       
  1034 
       
  1035   Returns:
       
  1036     Key
       
  1037   """
       
  1038   assert isinstance(value, entity_pb.PropertyValue)
       
  1039   assert value.has_referencevalue()
       
  1040   ref = value.referencevalue()
       
  1041 
       
  1042   key = Key()
       
  1043   key_ref = key._Key__reference
       
  1044   key_ref.set_app(ref.app())
       
  1045 
       
  1046   for pathelem in ref.pathelement_list():
       
  1047     key_ref.mutable_path().add_element().CopyFrom(pathelem)
       
  1048 
       
  1049   return key
       
  1050 
       
  1051 
       
  1052 _EPOCH = datetime.datetime.utcfromtimestamp(0)
       
  1053 
       
  1054 _PROPERTY_CONVERSIONS = {
       
  1055   entity_pb.Property.GD_WHEN:
       
  1056 
       
  1057 
       
  1058     lambda val: _EPOCH + datetime.timedelta(microseconds=val),
       
  1059   entity_pb.Property.ATOM_CATEGORY:     Category,
       
  1060   entity_pb.Property.ATOM_LINK:         Link,
       
  1061   entity_pb.Property.GD_EMAIL:          Email,
       
  1062   entity_pb.Property.GEORSS_POINT:      lambda coords: GeoPt(*coords),
       
  1063   entity_pb.Property.GD_IM:             IM,
       
  1064   entity_pb.Property.GD_PHONENUMBER:    PhoneNumber,
       
  1065   entity_pb.Property.GD_POSTALADDRESS:  PostalAddress,
       
  1066   entity_pb.Property.GD_RATING:         Rating,
       
  1067   entity_pb.Property.BLOB:              Blob,
       
  1068   entity_pb.Property.TEXT:              Text,
       
  1069   }
       
  1070 
       
  1071 def FromPropertyPb(pb):
       
  1072   """Converts a onestore property PB to a python value.
       
  1073 
       
  1074   Args:
       
  1075     pb: entity_pb.Property
       
  1076 
       
  1077   Returns:
       
  1078     # return type is determined by the type of the argument
       
  1079     string, int, bool, double, users.User, or one of the atom or gd types
       
  1080   """
       
  1081   if not isinstance(pb, entity_pb.Property):
       
  1082     raise datastore_errors.BadValueError(
       
  1083       'Expected PropertyValue; received %s (a %s).' % (pb, typename(pb)))
       
  1084 
       
  1085   pbval = pb.value()
       
  1086 
       
  1087   if (pbval.has_stringvalue()):
       
  1088     value = pbval.stringvalue()
       
  1089     if pb.meaning() != entity_pb.Property.BLOB:
       
  1090       value = unicode(value.decode('utf-8'))
       
  1091   elif pbval.has_pointvalue():
       
  1092     value = (pbval.pointvalue().x(), pbval.pointvalue().y())
       
  1093   elif pbval.has_uservalue():
       
  1094     email = unicode(pbval.uservalue().email().decode('utf-8'))
       
  1095     auth_domain = unicode(pbval.uservalue().auth_domain().decode('utf-8'))
       
  1096     value = users.User(email=email, _auth_domain=auth_domain)
       
  1097   elif pbval.has_referencevalue():
       
  1098     value = FromReferenceProperty(pbval)
       
  1099   elif pbval.has_int64value():
       
  1100     value = long(pbval.int64value())
       
  1101   elif pbval.has_booleanvalue():
       
  1102     value = bool(pbval.booleanvalue())
       
  1103   elif pbval.has_doublevalue():
       
  1104     value = float(pbval.doublevalue())
       
  1105   else:
       
  1106     if pb.multiple():
       
  1107       raise datastore_errors.BadValueError(
       
  1108           'Record indicated as multiple, but has no values.')
       
  1109     else:
       
  1110       value = None
       
  1111 
       
  1112   try:
       
  1113     if pb.has_meaning() and pb.meaning() in _PROPERTY_CONVERSIONS:
       
  1114       value = _PROPERTY_CONVERSIONS[pb.meaning()](value)
       
  1115   except (KeyError, ValueError, IndexError, TypeError, AttributeError), msg:
       
  1116     raise datastore_errors.BadValueError(
       
  1117       'Error converting pb: %s\nException was: %s' % (pb, msg))
       
  1118 
       
  1119   return value
       
  1120 
       
  1121 
       
  1122 def PropertyTypeName(value):
       
  1123   """Returns the name of the type of the given property value, as a string.
       
  1124 
       
  1125   Raises BadValueError if the value is not a valid property type.
       
  1126 
       
  1127   Args:
       
  1128     value: any valid property value
       
  1129 
       
  1130   Returns:
       
  1131     string
       
  1132   """
       
  1133   if value.__class__ in _PROPERTY_MEANINGS:
       
  1134     meaning = _PROPERTY_MEANINGS[value.__class__]
       
  1135     name = entity_pb.Property._Meaning_NAMES[meaning]
       
  1136     return name.lower().replace('_', ':')
       
  1137   elif isinstance(value, basestring):
       
  1138     return 'string'
       
  1139   elif isinstance(value, users.User):
       
  1140     return 'user'
       
  1141   elif isinstance(value, long):
       
  1142     return 'int'
       
  1143   elif value is None:
       
  1144     return 'null'
       
  1145   else:
       
  1146     return typename(value).lower()
       
  1147 
       
  1148 _PROPERTY_TYPE_STRINGS = {
       
  1149     'string':           unicode,
       
  1150     'bool':             bool,
       
  1151     'int':              long,
       
  1152     'null':             type(None),
       
  1153     'float':            float,
       
  1154     'key':              Key,
       
  1155     'blob':             Blob,
       
  1156     'text':             Text,
       
  1157     'user':             users.User,
       
  1158     'atom:category':    Category,
       
  1159     'atom:link':        Link,
       
  1160     'gd:email':         Email,
       
  1161     'gd:when':          datetime.datetime,
       
  1162     'georss:point':     GeoPt,
       
  1163     'gd:im':            IM,
       
  1164     'gd:phonenumber':   PhoneNumber,
       
  1165     'gd:postaladdress': PostalAddress,
       
  1166     'gd:rating':        Rating,
       
  1167     }
       
  1168 
       
  1169 
       
  1170 def FromPropertyTypeName(type_name):
       
  1171   """Returns the python type given a type name.
       
  1172 
       
  1173   Args:
       
  1174     type_name: A string representation of a datastore type name.
       
  1175 
       
  1176   Returns:
       
  1177     A python type.
       
  1178   """
       
  1179   return _PROPERTY_TYPE_STRINGS[type_name]
       
  1180 
       
  1181 
       
  1182 def PropertyValueFromString(type_, value_string, _auth_domain=None):
       
  1183   """Returns an instance of a property value given a type and string value.
       
  1184 
       
  1185   The reverse of this method is just str() and type() of the python value.
       
  1186 
       
  1187   Note that this does *not* support non-UTC offsets in ISO 8601-formatted
       
  1188   datetime strings, e.g. the -08:00 suffix in '2002-12-25 00:00:00-08:00'.
       
  1189   It only supports -00:00 and +00:00 suffixes, which are UTC.
       
  1190 
       
  1191   Args:
       
  1192     type_: A python class.
       
  1193     value_string: A string representation of the value of the property.
       
  1194 
       
  1195   Returns:
       
  1196     An instance of 'type'.
       
  1197 
       
  1198   Raises:
       
  1199     ValueError if type_ is datetime and value_string has a timezone offset.
       
  1200   """
       
  1201   if type_ == datetime.datetime:
       
  1202     if value_string[-6] in ('+', '-'):
       
  1203       if value_string[-5:] == '00:00':
       
  1204         value_string = value_string[:-6]
       
  1205       else:
       
  1206         raise ValueError('Non-UTC offsets in datetimes are not supported.')
       
  1207 
       
  1208     split = value_string.split('.')
       
  1209     iso_date = split[0]
       
  1210     microseconds = 0
       
  1211     if len(split) > 1:
       
  1212       microseconds = int(split[1])
       
  1213 
       
  1214     time_struct = time.strptime(iso_date, '%Y-%m-%d %H:%M:%S')[0:6]
       
  1215     value = datetime.datetime(*(time_struct + (microseconds,)))
       
  1216     return value
       
  1217   elif type_ == Rating:
       
  1218     return Rating(int(value_string))
       
  1219   elif type_ == bool:
       
  1220     return value_string == 'True'
       
  1221   elif type_ == users.User:
       
  1222     return users.User(value_string, _auth_domain)
       
  1223   elif type_ == type(None):
       
  1224     return None
       
  1225   return type_(value_string)