144 class DuplicatePropertyError(Error): |
145 class DuplicatePropertyError(Error): |
145 """Raised when a property is duplicated in a model definition.""" |
146 """Raised when a property is duplicated in a model definition.""" |
146 |
147 |
147 |
148 |
148 class ConfigurationError(Error): |
149 class ConfigurationError(Error): |
149 """Raised when a property is improperly configured.""" |
150 """Raised when a property or model is improperly configured.""" |
150 |
151 |
151 |
152 |
152 class ReservedWordError(Error): |
153 class ReservedWordError(Error): |
153 """Raised when a property is defined for a reserved word.""" |
154 """Raised when a property is defined for a reserved word.""" |
|
155 |
|
156 |
|
157 class DerivedPropertyError(Error): |
|
158 """Raised when attempting to assign a value to a derived property.""" |
154 |
159 |
155 |
160 |
156 _ALLOWED_PROPERTY_TYPES = set([ |
161 _ALLOWED_PROPERTY_TYPES = set([ |
157 basestring, |
162 basestring, |
158 str, |
163 str, |
224 "using a different name like %(attr_name)s_ and adding " |
229 "using a different name like %(attr_name)s_ and adding " |
225 "name='%(attr_name)s' to the parameter list of the property " |
230 "name='%(attr_name)s' to the parameter list of the property " |
226 "definition." % locals()) |
231 "definition." % locals()) |
227 |
232 |
228 |
233 |
|
234 def _initialize_properties(model_class, name, bases, dct): |
|
235 """Initialize Property attributes for Model-class. |
|
236 |
|
237 Args: |
|
238 model_class: Model class to initialize properties for. |
|
239 """ |
|
240 model_class._properties = {} |
|
241 defined = set() |
|
242 for base in bases: |
|
243 if hasattr(base, '_properties'): |
|
244 property_keys = base._properties.keys() |
|
245 duplicate_properties = defined.intersection(property_keys) |
|
246 if duplicate_properties: |
|
247 raise DuplicatePropertyError( |
|
248 'Duplicate properties in base class %s already defined: %s' % |
|
249 (base.__name__, list(duplicate_properties))) |
|
250 defined.update(property_keys) |
|
251 model_class._properties.update(base._properties) |
|
252 |
|
253 for attr_name in dct.keys(): |
|
254 attr = dct[attr_name] |
|
255 if isinstance(attr, Property): |
|
256 check_reserved_word(attr_name) |
|
257 if attr_name in defined: |
|
258 raise DuplicatePropertyError('Duplicate property: %s' % attr_name) |
|
259 defined.add(attr_name) |
|
260 model_class._properties[attr_name] = attr |
|
261 attr.__property_config__(model_class, attr_name) |
|
262 |
|
263 |
229 class PropertiedClass(type): |
264 class PropertiedClass(type): |
230 """Meta-class for initializing Model classes properties. |
265 """Meta-class for initializing Model classes properties. |
231 |
266 |
232 Used for initializing Properties defined in the context of a model. |
267 Used for initializing Properties defined in the context of a model. |
233 By using a meta-class much of the configuration of a Property |
268 By using a meta-class much of the configuration of a Property |
237 do appropriate initialization via __property_config__. |
272 do appropriate initialization via __property_config__. |
238 |
273 |
239 Duplicate properties are not permitted. |
274 Duplicate properties are not permitted. |
240 """ |
275 """ |
241 |
276 |
242 def __init__(cls, name, bases, dct): |
277 def __init__(cls, name, bases, dct, map_kind=True): |
243 """Initializes a class that might have property definitions. |
278 """Initializes a class that might have property definitions. |
244 |
279 |
245 This method is called when a class is created with the PropertiedClass |
280 This method is called when a class is created with the PropertiedClass |
246 meta-class. |
281 meta-class. |
247 |
282 |
270 ReservedWordError when a property is given a name that is in the list of |
305 ReservedWordError when a property is given a name that is in the list of |
271 reserved words, attributes of Model and names of the form '__.*__'. |
306 reserved words, attributes of Model and names of the form '__.*__'. |
272 """ |
307 """ |
273 super(PropertiedClass, cls).__init__(name, bases, dct) |
308 super(PropertiedClass, cls).__init__(name, bases, dct) |
274 |
309 |
275 cls._properties = {} |
310 _initialize_properties(cls, name, bases, dct) |
276 defined = set() |
311 |
277 for base in bases: |
312 if map_kind: |
278 if hasattr(base, '_properties'): |
313 _kind_map[cls.kind()] = cls |
279 property_keys = base._properties.keys() |
|
280 duplicate_properties = defined.intersection(property_keys) |
|
281 if duplicate_properties: |
|
282 raise DuplicatePropertyError( |
|
283 'Duplicate properties in base class %s already defined: %s' % |
|
284 (base.__name__, list(duplicate_properties))) |
|
285 defined.update(property_keys) |
|
286 cls._properties.update(base._properties) |
|
287 |
|
288 for attr_name in dct.keys(): |
|
289 attr = dct[attr_name] |
|
290 if isinstance(attr, Property): |
|
291 check_reserved_word(attr_name) |
|
292 if attr_name in defined: |
|
293 raise DuplicatePropertyError('Duplicate property: %s' % attr_name) |
|
294 defined.add(attr_name) |
|
295 cls._properties[attr_name] = attr |
|
296 attr.__property_config__(cls, attr_name) |
|
297 |
|
298 _kind_map[cls.kind()] = cls |
|
299 |
314 |
300 |
315 |
301 class Property(object): |
316 class Property(object): |
302 """A Property is an attribute of a Model. |
317 """A Property is an attribute of a Model. |
303 |
318 |
522 Args: |
545 Args: |
523 parent: Parent instance for this instance or None, indicating a top- |
546 parent: Parent instance for this instance or None, indicating a top- |
524 level instance. |
547 level instance. |
525 key_name: Name for new model instance. |
548 key_name: Name for new model instance. |
526 _app: Intentionally undocumented. |
549 _app: Intentionally undocumented. |
|
550 _from_entity: Intentionally undocumented. |
527 args: Keyword arguments mapping to properties of model. |
551 args: Keyword arguments mapping to properties of model. |
528 """ |
552 """ |
529 if key_name == '': |
553 if key_name == '': |
530 raise BadKeyError('Name cannot be empty.') |
554 raise BadKeyError('Name cannot be empty.') |
531 elif key_name is not None and not isinstance(key_name, basestring): |
555 elif key_name is not None and not isinstance(key_name, basestring): |
532 raise BadKeyError('Name must be string type, not %s' % |
556 raise BadKeyError('Name must be string type, not %s' % |
533 key_name.__class__.__name__) |
557 key_name.__class__.__name__) |
534 |
558 |
535 if parent is not None: |
559 if parent is not None: |
536 if not isinstance(parent, Model): |
560 if not isinstance(parent, (Model, Key)): |
537 raise TypeError('Expected Model type; received %s (is %s)' % |
561 raise TypeError('Expected Model type; received %s (is %s)' % |
538 (parent, parent.__class__.__name__)) |
562 (parent, parent.__class__.__name__)) |
539 if not parent.is_saved(): |
563 if isinstance(parent, Model) and not parent.has_key(): |
540 raise BadValueError( |
564 raise BadValueError( |
541 "%s instance must be saved before it can be used as a " |
565 "%s instance must have a complete key before it can be used as a " |
542 "parent." % parent.kind()) |
566 "parent." % parent.kind()) |
543 |
567 if isinstance(parent, Key): |
544 self._parent = parent |
568 self._parent_key = parent |
|
569 self._parent = None |
|
570 else: |
|
571 self._parent_key = parent.key() |
|
572 self._parent = parent |
|
573 else: |
|
574 self._parent_key = None |
|
575 self._parent = None |
545 self._entity = None |
576 self._entity = None |
546 self._key_name = key_name |
577 self._key_name = key_name |
547 self._app = _app |
578 self._app = _app |
548 |
579 |
549 properties = self.properties() |
580 properties = self.properties() |
550 for prop in self.properties().values(): |
581 for prop in self.properties().values(): |
551 if prop.name in kwds: |
582 if prop.name in kwds: |
552 value = kwds[prop.name] |
583 value = kwds[prop.name] |
553 else: |
584 else: |
554 value = prop.default_value() |
585 value = prop.default_value() |
555 prop.__set__(self, value) |
586 try: |
|
587 prop.__set__(self, value) |
|
588 except DerivedPropertyError, e: |
|
589 if prop.name in kwds and not _from_entity: |
|
590 raise |
556 |
591 |
557 def key(self): |
592 def key(self): |
558 """Unique key for this entity. |
593 """Unique key for this entity. |
559 |
594 |
560 This property is only available if this entity is already stored in the |
595 This property is only available if this entity is already stored in the |
567 Raises: |
602 Raises: |
568 NotSavedError when entity is not persistent. |
603 NotSavedError when entity is not persistent. |
569 """ |
604 """ |
570 if self.is_saved(): |
605 if self.is_saved(): |
571 return self._entity.key() |
606 return self._entity.key() |
|
607 elif self._key_name: |
|
608 parent = self._parent and self._parent.key() |
|
609 return Key.from_path(self.kind(), self._key_name, parent=parent) |
572 else: |
610 else: |
573 raise NotSavedError() |
611 raise NotSavedError() |
574 |
612 |
575 def _to_entity(self, entity): |
613 def _to_entity(self, entity): |
576 """Copies information from this model to provided entity. |
614 """Copies information from this model to provided entity. |
632 self._entity or a new Entity which is not stored on the instance. |
670 self._entity or a new Entity which is not stored on the instance. |
633 """ |
671 """ |
634 if self.is_saved(): |
672 if self.is_saved(): |
635 entity = self._entity |
673 entity = self._entity |
636 else: |
674 else: |
637 if self._parent is not None: |
675 if self._parent_key is not None: |
|
676 entity = _entity_class(self.kind(), |
|
677 parent=self._parent_key, |
|
678 name=self._key_name, |
|
679 _app=self._app) |
|
680 elif self._parent is not None: |
638 entity = _entity_class(self.kind(), |
681 entity = _entity_class(self.kind(), |
639 parent=self._parent._entity, |
682 parent=self._parent._entity, |
640 name=self._key_name, |
683 name=self._key_name, |
641 _app=self._app) |
684 _app=self._app) |
642 else: |
685 else: |
666 Returns: |
709 Returns: |
667 True if object has been persisted to the datastore, otherwise False. |
710 True if object has been persisted to the datastore, otherwise False. |
668 """ |
711 """ |
669 return self._entity is not None |
712 return self._entity is not None |
670 |
713 |
|
714 def has_key(self): |
|
715 """Determine if this model instance has a complete key. |
|
716 |
|
717 Ids are not assigned until the data is saved to the Datastore, but |
|
718 instances with a key name always have a full key. |
|
719 |
|
720 Returns: |
|
721 True if the object has been persisted to the datastore or has a key_name, |
|
722 otherwise False. |
|
723 """ |
|
724 return self.is_saved() or self._key_name |
|
725 |
671 def dynamic_properties(self): |
726 def dynamic_properties(self): |
672 """Returns a list of all dynamic properties defined for instance.""" |
727 """Returns a list of all dynamic properties defined for instance.""" |
673 return [] |
728 return [] |
674 |
729 |
675 def instance_properties(self): |
730 def instance_properties(self): |
681 |
736 |
682 Returns: |
737 Returns: |
683 Parent of contained entity or parent provided in constructor, None if |
738 Parent of contained entity or parent provided in constructor, None if |
684 instance has no parent. |
739 instance has no parent. |
685 """ |
740 """ |
686 if (self._parent is None and |
741 if self._parent is None: |
687 self._entity is not None and |
742 parent_key = self.parent_key() |
688 self._entity.parent() is not None): |
743 if parent_key is not None: |
689 self._parent = get(self._entity.parent()) |
744 self._parent = get(parent_key) |
690 return self._parent |
745 return self._parent |
691 |
746 |
692 def parent_key(self): |
747 def parent_key(self): |
693 """Get the parent's key. |
748 """Get the parent's key. |
694 |
749 |
696 but still get information about the instances parent. |
751 but still get information about the instances parent. |
697 |
752 |
698 Returns: |
753 Returns: |
699 Parent key of entity, None if there is no parent. |
754 Parent key of entity, None if there is no parent. |
700 """ |
755 """ |
701 if self._parent is not None: |
756 if self._parent_key is not None: |
|
757 return self._parent_key |
|
758 elif self._parent is not None: |
702 return self._parent.key() |
759 return self._parent.key() |
703 elif self._entity is not None: |
760 elif self._entity is not None: |
704 return self._entity.parent() |
761 return self._entity.parent() |
705 else: |
762 else: |
706 return None |
763 return None |
922 if cls.kind() != entity.kind(): |
979 if cls.kind() != entity.kind(): |
923 raise KindError('Class %s cannot handle kind \'%s\'' % |
980 raise KindError('Class %s cannot handle kind \'%s\'' % |
924 (repr(cls), entity.kind())) |
981 (repr(cls), entity.kind())) |
925 |
982 |
926 entity_values = cls._load_entity_values(entity) |
983 entity_values = cls._load_entity_values(entity) |
927 instance = cls(None, **entity_values) |
984 instance = cls(None, _from_entity=True, **entity_values) |
928 instance._entity = entity |
985 instance._entity = entity |
929 del instance._key_name |
986 del instance._key_name |
930 return instance |
987 return instance |
931 |
988 |
932 @classmethod |
989 @classmethod |
1014 |
1071 |
1015 def delete(models): |
1072 def delete(models): |
1016 """Delete one or more Model instances. |
1073 """Delete one or more Model instances. |
1017 |
1074 |
1018 Args: |
1075 Args: |
1019 models: Model instance or list of Model instances. |
1076 models_or_keys: Model instance or list of Model instances. |
1020 |
1077 |
1021 Raises: |
1078 Raises: |
1022 TransactionFailedError if the data could not be committed. |
1079 TransactionFailedError if the data could not be committed. |
1023 """ |
1080 """ |
1024 models, multiple = datastore.NormalizeAndTypeCheck(models, Model) |
1081 models_or_keys, multiple = datastore.NormalizeAndTypeCheck( |
1025 entities = [model.key() for model in models] |
1082 models, (Model, Key, basestring)) |
1026 keys = datastore.Delete(entities) |
1083 keys = [] |
|
1084 for model_or_key in models_or_keys: |
|
1085 if isinstance(model_or_key, Model): |
|
1086 key = model_or_key = model_or_key.key() |
|
1087 elif isinstance(model_or_key, basestring): |
|
1088 key = model_or_key = Key(model_or_key) |
|
1089 else: |
|
1090 key = model_or_key |
|
1091 keys.append(key) |
|
1092 datastore.Delete(keys) |
1027 |
1093 |
1028 |
1094 |
1029 class Expando(Model): |
1095 class Expando(Model): |
1030 """Dynamically expandable model. |
1096 """Dynamically expandable model. |
1031 |
1097 |
1131 type(value).__name__) |
1197 type(value).__name__) |
1132 if self._dynamic_properties is None: |
1198 if self._dynamic_properties is None: |
1133 self._dynamic_properties = {} |
1199 self._dynamic_properties = {} |
1134 self._dynamic_properties[key] = value |
1200 self._dynamic_properties[key] = value |
1135 else: |
1201 else: |
1136 Model.__setattr__(self, key, value) |
1202 super(Expando, self).__setattr__(key, value) |
1137 |
1203 |
1138 def __getattr__(self, key): |
1204 def __getattr__(self, key): |
1139 """If no explicit attribute defined, retrieve value from entity. |
1205 """If no explicit attribute defined, retrieve value from entity. |
1140 |
1206 |
1141 Tries to get the value on the object normally, but failing that |
1207 Tries to get the value on the object normally, but failing that |
1209 property on the model. |
1275 property on the model. |
1210 |
1276 |
1211 Args: |
1277 Args: |
1212 entity: Entity which contain values to search dyanmic properties for. |
1278 entity: Entity which contain values to search dyanmic properties for. |
1213 """ |
1279 """ |
1214 entity_values = Model._load_entity_values(entity) |
1280 entity_values = super(Expando, cls)._load_entity_values(entity) |
1215 for key, value in entity.iteritems(): |
1281 for key, value in entity.iteritems(): |
1216 if key not in entity_values: |
1282 if key not in entity_values: |
1217 entity_values[str(key)] = value |
1283 entity_values[str(key)] = value |
1218 return entity_values |
1284 return entity_values |
1219 |
1285 |
1535 if ancestor.has_id_or_name(): |
1601 if ancestor.has_id_or_name(): |
1536 self.__ancestor = ancestor |
1602 self.__ancestor = ancestor |
1537 else: |
1603 else: |
1538 raise NotSavedError() |
1604 raise NotSavedError() |
1539 elif isinstance(ancestor, Model): |
1605 elif isinstance(ancestor, Model): |
1540 if ancestor.is_saved(): |
1606 if ancestor.has_key(): |
1541 self.__ancestor = ancestor.key() |
1607 self.__ancestor = ancestor.key() |
1542 else: |
1608 else: |
1543 raise NotSavedError() |
1609 raise NotSavedError() |
1544 else: |
1610 else: |
1545 raise TypeError('ancestor should be Key or Model') |
1611 raise TypeError('ancestor should be Key or Model') |
2168 value = super(ListProperty, self).validate(value) |
2234 value = super(ListProperty, self).validate(value) |
2169 if value is not None: |
2235 if value is not None: |
2170 if not isinstance(value, list): |
2236 if not isinstance(value, list): |
2171 raise BadValueError('Property %s must be a list' % self.name) |
2237 raise BadValueError('Property %s must be a list' % self.name) |
2172 |
2238 |
2173 if self.item_type in (int, long): |
2239 value = self.validate_list_contents(value) |
2174 item_type = (int, long) |
2240 return value |
2175 else: |
2241 |
2176 item_type = self.item_type |
2242 def validate_list_contents(self, value): |
2177 |
2243 """Validates that all items in the list are of the correct type. |
2178 for item in value: |
2244 |
2179 if not isinstance(item, item_type): |
2245 Returns: |
2180 if item_type == (int, long): |
2246 The validated list. |
2181 raise BadValueError('Items in the %s list must all be integers.' % |
2247 |
2182 self.name) |
2248 Raises: |
2183 else: |
2249 BadValueError if the list has items are not instances of the |
2184 raise BadValueError( |
2250 item_type given to the constructor. |
2185 'Items in the %s list must all be %s instances' % |
2251 """ |
2186 (self.name, self.item_type.__name__)) |
2252 if self.item_type in (int, long): |
|
2253 item_type = (int, long) |
|
2254 else: |
|
2255 item_type = self.item_type |
|
2256 |
|
2257 for item in value: |
|
2258 if not isinstance(item, item_type): |
|
2259 if item_type == (int, long): |
|
2260 raise BadValueError('Items in the %s list must all be integers.' % |
|
2261 self.name) |
|
2262 else: |
|
2263 raise BadValueError( |
|
2264 'Items in the %s list must all be %s instances' % |
|
2265 (self.name, self.item_type.__name__)) |
2187 return value |
2266 return value |
2188 |
2267 |
2189 def empty(self, value): |
2268 def empty(self, value): |
2190 """Is list property empty. |
2269 """Is list property empty. |
2191 |
2270 |
2207 |
2286 |
2208 Returns: |
2287 Returns: |
2209 Copy of the default value. |
2288 Copy of the default value. |
2210 """ |
2289 """ |
2211 return list(super(ListProperty, self).default_value()) |
2290 return list(super(ListProperty, self).default_value()) |
|
2291 |
|
2292 def get_value_for_datastore(self, model_instance): |
|
2293 """Get value from property to send to datastore. |
|
2294 |
|
2295 Returns: |
|
2296 validated list appropriate to save in the datastore. |
|
2297 """ |
|
2298 return self.validate_list_contents( |
|
2299 super(ListProperty, self).get_value_for_datastore(model_instance)) |
2212 |
2300 |
2213 |
2301 |
2214 class StringListProperty(ListProperty): |
2302 class StringListProperty(ListProperty): |
2215 """A property that stores a list of strings. |
2303 """A property that stores a list of strings. |
2216 |
2304 |
2366 - Object not of correct model type for reference. |
2454 - Object not of correct model type for reference. |
2367 """ |
2455 """ |
2368 if isinstance(value, datastore.Key): |
2456 if isinstance(value, datastore.Key): |
2369 return value |
2457 return value |
2370 |
2458 |
2371 if value is not None and not value.is_saved(): |
2459 if value is not None and not value.has_key(): |
2372 raise BadValueError( |
2460 raise BadValueError( |
2373 '%s instance must be saved before it can be stored as a ' |
2461 '%s instance must have a complete key before it can be stored as a ' |
2374 'reference' % self.reference_class.kind()) |
2462 'reference' % self.reference_class.kind()) |
2375 |
2463 |
2376 value = super(ReferenceProperty, self).validate(value) |
2464 value = super(ReferenceProperty, self).validate(value) |
2377 |
2465 |
2378 if value is not None and not isinstance(value, self.reference_class): |
2466 if value is not None and not isinstance(value, self.reference_class): |