--- a/thirdparty/google_appengine/google/appengine/api/datastore_types.py Sat Sep 05 14:04:24 2009 +0200
+++ b/thirdparty/google_appengine/google/appengine/api/datastore_types.py Sun Sep 06 23:31:53 2009 +0200
@@ -47,6 +47,7 @@
from google.appengine.datastore import datastore_pb
from google.appengine.api import datastore_errors
from google.appengine.api import users
+from google.appengine.api import namespace_manager
from google.net.proto import ProtocolBuffer
from google.appengine.datastore import entity_pb
@@ -59,6 +60,8 @@
_KEY_SPECIAL_PROPERTY = '__key__'
_SPECIAL_PROPERTIES = frozenset([_KEY_SPECIAL_PROPERTY])
+_NAMESPACE_SEPARATOR='!'
+
class UtcTzinfo(datetime.tzinfo):
def utcoffset(self, dt): return datetime.timedelta(0)
def dst(self, dt): return datetime.timedelta(0)
@@ -80,7 +83,8 @@
def ValidateString(value,
name='unused',
exception=datastore_errors.BadValueError,
- max_len=_MAX_STRING_LENGTH):
+ max_len=_MAX_STRING_LENGTH,
+ empty_ok=False):
"""Raises an exception if value is not a valid string or a subclass thereof.
A string is valid if it's not empty, no more than _MAX_STRING_LENGTH bytes,
@@ -91,17 +95,49 @@
value: the value to validate.
name: the name of this value; used in the exception message.
exception: the type of exception to raise.
- max_len: the maximum allowed length, in bytes
+ max_len: the maximum allowed length, in bytes.
+ empty_ok: allow empty value.
"""
+ if value is None and empty_ok:
+ return
if not isinstance(value, basestring) or isinstance(value, Blob):
raise exception('%s should be a string; received %s (a %s):' %
(name, value, typename(value)))
- if not value:
+ if not value and not empty_ok:
raise exception('%s must not be empty.' % name)
if len(value.encode('utf-8')) > max_len:
raise exception('%s must be under %d bytes.' % (name, max_len))
+def ValidateInteger(value,
+ name='unused',
+ exception=datastore_errors.BadValueError,
+ empty_ok=False,
+ zero_ok=False,
+ negative_ok=False):
+ """Raises an exception if value is not a valid integer.
+
+ An integer is valid if it's not negative or empty and is an integer.
+ The exception type can be specified with the exception argument;
+ it defaults to BadValueError.
+
+ Args:
+ value: the value to validate.
+ name: the name of this value; used in the exception message.
+ exception: the type of exception to raise.
+ empty_ok: allow None value.
+ zero_ok: allow zero value.
+ negative_ok: allow negative value.
+ """
+ if value is None and empty_ok:
+ return
+ if not isinstance(value, int):
+ raise exception('%s should be an integer; received %s (a %s).' %
+ (name, value, typename(value)))
+ if not value and not zero_ok:
+ raise exception('%s must not be 0 (zero)' % name)
+ if value < 0 and not negative_ok:
+ raise exception('%s must not be negative.' % name)
def ResolveAppId(app, name='_app'):
"""Validate app id, providing a default.
@@ -124,6 +160,152 @@
return app
+class AppIdNamespace(object):
+ """Combined AppId and Namespace
+
+ An identifier that combines the application identifier and the
+ namespace.
+ """
+ __app_id = None
+ __namespace = None
+
+ def __init__(self, app_id, namespace):
+ """Constructor. Creates a AppIdNamespace from two strings.
+
+ Args:
+ app_id: application identifier string
+ namespace: namespace identifier string
+ Raises:
+ BadArgumentError if the values contain
+ the _NAMESPACE_SEPARATOR character (!) or
+ the app_id is empty.
+ """
+ self.__app_id = app_id
+ if namespace:
+ self.__namespace = namespace
+ else:
+ self.__namespace = None
+ ValidateString(self.__app_id, 'app_id', datastore_errors.BadArgumentError)
+ ValidateString(self.__namespace,
+ 'namespace', datastore_errors.BadArgumentError,
+ empty_ok=True)
+ if _NAMESPACE_SEPARATOR in self.__app_id:
+ raise datastore_errors.BadArgumentError(
+ 'app_id must not contain a "%s"' % _NAMESPACE_SEPARATOR)
+ if self.__namespace and _NAMESPACE_SEPARATOR in self.__namespace:
+ raise datastore_errors.BadArgumentError(
+ 'namespace must not contain a "%s"' % _NAMESPACE_SEPARATOR)
+
+ def __cmp__(self, other):
+ """Returns negative, zero, or positive when comparing two AppIdNamespace.
+
+ Args:
+ other: AppIdNamespace to compare to.
+
+ Returns:
+ Negative if self is less than "other"
+ Zero if "other" is equal to self
+ Positive if self is greater than "other"
+ """
+ if not isinstance(other, AppIdNamespace):
+ return cmp(id(self), id(other))
+ return cmp((self.__app_id, self.__namespace),
+ (other.__app_id, other.__namespace))
+
+ def to_encoded(self):
+ """Returns this AppIdNamespace's string equivalent
+
+ i.e. "app!namespace"
+ """
+ if not self.__namespace:
+ return self.__app_id
+ else:
+ return self.__app_id + _NAMESPACE_SEPARATOR + self.__namespace
+
+ def app_id(self):
+ """Returns this AppId portion of this AppIdNamespace.
+ """
+ return self.__app_id;
+
+ def namespace(self):
+ """Returns this namespace portion of this AppIdNamespace.
+ """
+ return self.__namespace;
+
+
+def PartitionString(value, separator):
+ """Equivalent to python2.5 str.partition()
+ TODO(gmariani) use str.partition() when python 2.5 is adopted.
+
+ Args:
+ value: String to be partitioned
+ separator: Separator string
+ """
+ index = value.find(separator);
+ if index == -1:
+ return (value, '', value[0:0]);
+ else:
+ return (value[0:index], separator, value[index+len(separator):len(value)])
+
+
+def parse_app_id_namespace(app_id_namespace):
+ """
+ An app_id_namespace string is valid if it's not empty, and contains
+ at most one namespace separator ('!'). Also, an app_id_namespace
+ with an empty namespace must not contain a namespace separator.
+
+ Args:
+ app_id_namespace: an encoded app_id_namespace.
+ Raises exception if format of app_id_namespace is invalid.
+ """
+ if not app_id_namespace:
+ raise datastore_errors.BadArgumentError(
+ 'app_id_namespace must be non empty')
+ parts = PartitionString(app_id_namespace, _NAMESPACE_SEPARATOR)
+ if parts[1] == _NAMESPACE_SEPARATOR:
+ if not parts[2]:
+ raise datastore_errors.BadArgumentError(
+ 'app_id_namespace must not contain a "%s" if the namespace is empty' %
+ _NAMESPACE_SEPARATOR)
+ if parts[2]:
+ return AppIdNamespace(parts[0], parts[2])
+ return AppIdNamespace(parts[0], None)
+
+def ResolveAppIdNamespace(
+ app_id=None, namespace=None, app_id_namespace=None):
+ """Validate an app id/namespace and substitute default values.
+
+ If the argument is None, $APPLICATION_ID!$NAMESPACE is substituted.
+
+ Args:
+ app_id: The app id argument value to be validated.
+ namespace: The namespace argument value to be validated.
+ app_id_namespace: An AppId/Namespace pair
+
+ Returns:
+ An AppIdNamespace object initialized with AppId and Namespace.
+
+ Raises:
+ BadArgumentError if the value is empty or not a string.
+ """
+ if app_id_namespace is None:
+ if app_id is None:
+ app_id = os.environ.get('APPLICATION_ID', '')
+ if namespace is None:
+ namespace = namespace_manager.get_request_namespace();
+ else:
+ if not app_id is None:
+ raise datastore_errors.BadArgumentError(
+ 'app_id is overspecified. Cannot define app_id_namespace and app_id')
+ if not namespace is None:
+ raise datastore_errors.BadArgumentError(
+ 'namespace is overspecified. ' +
+ 'Cannot define app_id_namespace and namespace')
+ return parse_app_id_namespace(app_id_namespace)
+
+ return AppIdNamespace(app_id, namespace)
+
+
class Key(object):
"""The primary key for a datastore entity.
@@ -172,6 +354,26 @@
else:
self.__reference = entity_pb.Reference()
+ def to_path(self):
+ """Construct the "path" of this key as a list.
+
+ Returns:
+ A list [kind_1, id_or_name_1, ..., kind_n, id_or_name_n] of the key path.
+
+ Raises:
+ datastore_errors.BadKeyError if this key does not have a valid path.
+ """
+ path = []
+ for path_element in self.__reference.path().element_list():
+ path.append(path_element.type().decode('utf-8'))
+ if path_element.has_name():
+ path.append(path_element.name().decode('utf-8'))
+ elif path_element.has_id():
+ path.append(path_element.id())
+ else:
+ raise datastore_errors.BadKeyError('Incomplete key found in to_path')
+ return path
+
@staticmethod
def from_path(*args, **kwds):
"""Static method to construct a Key out of a "path" (kind, id or name, ...).
@@ -202,7 +404,10 @@
BadKeyError if the parent key is incomplete.
"""
parent = kwds.pop('parent', None)
- _app = ResolveAppId(kwds.pop('_app', None))
+ _app_id_namespace_obj = ResolveAppIdNamespace(
+ kwds.pop('_app', None),
+ kwds.pop('_namespace', None),
+ kwds.pop('_app_id_namespace', None))
if kwds:
raise datastore_errors.BadArgumentError(
@@ -221,17 +426,18 @@
if not parent.has_id_or_name():
raise datastore_errors.BadKeyError(
'The parent Key is incomplete.')
- if _app != parent.app():
+ if _app_id_namespace_obj != parent.app_id_namespace():
raise datastore_errors.BadArgumentError(
- 'The _app argument (%r) should match parent.app() (%s)' %
- (_app, parent.app()))
+ 'The app_id/namespace arguments (%r) should match ' +
+ 'parent.app_id_namespace().to_encoded() (%s)' %
+ (_app_id_namespace_obj, parent.app_id_namespace()))
key = Key()
ref = key.__reference
if parent is not None:
ref.CopyFrom(parent.__reference)
else:
- ref.set_app(_app)
+ ref.set_app(_app_id_namespace_obj.to_encoded())
path = ref.mutable_path()
for i in xrange(0, len(args), 2):
@@ -248,9 +454,6 @@
elem.set_id(id_or_name)
elif isinstance(id_or_name, basestring):
ValidateString(id_or_name, 'name')
- if id_or_name and id_or_name[0] in string.digits:
- raise datastore_errors.BadArgumentError(
- 'Names may not begin with a digit; received %s.' % id_or_name)
elem.set_name(id_or_name.encode('utf-8'))
else:
raise datastore_errors.BadArgumentError(
@@ -263,7 +466,21 @@
def app(self):
"""Returns this entity's app id, a string."""
if self.__reference.app():
- return self.__reference.app().decode('utf-8')
+ return self.app_id_namespace().app_id().decode('utf-8')
+ else:
+ return None
+
+ def namespace(self):
+ """Returns this entity's app id, a string."""
+ if self.__reference.app():
+ return self.app_id_namespace().namespace().decode('utf-8')
+ else:
+ return None
+
+ def app_id_namespace(self):
+ """Returns this entity's app id/namespace, an appIdNamespace object."""
+ if self.__reference.app():
+ return parse_app_id_namespace(self.__reference.app())
else:
return None
@@ -339,11 +556,13 @@
raise datastore_errors.BadKeyError(
'ToTagUri() called for an entity with an incomplete key.')
- return u'tag:%s.%s,%s:%s[%s]' % (saxutils.escape(self.app()),
- os.environ['AUTH_DOMAIN'],
- datetime.date.today().isoformat(),
- saxutils.escape(self.kind()),
- saxutils.escape(str(self)))
+ return u'tag:%s.%s,%s:%s[%s]' % (
+ saxutils.escape(self.app_id_namespace().to_encoded()),
+ os.environ['AUTH_DOMAIN'],
+ datetime.date.today().isoformat(),
+ saxutils.escape(self.kind()),
+ saxutils.escape(str(self)))
+
ToXml = ToTagUri
def entity_group(self):
@@ -436,7 +655,7 @@
else:
args.append(repr(elem.id()))
- args.append('_app=%r' % self.__reference.app().decode('utf-8'))
+ args.append('_app_id_namespace=%r' % self.__reference.app().decode('utf-8'))
return u'datastore_types.Key.from_path(%s)' % ', '.join(args)
def __cmp__(self, other):
@@ -459,25 +678,29 @@
self_args = []
other_args = []
- self_args.append(self.__reference.app().decode('utf-8'))
- other_args.append(other.__reference.app().decode('utf-8'))
+ self_args.append(self.__reference.app())
+ other_args.append(other.__reference.app())
for elem in self.__reference.path().element_list():
- self_args.append(repr(elem.type()))
+ self_args.append(elem.type())
if elem.has_name():
- self_args.append(repr(elem.name().decode('utf-8')))
+ self_args.append(elem.name())
else:
self_args.append(elem.id())
for elem in other.__reference.path().element_list():
- other_args.append(repr(elem.type()))
+ other_args.append(elem.type())
if elem.has_name():
- other_args.append(repr(elem.name().decode('utf-8')))
+ other_args.append(elem.name())
else:
other_args.append(elem.id())
- result = cmp(self_args, other_args)
- return result
+ for self_component, other_component in zip(self_args, other_args):
+ comparison = cmp(self_component, other_component)
+ if comparison != 0:
+ return comparison
+
+ return cmp(len(self_args), len(other_args))
def __hash__(self):
"""Returns a 32-bit integer hash of this key.
@@ -698,6 +921,7 @@
except datastore_errors.BadValueError:
return NotImplemented
+
return cmp((self.address, self.protocol),
(other.address, other.protocol))
@@ -900,6 +1124,63 @@
return saxutils.escape(encoded)
+class BlobKey(object):
+ """Key used to identify a blob in Blobstore.
+
+ This object wraps a string that gets used internally by the Blobstore API
+ to identify application blobs. The BlobKey corresponds to the entity name
+ of the underlying BlobReference entity. The structure of the key is:
+
+ _<blob-key>
+
+ This class is exposed in the API in both google.appengine.ext.db and
+ google.appengine.ext.blobstore.
+ """
+
+ def __init__(self, blob_key):
+ """Constructor.
+
+ Used to convert a string to a BlobKey. Normally used internally by
+ Blobstore API.
+
+ Args:
+ blob_key: Key name of BlobReference that this key belongs to.
+ """
+ self.__blob_key = blob_key
+
+ def __str__(self):
+ """Convert to string."""
+ return self.__blob_key
+
+ def __repr__(self):
+ """Returns an eval()able string representation of this key.
+
+ Returns a Python string of the form 'datastore_types.BlobKey(...)'
+ that can be used to recreate this key.
+
+ Returns:
+ string
+ """
+ s = type(self).__module__
+ return '%s.%s(%r)' % (type(self).__module__,
+ type(self).__name__,
+ self.__blob_key)
+
+ def __cmp__(self, other):
+ if type(other) is type(self):
+ return cmp(str(self), str(other))
+ elif isinstance(other, basestring):
+ return cmp(self.__blob_key, other)
+ else:
+ return NotImplemented
+
+ def __hash__(self):
+ return hash(self.__blob_key)
+
+ def ToXml(self):
+ return str(self)
+
+
_PROPERTY_MEANINGS = {
@@ -916,6 +1197,7 @@
PhoneNumber: entity_pb.Property.GD_PHONENUMBER,
PostalAddress: entity_pb.Property.GD_POSTALADDRESS,
Rating: entity_pb.Property.GD_RATING,
+ BlobKey: entity_pb.Property.BLOBKEY,
}
_PROPERTY_TYPES = frozenset([
@@ -940,6 +1222,7 @@
type(None),
unicode,
users.User,
+ BlobKey,
])
_RAW_PROPERTY_TYPES = (Blob, Text)
@@ -1043,6 +1326,7 @@
type(None): ValidatePropertyNothing,
unicode: ValidatePropertyString,
users.User: ValidatePropertyNothing,
+ BlobKey: ValidatePropertyString,
}
assert set(_VALIDATE_PROPERTY_VALUES.iterkeys()) == _PROPERTY_TYPES
@@ -1222,6 +1506,7 @@
"""
pbvalue.set_doublevalue(value)
+
_PACK_PROPERTY_VALUES = {
Blob: PackBlob,
ByteString: PackBlob,
@@ -1244,6 +1529,7 @@
type(None): lambda name, value, pbvalue: None,
unicode: PackString,
users.User: PackUser,
+ BlobKey: PackString,
}
assert set(_PACK_PROPERTY_VALUES.iterkeys()) == _PROPERTY_TYPES
@@ -1331,7 +1617,6 @@
entity_pb.Property.ATOM_CATEGORY: Category,
entity_pb.Property.ATOM_LINK: Link,
entity_pb.Property.GD_EMAIL: Email,
- entity_pb.Property.GEORSS_POINT: lambda coords: GeoPt(*coords),
entity_pb.Property.GD_IM: IM,
entity_pb.Property.GD_PHONENUMBER: PhoneNumber,
entity_pb.Property.GD_POSTALADDRESS: PostalAddress,
@@ -1339,6 +1624,7 @@
entity_pb.Property.BLOB: Blob,
entity_pb.Property.BYTESTRING: ByteString,
entity_pb.Property.TEXT: Text,
+ entity_pb.Property.BLOBKEY: BlobKey,
}
@@ -1368,7 +1654,7 @@
elif pbval.has_referencevalue():
value = FromReferenceProperty(pbval)
elif pbval.has_pointvalue():
- value = (pbval.pointvalue().x(), pbval.pointvalue().y())
+ value = GeoPt(pbval.pointvalue().x(), pbval.pointvalue().y())
elif pbval.has_uservalue():
email = unicode(pbval.uservalue().email().decode('utf-8'))
auth_domain = unicode(pbval.uservalue().auth_domain().decode('utf-8'))
@@ -1381,7 +1667,7 @@
value = None
try:
- if pb.has_meaning():
+ if pb.has_meaning() and pb.meaning() in _PROPERTY_CONVERSIONS:
conversion = _PROPERTY_CONVERSIONS[meaning]
value = conversion(value)
except (KeyError, ValueError, IndexError, TypeError, AttributeError), msg:
@@ -1437,6 +1723,7 @@
'gd:phonenumber': PhoneNumber,
'gd:postaladdress': PostalAddress,
'gd:rating': Rating,
+ 'blobkey': BlobKey,
}