diff -r a440ced9a75f -r df109be0567c thirdparty/google_appengine/google/appengine/ext/db/__init__.py --- a/thirdparty/google_appengine/google/appengine/ext/db/__init__.py Sat Dec 06 14:50:45 2008 +0000 +++ b/thirdparty/google_appengine/google/appengine/ext/db/__init__.py Sat Dec 06 16:52:21 2008 +0000 @@ -77,7 +77,6 @@ - import datetime import logging import time @@ -119,8 +118,10 @@ Text = datastore_types.Text Blob = datastore_types.Blob + _kind_map = {} + _SELF_REFERENCE = object() @@ -146,13 +147,17 @@ class ConfigurationError(Error): - """Raised when a property is improperly configured.""" + """Raised when a property or model is improperly configured.""" class ReservedWordError(Error): """Raised when a property is defined for a reserved word.""" +class DerivedPropertyError(Error): + """Raised when attempting to assign a value to a derived property.""" + + _ALLOWED_PROPERTY_TYPES = set([ basestring, str, @@ -226,6 +231,36 @@ "definition." % locals()) +def _initialize_properties(model_class, name, bases, dct): + """Initialize Property attributes for Model-class. + + Args: + model_class: Model class to initialize properties for. + """ + model_class._properties = {} + defined = set() + for base in bases: + if hasattr(base, '_properties'): + property_keys = base._properties.keys() + duplicate_properties = defined.intersection(property_keys) + if duplicate_properties: + raise DuplicatePropertyError( + 'Duplicate properties in base class %s already defined: %s' % + (base.__name__, list(duplicate_properties))) + defined.update(property_keys) + model_class._properties.update(base._properties) + + for attr_name in dct.keys(): + attr = dct[attr_name] + if isinstance(attr, Property): + check_reserved_word(attr_name) + if attr_name in defined: + raise DuplicatePropertyError('Duplicate property: %s' % attr_name) + defined.add(attr_name) + model_class._properties[attr_name] = attr + attr.__property_config__(model_class, attr_name) + + class PropertiedClass(type): """Meta-class for initializing Model classes properties. @@ -239,7 +274,7 @@ Duplicate properties are not permitted. """ - def __init__(cls, name, bases, dct): + def __init__(cls, name, bases, dct, map_kind=True): """Initializes a class that might have property definitions. This method is called when a class is created with the PropertiedClass @@ -272,30 +307,10 @@ """ super(PropertiedClass, cls).__init__(name, bases, dct) - cls._properties = {} - defined = set() - for base in bases: - if hasattr(base, '_properties'): - property_keys = base._properties.keys() - duplicate_properties = defined.intersection(property_keys) - if duplicate_properties: - raise DuplicatePropertyError( - 'Duplicate properties in base class %s already defined: %s' % - (base.__name__, list(duplicate_properties))) - defined.update(property_keys) - cls._properties.update(base._properties) - - for attr_name in dct.keys(): - attr = dct[attr_name] - if isinstance(attr, Property): - check_reserved_word(attr_name) - if attr_name in defined: - raise DuplicatePropertyError('Duplicate property: %s' % attr_name) - defined.add(attr_name) - cls._properties[attr_name] = attr - attr.__property_config__(cls, attr_name) - - _kind_map[cls.kind()] = cls + _initialize_properties(cls, name, bases, dct) + + if map_kind: + _kind_map[cls.kind()] = cls class Property(object): @@ -466,7 +481,10 @@ return value def _attr_name(self): - """Attribute name we use for this property in model instances.""" + """Attribute name we use for this property in model instances. + + DO NOT USE THIS METHOD. + """ return '_' + self.name data_type = str @@ -500,7 +518,12 @@ __metaclass__ = PropertiedClass - def __init__(self, parent=None, key_name=None, _app=None, **kwds): + def __init__(self, + parent=None, + key_name=None, + _app=None, + _from_entity=False, + **kwds): """Creates a new instance of this model. To create a new entity, you instantiate a model and then call save(), @@ -524,6 +547,7 @@ level instance. key_name: Name for new model instance. _app: Intentionally undocumented. + _from_entity: Intentionally undocumented. args: Keyword arguments mapping to properties of model. """ if key_name == '': @@ -533,15 +557,22 @@ key_name.__class__.__name__) if parent is not None: - if not isinstance(parent, Model): + if not isinstance(parent, (Model, Key)): raise TypeError('Expected Model type; received %s (is %s)' % (parent, parent.__class__.__name__)) - if not parent.is_saved(): + if isinstance(parent, Model) and not parent.has_key(): raise BadValueError( - "%s instance must be saved before it can be used as a " + "%s instance must have a complete key before it can be used as a " "parent." % parent.kind()) - - self._parent = parent + if isinstance(parent, Key): + self._parent_key = parent + self._parent = None + else: + self._parent_key = parent.key() + self._parent = parent + else: + self._parent_key = None + self._parent = None self._entity = None self._key_name = key_name self._app = _app @@ -552,7 +583,11 @@ value = kwds[prop.name] else: value = prop.default_value() - prop.__set__(self, value) + try: + prop.__set__(self, value) + except DerivedPropertyError, e: + if prop.name in kwds and not _from_entity: + raise def key(self): """Unique key for this entity. @@ -569,6 +604,9 @@ """ if self.is_saved(): return self._entity.key() + elif self._key_name: + parent = self._parent and self._parent.key() + return Key.from_path(self.kind(), self._key_name, parent=parent) else: raise NotSavedError() @@ -634,7 +672,12 @@ if self.is_saved(): entity = self._entity else: - if self._parent is not None: + if self._parent_key is not None: + entity = _entity_class(self.kind(), + parent=self._parent_key, + name=self._key_name, + _app=self._app) + elif self._parent is not None: entity = _entity_class(self.kind(), parent=self._parent._entity, name=self._key_name, @@ -668,6 +711,18 @@ """ return self._entity is not None + def has_key(self): + """Determine if this model instance has a complete key. + + Ids are not assigned until the data is saved to the Datastore, but + instances with a key name always have a full key. + + Returns: + True if the object has been persisted to the datastore or has a key_name, + otherwise False. + """ + return self.is_saved() or self._key_name + def dynamic_properties(self): """Returns a list of all dynamic properties defined for instance.""" return [] @@ -683,10 +738,10 @@ Parent of contained entity or parent provided in constructor, None if instance has no parent. """ - if (self._parent is None and - self._entity is not None and - self._entity.parent() is not None): - self._parent = get(self._entity.parent()) + if self._parent is None: + parent_key = self.parent_key() + if parent_key is not None: + self._parent = get(parent_key) return self._parent def parent_key(self): @@ -698,7 +753,9 @@ Returns: Parent key of entity, None if there is no parent. """ - if self._parent is not None: + if self._parent_key is not None: + return self._parent_key + elif self._parent is not None: return self._parent.key() elif self._entity is not None: return self._entity.parent() @@ -924,7 +981,7 @@ (repr(cls), entity.kind())) entity_values = cls._load_entity_values(entity) - instance = cls(None, **entity_values) + instance = cls(None, _from_entity=True, **entity_values) instance._entity = entity del instance._key_name return instance @@ -1016,14 +1073,23 @@ """Delete one or more Model instances. Args: - models: Model instance or list of Model instances. + models_or_keys: Model instance or list of Model instances. Raises: TransactionFailedError if the data could not be committed. """ - models, multiple = datastore.NormalizeAndTypeCheck(models, Model) - entities = [model.key() for model in models] - keys = datastore.Delete(entities) + models_or_keys, multiple = datastore.NormalizeAndTypeCheck( + models, (Model, Key, basestring)) + keys = [] + for model_or_key in models_or_keys: + if isinstance(model_or_key, Model): + key = model_or_key = model_or_key.key() + elif isinstance(model_or_key, basestring): + key = model_or_key = Key(model_or_key) + else: + key = model_or_key + keys.append(key) + datastore.Delete(keys) class Expando(Model): @@ -1133,7 +1199,7 @@ self._dynamic_properties = {} self._dynamic_properties[key] = value else: - Model.__setattr__(self, key, value) + super(Expando, self).__setattr__(key, value) def __getattr__(self, key): """If no explicit attribute defined, retrieve value from entity. @@ -1211,7 +1277,7 @@ Args: entity: Entity which contain values to search dyanmic properties for. """ - entity_values = Model._load_entity_values(entity) + entity_values = super(Expando, cls)._load_entity_values(entity) for key, value in entity.iteritems(): if key not in entity_values: entity_values[str(key)] = value @@ -1537,7 +1603,7 @@ else: raise NotSavedError() elif isinstance(ancestor, Model): - if ancestor.is_saved(): + if ancestor.has_key(): self.__ancestor = ancestor.key() else: raise NotSavedError() @@ -2170,20 +2236,33 @@ if not isinstance(value, list): raise BadValueError('Property %s must be a list' % self.name) - if self.item_type in (int, long): - item_type = (int, long) - else: - item_type = self.item_type - - for item in value: - if not isinstance(item, item_type): - if item_type == (int, long): - raise BadValueError('Items in the %s list must all be integers.' % - self.name) - else: - raise BadValueError( - 'Items in the %s list must all be %s instances' % - (self.name, self.item_type.__name__)) + value = self.validate_list_contents(value) + return value + + def validate_list_contents(self, value): + """Validates that all items in the list are of the correct type. + + Returns: + The validated list. + + Raises: + BadValueError if the list has items are not instances of the + item_type given to the constructor. + """ + if self.item_type in (int, long): + item_type = (int, long) + else: + item_type = self.item_type + + for item in value: + if not isinstance(item, item_type): + if item_type == (int, long): + raise BadValueError('Items in the %s list must all be integers.' % + self.name) + else: + raise BadValueError( + 'Items in the %s list must all be %s instances' % + (self.name, self.item_type.__name__)) return value def empty(self, value): @@ -2210,6 +2289,15 @@ """ return list(super(ListProperty, self).default_value()) + def get_value_for_datastore(self, model_instance): + """Get value from property to send to datastore. + + Returns: + validated list appropriate to save in the datastore. + """ + return self.validate_list_contents( + super(ListProperty, self).get_value_for_datastore(model_instance)) + class StringListProperty(ListProperty): """A property that stores a list of strings. @@ -2368,9 +2456,9 @@ if isinstance(value, datastore.Key): return value - if value is not None and not value.is_saved(): + if value is not None and not value.has_key(): raise BadValueError( - '%s instance must be saved before it can be stored as a ' + '%s instance must have a complete key before it can be stored as a ' 'reference' % self.reference_class.kind()) value = super(ReferenceProperty, self).validate(value)