| #!/usr/bin/env python |
| # |
| # Copyright 2007 Google Inc. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| |
| |
| |
| """Simple, schema-based database abstraction layer for the datastore. |
| |
| Modeled after Django's abstraction layer on top of SQL databases, |
| http://www.djangoproject.com/documentation/mode_api/. Ours is a little simpler |
| and a lot less code because the datastore is so much simpler than SQL |
| databases. |
| |
| The programming model is to declare Python subclasses of the Model class, |
| declaring datastore properties as class members of that class. So if you want to |
| publish a story with title, body, and created date, you would do it like this: |
| |
| class Story(db.Model): |
| title = db.StringProperty() |
| body = db.TextProperty() |
| created = db.DateTimeProperty(auto_now_add=True) |
| |
| You can create a new Story in the datastore with this usage pattern: |
| |
| story = Story(title='My title') |
| story.body = 'My body' |
| story.put() |
| |
| You query for Story entities using built in query interfaces that map directly |
| to the syntax and semantics of the datastore: |
| |
| stories = Story.all().filter('date >=', yesterday).order('-date') |
| for story in stories: |
| print story.title |
| |
| The Property declarations enforce types by performing validation on assignment. |
| For example, the DateTimeProperty enforces that you assign valid datetime |
| objects, and if you supply the "required" option for a property, you will not |
| be able to assign None to that property. |
| |
| We also support references between models, so if a story has comments, you |
| would represent it like this: |
| |
| class Comment(db.Model): |
| story = db.ReferenceProperty(Story) |
| body = db.TextProperty() |
| |
| When you get a story out of the datastore, the story reference is resolved |
| automatically the first time it is referenced, which makes it easy to use |
| model instances without performing additional queries by hand: |
| |
| comment = Comment.get(key) |
| print comment.story.title |
| |
| Likewise, you can access the set of comments that refer to each story through |
| this property through a reverse reference called comment_set, which is a Query |
| preconfigured to return all matching comments: |
| |
| story = Story.get(key) |
| for comment in story.comment_set: |
| print comment.body |
| |
| """ |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| import copy |
| import datetime |
| import logging |
| import re |
| import time |
| import urlparse |
| import warnings |
| |
| from google.appengine.api import datastore |
| from google.appengine.api import datastore_errors |
| from google.appengine.api import datastore_types |
| from google.appengine.api import namespace_manager |
| from google.appengine.api import users |
| from google.appengine.datastore import datastore_rpc |
| from google.appengine.datastore import datastore_query |
| |
| |
| Error = datastore_errors.Error |
| BadValueError = datastore_errors.BadValueError |
| BadPropertyError = datastore_errors.BadPropertyError |
| BadRequestError = datastore_errors.BadRequestError |
| EntityNotFoundError = datastore_errors.EntityNotFoundError |
| BadArgumentError = datastore_errors.BadArgumentError |
| QueryNotFoundError = datastore_errors.QueryNotFoundError |
| TransactionNotFoundError = datastore_errors.TransactionNotFoundError |
| Rollback = datastore_errors.Rollback |
| TransactionFailedError = datastore_errors.TransactionFailedError |
| BadFilterError = datastore_errors.BadFilterError |
| BadQueryError = datastore_errors.BadQueryError |
| BadKeyError = datastore_errors.BadKeyError |
| InternalError = datastore_errors.InternalError |
| NeedIndexError = datastore_errors.NeedIndexError |
| ReferencePropertyResolveError = datastore_errors.ReferencePropertyResolveError |
| Timeout = datastore_errors.Timeout |
| CommittedButStillApplying = datastore_errors.CommittedButStillApplying |
| |
| ValidationError = BadValueError |
| |
| |
| Key = datastore_types.Key |
| Category = datastore_types.Category |
| Link = datastore_types.Link |
| Email = datastore_types.Email |
| GeoPt = datastore_types.GeoPt |
| IM = datastore_types.IM |
| PhoneNumber = datastore_types.PhoneNumber |
| PostalAddress = datastore_types.PostalAddress |
| Rating = datastore_types.Rating |
| Text = datastore_types.Text |
| Blob = datastore_types.Blob |
| ByteString = datastore_types.ByteString |
| BlobKey = datastore_types.BlobKey |
| |
| |
| READ_CAPABILITY = datastore.READ_CAPABILITY |
| WRITE_CAPABILITY = datastore.WRITE_CAPABILITY |
| |
| |
| STRONG_CONSISTENCY = datastore.STRONG_CONSISTENCY |
| EVENTUAL_CONSISTENCY = datastore.EVENTUAL_CONSISTENCY |
| |
| |
| NESTED = datastore_rpc.TransactionOptions.NESTED |
| MANDATORY = datastore_rpc.TransactionOptions.MANDATORY |
| ALLOWED = datastore_rpc.TransactionOptions.ALLOWED |
| INDEPENDENT = datastore_rpc.TransactionOptions.INDEPENDENT |
| |
| |
| KEY_RANGE_EMPTY = "Empty" |
| """Indicates the given key range is empty and the datastore's |
| automatic ID allocator will not assign keys in this range to new |
| entities. |
| """ |
| |
| KEY_RANGE_CONTENTION = "Contention" |
| """Indicates the given key range is empty but the datastore's |
| automatic ID allocator may assign new entities keys in this range. |
| However it is safe to manually assign keys in this range |
| if either of the following is true: |
| |
| - No other request will insert entities with the same kind and parent |
| as the given key range until all entities with manually assigned |
| keys from this range have been written. |
| - Overwriting entities written by other requests with the same kind |
| and parent as the given key range is acceptable. |
| |
| The datastore's automatic ID allocator will not assign a key to a new |
| entity that will overwrite an existing entity, so once the range is |
| populated there will no longer be any contention. |
| """ |
| |
| KEY_RANGE_COLLISION = "Collision" |
| """Indicates that entities with keys inside the given key range |
| already exist and writing to this range will overwrite those entities. |
| Additionally the implications of KEY_RANGE_COLLISION apply. If |
| overwriting entities that exist in this range is acceptable it is safe |
| to use the given range. |
| |
| The datastore's automatic ID allocator will never assign a key to |
| a new entity that will overwrite an existing entity so entities |
| written by the user to this range will never be overwritten by |
| an entity with an automatically assigned key. |
| """ |
| |
| |
| |
| _kind_map = {} |
| |
| |
| |
| _SELF_REFERENCE = object() |
| |
| |
| |
| |
| _RESERVED_WORDS = set(['key_name']) |
| |
| |
| |
| |
| |
| class NotSavedError(Error): |
| """Raised when a saved-object action is performed on a non-saved object.""" |
| |
| |
| class KindError(BadValueError): |
| """Raised when an entity is used with incorrect Model.""" |
| |
| |
| class PropertyError(Error): |
| """Raised when non-existent property is referenced.""" |
| |
| |
| class DuplicatePropertyError(Error): |
| """Raised when a property is duplicated in a model definition.""" |
| |
| |
| class ConfigurationError(Error): |
| """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, |
| unicode, |
| bool, |
| int, |
| long, |
| float, |
| Key, |
| datetime.datetime, |
| datetime.date, |
| datetime.time, |
| Blob, |
| datastore_types.EmbeddedEntity, |
| ByteString, |
| Text, |
| users.User, |
| Category, |
| Link, |
| Email, |
| GeoPt, |
| IM, |
| PhoneNumber, |
| PostalAddress, |
| Rating, |
| BlobKey, |
| ]) |
| |
| _ALLOWED_EXPANDO_PROPERTY_TYPES = set(_ALLOWED_PROPERTY_TYPES) |
| _ALLOWED_EXPANDO_PROPERTY_TYPES.update((list, tuple, type(None))) |
| |
| |
| |
| _OPERATORS = ['<', '<=', '>', '>=', '=', '==', '!=', 'in'] |
| _FILTER_REGEX = re.compile( |
| '^\s*([^\s]+)(\s+(%s)\s*)?$' % '|'.join(_OPERATORS), |
| re.IGNORECASE | re.UNICODE) |
| |
| |
| def class_for_kind(kind): |
| """Return base-class responsible for implementing kind. |
| |
| Necessary to recover the class responsible for implementing provided |
| kind. |
| |
| Args: |
| kind: Entity kind string. |
| |
| Returns: |
| Class implementation for kind. |
| |
| Raises: |
| KindError when there is no implementation for kind. |
| """ |
| try: |
| return _kind_map[kind] |
| except KeyError: |
| raise KindError('No implementation for kind \'%s\'' % kind) |
| |
| |
| def check_reserved_word(attr_name): |
| """Raise an exception if attribute name is a reserved word. |
| |
| Args: |
| attr_name: Name to check to see if it is a reserved word. |
| |
| Raises: |
| ReservedWordError when attr_name is determined to be a reserved word. |
| """ |
| if datastore_types.RESERVED_PROPERTY_NAME.match(attr_name): |
| raise ReservedWordError( |
| "Cannot define property. All names both beginning and " |
| "ending with '__' are reserved.") |
| |
| if attr_name in _RESERVED_WORDS or attr_name in dir(Model): |
| raise ReservedWordError( |
| "Cannot define property using reserved word '%(attr_name)s'. " |
| "If you would like to use this name in the datastore consider " |
| "using a different name like %(attr_name)s_ and adding " |
| "name='%(attr_name)s' to the parameter list of the property " |
| "definition." % locals()) |
| |
| |
| def query_descendants(model_instance): |
| """Returns a query for all the descendants of a model instance. |
| |
| Args: |
| model_instance: Model instance to find the descendants of. |
| |
| Returns: |
| Query that will retrieve all entities that have the given model instance |
| as an ancestor. Unlike normal ancestor queries, this does not include the |
| ancestor itself. |
| """ |
| |
| |
| result = Query().ancestor(model_instance) |
| |
| result.filter(datastore_types.KEY_SPECIAL_PROPERTY + ' >', |
| model_instance.key()) |
| return result |
| |
| |
| def model_to_protobuf(model_instance, _entity_class=datastore.Entity): |
| """Encodes a model instance as a protocol buffer. |
| |
| Args: |
| model_instance: Model instance to encode. |
| Returns: |
| entity_pb.EntityProto representation of the model instance |
| """ |
| |
| return model_instance._populate_entity(_entity_class).ToPb() |
| |
| |
| def model_from_protobuf(pb, _entity_class=datastore.Entity): |
| """Decodes a model instance from a protocol buffer. |
| |
| Args: |
| pb: The protocol buffer representation of the model instance. Can be an |
| entity_pb.EntityProto or str encoding of an entity_bp.EntityProto |
| |
| Returns: |
| Model instance resulting from decoding the protocol buffer |
| """ |
| |
| entity = _entity_class.FromPb(pb, default_kind=Expando.kind()) |
| return class_for_kind(entity.kind()).from_entity(entity) |
| |
| |
| def model_is_projection(model_instance): |
| """Returns true if the given db.Model instance only contains a projection of |
| the full entity. |
| """ |
| return model_instance._entity and model_instance._entity.is_projection() |
| |
| |
| 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 = {} |
| |
| |
| property_source = {} |
| |
| def get_attr_source(name, cls): |
| for src_cls in cls.mro(): |
| if name in src_cls.__dict__: |
| return src_cls |
| |
| defined = set() |
| |
| for base in bases: |
| if hasattr(base, '_properties'): |
| property_keys = set(base._properties.keys()) |
| duplicate_property_keys = defined & property_keys |
| for dupe_prop_name in duplicate_property_keys: |
| |
| |
| |
| old_source = property_source[dupe_prop_name] = get_attr_source( |
| dupe_prop_name, property_source[dupe_prop_name]) |
| new_source = get_attr_source(dupe_prop_name, base) |
| if old_source != new_source: |
| raise DuplicatePropertyError( |
| 'Duplicate property, %s, is inherited from both %s and %s.' % |
| (dupe_prop_name, old_source.__name__, new_source.__name__)) |
| |
| |
| |
| property_keys -= duplicate_property_keys |
| if property_keys: |
| defined |= property_keys |
| |
| |
| property_source.update(dict.fromkeys(property_keys, base)) |
| 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) |
| |
| |
| model_class._all_properties = frozenset( |
| prop.name for name, prop in model_class._properties.items()) |
| |
| |
| model_class._unindexed_properties = frozenset( |
| prop.name for name, prop in model_class._properties.items() |
| if not prop.indexed) |
| |
| |
| def _coerce_to_key(value): |
| """Returns the value's key. |
| |
| Args: |
| value: a Model or Key instance or string encoded key or None |
| |
| Returns: |
| The corresponding key, or None if value is None. |
| """ |
| if value is None: |
| return None |
| |
| value, multiple = datastore.NormalizeAndTypeCheck( |
| value, (Model, Key, basestring)) |
| |
| if len(value) > 1: |
| raise datastore_errors.BadArgumentError('Expected only one model or key') |
| value = value[0] |
| |
| if isinstance(value, Model): |
| return value.key() |
| elif isinstance(value, basestring): |
| return Key(value) |
| else: |
| return value |
| |
| |
| class PropertiedClass(type): |
| """Meta-class for initializing Model classes properties. |
| |
| Used for initializing Properties defined in the context of a model. |
| By using a meta-class much of the configuration of a Property |
| descriptor becomes implicit. By using this meta-class, descriptors |
| that are of class Model are notified about which class they |
| belong to and what attribute they are associated with and can |
| do appropriate initialization via __property_config__. |
| |
| Duplicate properties are not permitted. |
| """ |
| |
| 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 |
| meta-class. |
| |
| Loads all properties for this model and its base classes in to a dictionary |
| for easy reflection via the 'properties' method. |
| |
| Configures each property defined in the new class. |
| |
| Duplicate properties, either defined in the new class or defined separately |
| in two base classes are not permitted. |
| |
| Properties may not assigned to names which are in the list of |
| _RESERVED_WORDS. It is still possible to store a property using a reserved |
| word in the datastore by using the 'name' keyword argument to the Property |
| constructor. |
| |
| Args: |
| cls: Class being initialized. |
| name: Name of new class. |
| bases: Base classes of new class. |
| dct: Dictionary of new definitions for class. |
| |
| Raises: |
| DuplicatePropertyError when a property is duplicated either in the new |
| class or separately in two base classes. |
| ReservedWordError when a property is given a name that is in the list of |
| reserved words, attributes of Model and names of the form '__.*__'. |
| """ |
| super(PropertiedClass, cls).__init__(name, bases, dct) |
| |
| _initialize_properties(cls, name, bases, dct) |
| |
| |
| if map_kind: |
| _kind_map[cls.kind()] = cls |
| |
| |
| |
| |
| AUTO_UPDATE_UNCHANGED = object() |
| |
| |
| class Property(object): |
| """A Property is an attribute of a Model. |
| |
| It defines the type of the attribute, which determines how it is stored |
| in the datastore and how the property values are validated. Different property |
| types support different options, which change validation rules, default |
| values, etc. The simplest example of a property is a StringProperty: |
| |
| class Story(db.Model): |
| title = db.StringProperty() |
| """ |
| |
| |
| creation_counter = 0 |
| |
| def __init__(self, |
| verbose_name=None, |
| name=None, |
| default=None, |
| required=False, |
| validator=None, |
| choices=None, |
| indexed=True): |
| """Initializes this Property with the given options. |
| |
| Args: |
| verbose_name: User friendly name of property. |
| name: Storage name for property. By default, uses attribute name |
| as it is assigned in the Model sub-class. |
| default: Default value for property if none is assigned. |
| required: Whether property is required. |
| validator: User provided method used for validation. |
| choices: User provided set of valid property values. |
| indexed: Whether property is indexed. |
| """ |
| self.verbose_name = verbose_name |
| self.name = name |
| self.default = default |
| self.required = required |
| self.validator = validator |
| self.choices = choices |
| self.indexed = indexed |
| self.creation_counter = Property.creation_counter |
| Property.creation_counter += 1 |
| |
| def __property_config__(self, model_class, property_name): |
| """Configure property, connecting it to its model. |
| |
| Configure the property so that it knows its property name and what class |
| it belongs to. |
| |
| Args: |
| model_class: Model class which Property will belong to. |
| property_name: Name of property within Model instance to store property |
| values in. By default this will be the property name preceded by |
| an underscore, but may change for different subclasses. |
| """ |
| self.model_class = model_class |
| if self.name is None: |
| self.name = property_name |
| |
| def __get__(self, model_instance, model_class): |
| """Returns the value for this property on the given model instance. |
| |
| See http://docs.python.org/ref/descriptors.html for a description of |
| the arguments to this class and what they mean.""" |
| |
| |
| |
| if model_instance is None: |
| return self |
| |
| |
| |
| |
| |
| try: |
| return getattr(model_instance, self._attr_name()) |
| except AttributeError: |
| return None |
| |
| def __set__(self, model_instance, value): |
| """Sets the value for this property on the given model instance. |
| |
| See http://docs.python.org/ref/descriptors.html for a description of |
| the arguments to this class and what they mean. |
| """ |
| value = self.validate(value) |
| setattr(model_instance, self._attr_name(), value) |
| |
| def default_value(self): |
| """Default value for unassigned values. |
| |
| Returns: |
| Default value as provided by __init__(default). |
| """ |
| return self.default |
| |
| def validate(self, value): |
| """Assert that provided value is compatible with this property. |
| |
| Args: |
| value: Value to validate against this Property. |
| |
| Returns: |
| A valid value, either the input unchanged or adapted to the |
| required type. |
| |
| Raises: |
| BadValueError if the value is not appropriate for this |
| property in any way. |
| """ |
| if self.empty(value): |
| if self.required: |
| raise BadValueError('Property %s is required' % self.name) |
| else: |
| if self.choices: |
| if value not in self.choices: |
| raise BadValueError('Property %s is %r; must be one of %r' % |
| (self.name, value, self.choices)) |
| if self.validator is not None: |
| self.validator(value) |
| return value |
| |
| def empty(self, value): |
| """Determine if value is empty in the context of this property. |
| |
| For most kinds, this is equivalent to "not value", but for kinds like |
| bool, the test is more subtle, so subclasses can override this method |
| if necessary. |
| |
| Args: |
| value: Value to validate against this Property. |
| |
| Returns: |
| True if this value is considered empty in the context of this Property |
| type, otherwise False. |
| """ |
| return not value |
| |
| def get_value_for_datastore(self, model_instance): |
| """Datastore representation of this property. |
| |
| Looks for this property in the given model instance, and returns the proper |
| datastore representation of the value that can be stored in a datastore |
| entity. Most critically, it will fetch the datastore key value for |
| reference properties. |
| |
| Some properies (e.g. DateTimeProperty, UserProperty) optionally update their |
| value on every put(). This call must return the current value for such |
| properties (get_updated_value_for_datastore returns the new value). |
| |
| Args: |
| model_instance: Instance to fetch datastore value from. |
| |
| Returns: |
| Datastore representation of the model value in a form that is |
| appropriate for storing in the datastore. |
| """ |
| return self.__get__(model_instance, model_instance.__class__) |
| |
| def get_updated_value_for_datastore(self, model_instance): |
| """Determine new value for auto-updated property. |
| |
| Some properies (e.g. DateTimeProperty, UserProperty) optionally update their |
| value on every put(). This call must return the new desired value for such |
| properties. For all other properties, this call must return |
| AUTO_UPDATE_UNCHANGED. |
| |
| Args: |
| model_instance: Instance to get new value for. |
| |
| Returns: |
| Datastore representation of the new model value in a form that is |
| appropriate for storing in the datastore, or AUTO_UPDATE_UNCHANGED. |
| """ |
| return AUTO_UPDATE_UNCHANGED |
| |
| def make_value_from_datastore_index_value(self, index_value): |
| value = datastore_types.RestoreFromIndexValue(index_value, self.data_type) |
| return self.make_value_from_datastore(value) |
| |
| def make_value_from_datastore(self, value): |
| """Native representation of this property. |
| |
| Given a value retrieved from a datastore entity, return a value, |
| possibly converted, to be stored on the model instance. Usually |
| this returns the value unchanged, but a property class may |
| override this when it uses a different datatype on the model |
| instance than on the entity. |
| |
| This API is not quite symmetric with get_value_for_datastore(), |
| because the model instance on which to store the converted value |
| may not exist yet -- we may be collecting values to be passed to a |
| model constructor. |
| |
| Args: |
| value: value retrieved from the datastore entity. |
| |
| Returns: |
| The value converted for use as a model instance attribute. |
| """ |
| return value |
| |
| def _require_parameter(self, kwds, parameter, value): |
| """Sets kwds[parameter] to value. |
| |
| If kwds[parameter] exists and is not value, raises ConfigurationError. |
| |
| Args: |
| kwds: The parameter dict, which maps parameter names (strings) to values. |
| parameter: The name of the parameter to set. |
| value: The value to set it to. |
| """ |
| if parameter in kwds and kwds[parameter] != value: |
| raise ConfigurationError('%s must be %s.' % (parameter, value)) |
| |
| kwds[parameter] = value |
| |
| def _attr_name(self): |
| """Attribute name we use for this property in model instances. |
| |
| DO NOT USE THIS METHOD. |
| """ |
| |
| return '_' + self.name |
| |
| |
| |
| |
| |
| data_type = str |
| |
| def datastore_type(self): |
| """Deprecated backwards-compatible accessor method for self.data_type.""" |
| |
| return self.data_type |
| |
| |
| class Index(datastore._BaseIndex): |
| """A datastore index.""" |
| |
| id = datastore._BaseIndex._Id |
| kind = datastore._BaseIndex._Kind |
| has_ancestor = datastore._BaseIndex._HasAncestor |
| properties = datastore._BaseIndex._Properties |
| |
| |
| class Model(object): |
| """Model is the superclass of all object entities in the datastore. |
| |
| The programming model is to declare Python subclasses of the Model class, |
| declaring datastore properties as class members of that class. So if you want |
| to publish a story with title, body, and created date, you would do it like |
| this: |
| |
| class Story(db.Model): |
| title = db.StringProperty() |
| body = db.TextProperty() |
| created = db.DateTimeProperty(auto_now_add=True) |
| |
| A model instance can have a single parent. Model instances without any |
| parent are root entities. It is possible to efficiently query for |
| instances by their shared parent. All descendents of a single root |
| instance also behave as a transaction group. This means that when you |
| work one member of the group within a transaction all descendents of that |
| root join the transaction. All operations within a transaction on this |
| group are ACID. |
| """ |
| |
| |
| __metaclass__ = PropertiedClass |
| |
| def __new__(*args, **unused_kwds): |
| """Allow subclasses to call __new__() with arguments. |
| |
| Do NOT list 'cls' as the first argument, or in the case when |
| the 'unused_kwds' dictionary contains the key 'cls', the function |
| will complain about multiple argument values for 'cls'. |
| |
| Raises: |
| TypeError if there are no positional arguments. |
| """ |
| if args: |
| cls = args[0] |
| else: |
| raise TypeError('object.__new__(): not enough arguments') |
| |
| |
| |
| return super(Model, cls).__new__(cls) |
| |
| 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 put(), |
| which saves the entity to the datastore: |
| |
| person = Person() |
| person.name = 'Bret' |
| person.put() |
| |
| You can initialize properties in the model in the constructor with keyword |
| arguments: |
| |
| person = Person(name='Bret') |
| |
| We initialize all other properties to the default value (as defined by the |
| properties in the model definition) if they are not provided in the |
| constructor. |
| |
| Args: |
| parent: Parent instance for this instance or None, indicating a top- |
| level instance. |
| key_name: Name for new model instance. |
| _from_entity: Intentionally undocumented. |
| kwds: Keyword arguments mapping to properties of model. Also: |
| key: Key instance for this instance, if provided makes parent and |
| key_name redundant (they do not need to be set but if they are |
| they must match the key). |
| """ |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| namespace = None |
| if isinstance(_app, tuple): |
| if len(_app) != 2: |
| raise BadArgumentError('_app must have 2 values if type is tuple.') |
| _app, namespace = _app |
| key = kwds.get('key', None) |
| if key is not None: |
| if isinstance(key, (tuple, list)): |
| key = Key.from_path(*key) |
| if isinstance(key, basestring): |
| key = Key(encoded=key) |
| if not isinstance(key, Key): |
| raise TypeError('Expected Key type; received %s (is %s)' % |
| (key, key.__class__.__name__)) |
| if not key.has_id_or_name(): |
| raise BadKeyError('Key must have an id or name') |
| if key.kind() != self.kind(): |
| raise BadKeyError('Expected Key kind to be %s; received %s' % |
| (self.kind(), key.kind())) |
| if _app is not None and key.app() != _app: |
| raise BadKeyError('Expected Key app to be %s; received %s' % |
| (_app, key.app())) |
| if namespace is not None and key.namespace() != namespace: |
| raise BadKeyError('Expected Key namespace to be %s; received %s' % |
| (namespace, key.namespace())) |
| if key_name and key_name != key.name(): |
| raise BadArgumentError('Cannot use key and key_name at the same time' |
| ' with different values') |
| if parent and parent != key.parent(): |
| raise BadArgumentError('Cannot use key and parent at the same time' |
| ' with different values') |
| namespace = key.namespace() |
| self._key = key |
| self._key_name = None |
| self._parent = None |
| self._parent_key = None |
| else: |
| if key_name == '': |
| raise BadKeyError('Name cannot be empty.') |
| elif key_name is not None and not isinstance(key_name, basestring): |
| raise BadKeyError('Name must be string type, not %s' % |
| key_name.__class__.__name__) |
| |
| if parent is not None: |
| if not isinstance(parent, (Model, Key)): |
| raise TypeError('Expected Model type; received %s (is %s)' % |
| (parent, parent.__class__.__name__)) |
| if isinstance(parent, Model) and not parent.has_key(): |
| raise BadValueError( |
| "%s instance must have a complete key before it can be used as a " |
| "parent." % parent.kind()) |
| 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._key_name = key_name |
| self._key = None |
| |
| if self._parent_key is not None: |
| if namespace is not None and self._parent_key.namespace() != namespace: |
| raise BadArgumentError( |
| 'Expected parent namespace to be %r; received %r' % |
| (namespace, self._parent_key.namespace())) |
| namespace = self._parent_key.namespace() |
| |
| self._entity = None |
| |
| if _app is not None and isinstance(_app, Key): |
| raise BadArgumentError('_app should be a string; received Key(\'%s\'):\n' |
| ' This may be the result of passing \'key\' as ' |
| 'a positional parameter in SDK 1.2.6. Please ' |
| 'only pass \'key\' as a keyword parameter.' % _app) |
| |
| if namespace is None: |
| namespace = namespace_manager.get_namespace() |
| |
| self._app = _app |
| self.__namespace = namespace |
| |
| is_projection = False |
| |
| if isinstance(_from_entity, datastore.Entity) and _from_entity.is_saved(): |
| self._entity = _from_entity |
| is_projection = _from_entity.is_projection() |
| del self._key_name |
| del self._key |
| |
| |
| |
| for prop in self.properties().values(): |
| if prop.name in kwds: |
| value = kwds[prop.name] |
| elif is_projection: |
| continue |
| else: |
| value = prop.default_value() |
| try: |
| prop.__set__(self, value) |
| except DerivedPropertyError: |
| |
| |
| |
| |
| |
| if prop.name in kwds and not _from_entity: |
| raise |
| |
| def key(self): |
| """Unique key for this entity. |
| |
| This property is only available if this entity is already stored in the |
| datastore or if it has a full key, so it is available if this entity was |
| fetched returned from a query, or after put() is called the first time |
| for new entities, or if a complete key was given when constructed. |
| |
| Returns: |
| Datastore key of persisted entity. |
| |
| Raises: |
| NotSavedError when entity is not persistent. |
| """ |
| if self.is_saved(): |
| return self._entity.key() |
| elif self._key: |
| return self._key |
| elif self._key_name: |
| parent = self._parent_key or (self._parent and self._parent.key()) |
| self._key = Key.from_path(self.kind(), self._key_name, parent=parent, |
| _app=self._app, namespace=self.__namespace) |
| return self._key |
| else: |
| raise NotSavedError() |
| |
| def __set_property(self, entity, name, datastore_value): |
| if datastore_value == []: |
| |
| |
| |
| entity.pop(name, None) |
| else: |
| entity[name] = datastore_value |
| |
| def _to_entity(self, entity): |
| """Copies information from this model to provided entity. |
| |
| Args: |
| entity: Entity to save information on. |
| """ |
| |
| for prop in self.properties().values(): |
| self.__set_property(entity, prop.name, prop.get_value_for_datastore(self)) |
| |
| |
| set_unindexed_properties = getattr(entity, 'set_unindexed_properties', None) |
| if set_unindexed_properties: |
| set_unindexed_properties(self._unindexed_properties) |
| |
| def _populate_internal_entity(self, _entity_class=datastore.Entity): |
| """Populates self._entity, saving its state to the datastore. |
| |
| After this method is called, calling is_saved() will return True. |
| |
| Returns: |
| Populated self._entity |
| """ |
| self._entity = self._populate_entity(_entity_class=_entity_class) |
| |
| |
| for prop in self.properties().values(): |
| new_value = prop.get_updated_value_for_datastore(self) |
| if new_value is not AUTO_UPDATE_UNCHANGED: |
| self.__set_property(self._entity, prop.name, new_value) |
| |
| for attr in ('_key_name', '_key'): |
| try: |
| delattr(self, attr) |
| except AttributeError: |
| pass |
| return self._entity |
| |
| def put(self, **kwargs): |
| """Writes this model instance to the datastore. |
| |
| If this instance is new, we add an entity to the datastore. |
| Otherwise, we update this instance, and the key will remain the |
| same. |
| |
| Args: |
| config: datastore_rpc.Configuration to use for this request. |
| |
| Returns: |
| The key of the instance (either the existing key or a new key). |
| |
| Raises: |
| TransactionFailedError if the data could not be committed. |
| """ |
| self._populate_internal_entity() |
| return datastore.Put(self._entity, **kwargs) |
| |
| |
| |
| save = put |
| |
| def _populate_entity(self, _entity_class=datastore.Entity): |
| """Internal helper -- Populate self._entity or create a new one |
| if that one does not exist. Does not change any state of the instance |
| other than the internal state of the entity. |
| |
| This method is separate from _populate_internal_entity so that it is |
| possible to call to_xml without changing the state of an unsaved entity |
| to saved. |
| |
| Returns: |
| self._entity or a new Entity which is not stored on the instance. |
| """ |
| if self.is_saved(): |
| entity = self._entity |
| else: |
| kwds = {'_app': self._app, 'namespace': self.__namespace, |
| 'unindexed_properties': self._unindexed_properties} |
| if self._key is not None: |
| if self._key.id(): |
| kwds['id'] = self._key.id() |
| else: |
| kwds['name'] = self._key.name() |
| if self._key.parent(): |
| kwds['parent'] = self._key.parent() |
| else: |
| if self._key_name is not None: |
| kwds['name'] = self._key_name |
| if self._parent_key is not None: |
| kwds['parent'] = self._parent_key |
| elif self._parent is not None: |
| kwds['parent'] = self._parent._entity |
| entity = _entity_class(self.kind(), **kwds) |
| |
| self._to_entity(entity) |
| return entity |
| |
| def delete(self, **kwargs): |
| """Deletes this entity from the datastore. |
| |
| Args: |
| config: datastore_rpc.Configuration to use for this request. |
| |
| Raises: |
| TransactionFailedError if the data could not be committed. |
| """ |
| datastore.Delete(self.key(), **kwargs) |
| |
| |
| self._key = self.key() |
| self._key_name = None |
| self._parent_key = None |
| self._entity = None |
| |
| |
| |
| def is_saved(self): |
| """Determine if entity is persisted in the datastore. |
| |
| New instances of Model do not start out saved in the data. Objects which |
| are saved to or loaded from the Datastore will have a True saved state. |
| |
| Returns: |
| True if object has been persisted to the datastore, otherwise False. |
| """ |
| return self._entity is not None |
| |
| def has_key(self): |
| """Determine if this model instance has a complete key. |
| |
| When not using a fully self-assigned 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 |
| or has a key_name, otherwise False. |
| """ |
| return self.is_saved() or self._key or self._key_name |
| |
| def dynamic_properties(self): |
| """Returns a list of all dynamic properties defined for instance.""" |
| return [] |
| |
| |
| def instance_properties(self): |
| """Alias for dyanmic_properties.""" |
| return self.dynamic_properties() |
| |
| def parent(self): |
| """Get the parent of the model instance. |
| |
| Returns: |
| Parent of contained entity or parent provided in constructor, None if |
| instance has no 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): |
| """Get the parent's key. |
| |
| This method is useful for avoiding a potential fetch from the datastore |
| but still get information about the instances parent. |
| |
| Returns: |
| Parent key of entity, None if there is no parent. |
| """ |
| 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() |
| elif self._key is not None: |
| return self._key.parent() |
| else: |
| return None |
| |
| def to_xml(self, _entity_class=datastore.Entity): |
| """Generate an XML representation of this model instance. |
| |
| atom and gd:namespace properties are converted to XML according to their |
| respective schemas. For more information, see: |
| |
| http://www.atomenabled.org/developers/syndication/ |
| http://code.google.com/apis/gdata/common-elements.html |
| """ |
| entity = self._populate_entity(_entity_class) |
| return entity.ToXml() |
| |
| @classmethod |
| def get(cls, keys, **kwargs): |
| """Fetch instance from the datastore of a specific Model type using key. |
| |
| We support Key objects and string keys (we convert them to Key objects |
| automatically). |
| |
| Useful for ensuring that specific instance types are retrieved from the |
| datastore. It also helps that the source code clearly indicates what |
| kind of object is being retreived. Example: |
| |
| story = Story.get(story_key) |
| |
| Args: |
| keys: Key within datastore entity collection to find; or string key; |
| or list of Keys or string keys. |
| config: datastore_rpc.Configuration to use for this request. |
| |
| Returns: |
| If a single key was given: a Model instance associated with key |
| for the provided class if it exists in the datastore, otherwise |
| None. If a list of keys was given: a list where list[i] is the |
| Model instance for keys[i], or None if no instance exists. |
| |
| Raises: |
| KindError if any of the retreived objects are not instances of the |
| type associated with call to 'get'. |
| """ |
| results = get(keys, **kwargs) |
| if results is None: |
| return None |
| |
| if isinstance(results, Model): |
| instances = [results] |
| else: |
| instances = results |
| |
| for instance in instances: |
| if not(instance is None or isinstance(instance, cls)): |
| raise KindError('Kind %r is not a subclass of kind %r' % |
| (instance.kind(), cls.kind())) |
| |
| return results |
| |
| @classmethod |
| def get_by_key_name(cls, key_names, parent=None, **kwargs): |
| """Get instance of Model class by its key's name. |
| |
| Args: |
| key_names: A single key-name or a list of key-names. |
| parent: Parent of instances to get. Can be a model or key. |
| config: datastore_rpc.Configuration to use for this request. |
| """ |
| try: |
| parent = _coerce_to_key(parent) |
| except BadKeyError, e: |
| |
| raise BadArgumentError(str(e)) |
| |
| key_names, multiple = datastore.NormalizeAndTypeCheck(key_names, basestring) |
| keys = [datastore.Key.from_path(cls.kind(), name, parent=parent) |
| for name in key_names] |
| if multiple: |
| return get(keys, **kwargs) |
| else: |
| return get(keys[0], **kwargs) |
| |
| @classmethod |
| def get_by_id(cls, ids, parent=None, **kwargs): |
| """Get instance of Model class by id. |
| |
| Args: |
| key_names: A single id or a list of ids. |
| parent: Parent of instances to get. Can be a model or key. |
| config: datastore_rpc.Configuration to use for this request. |
| """ |
| if isinstance(parent, Model): |
| parent = parent.key() |
| ids, multiple = datastore.NormalizeAndTypeCheck(ids, (int, long)) |
| keys = [datastore.Key.from_path(cls.kind(), id, parent=parent) |
| for id in ids] |
| if multiple: |
| return get(keys, **kwargs) |
| else: |
| return get(keys[0], **kwargs) |
| |
| |
| |
| |
| @classmethod |
| def get_or_insert(cls, key_name, **kwds): |
| """Transactionally retrieve or create an instance of Model class. |
| |
| This acts much like the Python dictionary setdefault() method, where we |
| first try to retrieve a Model instance with the given key name and parent. |
| If it's not present, then we create a new instance (using the *kwds |
| supplied) and insert that with the supplied key name. |
| |
| Subsequent calls to this method with the same key_name and parent will |
| always yield the same entity (though not the same actual object instance), |
| regardless of the *kwds supplied. If the specified entity has somehow |
| been deleted separately, then the next call will create a new entity and |
| return it. |
| |
| If the 'parent' keyword argument is supplied, it must be a Model instance. |
| It will be used as the parent of the new instance of this Model class if |
| one is created. |
| |
| This method is especially useful for having just one unique entity for |
| a specific identifier. Insertion/retrieval is done transactionally, which |
| guarantees uniqueness. |
| |
| Example usage: |
| |
| class WikiTopic(db.Model): |
| creation_date = db.DatetimeProperty(auto_now_add=True) |
| body = db.TextProperty(required=True) |
| |
| # The first time through we'll create the new topic. |
| wiki_word = 'CommonIdioms' |
| topic = WikiTopic.get_or_insert(wiki_word, |
| body='This topic is totally new!') |
| assert topic.key().name() == 'CommonIdioms' |
| assert topic.body == 'This topic is totally new!' |
| |
| # The second time through will just retrieve the entity. |
| overwrite_topic = WikiTopic.get_or_insert(wiki_word, |
| body='A totally different message!') |
| assert topic.key().name() == 'CommonIdioms' |
| assert topic.body == 'This topic is totally new!' |
| |
| Args: |
| key_name: Key name to retrieve or create. |
| **kwds: Keyword arguments to pass to the constructor of the model class |
| if an instance for the specified key name does not already exist. If |
| an instance with the supplied key_name and parent already exists, the |
| rest of these arguments will be discarded. |
| |
| Returns: |
| Existing instance of Model class with the specified key_name and parent |
| or a new one that has just been created. |
| |
| Raises: |
| TransactionFailedError if the specified Model instance could not be |
| retrieved or created transactionally (due to high contention, etc). |
| """ |
| def txn(): |
| entity = cls.get_by_key_name(key_name, parent=kwds.get('parent')) |
| if entity is None: |
| entity = cls(key_name=key_name, **kwds) |
| entity.put() |
| return entity |
| return run_in_transaction(txn) |
| |
| @classmethod |
| def all(cls, **kwds): |
| """Returns a query over all instances of this model from the datastore. |
| |
| Returns: |
| Query that will retrieve all instances from entity collection. |
| """ |
| return Query(cls, **kwds) |
| |
| @classmethod |
| def gql(cls, query_string, *args, **kwds): |
| """Returns a query using GQL query string. |
| |
| See appengine/ext/gql for more information about GQL. |
| |
| Args: |
| query_string: properly formatted GQL query string with the |
| 'SELECT * FROM <entity>' part omitted |
| *args: rest of the positional arguments used to bind numeric references |
| in the query. |
| **kwds: dictionary-based arguments (for named parameters). |
| """ |
| |
| |
| return GqlQuery('SELECT * FROM %s %s' % (cls.kind(), query_string), |
| *args, **kwds) |
| |
| @classmethod |
| def _load_entity_values(cls, entity): |
| """Load dynamic properties from entity. |
| |
| Loads attributes which are not defined as part of the entity in |
| to the model instance. |
| |
| Args: |
| entity: Entity which contain values to search dyanmic properties for. |
| """ |
| entity_values = {} |
| for prop in cls.properties().values(): |
| if prop.name in entity: |
| try: |
| value = entity[prop.name] |
| except KeyError: |
| |
| entity_values[prop.name] = [] |
| else: |
| if entity.is_projection(): |
| value = prop.make_value_from_datastore_index_value(value) |
| else: |
| value = prop.make_value_from_datastore(value) |
| entity_values[prop.name] = value |
| |
| |
| return entity_values |
| |
| @classmethod |
| def from_entity(cls, entity): |
| """Converts the entity representation of this model to an instance. |
| |
| Converts datastore.Entity instance to an instance of cls. |
| |
| Args: |
| entity: Entity loaded directly from datastore. |
| |
| Raises: |
| KindError when cls is incorrect model for entity. |
| """ |
| if cls.kind() != entity.kind(): |
| raise KindError('Class %s cannot handle kind \'%s\'' % |
| (repr(cls), entity.kind())) |
| |
| entity_values = cls._load_entity_values(entity) |
| if entity.key().has_id_or_name(): |
| entity_values['key'] = entity.key() |
| return cls(None, _from_entity=entity, **entity_values) |
| |
| @classmethod |
| def kind(cls): |
| """Returns the datastore kind we use for this model. |
| |
| We just use the name of the model for now, ignoring potential collisions. |
| """ |
| return cls.__name__ |
| |
| @classmethod |
| def entity_type(cls): |
| """Soon to be removed alias for kind.""" |
| return cls.kind() |
| |
| @classmethod |
| def properties(cls): |
| """Returns a dictionary of all the properties defined for this model.""" |
| return dict(cls._properties) |
| |
| @classmethod |
| def fields(cls): |
| """Soon to be removed alias for properties.""" |
| return cls.properties() |
| |
| |
| def create_rpc(deadline=None, callback=None, read_policy=STRONG_CONSISTENCY): |
| """Create an rpc for use in configuring datastore calls. |
| |
| NOTE: This functions exists for backwards compatibility. Please use |
| create_config() instead. NOTE: the latter uses 'on_completion', |
| which is a function taking an argument, wherease create_rpc uses |
| 'callback' which is a function without arguments. |
| |
| Args: |
| deadline: float, deadline for calls in seconds. |
| callback: callable, a callback triggered when this rpc completes, |
| accepts one argument: the returned rpc. |
| read_policy: flag, set to EVENTUAL_CONSISTENCY to enable eventually |
| consistent reads |
| |
| Returns: |
| A datastore.DatastoreRPC instance. |
| """ |
| return datastore.CreateRPC( |
| deadline=deadline, callback=callback, read_policy=read_policy) |
| |
| |
| def get_async(keys, **kwargs): |
| """Asynchronously fetch the specified Model instance(s) from the datastore. |
| |
| Identical to db.get() except returns an asynchronous object. Call |
| get_result() on the return value to block on the call and get the results. |
| """ |
| keys, multiple = datastore.NormalizeAndTypeCheckKeys(keys) |
| def extra_hook(entities): |
| if not multiple and not entities: |
| return None |
| |
| models = [] |
| for entity in entities: |
| if entity is None: |
| model = None |
| else: |
| cls1 = class_for_kind(entity.kind()) |
| model = cls1.from_entity(entity) |
| models.append(model) |
| |
| if multiple: |
| return models |
| assert len(models) == 1 |
| return models[0] |
| |
| return datastore.GetAsync(keys, extra_hook=extra_hook, **kwargs) |
| |
| |
| |
| def get(keys, **kwargs): |
| """Fetch the specific Model instance with the given key from the datastore. |
| |
| We support Key objects and string keys (we convert them to Key objects |
| automatically). |
| |
| Args: |
| keys: Key within datastore entity collection to find; or string key; |
| or list of Keys or string keys. |
| config: datastore_rpc.Configuration to use for this request, must be |
| specified as a keyword argument. |
| |
| Returns: |
| If a single key was given: a Model instance associated with key |
| if it exists in the datastore, otherwise None. If a list of keys was |
| given: a list where list[i] is the Model instance for keys[i], or |
| None if no instance exists. |
| """ |
| return get_async(keys, **kwargs).get_result() |
| |
| |
| def put_async(models, **kwargs): |
| """Asynchronously store one or more Model instances. |
| |
| Identical to db.put() except returns an asynchronous object. Call |
| get_result() on the return value to block on the call and get the results. |
| """ |
| models, multiple = datastore.NormalizeAndTypeCheck(models, Model) |
| entities = [model._populate_internal_entity() for model in models] |
| |
| def extra_hook(keys): |
| if multiple: |
| return keys |
| assert len(keys) == 1 |
| return keys[0] |
| |
| return datastore.PutAsync(entities, extra_hook=extra_hook, **kwargs) |
| |
| |
| def put(models, **kwargs): |
| """Store one or more Model instances. |
| |
| Args: |
| models: Model instance or list of Model instances. |
| config: datastore_rpc.Configuration to use for this request, must be |
| specified as a keyword argument. |
| |
| Returns: |
| A Key if models is an instance, a list of Keys in the same order |
| as models if models is a list. |
| |
| Raises: |
| TransactionFailedError if the data could not be committed. |
| """ |
| return put_async(models, **kwargs).get_result() |
| |
| |
| |
| |
| save = put |
| |
| |
| def delete_async(models, **kwargs): |
| """Asynchronous version of delete one or more Model instances. |
| |
| Identical to db.delete() except returns an asynchronous object. Call |
| get_result() on the return value to block on the call. |
| """ |
| |
| if isinstance(models, (basestring, Model, Key)): |
| models = [models] |
| else: |
| try: |
| models = iter(models) |
| except TypeError: |
| models = [models] |
| keys = [_coerce_to_key(v) for v in models] |
| |
| return datastore.DeleteAsync(keys, **kwargs) |
| |
| |
| def delete(models, **kwargs): |
| """Delete one or more Model instances. |
| |
| Args: |
| models: Model instance, key, key string or iterable thereof. |
| config: datastore_rpc.Configuration to use for this request, must be |
| specified as a keyword argument. |
| |
| Raises: |
| TransactionFailedError if the data could not be committed. |
| """ |
| delete_async(models, **kwargs).get_result() |
| |
| |
| def allocate_ids_async(model, size, **kwargs): |
| """Asynchronously allocates a range of IDs. |
| |
| Identical to allocate_ids() except returns an asynchronous object. Call |
| get_result() on the return value to block on the call and return the result. |
| """ |
| return datastore.AllocateIdsAsync(_coerce_to_key(model), size=size, **kwargs) |
| |
| |
| def allocate_ids(model, size, **kwargs): |
| """Allocates a range of IDs of size for the model_key defined by model. |
| |
| Allocates a range of IDs in the datastore such that those IDs will not |
| be automatically assigned to new entities. You can only allocate IDs |
| for model keys from your app. If there is an error, raises a subclass of |
| datastore_errors.Error. |
| |
| Args: |
| model: Model instance, Key or string to serve as a template specifying the |
| ID sequence in which to allocate IDs. Returned ids should only be used |
| in entities with the same parent (if any) and kind as this key. |
| size: Number of IDs to allocate. |
| config: datastore_rpc.Configuration to use for this request. |
| |
| Returns: |
| (start, end) of the allocated range, inclusive. |
| """ |
| return allocate_ids_async(model, size, **kwargs).get_result() |
| |
| |
| def allocate_id_range(model, start, end, **kwargs): |
| """Allocates a range of IDs with specific endpoints. |
| |
| Once these IDs have been allocated they may be provided manually to |
| newly created entities. |
| |
| Since the datastore's automatic ID allocator will never assign |
| a key to a new entity that will cause an existing entity to be |
| overwritten, entities written to the given key range will never be |
| overwritten. However, writing entities with manually assigned keys in this |
| range may overwrite existing entities (or new entities written by a |
| separate request) depending on the key range state returned. |
| |
| This method should only be used if you have an existing numeric id |
| range that you want to reserve, e.g. bulk loading entities that already |
| have IDs. If you don't care about which IDs you receive, use allocate_ids |
| instead. |
| |
| Args: |
| model: Model instance, Key or string to serve as a template specifying the |
| ID sequence in which to allocate IDs. Allocated ids should only be used |
| in entities with the same parent (if any) and kind as this key. |
| start: first id of the range to allocate, inclusive. |
| end: last id of the range to allocate, inclusive. |
| config: datastore_rpc.Configuration to use for this request. |
| |
| Returns: |
| One of (KEY_RANGE_EMPTY, KEY_RANGE_CONTENTION, KEY_RANGE_COLLISION). If not |
| KEY_RANGE_EMPTY, this represents a potential issue with using the allocated |
| key range. |
| """ |
| key = _coerce_to_key(model) |
| datastore.NormalizeAndTypeCheck((start, end), (int, long)) |
| if start < 1 or end < 1: |
| raise BadArgumentError('Start %d and end %d must both be > 0.' % |
| (start, end)) |
| if start > end: |
| raise BadArgumentError('Range end %d cannot be less than start %d.' % |
| (end, start)) |
| |
| safe_start, _ = datastore.AllocateIds(key, max=end, **kwargs) |
| |
| |
| |
| race_condition = safe_start > start |
| |
| |
| |
| |
| |
| |
| |
| start_key = Key.from_path(key.kind(), start, parent=key.parent(), |
| _app=key.app(), namespace=key.namespace()) |
| end_key = Key.from_path(key.kind(), end, parent=key.parent(), |
| _app=key.app(), namespace=key.namespace()) |
| collision = (Query(keys_only=True, namespace=key.namespace(), _app=key.app()) |
| .filter('__key__ >=', start_key) |
| .filter('__key__ <=', end_key).fetch(1)) |
| |
| if collision: |
| return KEY_RANGE_COLLISION |
| elif race_condition: |
| return KEY_RANGE_CONTENTION |
| else: |
| return KEY_RANGE_EMPTY |
| |
| |
| def _index_converter(index): |
| return Index(index.Id(), |
| index.Kind(), |
| index.HasAncestor(), |
| index.Properties()) |
| |
| |
| def get_indexes_async(**kwargs): |
| """Asynchronously retrieves the application indexes and their states. |
| |
| Identical to get_indexes() except returns an asynchronous object. Call |
| get_result() on the return value to block on the call and get the results. |
| """ |
| def extra_hook(indexes): |
| return [(_index_converter(index), state) for index, state in indexes] |
| |
| return datastore.GetIndexesAsync(extra_hook=extra_hook, **kwargs) |
| |
| |
| def get_indexes(**kwargs): |
| """Retrieves the application indexes and their states. |
| |
| Args: |
| config: datastore_rpc.Configuration to use for this request, must be |
| specified as a keyword argument. |
| |
| Returns: |
| A list of (Index, Index.[BUILDING|SERVING|DELETING|ERROR]) tuples. |
| An index can be in the following states: |
| Index.BUILDING: Index is being built and therefore can not serve queries |
| Index.SERVING: Index is ready to service queries |
| Index.DELETING: Index is being deleted |
| Index.ERROR: Index encounted an error in the BUILDING state |
| """ |
| return get_indexes_async(**kwargs).get_result() |
| |
| |
| class Expando(Model): |
| """Dynamically expandable model. |
| |
| An Expando does not require (but can still benefit from) the definition |
| of any properties before it can be used to store information in the |
| datastore. Properties can be added to an expando object by simply |
| performing an assignment. The assignment of properties is done on |
| an instance by instance basis, so it is possible for one object of an |
| expando type to have different properties from another or even the same |
| properties with different types. It is still possible to define |
| properties on an expando, allowing those properties to behave the same |
| as on any other model. |
| |
| Example: |
| import datetime |
| |
| class Song(db.Expando): |
| title = db.StringProperty() |
| |
| crazy = Song(title='Crazy like a diamond', |
| author='Lucy Sky', |
| publish_date='yesterday', |
| rating=5.0) |
| |
| hoboken = Song(title='The man from Hoboken', |
| author=['Anthony', 'Lou'], |
| publish_date=datetime.datetime(1977, 5, 3)) |
| |
| crazy.last_minute_note=db.Text('Get a train to the station.') |
| |
| Possible Uses: |
| |
| One use of an expando is to create an object without any specific |
| structure and later, when your application mature and it in the right |
| state, change it to a normal model object and define explicit properties. |
| |
| Additional exceptions for expando: |
| |
| Protected attributes (ones whose names begin with '_') cannot be used |
| as dynamic properties. These are names that are reserved for protected |
| transient (non-persisted) attributes. |
| |
| Order of lookup: |
| |
| When trying to set or access an attribute value, any other defined |
| properties, such as methods and other values in __dict__ take precedence |
| over values in the datastore. |
| |
| 1 - Because it is not possible for the datastore to know what kind of |
| property to store on an undefined expando value, setting a property to |
| None is the same as deleting it from the expando. |
| |
| 2 - Persistent variables on Expando must not begin with '_'. These |
| variables considered to be 'protected' in Python, and are used |
| internally. |
| |
| 3 - Expando's dynamic properties are not able to store empty lists. |
| Attempting to assign an empty list to a dynamic property will raise |
| ValueError. Static properties on Expando can still support empty |
| lists but like normal Model properties is restricted from using |
| None. |
| """ |
| |
| _dynamic_properties = None |
| |
| def __init__(self, parent=None, key_name=None, _app=None, **kwds): |
| """Creates a new instance of this expando model. |
| |
| Args: |
| parent: Parent instance for this instance or None, indicating a top- |
| level instance. |
| key_name: Name for new model instance. |
| _app: Intentionally undocumented. |
| args: Keyword arguments mapping to properties of model. |
| """ |
| super(Expando, self).__init__(parent, key_name, _app, **kwds) |
| self._dynamic_properties = {} |
| for prop, value in kwds.iteritems(): |
| if prop not in self._all_properties and prop != 'key': |
| |
| |
| |
| if not (hasattr(getattr(type(self), prop, None), '__set__')): |
| setattr(self, prop, value) |
| else: |
| check_reserved_word(prop) |
| |
| def __setattr__(self, key, value): |
| """Dynamically set field values that are not defined. |
| |
| Tries to set the value on the object normally, but failing that |
| sets the value on the contained entity. |
| |
| Args: |
| key: Name of attribute. |
| value: Value to set for attribute. Must be compatible with |
| datastore. |
| |
| Raises: |
| ValueError on attempt to assign empty list. |
| """ |
| check_reserved_word(key) |
| if (key[:1] != '_' and |
| |
| |
| |
| not hasattr(getattr(type(self), key, None), '__set__')): |
| if value == []: |
| raise ValueError('Cannot store empty list to dynamic property %s' % |
| key) |
| if type(value) not in _ALLOWED_EXPANDO_PROPERTY_TYPES: |
| raise TypeError("Expando cannot accept values of type '%s'." % |
| type(value).__name__) |
| |
| if self._dynamic_properties is None: |
| self._dynamic_properties = {} |
| self._dynamic_properties[key] = value |
| else: |
| super(Expando, self).__setattr__(key, value) |
| |
| def __getattribute__(self, key): |
| """Get attribute from expando. |
| |
| Must be overridden to allow dynamic properties to obscure class attributes. |
| Since all attributes are stored in self._dynamic_properties, the normal |
| __getattribute__ does not attempt to access it until __setattr__ is called. |
| By then, the static attribute being overwritten has already been located |
| and returned from the call. |
| |
| This method short circuits the usual __getattribute__ call when finding a |
| dynamic property and returns it to the user via __getattr__. __getattr__ |
| is called to preserve backward compatibility with older Expando models |
| that may have overridden the original __getattr__. |
| |
| NOTE: Access to properties defined by Python descriptors are not obscured |
| because setting those attributes are done through the descriptor and does |
| not place those attributes in self._dynamic_properties. |
| """ |
| if not key.startswith('_'): |
| dynamic_properties = self._dynamic_properties |
| if dynamic_properties is not None and key in dynamic_properties: |
| |
| |
| |
| return self.__getattr__(key) |
| |
| return super(Expando, self).__getattribute__(key) |
| |
| |
| |
| def __getattr__(self, key): |
| """If no explicit attribute defined, retrieve value from entity. |
| |
| Tries to get the value on the object normally, but failing that |
| retrieves value from contained entity. |
| |
| Args: |
| key: Name of attribute. |
| |
| Raises: |
| AttributeError when there is no attribute for key on object or |
| contained entity. |
| """ |
| _dynamic_properties = self._dynamic_properties |
| if _dynamic_properties is not None and key in _dynamic_properties: |
| return _dynamic_properties[key] |
| else: |
| return getattr(super(Expando, self), key) |
| |
| def __delattr__(self, key): |
| """Remove attribute from expando. |
| |
| Expando is not like normal entities in that undefined fields |
| can be removed. |
| |
| Args: |
| key: Dynamic property to be deleted. |
| """ |
| if self._dynamic_properties and key in self._dynamic_properties: |
| del self._dynamic_properties[key] |
| else: |
| object.__delattr__(self, key) |
| |
| def dynamic_properties(self): |
| """Determine which properties are particular to instance of entity. |
| |
| Returns: |
| Set of names which correspond only to the dynamic properties. |
| """ |
| if self._dynamic_properties is None: |
| return [] |
| return self._dynamic_properties.keys() |
| |
| def _to_entity(self, entity): |
| """Store to entity, deleting dynamic properties that no longer exist. |
| |
| When the expando is saved, it is possible that a given property no longer |
| exists. In this case, the property will be removed from the saved instance. |
| |
| Args: |
| entity: Entity which will receive dynamic properties. |
| """ |
| super(Expando, self)._to_entity(entity) |
| |
| if self._dynamic_properties is None: |
| self._dynamic_properties = {} |
| |
| for key, value in self._dynamic_properties.iteritems(): |
| entity[key] = value |
| |
| all_properties = set(self._dynamic_properties.iterkeys()) |
| all_properties.update(self._all_properties) |
| for key in entity.keys(): |
| if key not in all_properties: |
| del entity[key] |
| |
| @classmethod |
| def _load_entity_values(cls, entity): |
| """Load dynamic properties from entity. |
| |
| Expando needs to do a second pass to add the entity values which were |
| ignored by Model because they didn't have an corresponding predefined |
| property on the model. |
| |
| Args: |
| entity: Entity which contain values to search dyanmic properties for. |
| """ |
| 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 |
| return entity_values |
| |
| |
| class _BaseQuery(object): |
| """Base class for both Query and GqlQuery.""" |
| |
| _last_raw_query = None |
| _last_index_list = None |
| _cursor = None |
| _end_cursor = None |
| |
| def __init__(self, model_class=None): |
| """Constructor. |
| |
| Args: |
| model_class: Model class from which entities are constructed. |
| keys_only: Whether the query should return full entities or only keys. |
| compile: Whether the query should also return a compiled query. |
| cursor: A compiled query from which to resume. |
| namespace: The namespace to query. |
| """ |
| self._model_class = model_class |
| |
| def is_keys_only(self): |
| """Returns whether this query is keys only. |
| |
| Returns: |
| True if this query returns keys, False if it returns entities. |
| """ |
| raise NotImplementedError |
| |
| def projection(self): |
| """Returns the tuple of properties in the projection or None. |
| |
| Projected results differ from normal results in multiple ways: |
| - they only contain a portion of the original entity and cannot be put; |
| - properties defined on the model, but not included in the projections will |
| have a value of None, even if the property is required or has a default |
| value; |
| - multi-valued properties (such as a ListProperty) will only contain a single |
| value. |
| - dynamic properties not included in the projection will not appear |
| on the model instance. |
| - dynamic properties included in the projection are deserialized into |
| their indexed type. Specifically one of str, bool, long, float, GeoPt, Key |
| or User. If the original type is known, it can be restored using |
| datastore_types.RestoreFromIndexValue. |
| |
| However, projection queries are significantly faster than normal queries. |
| |
| Projection queries on entities with multi-valued properties will return the |
| same entity multiple times, once for each unique combination of values for |
| properties included in the order, an inequaly property, or the projected |
| properties. |
| |
| Returns: |
| The list of properties in the projection, or None if no projection is |
| set on this query. |
| """ |
| raise NotImplementedError |
| |
| def is_distinct(self): |
| """Returns true if the projection query should be distinct. |
| |
| This is equivalent to the SQL syntax: SELECT DISTINCT. It is only available |
| for projection queries, it is not valid to specify distinct without also |
| specifying projection properties. |
| |
| Distinct projection queries on entities with multi-valued properties will |
| return the same entity multiple times, once for each unique combination of |
| properties included in the projection. |
| |
| Returns: |
| True if this projection query is distinct. |
| """ |
| raise NotImplementedError |
| |
| def _get_query(self): |
| """Subclass must override (and not call their super method). |
| |
| Returns: |
| A datastore.Query instance representing the query. |
| """ |
| raise NotImplementedError |
| |
| def run(self, **kwargs): |
| """Iterator for this query. |
| |
| If you know the number of results you need, use run(limit=...) instead, |
| or use a GQL query with a LIMIT clause. It's more efficient. If you want |
| all results use run(batch_size=<large number>). |
| |
| Args: |
| kwargs: Any keyword arguments accepted by datastore_query.QueryOptions(). |
| |
| Returns: |
| Iterator for this query. |
| """ |
| raw_query = self._get_query() |
| iterator = raw_query.Run(**kwargs) |
| self._last_raw_query = raw_query |
| |
| keys_only = kwargs.get('keys_only') |
| if keys_only is None: |
| keys_only = self.is_keys_only() |
| |
| if keys_only: |
| return iterator |
| else: |
| return _QueryIterator(self._model_class, iter(iterator)) |
| |
| def __iter__(self): |
| """Iterator for this query. |
| |
| If you know the number of results you need, consider fetch() instead, |
| or use a GQL query with a LIMIT clause. It's more efficient. |
| """ |
| return self.run() |
| |
| def __getstate__(self): |
| state = self.__dict__.copy() |
| state['_last_raw_query'] = None |
| return state |
| |
| def get(self, **kwargs): |
| """Get first result from this. |
| |
| Beware: get() ignores the LIMIT clause on GQL queries. |
| |
| Args: |
| kwargs: Any keyword arguments accepted by datastore_query.QueryOptions(). |
| |
| Returns: |
| First result from running the query if there are any, else None. |
| """ |
| results = self.run(limit=1, **kwargs) |
| try: |
| return results.next() |
| except StopIteration: |
| return None |
| |
| def count(self, limit=1000, **kwargs): |
| """Number of entities this query fetches. |
| |
| Beware: count() ignores the LIMIT clause on GQL queries. |
| |
| Args: |
| limit: A number. If there are more results than this, stop short and |
| just return this number. Providing this argument makes the count |
| operation more efficient. |
| kwargs: Any keyword arguments accepted by datastore_query.QueryOptions(). |
| |
| Returns: |
| Number of entities this query fetches. |
| """ |
| raw_query = self._get_query() |
| result = raw_query.Count(limit=limit, **kwargs) |
| self._last_raw_query = raw_query |
| |
| return result |
| |
| def fetch(self, limit, offset=0, **kwargs): |
| """Return a list of items selected using SQL-like limit and offset. |
| |
| Always use run(limit=...) instead of fetch() when iterating over a query. |
| |
| Beware: offset must read and discard all skipped entities. Use |
| cursor()/with_cursor() instead. |
| |
| Args: |
| limit: Maximum number of results to return. |
| offset: Optional number of results to skip first; default zero. |
| kwargs: Any keyword arguments accepted by datastore_query.QueryOptions(). |
| |
| Returns: |
| A list of db.Model instances. There may be fewer than 'limit' |
| results if there aren't enough results to satisfy the request. |
| """ |
| if limit is None: |
| kwargs.setdefault('batch_size', datastore._MAX_INT_32) |
| return list(self.run(limit=limit, offset=offset, **kwargs)) |
| |
| def index_list(self): |
| """Get the index list for an already executed query. |
| |
| Returns: |
| A list of indexes used by the query. |
| |
| Raises: |
| AssertionError: If the query has not been executed. |
| """ |
| if self._last_raw_query is None: |
| raise AssertionError('No index list because query has not been run.') |
| if self._last_index_list is None: |
| raw_index_list = self._last_raw_query.GetIndexList() |
| self._last_index_list = [_index_converter(raw_index) |
| for raw_index in raw_index_list] |
| return self._last_index_list |
| |
| def cursor(self): |
| """Get a serialized cursor for an already executed query. |
| |
| The returned cursor effectively lets a future invocation of a similar |
| query to begin fetching results immediately after the last returned |
| result from this query invocation. |
| |
| Returns: |
| A base64-encoded serialized cursor. |
| |
| Raises: |
| AssertionError: If the query has not been executed. |
| """ |
| if self._last_raw_query is None: |
| raise AssertionError('No cursor available.') |
| cursor = self._last_raw_query.GetCursor() |
| return websafe_encode_cursor(cursor) |
| |
| def with_cursor(self, start_cursor=None, end_cursor=None): |
| """Set the start and end of this query using serialized cursors. |
| |
| Conceptually cursors point to the position between the last result returned |
| and the next result so running a query with each of the following cursors |
| combinations will return all results in four chunks with no duplicate |
| results: |
| |
| query.with_cursor(end_cursor=cursor1) |
| query.with_cursors(cursor1, cursor2) |
| query.with_cursors(cursor2, cursor3) |
| query.with_cursors(start_cursor=cursor3) |
| |
| For example if the cursors pointed to: |
| cursor: 1 2 3 |
| result: a b c d e f g h |
| |
| The results returned by these queries would be [a, b], [c, d], [e, f], |
| [g, h] respectively. |
| |
| Cursors are pinned to the position just after the previous result (last |
| result, exclusive), so if results are inserted or deleted between the time |
| the cursor was made and these queries are executed, the cursors stay pinned |
| to these positions. For example: |
| |
| delete(b, f, g, h) |
| put(a1, b1, c1, d1) |
| cursor: 1(b) 2(d) 3(f) |
| result: a a1 b1 c c1 d d1 e |
| |
| The results returned by these queries would now be: [a, a1], [b1, c, c1, d], |
| [d1, e], [] respectively. |
| |
| Args: |
| start_cursor: The cursor position at which to start or None |
| end_cursor: The cursor position at which to end or None |
| |
| Returns: |
| This Query instance, for chaining. |
| |
| Raises: |
| BadValueError when cursor is not valid. |
| """ |
| if start_cursor is None: |
| self._cursor = None |
| else: |
| self._cursor = websafe_decode_cursor(start_cursor) |
| |
| if end_cursor is None: |
| self._end_cursor = None |
| else: |
| self._end_cursor = websafe_decode_cursor(end_cursor) |
| |
| return self |
| |
| def __getitem__(self, arg): |
| """Support for query[index] and query[start:stop]. |
| |
| Beware: this ignores the LIMIT clause on GQL queries. |
| |
| Args: |
| arg: Either a single integer, corresponding to the query[index] |
| syntax, or a Python slice object, corresponding to the |
| query[start:stop] or query[start:stop:step] syntax. |
| |
| Returns: |
| A single Model instance when the argument is a single integer. |
| A list of Model instances when the argument is a slice. |
| """ |
| |
| if isinstance(arg, slice): |
| start, stop, step = arg.start, arg.stop, arg.step |
| if start is None: |
| start = 0 |
| if stop is None: |
| raise ValueError('Open-ended slices are not supported') |
| if step is None: |
| step = 1 |
| if start < 0 or stop < 0 or step != 1: |
| raise ValueError( |
| 'Only slices with start>=0, stop>=0, step==1 are supported') |
| limit = stop - start |
| if limit < 0: |
| return [] |
| return self.fetch(limit, start) |
| elif isinstance(arg, (int, long)): |
| if arg < 0: |
| raise ValueError('Only indices >= 0 are supported') |
| results = self.fetch(1, arg) |
| if results: |
| return results[0] |
| else: |
| raise IndexError('The query returned fewer than %d results' % (arg+1)) |
| else: |
| raise TypeError('Only integer indices and slices are supported') |
| |
| |
| class _QueryIterator(object): |
| """Wraps the datastore iterator to return Model instances. |
| |
| The datastore returns entities. We wrap the datastore iterator to |
| return Model instances instead. |
| """ |
| |
| def __init__(self, model_class, datastore_iterator): |
| """Iterator constructor |
| |
| Args: |
| model_class: Model class from which entities are constructed. |
| datastore_iterator: Underlying datastore iterator. |
| """ |
| self.__model_class = model_class |
| self.__iterator = datastore_iterator |
| |
| def __iter__(self): |
| """Iterator on self. |
| |
| Returns: |
| Self. |
| """ |
| return self |
| |
| def next(self): |
| """Get next Model instance in query results. |
| |
| Returns: |
| Next model instance. |
| |
| Raises: |
| StopIteration when there are no more results in query. |
| """ |
| if self.__model_class is not None: |
| return self.__model_class.from_entity(self.__iterator.next()) |
| else: |
| while True: |
| entity = self.__iterator.next() |
| try: |
| model_class = class_for_kind(entity.kind()) |
| except KindError: |
| |
| if datastore_types.RESERVED_PROPERTY_NAME.match(entity.kind()): |
| continue |
| raise |
| else: |
| return model_class.from_entity(entity) |
| |
| |
| def _normalize_query_parameter(value): |
| """Make any necessary type conversions to a query parameter. |
| |
| The following conversions are made: |
| - Model instances are converted to Key instances. This is necessary so |
| that querying reference properties will work. |
| - datetime.date objects are converted to datetime.datetime objects (see |
| _date_to_datetime for details on this conversion). This is necessary so |
| that querying date properties with date objects will work. |
| - datetime.time objects are converted to datetime.datetime objects (see |
| _time_to_datetime for details on this conversion). This is necessary so |
| that querying time properties with time objects will work. |
| |
| Args: |
| value: The query parameter value. |
| |
| Returns: |
| The input value, or a converted value if value matches one of the |
| conversions specified above. |
| """ |
| |
| |
| |
| if isinstance(value, Model): |
| value = value.key() |
| |
| if (isinstance(value, datetime.date) and |
| not isinstance(value, datetime.datetime)): |
| value = _date_to_datetime(value) |
| elif isinstance(value, datetime.time): |
| value = _time_to_datetime(value) |
| return value |
| |
| |
| class Query(_BaseQuery): |
| """A Query instance queries over instances of Models. |
| |
| You construct a query with a model class, like this: |
| |
| class Story(db.Model): |
| title = db.StringProperty() |
| date = db.DateTimeProperty() |
| |
| query = Query(Story) |
| |
| You modify a query with filters and orders like this: |
| |
| query.filter('title =', 'Foo') |
| query.order('-date') |
| query.ancestor(key_or_model_instance) |
| |
| Every query can return an iterator, so you access the results of a query |
| by iterating over it: |
| |
| for story in query: |
| print story.title |
| |
| For convenience, all of the filtering and ordering methods return "self", |
| so the easiest way to use the query interface is to cascade all filters and |
| orders in the iterator line like this: |
| |
| for story in Query(story).filter('title =', 'Foo').order('-date'): |
| print story.title |
| """ |
| |
| |
| _keys_only = False |
| _distinct = False |
| _projection = None |
| _namespace = None |
| _app = None |
| __ancestor = None |
| |
| def __init__(self, model_class=None, keys_only=False, cursor=None, |
| namespace=None, _app=None, distinct=False, projection=None): |
| """Constructs a query over instances of the given Model. |
| |
| Args: |
| model_class: Model class to build query for. |
| keys_only: Whether the query should return full entities or only keys. |
| projection: A tuple of strings representing the property names to include |
| in the projection this query should produce or None. Setting a |
| projection is similar to specifying 'SELECT prop1, prop2, ...' in SQL. |
| See _BaseQuery.projection for details on projection queries. |
| distinct: A boolean, true if the projection should be distinct. |
| See _BaseQuery.is_distinct for details on distinct queries. |
| cursor: A compiled query from which to resume. |
| namespace: The namespace to use for this query. |
| """ |
| super(Query, self).__init__(model_class) |
| |
| if keys_only: |
| self._keys_only = True |
| if projection: |
| self._projection = projection |
| if namespace is not None: |
| self._namespace = namespace |
| if _app is not None: |
| self._app = _app |
| if distinct: |
| self._distinct = True |
| |
| self.__query_sets = [{}] |
| self.__orderings = [] |
| self.with_cursor(cursor) |
| |
| def is_keys_only(self): |
| return self._keys_only |
| |
| def projection(self): |
| return self._projection |
| |
| def is_distinct(self): |
| return self._distinct |
| |
| def _get_query(self, |
| _query_class=datastore.Query, |
| _multi_query_class=datastore.MultiQuery): |
| |
| |
| |
| |
| |
| |
| queries = [] |
| for query_set in self.__query_sets: |
| if self._model_class is not None: |
| kind = self._model_class.kind() |
| else: |
| kind = None |
| query = _query_class(kind, |
| query_set, |
| keys_only=self._keys_only, |
| projection=self._projection, |
| distinct=self._distinct, |
| compile=True, |
| cursor=self._cursor, |
| end_cursor=self._end_cursor, |
| namespace=self._namespace, |
| _app=self._app) |
| query.Order(*self.__orderings) |
| if self.__ancestor is not None: |
| query.Ancestor(self.__ancestor) |
| queries.append(query) |
| |
| |
| if (_query_class != datastore.Query and |
| _multi_query_class == datastore.MultiQuery): |
| |
| |
| |
| warnings.warn( |
| 'Custom _query_class specified without corresponding custom' |
| ' _query_multi_class. Things will break if you use queries with' |
| ' the "IN" or "!=" operators.', RuntimeWarning) |
| if len(queries) > 1: |
| raise datastore_errors.BadArgumentError( |
| 'Query requires multiple subqueries to satisfy. If _query_class' |
| ' is overridden, _multi_query_class must also be overridden.') |
| elif (_query_class == datastore.Query and |
| _multi_query_class != datastore.MultiQuery): |
| raise BadArgumentError('_query_class must also be overridden if' |
| ' _multi_query_class is overridden.') |
| |
| if len(queries) == 1: |
| return queries[0] |
| else: |
| return _multi_query_class(queries, self.__orderings) |
| |
| def __filter_disjunction(self, operations, values): |
| """Add a disjunction of several filters and several values to the query. |
| |
| This is implemented by duplicating queries and combining the |
| results later. |
| |
| Args: |
| operations: a string or list of strings. Each string contains a |
| property name and an operator to filter by. The operators |
| themselves must not require multiple queries to evaluate |
| (currently, this means that 'in' and '!=' are invalid). |
| |
| values: a value or list of filter values, normalized by |
| _normalize_query_parameter. |
| """ |
| if not isinstance(operations, (list, tuple)): |
| operations = [operations] |
| if not isinstance(values, (list, tuple)): |
| values = [values] |
| |
| new_query_sets = [] |
| for operation in operations: |
| |
| |
| if operation.lower().endswith('in') or operation.endswith('!='): |
| raise BadQueryError('Cannot use "in" or "!=" in a disjunction.') |
| for query_set in self.__query_sets: |
| for value in values: |
| |
| |
| |
| new_query_set = copy.deepcopy(query_set) |
| datastore._AddOrAppend(new_query_set, operation, value) |
| new_query_sets.append(new_query_set) |
| self.__query_sets = new_query_sets |
| |
| def filter(self, property_operator, value): |
| """Add filter to query. |
| |
| Args: |
| property_operator: string with the property and operator to filter by. |
| value: the filter value. |
| |
| Returns: |
| Self to support method chaining. |
| |
| Raises: |
| PropertyError if invalid property is provided. |
| """ |
| match = _FILTER_REGEX.match(property_operator) |
| prop = match.group(1) |
| if match.group(3) is not None: |
| operator = match.group(3) |
| else: |
| operator = '==' |
| |
| if self._model_class is None: |
| if prop != datastore_types.KEY_SPECIAL_PROPERTY: |
| raise BadQueryError( |
| 'Only %s filters are allowed on kindless queries.' % |
| datastore_types.KEY_SPECIAL_PROPERTY) |
| elif prop in self._model_class._unindexed_properties: |
| raise PropertyError('Property \'%s\' is not indexed' % prop) |
| |
| if operator.lower() == 'in': |
| if self._keys_only: |
| raise BadQueryError('Keys only queries do not support IN filters.') |
| elif not isinstance(value, (list, tuple)): |
| raise BadValueError('Argument to the "in" operator must be a list') |
| values = [_normalize_query_parameter(v) for v in value] |
| self.__filter_disjunction(prop + ' =', values) |
| else: |
| if isinstance(value, (list, tuple)): |
| raise BadValueError('Filtering on lists is not supported') |
| if operator == '!=': |
| if self._keys_only: |
| raise BadQueryError('Keys only queries do not support != filters.') |
| self.__filter_disjunction([prop + ' <', prop + ' >'], |
| _normalize_query_parameter(value)) |
| else: |
| value = _normalize_query_parameter(value) |
| for query_set in self.__query_sets: |
| datastore._AddOrAppend(query_set, property_operator, value) |
| |
| return self |
| |
| def order(self, property): |
| """Set order of query result. |
| |
| To use descending order, prepend '-' (minus) to the property |
| name, e.g., '-date' rather than 'date'. |
| |
| Args: |
| property: Property to sort on. |
| |
| Returns: |
| Self to support method chaining. |
| |
| Raises: |
| PropertyError if invalid property is provided. |
| """ |
| |
| |
| |
| if property.startswith('-'): |
| property = property[1:] |
| order = datastore.Query.DESCENDING |
| else: |
| order = datastore.Query.ASCENDING |
| |
| if self._model_class is None: |
| if (property != datastore_types.KEY_SPECIAL_PROPERTY or |
| order != datastore.Query.ASCENDING): |
| raise BadQueryError( |
| 'Only %s ascending orders are supported on kindless queries' % |
| datastore_types.KEY_SPECIAL_PROPERTY) |
| else: |
| |
| if not issubclass(self._model_class, Expando): |
| if (property not in self._model_class._all_properties and |
| property not in datastore_types._SPECIAL_PROPERTIES): |
| raise PropertyError('Invalid property name \'%s\'' % property) |
| |
| if property in self._model_class._unindexed_properties: |
| raise PropertyError('Property \'%s\' is not indexed' % property) |
| |
| self.__orderings.append((property, order)) |
| return self |
| |
| def ancestor(self, ancestor): |
| """Sets an ancestor for this query. |
| |
| This restricts the query to only return results that descend from |
| a given model instance. In other words, all of the results will |
| have the ancestor as their parent, or parent's parent, etc. The |
| ancestor itself is also a possible result! |
| |
| Args: |
| ancestor: Model or Key (that has already been saved) |
| |
| Returns: |
| Self to support method chaining. |
| |
| Raises: |
| TypeError if the argument isn't a Key or Model; NotSavedError |
| if it is, but isn't saved yet. |
| """ |
| if isinstance(ancestor, datastore.Key): |
| if ancestor.has_id_or_name(): |
| self.__ancestor = ancestor |
| else: |
| raise NotSavedError() |
| elif isinstance(ancestor, Model): |
| if ancestor.has_key(): |
| self.__ancestor = ancestor.key() |
| else: |
| raise NotSavedError() |
| else: |
| raise TypeError('ancestor should be Key or Model') |
| return self |
| |
| |
| class GqlQuery(_BaseQuery): |
| """A Query class that uses GQL query syntax instead of .filter() etc.""" |
| |
| |
| |
| def __init__(self, query_string, *args, **kwds): |
| """Constructor. |
| |
| Args: |
| query_string: Properly formatted GQL query string. |
| *args: Positional arguments used to bind numeric references in the query. |
| **kwds: Dictionary-based arguments for named references. |
| |
| Raises: |
| PropertyError if the query filters or sorts on a property that's not |
| indexed. |
| """ |
| |
| |
| from google.appengine.ext import gql |
| app = kwds.pop('_app', None) |
| namespace = None |
| if isinstance(app, tuple): |
| if len(app) != 2: |
| raise BadArgumentError('_app must have 2 values if type is tuple.') |
| app, namespace = app |
| |
| self._proto_query = gql.GQL(query_string, _app=app, namespace=namespace) |
| if self._proto_query._kind is not None: |
| model_class = class_for_kind(self._proto_query._kind) |
| else: |
| model_class = None |
| |
| super(GqlQuery, self).__init__(model_class) |
| |
| if model_class is not None: |
| |
| for property, unused in (self._proto_query.filters().keys() + |
| self._proto_query.orderings()): |
| if property in model_class._unindexed_properties: |
| raise PropertyError('Property \'%s\' is not indexed' % property) |
| |
| self.bind(*args, **kwds) |
| |
| def is_keys_only(self): |
| return self._proto_query._keys_only |
| |
| def projection(self): |
| return self._proto_query.projection() |
| |
| def is_distinct(self): |
| return self._proto_query.is_distinct() |
| |
| def bind(self, *args, **kwds): |
| """Bind arguments (positional or keyword) to the query. |
| |
| Note that you can also pass arguments directly to the query |
| constructor. Each time you call bind() the previous set of |
| arguments is replaced with the new set. This is useful because |
| the hard work in in parsing the query; so if you expect to be |
| using the same query with different sets of arguments, you should |
| hold on to the GqlQuery() object and call bind() on it each time. |
| |
| Args: |
| *args: Positional arguments used to bind numeric references in the query. |
| **kwds: Dictionary-based arguments for named references. |
| """ |
| self._args = [] |
| for arg in args: |
| self._args.append(_normalize_query_parameter(arg)) |
| self._kwds = {} |
| for name, arg in kwds.iteritems(): |
| self._kwds[name] = _normalize_query_parameter(arg) |
| |
| def run(self, **kwargs): |
| """Iterator for this query that handles the LIMIT clause property. |
| |
| If the GQL query string contains a LIMIT clause, this function fetches |
| all results before returning an iterator. Otherwise results are retrieved |
| in batches by the iterator. |
| |
| Args: |
| kwargs: Any keyword arguments accepted by datastore_query.QueryOptions(). |
| |
| Returns: |
| Iterator for this query. |
| """ |
| if self._proto_query.limit() > 0: |
| kwargs.setdefault('limit', self._proto_query.limit()) |
| kwargs.setdefault('offset', self._proto_query.offset()) |
| return _BaseQuery.run(self, **kwargs) |
| |
| def _get_query(self): |
| return self._proto_query.Bind(self._args, self._kwds, |
| self._cursor, self._end_cursor) |
| |
| |
| class UnindexedProperty(Property): |
| """A property that isn't indexed by either built-in or composite indices. |
| |
| TextProperty and BlobProperty derive from this class. |
| """ |
| def __init__(self, *args, **kwds): |
| """Construct property. See the Property class for details. |
| |
| Raises: |
| ConfigurationError if indexed=True. |
| """ |
| self._require_parameter(kwds, 'indexed', False) |
| |
| |
| |
| kwds['indexed'] = True |
| super(UnindexedProperty, self).__init__(*args, **kwds) |
| |
| def validate(self, value): |
| """Validate property. |
| |
| Returns: |
| A valid value. |
| |
| Raises: |
| BadValueError if property is not an instance of data_type. |
| """ |
| if value is not None and not isinstance(value, self.data_type): |
| try: |
| value = self.data_type(value) |
| except TypeError, err: |
| |
| |
| |
| raise BadValueError('Property %s must be convertible ' |
| 'to a %s instance (%s)' % |
| (self.name, self.data_type.__name__, err)) |
| value = super(UnindexedProperty, self).validate(value) |
| if value is not None and not isinstance(value, self.data_type): |
| raise BadValueError('Property %s must be a %s instance' % |
| (self.name, self.data_type.__name__)) |
| return value |
| |
| |
| class TextProperty(UnindexedProperty): |
| """A string that can be longer than 500 bytes.""" |
| |
| data_type = Text |
| |
| |
| class StringProperty(Property): |
| """A textual property, which can be multi- or single-line.""" |
| |
| def __init__(self, verbose_name=None, multiline=False, **kwds): |
| """Construct string property. |
| |
| Args: |
| verbose_name: Verbose name is always first parameter. |
| multi-line: Carriage returns permitted in property. |
| """ |
| super(StringProperty, self).__init__(verbose_name, **kwds) |
| self.multiline = multiline |
| |
| def validate(self, value): |
| """Validate string property. |
| |
| Returns: |
| A valid value. |
| |
| Raises: |
| BadValueError if property is not multi-line but value is. |
| """ |
| value = super(StringProperty, self).validate(value) |
| if value is not None and not isinstance(value, basestring): |
| raise BadValueError( |
| 'Property %s must be a str or unicode instance, not a %s' |
| % (self.name, type(value).__name__)) |
| if not self.multiline and value and value.find('\n') != -1: |
| raise BadValueError('Property %s is not multi-line' % self.name) |
| if value is not None and len(value) > self.MAX_LENGTH: |
| raise BadValueError( |
| 'Property %s is %d characters long; it must be %d or less.' |
| % (self.name, len(value), self.MAX_LENGTH)) |
| return value |
| |
| MAX_LENGTH = 500 |
| data_type = basestring |
| |
| |
| class _CoercingProperty(Property): |
| """A Property subclass that extends validate() to coerce to self.data_type.""" |
| |
| def validate(self, value): |
| """Coerce values (except None) to self.data_type. |
| |
| Args: |
| value: The value to be validated and coerced. |
| |
| Returns: |
| The coerced and validated value. It is guaranteed that this is |
| either None or an instance of self.data_type; otherwise an exception |
| is raised. |
| |
| Raises: |
| BadValueError if the value could not be validated or coerced. |
| """ |
| value = super(_CoercingProperty, self).validate(value) |
| if value is not None and not isinstance(value, self.data_type): |
| |
| value = self.data_type(value) |
| return value |
| |
| |
| class CategoryProperty(_CoercingProperty): |
| """A property whose values are Category instances.""" |
| |
| data_type = Category |
| |
| |
| class LinkProperty(_CoercingProperty): |
| """A property whose values are Link instances.""" |
| |
| def validate(self, value): |
| value = super(LinkProperty, self).validate(value) |
| if value is not None: |
| |
| |
| scheme, netloc, path, query, fragment = urlparse.urlsplit(value) |
| if not scheme or not netloc: |
| raise BadValueError('Property %s must be a full URL (\'%s\')' % |
| (self.name, value)) |
| return value |
| |
| data_type = Link |
| |
| URLProperty = LinkProperty |
| |
| |
| class EmailProperty(_CoercingProperty): |
| """A property whose values are Email instances.""" |
| |
| data_type = Email |
| |
| |
| class GeoPtProperty(_CoercingProperty): |
| """A property whose values are GeoPt instances.""" |
| |
| data_type = GeoPt |
| |
| |
| class IMProperty(_CoercingProperty): |
| """A property whose values are IM instances.""" |
| |
| data_type = IM |
| |
| |
| class PhoneNumberProperty(_CoercingProperty): |
| """A property whose values are PhoneNumber instances.""" |
| |
| data_type = PhoneNumber |
| |
| |
| class PostalAddressProperty(_CoercingProperty): |
| """A property whose values are PostalAddress instances.""" |
| |
| data_type = PostalAddress |
| |
| |
| class BlobProperty(UnindexedProperty): |
| """A byte string that can be longer than 500 bytes.""" |
| |
| data_type = Blob |
| |
| |
| class ByteStringProperty(Property): |
| """A short (<=500 bytes) byte string. |
| |
| This type should be used for short binary values that need to be indexed. If |
| you do not require indexing (regardless of length), use BlobProperty instead. |
| """ |
| |
| def validate(self, value): |
| """Validate ByteString property. |
| |
| Returns: |
| A valid value. |
| |
| Raises: |
| BadValueError if property is not instance of 'ByteString'. |
| """ |
| if value is not None and not isinstance(value, ByteString): |
| try: |
| value = ByteString(value) |
| except TypeError, err: |
| raise BadValueError('Property %s must be convertible ' |
| 'to a ByteString instance (%s)' % (self.name, err)) |
| value = super(ByteStringProperty, self).validate(value) |
| if value is not None and not isinstance(value, ByteString): |
| raise BadValueError('Property %s must be a ByteString instance' |
| % self.name) |
| if value is not None and len(value) > self.MAX_LENGTH: |
| raise BadValueError( |
| 'Property %s is %d bytes long; it must be %d or less.' |
| % (self.name, len(value), self.MAX_LENGTH)) |
| return value |
| |
| MAX_LENGTH = 500 |
| data_type = ByteString |
| |
| |
| class DateTimeProperty(Property): |
| """The base class of all of our date/time properties. |
| |
| We handle common operations, like converting between time tuples and |
| datetime instances. |
| """ |
| |
| def __init__(self, verbose_name=None, auto_now=False, auto_now_add=False, |
| **kwds): |
| """Construct a DateTimeProperty |
| |
| Args: |
| verbose_name: Verbose name is always first parameter. |
| auto_now: Date/time property is updated with the current time every time |
| it is saved to the datastore. Useful for properties that want to track |
| the modification time of an instance. |
| auto_now_add: Date/time is set to the when its instance is created. |
| Useful for properties that record the creation time of an entity. |
| """ |
| super(DateTimeProperty, self).__init__(verbose_name, **kwds) |
| self.auto_now = auto_now |
| self.auto_now_add = auto_now_add |
| |
| def validate(self, value): |
| """Validate datetime. |
| |
| Returns: |
| A valid value. |
| |
| Raises: |
| BadValueError if property is not instance of 'datetime'. |
| """ |
| value = super(DateTimeProperty, self).validate(value) |
| if value and not isinstance(value, self.data_type): |
| raise BadValueError('Property %s must be a %s, but was %r' % |
| (self.name, self.data_type.__name__, value)) |
| return value |
| |
| def default_value(self): |
| """Default value for datetime. |
| |
| Returns: |
| value of now() as appropriate to the date-time instance if auto_now |
| or auto_now_add is set, else user configured default value implementation. |
| """ |
| if self.auto_now or self.auto_now_add: |
| return self.now() |
| return Property.default_value(self) |
| |
| def get_updated_value_for_datastore(self, model_instance): |
| """Get new value for property to send to datastore. |
| |
| Returns: |
| now() as appropriate to the date-time instance in the odd case where |
| auto_now is set to True, else AUTO_UPDATE_UNCHANGED. |
| """ |
| if self.auto_now: |
| return self.now() |
| return AUTO_UPDATE_UNCHANGED |
| |
| data_type = datetime.datetime |
| |
| @staticmethod |
| def now(): |
| """Get now as a full datetime value. |
| |
| Returns: |
| 'now' as a whole timestamp, including both time and date. |
| """ |
| return datetime.datetime.utcnow() |
| |
| |
| def _date_to_datetime(value): |
| """Convert a date to a datetime for datastore storage. |
| |
| Args: |
| value: A datetime.date object. |
| |
| Returns: |
| A datetime object with time set to 0:00. |
| """ |
| assert isinstance(value, datetime.date) |
| return datetime.datetime(value.year, value.month, value.day) |
| |
| |
| def _time_to_datetime(value): |
| """Convert a time to a datetime for datastore storage. |
| |
| Args: |
| value: A datetime.time object. |
| |
| Returns: |
| A datetime object with date set to 1970-01-01. |
| """ |
| assert isinstance(value, datetime.time) |
| return datetime.datetime(1970, 1, 1, |
| value.hour, value.minute, value.second, |
| value.microsecond) |
| |
| |
| class DateProperty(DateTimeProperty): |
| """A date property, which stores a date without a time.""" |
| |
| |
| |
| |
| |
| |
| |
| |
| @staticmethod |
| def now(): |
| """Get now as a date datetime value. |
| |
| Returns: |
| 'date' part of 'now' only. |
| """ |
| return datetime.datetime.utcnow().date() |
| |
| def validate(self, value): |
| """Validate date. |
| |
| Returns: |
| A valid value. |
| |
| Raises: |
| BadValueError if property is not instance of 'date', |
| or if it is an instance of 'datetime' (which is a subclass |
| of 'date', but for all practical purposes a different type). |
| """ |
| value = super(DateProperty, self).validate(value) |
| if isinstance(value, datetime.datetime): |
| raise BadValueError('Property %s must be a %s, not a datetime' % |
| (self.name, self.data_type.__name__)) |
| return value |
| |
| def get_updated_value_for_datastore(self, model_instance): |
| """Get new value for property to send to datastore. |
| |
| Returns: |
| now() as appropriate to the date instance in the odd case where |
| auto_now is set to True, else AUTO_UPDATE_UNCHANGED. |
| """ |
| if self.auto_now: |
| return _date_to_datetime(self.now()) |
| return AUTO_UPDATE_UNCHANGED |
| |
| def get_value_for_datastore(self, model_instance): |
| """Get value from property to send to datastore. |
| |
| We retrieve a datetime.date from the model instance and return a |
| datetime.datetime instance with the time set to zero. |
| |
| See base class method documentation for details. |
| """ |
| value = super(DateProperty, self).get_value_for_datastore(model_instance) |
| if value is not None: |
| assert isinstance(value, datetime.date) |
| value = _date_to_datetime(value) |
| return value |
| |
| def make_value_from_datastore(self, value): |
| """Native representation of this property. |
| |
| We receive a datetime.datetime retrieved from the entity and return |
| a datetime.date instance representing its date portion. |
| |
| See base class method documentation for details. |
| """ |
| if value is not None: |
| assert isinstance(value, datetime.datetime) |
| value = value.date() |
| return value |
| |
| data_type = datetime.date |
| |
| |
| class TimeProperty(DateTimeProperty): |
| """A time property, which stores a time without a date.""" |
| |
| |
| |
| |
| |
| |
| |
| |
| @staticmethod |
| def now(): |
| """Get now as a time datetime value. |
| |
| Returns: |
| 'time' part of 'now' only. |
| """ |
| return datetime.datetime.utcnow().time() |
| |
| def empty(self, value): |
| """Is time property empty. |
| |
| "0:0" (midnight) is not an empty value. |
| |
| Returns: |
| True if value is None, else False. |
| """ |
| return value is None |
| |
| def get_updated_value_for_datastore(self, model_instance): |
| """Get new value for property to send to datastore. |
| |
| Returns: |
| now() as appropriate to the time instance in the odd case where |
| auto_now is set to True, else AUTO_UPDATE_UNCHANGED. |
| """ |
| if self.auto_now: |
| return _time_to_datetime(self.now()) |
| return AUTO_UPDATE_UNCHANGED |
| |
| def get_value_for_datastore(self, model_instance): |
| """Get value from property to send to datastore. |
| |
| We retrieve a datetime.time from the model instance and return a |
| datetime.datetime instance with the date set to 1/1/1970. |
| |
| See base class method documentation for details. |
| """ |
| value = super(TimeProperty, self).get_value_for_datastore(model_instance) |
| if value is not None: |
| assert isinstance(value, datetime.time), repr(value) |
| value = _time_to_datetime(value) |
| return value |
| |
| def make_value_from_datastore(self, value): |
| """Native representation of this property. |
| |
| We receive a datetime.datetime retrieved from the entity and return |
| a datetime.date instance representing its time portion. |
| |
| See base class method documentation for details. |
| """ |
| if value is not None: |
| assert isinstance(value, datetime.datetime) |
| value = value.time() |
| return value |
| |
| data_type = datetime.time |
| |
| |
| class IntegerProperty(Property): |
| """An integer property.""" |
| |
| def validate(self, value): |
| """Validate integer property. |
| |
| Returns: |
| A valid value. |
| |
| Raises: |
| BadValueError if value is not an integer or long instance. |
| """ |
| value = super(IntegerProperty, self).validate(value) |
| if value is None: |
| return value |
| |
| |
| |
| |
| |
| if not isinstance(value, (int, long)) or isinstance(value, bool): |
| raise BadValueError('Property %s must be an int or long, not a %s' |
| % (self.name, type(value).__name__)) |
| if value < -0x8000000000000000 or value > 0x7fffffffffffffff: |
| raise BadValueError('Property %s must fit in 64 bits' % self.name) |
| return value |
| |
| data_type = int |
| |
| def empty(self, value): |
| """Is integer property empty. |
| |
| 0 is not an empty value. |
| |
| Returns: |
| True if value is None, else False. |
| """ |
| return value is None |
| |
| |
| class RatingProperty(_CoercingProperty, IntegerProperty): |
| """A property whose values are Rating instances.""" |
| |
| data_type = Rating |
| |
| |
| class FloatProperty(Property): |
| """A float property.""" |
| |
| def validate(self, value): |
| """Validate float. |
| |
| Returns: |
| A valid value. |
| |
| Raises: |
| BadValueError if property is not instance of 'float'. |
| """ |
| value = super(FloatProperty, self).validate(value) |
| if value is not None and not isinstance(value, float): |
| raise BadValueError('Property %s must be a float' % self.name) |
| return value |
| |
| data_type = float |
| |
| def empty(self, value): |
| """Is float property empty. |
| |
| 0.0 is not an empty value. |
| |
| Returns: |
| True if value is None, else False. |
| """ |
| return value is None |
| |
| |
| class BooleanProperty(Property): |
| """A boolean property.""" |
| |
| def validate(self, value): |
| """Validate boolean. |
| |
| Returns: |
| A valid value. |
| |
| Raises: |
| BadValueError if property is not instance of 'bool'. |
| """ |
| value = super(BooleanProperty, self).validate(value) |
| if value is not None and not isinstance(value, bool): |
| raise BadValueError('Property %s must be a bool' % self.name) |
| return value |
| |
| data_type = bool |
| |
| def empty(self, value): |
| """Is boolean property empty. |
| |
| False is not an empty value. |
| |
| Returns: |
| True if value is None, else False. |
| """ |
| return value is None |
| |
| |
| class UserProperty(Property): |
| """A user property.""" |
| |
| def __init__(self, |
| verbose_name=None, |
| name=None, |
| required=False, |
| validator=None, |
| choices=None, |
| auto_current_user=False, |
| auto_current_user_add=False, |
| indexed=True): |
| """Initializes this Property with the given options. |
| |
| Note: this does *not* support the 'default' keyword argument. |
| Use auto_current_user_add=True instead. |
| |
| Args: |
| verbose_name: User friendly name of property. |
| name: Storage name for property. By default, uses attribute name |
| as it is assigned in the Model sub-class. |
| required: Whether property is required. |
| validator: User provided method used for validation. |
| choices: User provided set of valid property values. |
| auto_current_user: If true, the value is set to the current user |
| each time the entity is written to the datastore. |
| auto_current_user_add: If true, the value is set to the current user |
| the first time the entity is written to the datastore. |
| indexed: Whether property is indexed. |
| """ |
| super(UserProperty, self).__init__(verbose_name, name, |
| required=required, |
| validator=validator, |
| choices=choices, |
| indexed=indexed) |
| self.auto_current_user = auto_current_user |
| self.auto_current_user_add = auto_current_user_add |
| |
| def validate(self, value): |
| """Validate user. |
| |
| Returns: |
| A valid value. |
| |
| Raises: |
| BadValueError if property is not instance of 'User'. |
| """ |
| value = super(UserProperty, self).validate(value) |
| if value is not None and not isinstance(value, users.User): |
| raise BadValueError('Property %s must be a User' % self.name) |
| return value |
| |
| def default_value(self): |
| """Default value for user. |
| |
| Returns: |
| Value of users.get_current_user() if auto_current_user or |
| auto_current_user_add is set; else None. (But *not* the default |
| implementation, since we don't support the 'default' keyword |
| argument.) |
| """ |
| if self.auto_current_user or self.auto_current_user_add: |
| return users.get_current_user() |
| return None |
| |
| def get_updated_value_for_datastore(self, model_instance): |
| """Get new value for property to send to datastore. |
| |
| Returns: |
| Value of users.get_current_user() if auto_current_user is set; |
| else AUTO_UPDATE_UNCHANGED. |
| """ |
| if self.auto_current_user: |
| return users.get_current_user() |
| return AUTO_UPDATE_UNCHANGED |
| |
| data_type = users.User |
| |
| |
| class ListProperty(Property): |
| """A property that stores a list of things. |
| |
| This is a parameterized property; the parameter must be a valid |
| non-list data type, and all items must conform to this type. |
| """ |
| |
| def __init__(self, item_type, verbose_name=None, default=None, **kwds): |
| """Construct ListProperty. |
| |
| Args: |
| item_type: Type for the list items; must be one of the allowed property |
| types. |
| verbose_name: Optional verbose name. |
| default: Optional default value; if omitted, an empty list is used. |
| **kwds: Optional additional keyword arguments, passed to base class. |
| |
| Note that the only permissible value for 'required' is True. |
| """ |
| if item_type is str: |
| item_type = basestring |
| if not isinstance(item_type, type): |
| raise TypeError('Item type should be a type object') |
| if item_type not in _ALLOWED_PROPERTY_TYPES: |
| raise ValueError('Item type %s is not acceptable' % item_type.__name__) |
| if issubclass(item_type, (Blob, Text)): |
| self._require_parameter(kwds, 'indexed', False) |
| |
| |
| |
| kwds['indexed'] = True |
| self._require_parameter(kwds, 'required', True) |
| if default is None: |
| default = [] |
| self.item_type = item_type |
| super(ListProperty, self).__init__(verbose_name, |
| default=default, |
| **kwds) |
| |
| def validate(self, value): |
| """Validate list. |
| |
| Returns: |
| A valid value. |
| |
| Raises: |
| BadValueError if property is not a list whose items are instances of |
| the item_type given to the constructor. |
| """ |
| value = super(ListProperty, self).validate(value) |
| if value is not None: |
| if not isinstance(value, list): |
| raise BadValueError('Property %s must be a list' % self.name) |
| |
| value = self.validate_list_contents(value) |
| return value |
| |
| def _load(self, model_instance, value): |
| |
| if not isinstance(value, list): |
| value = [value] |
| return super(ListProperty, self)._load(model_instance, 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): |
| """Is list property empty. |
| |
| [] is not an empty value. |
| |
| Returns: |
| True if value is None, else false. |
| """ |
| return value is None |
| |
| data_type = list |
| |
| def default_value(self): |
| """Default value for list. |
| |
| Because the property supplied to 'default' is a static value, |
| that value must be shallow copied to prevent all fields with |
| default values from sharing the same instance. |
| |
| Returns: |
| Copy of the default value. |
| """ |
| 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. |
| """ |
| value = super(ListProperty, self).get_value_for_datastore(model_instance) |
| if not value: |
| return value |
| value = self.validate_list_contents(value) |
| if self.validator: |
| self.validator(value) |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if self.item_type == datetime.date: |
| value = map(_date_to_datetime, value) |
| elif self.item_type == datetime.time: |
| value = map(_time_to_datetime, value) |
| |
| return value |
| |
| def make_value_from_datastore(self, value): |
| """Native representation of this property. |
| |
| If this list is a list of datetime.date or datetime.time, we convert |
| the list of datetime.datetime retrieved from the entity into |
| datetime.date or datetime.time. |
| |
| See base class method documentation for details. |
| """ |
| |
| if self.item_type == datetime.date: |
| for v in value: |
| assert isinstance(v, datetime.datetime) |
| value = map(lambda x: x.date(), value) |
| elif self.item_type == datetime.time: |
| for v in value: |
| assert isinstance(v, datetime.datetime) |
| value = map(lambda x: x.time(), value) |
| |
| return value |
| |
| def make_value_from_datastore_index_value(self, index_value): |
| value = [datastore_types.RestoreFromIndexValue(index_value, self.item_type)] |
| return self.make_value_from_datastore(value) |
| |
| |
| class StringListProperty(ListProperty): |
| """A property that stores a list of strings. |
| |
| A shorthand for the most common type of ListProperty. |
| """ |
| |
| def __init__(self, verbose_name=None, default=None, **kwds): |
| """Construct StringListProperty. |
| |
| Args: |
| verbose_name: Optional verbose name. |
| default: Optional default value; if omitted, an empty list is used. |
| **kwds: Optional additional keyword arguments, passed to ListProperty(). |
| """ |
| super(StringListProperty, self).__init__(basestring, |
| verbose_name=verbose_name, |
| default=default, |
| **kwds) |
| |
| |
| class ReferenceProperty(Property): |
| """A property that represents a many-to-one reference to another model. |
| |
| For example, a reference property in model A that refers to model B forms |
| a many-to-one relationship from A to B: every instance of A refers to a |
| single B instance, and every B instance can have many A instances refer |
| to it. |
| """ |
| |
| def __init__(self, |
| reference_class=None, |
| verbose_name=None, |
| collection_name=None, |
| **attrs): |
| """Construct ReferenceProperty. |
| |
| Args: |
| reference_class: Which model class this property references. |
| verbose_name: User friendly name of property. |
| collection_name: If provided, alternate name of collection on |
| reference_class to store back references. Use this to allow |
| a Model to have multiple fields which refer to the same class. |
| """ |
| super(ReferenceProperty, self).__init__(verbose_name, **attrs) |
| |
| self.collection_name = collection_name |
| |
| if reference_class is None: |
| reference_class = Model |
| if not ((isinstance(reference_class, type) and |
| issubclass(reference_class, Model)) or |
| reference_class is _SELF_REFERENCE): |
| raise KindError('reference_class must be Model or _SELF_REFERENCE') |
| |
| self.reference_class = self.data_type = reference_class |
| |
| def make_value_from_datastore_index_value(self, index_value): |
| value = datastore_types.RestoreFromIndexValue(index_value, Key) |
| return self.make_value_from_datastore(value) |
| |
| def __property_config__(self, model_class, property_name): |
| """Loads all of the references that point to this model. |
| |
| We need to do this to create the ReverseReferenceProperty properties for |
| this model and create the <reference>_set attributes on the referenced |
| model, e.g.: |
| |
| class Story(db.Model): |
| title = db.StringProperty() |
| class Comment(db.Model): |
| story = db.ReferenceProperty(Story) |
| story = Story.get(id) |
| print [c for c in story.comment_set] |
| |
| In this example, the comment_set property was created based on the reference |
| from Comment to Story (which is inherently one to many). |
| |
| Args: |
| model_class: Model class which will have its reference properties |
| initialized. |
| property_name: Name of property being configured. |
| |
| Raises: |
| DuplicatePropertyError if referenced class already has the provided |
| collection name as a property. |
| """ |
| super(ReferenceProperty, self).__property_config__(model_class, |
| property_name) |
| |
| if self.reference_class is _SELF_REFERENCE: |
| self.reference_class = self.data_type = model_class |
| |
| if self.collection_name is None: |
| self.collection_name = '%s_set' % (model_class.__name__.lower()) |
| existing_prop = getattr(self.reference_class, self.collection_name, None) |
| if existing_prop is not None: |
| |
| |
| |
| |
| |
| |
| if not (isinstance(existing_prop, _ReverseReferenceProperty) and |
| existing_prop._prop_name == property_name and |
| existing_prop._model.__name__ == model_class.__name__ and |
| existing_prop._model.__module__ == model_class.__module__): |
| raise DuplicatePropertyError('Class %s already has property %s ' |
| % (self.reference_class.__name__, |
| self.collection_name)) |
| setattr(self.reference_class, |
| self.collection_name, |
| _ReverseReferenceProperty(model_class, property_name)) |
| |
| def __get__(self, model_instance, model_class): |
| """Get reference object. |
| |
| This method will fetch unresolved entities from the datastore if |
| they are not already loaded. |
| |
| Returns: |
| ReferenceProperty to Model object if property is set, else None. |
| |
| Raises: |
| ReferencePropertyResolveError: if the referenced model does not exist. |
| """ |
| if model_instance is None: |
| return self |
| |
| |
| if hasattr(model_instance, self.__id_attr_name()): |
| reference_id = getattr(model_instance, self.__id_attr_name()) |
| else: |
| reference_id = None |
| if reference_id is not None: |
| resolved = getattr(model_instance, self.__resolved_attr_name()) |
| if resolved is not None: |
| return resolved |
| else: |
| instance = get(reference_id) |
| if instance is None: |
| raise ReferencePropertyResolveError( |
| 'ReferenceProperty failed to be resolved: %s' % |
| reference_id.to_path()) |
| setattr(model_instance, self.__resolved_attr_name(), instance) |
| return instance |
| else: |
| return None |
| |
| def __set__(self, model_instance, value): |
| """Set reference.""" |
| |
| |
| |
| value = self.validate(value) |
| if value is not None: |
| if isinstance(value, datastore.Key): |
| |
| setattr(model_instance, self.__id_attr_name(), value) |
| setattr(model_instance, self.__resolved_attr_name(), None) |
| else: |
| setattr(model_instance, self.__id_attr_name(), value.key()) |
| setattr(model_instance, self.__resolved_attr_name(), value) |
| else: |
| setattr(model_instance, self.__id_attr_name(), None) |
| setattr(model_instance, self.__resolved_attr_name(), None) |
| |
| def get_value_for_datastore(self, model_instance): |
| """Get key of reference rather than reference itself.""" |
| |
| return getattr(model_instance, self.__id_attr_name()) |
| |
| def validate(self, value): |
| """Validate reference. |
| |
| Returns: |
| A valid value. |
| |
| Raises: |
| BadValueError for the following reasons: |
| - Value is not saved. |
| - Object not of correct model type for reference. |
| """ |
| if isinstance(value, datastore.Key): |
| return value |
| |
| if value is not None and not value.has_key(): |
| raise BadValueError( |
| '%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) |
| |
| if value is not None and not isinstance(value, self.reference_class): |
| raise KindError('Property %s must be an instance of %s' % |
| (self.name, self.reference_class.kind())) |
| |
| return value |
| |
| def __id_attr_name(self): |
| """Get attribute of referenced id. |
| |
| Returns: |
| Attribute where to store id of referenced entity. |
| """ |
| return self._attr_name() |
| |
| def __resolved_attr_name(self): |
| """Get attribute of resolved attribute. |
| |
| The resolved attribute is where the actual loaded reference instance is |
| stored on the referring model instance. |
| |
| Returns: |
| Attribute name of where to store resolved reference model instance. |
| """ |
| return '_RESOLVED' + self._attr_name() |
| |
| |
| |
| Reference = ReferenceProperty |
| |
| |
| def SelfReferenceProperty(verbose_name=None, collection_name=None, **attrs): |
| """Create a self reference. |
| |
| Function for declaring a self referencing property on a model. |
| |
| Example: |
| class HtmlNode(db.Model): |
| parent = db.SelfReferenceProperty('Parent', 'children') |
| |
| Args: |
| verbose_name: User friendly name of property. |
| collection_name: Name of collection on model. |
| |
| Raises: |
| ConfigurationError if reference_class provided as parameter. |
| """ |
| if 'reference_class' in attrs: |
| raise ConfigurationError( |
| 'Do not provide reference_class to self-reference.') |
| return ReferenceProperty(_SELF_REFERENCE, |
| verbose_name, |
| collection_name, |
| **attrs) |
| |
| |
| |
| SelfReference = SelfReferenceProperty |
| |
| |
| class _ReverseReferenceProperty(Property): |
| """The inverse of the Reference property above. |
| |
| We construct reverse references automatically for the model to which |
| the Reference property is pointing to create the one-to-many property for |
| that model. For example, if you put a Reference property in model A that |
| refers to model B, we automatically create a _ReverseReference property in |
| B called a_set that can fetch all of the model A instances that refer to |
| that instance of model B. |
| """ |
| |
| def __init__(self, model, prop): |
| """Constructor for reverse reference. |
| |
| Constructor does not take standard values of other property types. |
| |
| Args: |
| model: Model class that this property is a collection of. |
| property: Name of foreign property on referred model that points back |
| to this properties entity. |
| """ |
| self.__model = model |
| self.__property = prop |
| |
| @property |
| def _model(self): |
| """Internal helper to access the model class, read-only.""" |
| return self.__model |
| |
| @property |
| def _prop_name(self): |
| """Internal helper to access the property name, read-only.""" |
| return self.__property |
| |
| def __get__(self, model_instance, model_class): |
| """Fetches collection of model instances of this collection property.""" |
| if model_instance is not None: |
| query = Query(self.__model) |
| return query.filter(self.__property + ' =', model_instance.key()) |
| else: |
| return self |
| |
| def __set__(self, model_instance, value): |
| """Not possible to set a new collection.""" |
| |
| raise BadValueError('Virtual property is read-only') |
| |
| |
| class ComputedProperty(Property): |
| """Property used for creating properties derived from other values. |
| |
| Certain attributes should never be set by users but automatically |
| calculated at run-time from other values of the same entity. These |
| values are implemented as persistent properties because they provide |
| useful search keys. |
| |
| A computed property behaves the same as normal properties except that |
| you may not set values on them. Attempting to do so raises |
| db.DerivedPropertyError which db.Model knows to ignore during entity |
| loading time. Whenever getattr is used for the property |
| the value is recaclulated. This happens when the model calls |
| get_value_for_datastore on the property. |
| |
| Example: |
| |
| import string |
| |
| class Person(Model): |
| |
| name = StringProperty(required=True) |
| |
| @db.ComputedProperty |
| def lower_case_name(self): |
| return self.name.lower() |
| |
| # Find all people regardless of case used in name. |
| Person.gql('WHERE lower_case_name=:1' % name_to_search_for.lower()) |
| """ |
| |
| def __init__(self, value_function, indexed=True): |
| """Constructor. |
| |
| Args: |
| value_function: Callable f(model_instance) -> value used to derive |
| persistent property value for storage in datastore. |
| indexed: Whether or not the attribute should be indexed. |
| """ |
| super(ComputedProperty, self).__init__(indexed=indexed) |
| self.__value_function = value_function |
| |
| def __set__(self, *args): |
| """Disallow setting this value. |
| |
| Raises: |
| DerivedPropertyError when developer attempts to set attribute manually. |
| Model knows to ignore this exception when getting from datastore. |
| """ |
| raise DerivedPropertyError( |
| 'Computed property %s cannot be set.' % self.name) |
| |
| def __get__(self, model_instance, model_class): |
| """Derive property value. |
| |
| Args: |
| model_instance: Instance to derive property for in bound method case, |
| else None. |
| model_class: Model class associated with this property descriptor. |
| |
| Returns: |
| Result of calling self.__value_funcion as provided by property |
| constructor. |
| """ |
| if model_instance is None: |
| return self |
| return self.__value_function(model_instance) |
| |
| |
| |
| def to_dict(model_instance, dictionary=None): |
| """Convert model to dictionary. |
| |
| Args: |
| model_instance: Model instance for which to make dictionary. |
| dictionary: dict instance or compatible to receive model values. |
| The dictionary is not cleared of original values. Similar to using |
| dictionary.update. If dictionary is None, a new dictionary instance is |
| created and returned. |
| |
| Returns: |
| New dictionary appropriate populated with model instances values |
| if entity is None, else entity. |
| """ |
| if dictionary is None: |
| dictionary = {} |
| |
| model_instance._to_entity(dictionary) |
| return dictionary |
| |
| |
| |
| |
| run_in_transaction = datastore.RunInTransaction |
| run_in_transaction_custom_retries = datastore.RunInTransactionCustomRetries |
| run_in_transaction_options = datastore.RunInTransactionOptions |
| |
| |
| |
| RunInTransaction = run_in_transaction |
| RunInTransactionCustomRetries = run_in_transaction_custom_retries |
| websafe_encode_cursor = datastore_query.Cursor.to_websafe_string |
| websafe_decode_cursor = datastore_query.Cursor.from_websafe_string |
| |
| |
| is_in_transaction = datastore.IsInTransaction |
| |
| |
| transactional = datastore.Transactional |
| non_transactional = datastore.NonTransactional |
| |
| |
| create_config = datastore.CreateConfig |
| create_transaction_options = datastore.CreateTransactionOptions |