Module echo.datastore.entity

Expand source code
from google.cloud.datastore import Entity as DatastoreEntity, Key as DatastoreKey
from six import string_types
from echo.datastore.errors import InvalidKeyError, NotSavedException
from echo.datastore import properties, db_utils

# Override documentation for paths in this dictionary
__pdoc__ = {}


class BaseEntityMeta(type):
    __pdoc__['BaseEntityMeta'] = False

    # Needed to support setting property names on python2
    def __new__(mcls, name, bases, attrs):
        cls = super(BaseEntityMeta, mcls).__new__(mcls, name, bases, attrs)
        for attr, obj in attrs.items():
            if isinstance(obj, properties.Property):
                obj.__set_name__(cls, attr)
        return cls


class Entity(object):
    """Creates a datastore document under the entity [EntityName]

    Args:
        **data (kwargs): Values for properties in the new record, e.g User(name="Bob")

    Notes:
        Entities can be directly compared for equality each other e.g.
        ```entity.get(some_key) == entity.get(some_key)```
    """
    __metaclass__ = BaseEntityMeta

    def __init__(self, **data):
        if type(self) is Entity:
            raise Exception("You must extend Entity")
        project = db_utils.__client__().project
        self.__changes__ = []
        if "__entity__" in data:
            self.__datastore_entity__ = data.get("__entity__")
            del data["__entity__"]
        elif "id" in data:
            self.__datastore_entity__ = DatastoreEntity(key=Key(self.__entity_name__(), data.get("id"), project=project))
            del data["id"]
        else:
            self.__datastore_entity__ = DatastoreEntity(key=Key(self.__entity_name__(), project=project))

        for key, value in data.items():
            setattr(self, key, value)
        self.__field_set = self.__get_field_set()

    def __get_field_set(self):
        props = []
        for key, value in self.__class__.__dict__.items():
            try:
                if issubclass(value.__class__, properties.Property):
                    props.append((key, value.required, value.default))
            except TypeError:
                pass
        return props

    def key(self):
        """Generates a key for this Entity
        Returns:
            An instance of a key, convert to string to get a urlsafe key

        Raises:
            NotSavedException: Raised if reading a key of an unsaved entity unless the ID is
                explicitly provided
        """
        if self.__datastore_entity__.key.is_partial:
            raise NotSavedException()
        return Key(*self.__datastore_entity__.key.flat_path, project=db_utils.__client__().project)

    @classmethod
    def get(cls, key):
        """
        Get an entity with the specified key

        Args:
            key: A urlsafe key string or an instance of a Key

        Returns:
            An instance of the entity with the provided id

            Returns None if the id doesn't exist in the database

        Raises:
            InvalidKeyError: Raised if the key provided is invalid for this entity
        """
        if isinstance(key, string_types):
            try:
                key = Key.from_legacy_urlsafe(key)
            except Exception:
                raise InvalidKeyError(cls)

        if not isinstance(key, Key) or cls.__entity_name__() != key.kind:
            raise InvalidKeyError(cls)
        ds_entity = db_utils.__client__().get(key=key)
        if ds_entity:
            entity = cls(__entity__=ds_entity)
            return entity

    @classmethod
    def get_by_id(cls, entity_id):
        """
        Get an entity with a specified ID(Integer) or Name(String).

        Args:
            entity_id: An integer(id) or string(name) uniquely identifying the object

        Returns:
            An instance of the entity with the provided id

            Returns None if the id doesn't exist in the database
        """
        key = Key(cls.__name__, entity_id, project=db_utils.__client__().project)
        return cls.get(key)

    @classmethod
    def query(cls, limit=None, eventual=False, keys_only=False, order_by=None):
        """
        Filter entities based on certain conditions, an empty query will return all entities

        Args:
            limit: Maximum number of results to return, returns null by default
            eventual:
                Defaults to strongly consistent (False). Setting True will use eventual consistency,
                but cannot be used inside a transaction or will raise ValueError
            keys_only: Sets the results to include keys only
            order_by:
                A list of field names to order by, add `-` to order descending
                e.g. ["name", "-age"]

        Returns:
            A iterable query instance; call fetch() to get the results as a list.
        """
        return Query(cls, keys_only=keys_only, eventual=eventual, limit=limit, order_by=order_by)

    def is_saved(self):
        """Checks if an entity has any changes since read via get or query or last put.
        Always returns true for a new entity

        Returns:
            Boolean: True if no changes have been made
        """
        if self.__changes__ or self.__datastore_entity__.key.is_partial:
            return False
        return True

    def __pre_put_check__(self):
        for name, required, default in self.__field_set:
            if name not in self.__datastore_entity__:
                if default is None and required:
                    raise ValueError("Required field '%s' is not set for %s" % (name, self.__entity_name__()))
                if default is not None:
                    self.__datastore_entity__[name] = default

    def put(self):
        """Save changes made on this entity to datastore. Won't call datastore if no changes were made"""
        db_utils.put(self)

    def post_put(self, changes):
        """Override this function to run logic after saving the entity

        Args:
            changes (list): A list of fields that have been updated during put

        Notes:
            This function won't be called if there're no changes
        """

    def pre_put(self):
        """Override this function to run logic just before saving the entity
        NB: This function won't be called if no changes were made. i.e. when self.is_saved() == True
        """

    def delete(self):
        """Delete an entity from datastore"""
        db_utils.delete(self)

    def pre_delete(self):
        """Override this function to run any logic before deleting the entity. e.g. clear cache"""

    @classmethod
    def __entity_name__(cls):
        return cls.__name__

    def __eq__(self, other):
        return self.__datastore_entity__ == other.__datastore_entity__


class Query(object):
    def __init__(self, entity, keys_only=False, eventual=False, limit=None, order_by=None):
        order = []
        if isinstance(order_by, (list, tuple)):
            order = order_by
        elif isinstance(order_by, str):
            order = [order_by]
        self.__datastore_query = db_utils.__client__().query(kind=entity.__entity_name__(), order=order)
        self.entity = entity
        self.keys_only = keys_only
        if keys_only:
            self.__datastore_query.keys_only()
        self.limit = limit
        self.eventual = eventual
        self.__iterator = None

    def equal(self, field, value):
        """
        Equal to filter
        Args:
            field: Field name
            value: Value to compare

        Returns:
            Current Query Instance
        """
        self.__datastore_query.add_filter(field, '=', value)
        return self

    def gt(self, field, value):
        """
        Greater Than filter
        Args:
            field: Field name
            value: Value to compare

        Returns:
            Current Query Instance
        """
        self.__datastore_query.add_filter(field, '>', value)
        return self

    def gte(self, field, value):
        """
        Greater Than or Equal to filter
        Args:
            field: Field name
            value: Value to compare

        Returns:
            Current Query Instance
        """
        self.__datastore_query.add_filter(field, '>=', value)
        return self

    def lt(self, field, value):
        """
        Less Than filter
        Args:
            field: Field name
            value: Value to compare

        Returns:
            Current Query Instance
        """
        self.__datastore_query.add_filter(field, '<', value)
        return self

    def lte(self, field, value):
        """
        Less Than or Equal to filter
        Args:
            field: Field name
            value: Value to compare

        Returns:
            Current Query Instance
        """
        self.__datastore_query.add_filter(field, '<=', value)
        return self

    def fetch(self):
        """
        Get Query results as a list
        """
        return [entity for entity in self]

    def __process_result_item(self, result_item):
        if self.keys_only:
            # The result is a datastore entity with only a key
            return Key(result_item.kind, result_item.id, project=db_utils.__client__().project)
        entity = self.entity(__entity__=result_item)
        return entity

    def __iter__(self):
        return self

    def __next__(self):
        if not self.__iterator:
            self.__iterator = self.__datastore_query.fetch(limit=self.limit, eventual=self.eventual).__iter__()
        return self.__process_result_item(self.__iterator.__next__())

    __pdoc__["Query.next"] = False

    def next(self):
        # Support python2 iterators
        if not self.__iterator:
            self.__iterator = self.__datastore_query.fetch(limit=self.limit, eventual=self.eventual).__iter__()
        return self.__process_result_item(self.__iterator.next())


class Key(DatastoreKey):
    def __repr__(self):
        return self.to_legacy_urlsafe().decode('utf-8')

    def __str__(self):
        return self.__repr__()

Classes

class Entity (**data)

Creates a datastore document under the entity [EntityName]

Args

**data : kwargs
Values for properties in the new record, e.g User(name="Bob")

Notes

Entities can be directly compared for equality each other e.g. entity.get(some_key) == entity.get(some_key)

Expand source code
class Entity(object):
    """Creates a datastore document under the entity [EntityName]

    Args:
        **data (kwargs): Values for properties in the new record, e.g User(name="Bob")

    Notes:
        Entities can be directly compared for equality each other e.g.
        ```entity.get(some_key) == entity.get(some_key)```
    """
    __metaclass__ = BaseEntityMeta

    def __init__(self, **data):
        if type(self) is Entity:
            raise Exception("You must extend Entity")
        project = db_utils.__client__().project
        self.__changes__ = []
        if "__entity__" in data:
            self.__datastore_entity__ = data.get("__entity__")
            del data["__entity__"]
        elif "id" in data:
            self.__datastore_entity__ = DatastoreEntity(key=Key(self.__entity_name__(), data.get("id"), project=project))
            del data["id"]
        else:
            self.__datastore_entity__ = DatastoreEntity(key=Key(self.__entity_name__(), project=project))

        for key, value in data.items():
            setattr(self, key, value)
        self.__field_set = self.__get_field_set()

    def __get_field_set(self):
        props = []
        for key, value in self.__class__.__dict__.items():
            try:
                if issubclass(value.__class__, properties.Property):
                    props.append((key, value.required, value.default))
            except TypeError:
                pass
        return props

    def key(self):
        """Generates a key for this Entity
        Returns:
            An instance of a key, convert to string to get a urlsafe key

        Raises:
            NotSavedException: Raised if reading a key of an unsaved entity unless the ID is
                explicitly provided
        """
        if self.__datastore_entity__.key.is_partial:
            raise NotSavedException()
        return Key(*self.__datastore_entity__.key.flat_path, project=db_utils.__client__().project)

    @classmethod
    def get(cls, key):
        """
        Get an entity with the specified key

        Args:
            key: A urlsafe key string or an instance of a Key

        Returns:
            An instance of the entity with the provided id

            Returns None if the id doesn't exist in the database

        Raises:
            InvalidKeyError: Raised if the key provided is invalid for this entity
        """
        if isinstance(key, string_types):
            try:
                key = Key.from_legacy_urlsafe(key)
            except Exception:
                raise InvalidKeyError(cls)

        if not isinstance(key, Key) or cls.__entity_name__() != key.kind:
            raise InvalidKeyError(cls)
        ds_entity = db_utils.__client__().get(key=key)
        if ds_entity:
            entity = cls(__entity__=ds_entity)
            return entity

    @classmethod
    def get_by_id(cls, entity_id):
        """
        Get an entity with a specified ID(Integer) or Name(String).

        Args:
            entity_id: An integer(id) or string(name) uniquely identifying the object

        Returns:
            An instance of the entity with the provided id

            Returns None if the id doesn't exist in the database
        """
        key = Key(cls.__name__, entity_id, project=db_utils.__client__().project)
        return cls.get(key)

    @classmethod
    def query(cls, limit=None, eventual=False, keys_only=False, order_by=None):
        """
        Filter entities based on certain conditions, an empty query will return all entities

        Args:
            limit: Maximum number of results to return, returns null by default
            eventual:
                Defaults to strongly consistent (False). Setting True will use eventual consistency,
                but cannot be used inside a transaction or will raise ValueError
            keys_only: Sets the results to include keys only
            order_by:
                A list of field names to order by, add `-` to order descending
                e.g. ["name", "-age"]

        Returns:
            A iterable query instance; call fetch() to get the results as a list.
        """
        return Query(cls, keys_only=keys_only, eventual=eventual, limit=limit, order_by=order_by)

    def is_saved(self):
        """Checks if an entity has any changes since read via get or query or last put.
        Always returns true for a new entity

        Returns:
            Boolean: True if no changes have been made
        """
        if self.__changes__ or self.__datastore_entity__.key.is_partial:
            return False
        return True

    def __pre_put_check__(self):
        for name, required, default in self.__field_set:
            if name not in self.__datastore_entity__:
                if default is None and required:
                    raise ValueError("Required field '%s' is not set for %s" % (name, self.__entity_name__()))
                if default is not None:
                    self.__datastore_entity__[name] = default

    def put(self):
        """Save changes made on this entity to datastore. Won't call datastore if no changes were made"""
        db_utils.put(self)

    def post_put(self, changes):
        """Override this function to run logic after saving the entity

        Args:
            changes (list): A list of fields that have been updated during put

        Notes:
            This function won't be called if there're no changes
        """

    def pre_put(self):
        """Override this function to run logic just before saving the entity
        NB: This function won't be called if no changes were made. i.e. when self.is_saved() == True
        """

    def delete(self):
        """Delete an entity from datastore"""
        db_utils.delete(self)

    def pre_delete(self):
        """Override this function to run any logic before deleting the entity. e.g. clear cache"""

    @classmethod
    def __entity_name__(cls):
        return cls.__name__

    def __eq__(self, other):
        return self.__datastore_entity__ == other.__datastore_entity__

Static methods

def get(key)

Get an entity with the specified key

Args

key
A urlsafe key string or an instance of a Key

Returns

An instance of the entity with the provided id
 
Returns None if the id doesn't exist in the database
 

Raises

InvalidKeyError
Raised if the key provided is invalid for this entity
Expand source code
@classmethod
def get(cls, key):
    """
    Get an entity with the specified key

    Args:
        key: A urlsafe key string or an instance of a Key

    Returns:
        An instance of the entity with the provided id

        Returns None if the id doesn't exist in the database

    Raises:
        InvalidKeyError: Raised if the key provided is invalid for this entity
    """
    if isinstance(key, string_types):
        try:
            key = Key.from_legacy_urlsafe(key)
        except Exception:
            raise InvalidKeyError(cls)

    if not isinstance(key, Key) or cls.__entity_name__() != key.kind:
        raise InvalidKeyError(cls)
    ds_entity = db_utils.__client__().get(key=key)
    if ds_entity:
        entity = cls(__entity__=ds_entity)
        return entity
def get_by_id(entity_id)

Get an entity with a specified ID(Integer) or Name(String).

Args

entity_id
An integer(id) or string(name) uniquely identifying the object

Returns

An instance of the entity with the provided id
 
Returns None if the id doesn't exist in the database
 
Expand source code
@classmethod
def get_by_id(cls, entity_id):
    """
    Get an entity with a specified ID(Integer) or Name(String).

    Args:
        entity_id: An integer(id) or string(name) uniquely identifying the object

    Returns:
        An instance of the entity with the provided id

        Returns None if the id doesn't exist in the database
    """
    key = Key(cls.__name__, entity_id, project=db_utils.__client__().project)
    return cls.get(key)
def query(limit=None, eventual=False, keys_only=False, order_by=None)

Filter entities based on certain conditions, an empty query will return all entities

Args

limit
Maximum number of results to return, returns null by default
eventual:
Defaults to strongly consistent (False). Setting True will use eventual consistency,
but cannot be used inside a transaction or will raise ValueError
keys_only
Sets the results to include keys only

order_by: A list of field names to order by, add - to order descending e.g. ["name", "-age"]

Returns

A iterable query instance; call fetch() to get the results as a list.

Expand source code
@classmethod
def query(cls, limit=None, eventual=False, keys_only=False, order_by=None):
    """
    Filter entities based on certain conditions, an empty query will return all entities

    Args:
        limit: Maximum number of results to return, returns null by default
        eventual:
            Defaults to strongly consistent (False). Setting True will use eventual consistency,
            but cannot be used inside a transaction or will raise ValueError
        keys_only: Sets the results to include keys only
        order_by:
            A list of field names to order by, add `-` to order descending
            e.g. ["name", "-age"]

    Returns:
        A iterable query instance; call fetch() to get the results as a list.
    """
    return Query(cls, keys_only=keys_only, eventual=eventual, limit=limit, order_by=order_by)

Methods

def delete(self)

Delete an entity from datastore

Expand source code
def delete(self):
    """Delete an entity from datastore"""
    db_utils.delete(self)
def is_saved(self)

Checks if an entity has any changes since read via get or query or last put. Always returns true for a new entity

Returns

Boolean
True if no changes have been made
Expand source code
def is_saved(self):
    """Checks if an entity has any changes since read via get or query or last put.
    Always returns true for a new entity

    Returns:
        Boolean: True if no changes have been made
    """
    if self.__changes__ or self.__datastore_entity__.key.is_partial:
        return False
    return True
def key(self)

Generates a key for this Entity

Returns

An instance of a key, convert to string to get a urlsafe key
 

Raises

NotSavedException
Raised if reading a key of an unsaved entity unless the ID is explicitly provided
Expand source code
def key(self):
    """Generates a key for this Entity
    Returns:
        An instance of a key, convert to string to get a urlsafe key

    Raises:
        NotSavedException: Raised if reading a key of an unsaved entity unless the ID is
            explicitly provided
    """
    if self.__datastore_entity__.key.is_partial:
        raise NotSavedException()
    return Key(*self.__datastore_entity__.key.flat_path, project=db_utils.__client__().project)
def post_put(self, changes)

Override this function to run logic after saving the entity

Args

changes : list
A list of fields that have been updated during put

Notes

This function won't be called if there're no changes

Expand source code
def post_put(self, changes):
    """Override this function to run logic after saving the entity

    Args:
        changes (list): A list of fields that have been updated during put

    Notes:
        This function won't be called if there're no changes
    """
def pre_delete(self)

Override this function to run any logic before deleting the entity. e.g. clear cache

Expand source code
def pre_delete(self):
    """Override this function to run any logic before deleting the entity. e.g. clear cache"""
def pre_put(self)

Override this function to run logic just before saving the entity NB: This function won't be called if no changes were made. i.e. when self.is_saved() == True

Expand source code
def pre_put(self):
    """Override this function to run logic just before saving the entity
    NB: This function won't be called if no changes were made. i.e. when self.is_saved() == True
    """
def put(self)

Save changes made on this entity to datastore. Won't call datastore if no changes were made

Expand source code
def put(self):
    """Save changes made on this entity to datastore. Won't call datastore if no changes were made"""
    db_utils.put(self)
class Key (*path_args, **kwargs)

An immutable representation of a datastore Key.

Testsetup: key-ctor

from google.cloud import datastore

project = 'my-special-pony' client = datastore.Client(project=project) Key = datastore.Key

parent_key = client.key('Parent', 'foo')

To create a basic key directly:

Doctest: key-ctor

Key('EntityKind', 1234, project=project) Key('EntityKind', 'foo', project=project)

Though typical usage comes via the :meth:~google.cloud.datastore.client.Client.key factory:

Doctest: key-ctor

client.key('EntityKind', 1234) client.key('EntityKind', 'foo')

To create a key with a parent:

Doctest: key-ctor

client.key('Parent', 'foo', 'Child', 1234) client.key('Child', 1234, parent=parent_key)

To create a partial key:

Doctest: key-ctor

client.key('Parent', 'foo', 'Child')

:type path_args: tuple of string and integer :param path_args: May represent a partial (odd length) or full (even length) key path.

:param kwargs: Keyword arguments to be passed in.

Accepted keyword arguments are

  • namespace (string): A namespace identifier for the key.
  • project (string): The project associated with the key.
  • parent (:class:~google.cloud.datastore.key.Key): The parent of the key.

The project argument is required unless it has been set implicitly.

Expand source code
class Key(DatastoreKey):
    def __repr__(self):
        return self.to_legacy_urlsafe().decode('utf-8')

    def __str__(self):
        return self.__repr__()

Ancestors

  • google.cloud.datastore.key.Key
class Query (entity, keys_only=False, eventual=False, limit=None, order_by=None)
Expand source code
class Query(object):
    def __init__(self, entity, keys_only=False, eventual=False, limit=None, order_by=None):
        order = []
        if isinstance(order_by, (list, tuple)):
            order = order_by
        elif isinstance(order_by, str):
            order = [order_by]
        self.__datastore_query = db_utils.__client__().query(kind=entity.__entity_name__(), order=order)
        self.entity = entity
        self.keys_only = keys_only
        if keys_only:
            self.__datastore_query.keys_only()
        self.limit = limit
        self.eventual = eventual
        self.__iterator = None

    def equal(self, field, value):
        """
        Equal to filter
        Args:
            field: Field name
            value: Value to compare

        Returns:
            Current Query Instance
        """
        self.__datastore_query.add_filter(field, '=', value)
        return self

    def gt(self, field, value):
        """
        Greater Than filter
        Args:
            field: Field name
            value: Value to compare

        Returns:
            Current Query Instance
        """
        self.__datastore_query.add_filter(field, '>', value)
        return self

    def gte(self, field, value):
        """
        Greater Than or Equal to filter
        Args:
            field: Field name
            value: Value to compare

        Returns:
            Current Query Instance
        """
        self.__datastore_query.add_filter(field, '>=', value)
        return self

    def lt(self, field, value):
        """
        Less Than filter
        Args:
            field: Field name
            value: Value to compare

        Returns:
            Current Query Instance
        """
        self.__datastore_query.add_filter(field, '<', value)
        return self

    def lte(self, field, value):
        """
        Less Than or Equal to filter
        Args:
            field: Field name
            value: Value to compare

        Returns:
            Current Query Instance
        """
        self.__datastore_query.add_filter(field, '<=', value)
        return self

    def fetch(self):
        """
        Get Query results as a list
        """
        return [entity for entity in self]

    def __process_result_item(self, result_item):
        if self.keys_only:
            # The result is a datastore entity with only a key
            return Key(result_item.kind, result_item.id, project=db_utils.__client__().project)
        entity = self.entity(__entity__=result_item)
        return entity

    def __iter__(self):
        return self

    def __next__(self):
        if not self.__iterator:
            self.__iterator = self.__datastore_query.fetch(limit=self.limit, eventual=self.eventual).__iter__()
        return self.__process_result_item(self.__iterator.__next__())

    __pdoc__["Query.next"] = False

    def next(self):
        # Support python2 iterators
        if not self.__iterator:
            self.__iterator = self.__datastore_query.fetch(limit=self.limit, eventual=self.eventual).__iter__()
        return self.__process_result_item(self.__iterator.next())

Methods

def equal(self, field, value)

Equal to filter

Args

field
Field name
value
Value to compare

Returns

Current Query Instance
 
Expand source code
def equal(self, field, value):
    """
    Equal to filter
    Args:
        field: Field name
        value: Value to compare

    Returns:
        Current Query Instance
    """
    self.__datastore_query.add_filter(field, '=', value)
    return self
def fetch(self)

Get Query results as a list

Expand source code
def fetch(self):
    """
    Get Query results as a list
    """
    return [entity for entity in self]
def gt(self, field, value)

Greater Than filter

Args

field
Field name
value
Value to compare

Returns

Current Query Instance
 
Expand source code
def gt(self, field, value):
    """
    Greater Than filter
    Args:
        field: Field name
        value: Value to compare

    Returns:
        Current Query Instance
    """
    self.__datastore_query.add_filter(field, '>', value)
    return self
def gte(self, field, value)

Greater Than or Equal to filter

Args

field
Field name
value
Value to compare

Returns

Current Query Instance
 
Expand source code
def gte(self, field, value):
    """
    Greater Than or Equal to filter
    Args:
        field: Field name
        value: Value to compare

    Returns:
        Current Query Instance
    """
    self.__datastore_query.add_filter(field, '>=', value)
    return self
def lt(self, field, value)

Less Than filter

Args

field
Field name
value
Value to compare

Returns

Current Query Instance
 
Expand source code
def lt(self, field, value):
    """
    Less Than filter
    Args:
        field: Field name
        value: Value to compare

    Returns:
        Current Query Instance
    """
    self.__datastore_query.add_filter(field, '<', value)
    return self
def lte(self, field, value)

Less Than or Equal to filter

Args

field
Field name
value
Value to compare

Returns

Current Query Instance
 
Expand source code
def lte(self, field, value):
    """
    Less Than or Equal to filter
    Args:
        field: Field name
        value: Value to compare

    Returns:
        Current Query Instance
    """
    self.__datastore_query.add_filter(field, '<=', value)
    return self