# Copyright 2018 Google LLC
#
# 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
#
#     https://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.

"""Model classes for datastore objects and properties for models.

.. testsetup:: *

    from google.cloud import ndb
"""


import datetime
import functools
import inspect
import json
import pickle
import zlib

from google.cloud.datastore import entity as entity_module
from google.cloud.datastore import helpers

from google.cloud.ndb import _datastore_types
from google.cloud.ndb import exceptions
from google.cloud.ndb import key as key_module


__all__ = [
    "Key",
    "BlobKey",
    "GeoPt",
    "Rollback",
    "KindError",
    "InvalidPropertyError",
    "BadProjectionError",
    "UnprojectedPropertyError",
    "ReadonlyPropertyError",
    "ComputedPropertyError",
    "UserNotFoundError",
    "IndexProperty",
    "Index",
    "IndexState",
    "ModelAdapter",
    "make_connection",
    "ModelAttribute",
    "Property",
    "ModelKey",
    "BooleanProperty",
    "IntegerProperty",
    "FloatProperty",
    "BlobProperty",
    "TextProperty",
    "StringProperty",
    "GeoPtProperty",
    "PickleProperty",
    "JsonProperty",
    "User",
    "UserProperty",
    "KeyProperty",
    "BlobKeyProperty",
    "DateTimeProperty",
    "DateProperty",
    "TimeProperty",
    "StructuredProperty",
    "LocalStructuredProperty",
    "GenericProperty",
    "ComputedProperty",
    "MetaModel",
    "Model",
    "Expando",
    "transaction",
    "transaction_async",
    "in_transaction",
    "transactional",
    "transactional_async",
    "transactional_tasklet",
    "non_transactional",
    "get_multi_async",
    "get_multi",
    "put_multi_async",
    "put_multi",
    "delete_multi_async",
    "delete_multi",
    "get_indexes_async",
    "get_indexes",
]


_MEANING_PREDEFINED_ENTITY_USER = 20
_MAX_STRING_LENGTH = 1500
_NO_LONGER_IMPLEMENTED = "No longer used"
Key = key_module.Key
BlobKey = _datastore_types.BlobKey
GeoPt = helpers.GeoPoint
Rollback = exceptions.Rollback


class KindError(exceptions.BadValueError):
    """Raised when an implementation for a kind can't be found.

    May also be raised when the kind is not a byte string.
    """


class InvalidPropertyError(exceptions.Error):
    """Raised when a property is not applicable to a given use.

    For example, a property must exist and be indexed to be used in a query's
    projection or group by clause.
    """


BadProjectionError = InvalidPropertyError
"""This alias for :class:`InvalidPropertyError` is for legacy support."""


class UnprojectedPropertyError(exceptions.Error):
    """Raised when getting a property value that's not in the projection."""


class ReadonlyPropertyError(exceptions.Error):
    """Raised when attempting to set a property value that is read-only."""


class ComputedPropertyError(ReadonlyPropertyError):
    """Raised when attempting to set or delete a computed property."""


class UserNotFoundError(exceptions.Error):
    """No email argument was specified, and no user is logged in."""


class IndexProperty:
    """Immutable object representing a single property in an index."""

    __slots__ = ("_name", "_direction")

    def __new__(cls, *, name, direction):
        instance = super(IndexProperty, cls).__new__(cls)
        instance._name = name
        instance._direction = direction
        return instance

    @property
    def name(self):
        """str: The property name being indexed."""
        return self._name

    @property
    def direction(self):
        """str: The direction in the index, ``asc`` or ``desc``."""
        return self._direction

    def __repr__(self):
        """Return a string representation."""
        return "{}(name={!r}, direction={!r})".format(
            type(self).__name__, self.name, self.direction
        )

    def __eq__(self, other):
        """Compare two index properties for equality."""
        if not isinstance(other, IndexProperty):
            return NotImplemented
        return self.name == other.name and self.direction == other.direction

    def __hash__(self):
        return hash((self.name, self.direction))


class Index:
    """Immutable object representing an index."""

    __slots__ = ("_kind", "_properties", "_ancestor")

    def __new__(cls, *, kind, properties, ancestor):
        instance = super(Index, cls).__new__(cls)
        instance._kind = kind
        instance._properties = properties
        instance._ancestor = ancestor
        return instance

    @property
    def kind(self):
        """str: The kind being indexed."""
        return self._kind

    @property
    def properties(self):
        """List[IndexProperty]: The properties being indexed."""
        return self._properties

    @property
    def ancestor(self):
        """bool: Indicates if this is an ancestor index."""
        return self._ancestor

    def __repr__(self):
        """Return a string representation."""
        return "{}(kind={!r}, properties={!r}, ancestor={})".format(
            type(self).__name__, self.kind, self.properties, self.ancestor
        )

    def __eq__(self, other):
        """Compare two indexes."""
        if not isinstance(other, Index):
            return NotImplemented

        return (
            self.kind == other.kind
            and self.properties == other.properties
            and self.ancestor == other.ancestor
        )

    def __hash__(self):
        return hash((self.kind, self.properties, self.ancestor))


class IndexState:
    """Immutable object representing an index and its state."""

    __slots__ = ("_definition", "_state", "_id")

    def __new__(cls, *, definition, state, id):
        instance = super(IndexState, cls).__new__(cls)
        instance._definition = definition
        instance._state = state
        instance._id = id
        return instance

    @property
    def definition(self):
        """Index: The index corresponding to the tracked state."""
        return self._definition

    @property
    def state(self):
        """str: The index state.

        Possible values are ``error``, ``deleting``, ``serving`` or
        ``building``.
        """
        return self._state

    @property
    def id(self):
        """int: The index ID."""
        return self._id

    def __repr__(self):
        """Return a string representation."""
        return "{}(definition={!r}, state={!r}, id={:d})".format(
            type(self).__name__, self.definition, self.state, self.id
        )

    def __eq__(self, other):
        """Compare two index states."""
        if not isinstance(other, IndexState):
            return NotImplemented

        return (
            self.definition == other.definition
            and self.state == other.state
            and self.id == other.id
        )

    def __hash__(self):
        return hash((self.definition, self.state, self.id))


class ModelAdapter:
    __slots__ = ()

    def __new__(self, *args, **kwargs):
        raise NotImplementedError(_NO_LONGER_IMPLEMENTED)


def _entity_from_protobuf(protobuf):
    """Deserialize an entity from a protobuffer.

    Args:
        protobuf (google.cloud.datastore_v1.proto.entity.Entity): An
            entity protobuf to be deserialized.

    Returns:
        .Model: The deserialized entity.
    """
    ds_entity = helpers.entity_from_protobuf(protobuf)
    model_class = Model._lookup_model(ds_entity.kind)
    entity = model_class()
    entity._key = key_module.Key._from_ds_key(ds_entity.key)
    for name, value in ds_entity.items():
        prop = getattr(model_class, name, None)
        if not (prop is not None and isinstance(prop, Property)):
            continue
        if value is not None:
            if prop._repeated:
                value = [_BaseValue(sub_value) for sub_value in value]
            else:
                value = _BaseValue(value)
        prop._store_value(entity, value)

    return entity


def make_connection(*args, **kwargs):
    raise NotImplementedError


class ModelAttribute:
    """Base for classes that implement a ``_fix_up()`` method."""

    __slots__ = ()

    def _fix_up(self, cls, code_name):
        """Fix-up property name. To be implemented by subclasses.

        Args:
            cls (type): The model class that owns the property.
            code_name (str): The name of the :class:`Property` being fixed up.
        """


class _BaseValue:
    """A marker object wrapping a "base type" value.

    This is used to be able to tell whether ``entity._values[name]`` is a
    user value (i.e. of a type that the Python code understands) or a
    base value (i.e of a type that serialization understands).
    User values are unwrapped; base values are wrapped in a
    :class:`_BaseValue` instance.

    Args:
        b_val (Any): The base value to be wrapped.

    Raises:
        TypeError: If ``b_val`` is :data:`None`.
        TypeError: If ``b_val`` is a list.
    """

    __slots__ = ("b_val",)

    def __init__(self, b_val):
        if b_val is None:
            raise TypeError("Cannot wrap None")
        if isinstance(b_val, list):
            raise TypeError("Lists cannot be wrapped. Received", b_val)
        self.b_val = b_val

    def __repr__(self):
        return "_BaseValue({!r})".format(self.b_val)

    def __eq__(self, other):
        """Compare two :class:`_BaseValue` instances."""
        if not isinstance(other, _BaseValue):
            return NotImplemented

        return self.b_val == other.b_val

    def __hash__(self):
        raise TypeError("_BaseValue is not immutable")


class Property(ModelAttribute):
    """A class describing a typed, persisted attribute of an entity.

    .. warning::

        This is not to be confused with Python's ``@property`` built-in.

    .. note::

        This is just a base class; there are specific subclasses that
        describe properties of various types (and :class:`GenericProperty`
        which describes a dynamically typed property).

    The :class:`Property` does not reserve any "public" names (i.e. names
    that don't start with an underscore). This is intentional; the subclass
    :class:`StructuredProperty` uses the public attribute namespace to refer to
    nested property names (this is essential for specifying queries on
    subproperties).

    The :meth:`IN` attribute is provided as an alias for ``_IN``, but ``IN``
    can be overridden if a subproperty has the same name.

    The :class:`Property` class and its predefined subclasses allow easy
    subclassing using composable (or stackable) validation and
    conversion APIs. These require some terminology definitions:

    * A **user value** is a value such as would be set and accessed by the
      application code using standard attributes on the entity.
    * A **base value** is a value such as would be serialized to
      and deserialized from Cloud Datastore.

    A property will be a member of a :class:`Model` and will be used to help
    store values in an ``entity`` (i.e. instance of a model subclass). The
    underlying stored values can be either user values or base values.

    To interact with the composable conversion and validation API, a
    :class:`Property` subclass can define

    * ``_to_base_type()``
    * ``_from_base_type()``
    * ``_validate()``

    These should **not** call their ``super()`` method, since the methods
    are meant to be composed. For example with composable validation:

    .. code-block:: python

        class Positive(ndb.IntegerProperty):
            def _validate(self, value):
                if value < 1:
                    raise ndb.exceptions.BadValueError("Non-positive", value)


        class SingleDigit(Positive):
            def _validate(self, value):
                if value > 9:
                    raise ndb.exceptions.BadValueError("Multi-digit", value)

    neither ``_validate()`` method calls ``super()``. Instead, when a
    ``SingleDigit`` property validates a value, it composes all validation
    calls in order:

    * ``SingleDigit._validate``
    * ``Positive._validate``
    * ``IntegerProperty._validate``

    The API supports "stacking" classes with ever more sophisticated
    user / base conversions:

    * the user to base conversion goes from more sophisticated to less
      sophisticated
    * the base to user conversion goes from less sophisticated to more
      sophisticated

    For example, see the relationship between :class:`BlobProperty`,
    :class:`TextProperty` and :class:`StringProperty`.

    The validation API distinguishes between "lax" and "strict" user values.
    The set of lax values is a superset of the set of strict values. The
    ``_validate()`` method takes a lax value and if necessary converts it to
    a strict value. For example, an integer (lax) can be converted to a
    floating point (strict) value. This means that when setting the property
    value, lax values are accepted, while when getting the property value, only
    strict values will be returned. If no conversion is needed, ``_validate()``
    may return :data:`None`. If the argument is outside the set of accepted lax
    values, ``_validate()`` should raise an exception, preferably
    :exc:`TypeError` or :exc:`.BadValueError`.

    A class utilizing all three may resemble:

    .. code-block:: python

        class WidgetProperty(ndb.Property):

            def _validate(self, value):
                # Lax user value to strict user value.
                if not isinstance(value, Widget):
                    raise nbd.exceptions.BadValueError(value)

            def _to_base_type(self, value):
                # (Strict) user value to base value.
                if isinstance(value, Widget):
                    return value.to_internal()

            def _from_base_type(self, value):
                # Base value to (strict) user value.'
                if not isinstance(value, _WidgetInternal):
                    return Widget(value)

    There are some things that ``_validate()``, ``_to_base_type()`` and
    ``_from_base_type()`` do **not** need to handle:

    * :data:`None`: They will not be called with :data:`None` (and if they
      return :data:`None`, this means that the value does not need conversion).
    * Repeated values: The infrastructure takes care of calling
      ``_from_base_type()`` or ``_to_base_type()`` for each list item in a
      repeated value.
    * Wrapping "base" values: The wrapping and unwrapping is taken care of by
      the infrastructure that calls the composable APIs.
    * Comparisons: The comparison operations call ``_to_base_type()`` on
      their operand.
    * Distinguishing between user and base values: the infrastructure
      guarantees that ``_from_base_type()`` will be called with an
      (unwrapped) base value, and that ``_to_base_type()`` will be called
      with a user value.
    * Returning the original value: if any of these return :data:`None`, the
      original value is kept. (Returning a different value not equal to
      :data:`None` will substitute the different value.)

    Additionally, :meth:`_prepare_for_put` can be used to integrate with
    datastore save hooks used by :class:`Model` instances.

    .. automethod:: _prepare_for_put

    Args:
        name (str): The name of the property.
        indexed (bool): Indicates if the value should be indexed.
        repeated (bool): Indicates if this property is repeated, i.e. contains
            multiple values.
        required (bool): Indicates if this property is required on the given
            model type.
        default (Any): The default value for this property.
        choices (Iterable[Any]): A container of allowed values for this
            property.
        validator (Callable[[~google.cloud.ndb.model.Property, Any], bool]): A
            validator to be used to check values.
        verbose_name (str): A longer, user-friendly name for this property.
        write_empty_list (bool): Indicates if an empty list should be written
            to the datastore.
    """

    # Instance default fallbacks provided by class.
    _code_name = None
    _name = None
    _indexed = True
    _repeated = False
    _required = False
    _default = None
    _choices = None
    _validator = None
    _verbose_name = None
    _write_empty_list = False
    # Non-public class attributes.
    _FIND_METHODS_CACHE = {}

    def __init__(
        self,
        name=None,
        *,
        indexed=None,
        repeated=None,
        required=None,
        default=None,
        choices=None,
        validator=None,
        verbose_name=None,
        write_empty_list=None
    ):
        # NOTE: These explicitly avoid setting the values so that the
        #       instances will fall back to the class on lookup.
        if name is not None:
            self._name = self._verify_name(name)
        if indexed is not None:
            self._indexed = indexed
        if repeated is not None:
            self._repeated = repeated
        if required is not None:
            self._required = required
        if default is not None:
            self._default = default
        self._verify_repeated()
        if choices is not None:
            self._choices = self._verify_choices(choices)
        if validator is not None:
            self._validator = self._verify_validator(validator)
        if verbose_name is not None:
            self._verbose_name = verbose_name
        if write_empty_list is not None:
            self._write_empty_list = write_empty_list

    @staticmethod
    def _verify_name(name):
        """Verify the name of the property.

        Args:
            name (str): The name of the property.

        Returns:
            str: The ``name`` passed in.

        Raises:
            TypeError: If the ``name`` is not a string.
            ValueError: If the name contains a ``.``.
        """
        if not isinstance(name, str):
            raise TypeError("Name {!r} is not a string".format(name))

        if "." in name:
            raise ValueError(
                "Name {!r} cannot contain period characters".format(name)
            )

        return name

    def _verify_repeated(self):
        """Checks if the repeated / required / default values are compatible.

        Raises:
            ValueError: If ``repeated`` is :data:`True` but one of
                ``required`` or ``default`` is set.
        """
        if self._repeated and (self._required or self._default is not None):
            raise ValueError(
                "repeated is incompatible with required or default"
            )

    @staticmethod
    def _verify_choices(choices):
        """Verify the choices for a property with a limited set of values.

        Args:
            choices (Union[list, tuple, set, frozenset]): An iterable of
                allowed values for the property.

        Returns:
            frozenset: The ``choices`` cast to a frozen set.

        Raises:
            TypeError: If ``choices`` is not one of the expected container
                types.
        """
        if not isinstance(choices, (list, tuple, set, frozenset)):
            raise TypeError(
                "choices must be a list, tuple or set; received {!r}".format(
                    choices
                )
            )
        return frozenset(choices)

    @staticmethod
    def _verify_validator(validator):
        """Verify the validator for a property.

        The validator will be called as follows:

        .. code-block:: python

            value = validator(prop, value)

        The ``validator`` should be idempotent, i.e. calling it a second time
        should not further modify the value. So a validator that returns e.g.
        ``value.lower()`` or ``value.strip()`` is fine, but one that returns
        ``value + "$"`` is not.

        Args:
            validator (Callable[[Property, Any], bool]): A callable that can
                validate a property value.

        Returns:
            Callable[[Property, Any], bool]: The ``validator``.

        Raises:
            TypeError: If ``validator`` is not callable. This is determined by
                checking is the attribute ``__call__`` is defined.
        """
        # NOTE: Checking for ``_call__`` is done to match the original
        #       implementation. It's not clear why ``callable()`` was not used.
        if getattr(validator, "__call__", None) is None:
            raise TypeError(
                "validator must be callable or None; received {!r}".format(
                    validator
                )
            )

        return validator

    def _constructor_info(self):
        """Helper for :meth:`__repr__`.

        Yields:
            Tuple[str, bool]: Pairs of argument name and a boolean indicating
            if that argument is a keyword.
        """
        signature = inspect.signature(self.__init__)
        for name, parameter in signature.parameters.items():
            is_keyword = parameter.kind == inspect.Parameter.KEYWORD_ONLY
            yield name, is_keyword

    def __repr__(self):
        """Return a compact unambiguous string representation of a property.

        This cycles through all stored attributes and displays the ones that
        differ from the default values.
        """
        args = []
        cls = type(self)
        for name, is_keyword in self._constructor_info():
            attr = "_{}".format(name)
            instance_val = getattr(self, attr)
            default_val = getattr(cls, attr)

            if instance_val is not default_val:
                if isinstance(instance_val, type):
                    as_str = instance_val.__qualname__
                else:
                    as_str = repr(instance_val)

                if is_keyword:
                    as_str = "{}={}".format(name, as_str)
                args.append(as_str)

        return "{}({})".format(cls.__name__, ", ".join(args))

    def _datastore_type(self, value):
        """Internal hook used by property filters.

        Sometimes the low-level query interface needs a specific data type
        in order for the right filter to be constructed. See
        :meth:`_comparison`.

        Args:
            value (Any): The value to be converted to a low-level type.

        Returns:
            Any: The passed-in ``value``, always. Subclasses may alter this
            behavior.
        """
        return value

    def _comparison(self, op, value):
        """Internal helper for comparison operators.

        Args:
            op (str): The comparison operator. One of ``=``, ``!=``, ``<``,
                ``<=``, ``>``, ``>=`` or ``in``.
            value (Any): The value to compare against.

        Returns:
            FilterNode: A FilterNode instance representing the requested
            comparison.

        Raises:
            BadFilterError: If the current property is not indexed.
        """
        # Import late to avoid circular imports.
        from google.cloud.ndb import query

        if not self._indexed:
            raise exceptions.BadFilterError(
                "Cannot query for unindexed property {}".format(self._name)
            )

        if value is not None:
            value = self._do_validate(value)
            value = self._call_to_base_type(value)
            value = self._datastore_type(value)

        return query.FilterNode(self._name, op, value)

    # Comparison operators on Property instances don't compare the
    # properties; instead they return ``FilterNode``` instances that can be
    # used in queries.

    def __eq__(self, value):
        """FilterNode: Represents the ``=`` comparison."""
        return self._comparison("=", value)

    def __ne__(self, value):
        """FilterNode: Represents the ``!=`` comparison."""
        return self._comparison("!=", value)

    def __lt__(self, value):
        """FilterNode: Represents the ``<`` comparison."""
        return self._comparison("<", value)

    def __le__(self, value):
        """FilterNode: Represents the ``<=`` comparison."""
        return self._comparison("<=", value)

    def __gt__(self, value):
        """FilterNode: Represents the ``>`` comparison."""
        return self._comparison(">", value)

    def __ge__(self, value):
        """FilterNode: Represents the ``>=`` comparison."""
        return self._comparison(">=", value)

    def _IN(self, value):
        """For the ``in`` comparison operator.

        The ``in`` operator cannot be overloaded in the way we want
        to, so we define a method. For example:

        .. code-block:: python

            Employee.query(Employee.rank.IN([4, 5, 6]))

        Note that the method is called ``_IN()`` but may normally be invoked
        as ``IN()``; ``_IN()`` is provided for the case that a
        :class:`.StructuredProperty` refers to a model that has a property
        named ``IN``.

        Args:
            value (Iterable[Any]): The set of values that the property value
                must be contained in.

        Returns:
            Union[~google.cloud.ndb.query.DisjunctionNode, \
                ~google.cloud.ndb.query.FilterNode, \
                ~google.cloud.ndb.query.FalseNode]: A node corresponding
            to the desired in filter.

            * If ``value`` is empty, this will return a :class:`.FalseNode`
            * If ``len(value) == 1``, this will return a :class:`.FilterNode`
            * Otherwise, this will return a :class:`.DisjunctionNode`

        Raises:
            ~google.cloud.ndb.exceptions.BadFilterError: If the current
                property is not indexed.
            ~google.cloud.ndb.exceptions.BadArgumentError: If ``value`` is not
                a basic container (:class:`list`, :class:`tuple`, :class:`set`
                or :class:`frozenset`).
        """
        # Import late to avoid circular imports.
        from google.cloud.ndb import query

        if not self._indexed:
            raise exceptions.BadFilterError(
                "Cannot query for unindexed property {}".format(self._name)
            )

        if not isinstance(value, (list, tuple, set, frozenset)):
            raise exceptions.BadArgumentError(
                "Expected list, tuple or set, got {!r}".format(value)
            )

        values = []
        for sub_value in value:
            if sub_value is not None:
                sub_value = self._do_validate(sub_value)
                sub_value = self._call_to_base_type(sub_value)
                sub_value = self._datastore_type(sub_value)
            values.append(sub_value)

        return query.FilterNode(self._name, "in", values)

    IN = _IN
    """Used to check if a property value is contained in a set of values.

    For example:

    .. code-block:: python

        Employee.query(Employee.rank.IN([4, 5, 6]))
    """

    def __neg__(self):
        """Return a descending sort order on this property.

        For example:

        .. code-block:: python

            Employee.query().order(-Employee.rank)

        Raises:
            NotImplementedError: Always, the original implementation relied on
                a low-level datastore query module.
        """
        raise NotImplementedError("Missing datastore_query.PropertyOrder")

    def __pos__(self):
        """Return an ascending sort order on this property.

        Note that this is redundant but provided for consistency with
        :meth:`__neg__`. For example, the following two are equivalent:

        .. code-block:: python

            Employee.query().order(+Employee.rank)
            Employee.query().order(Employee.rank)

        Raises:
            NotImplementedError: Always, the original implementation relied on
                a low-level datastore query module.
        """
        raise NotImplementedError("Missing datastore_query.PropertyOrder")

    def _do_validate(self, value):
        """Call all validations on the value.

        This transforms the ``value`` via:

        * Calling the derived ``_validate()`` method(s) (on subclasses that
          don't define ``_to_base_type()``),
        * Calling the custom validator function

        After transforming, it checks if the transformed value is in
        ``choices`` (if defined).

        It's possible that one of the ``_validate()`` methods will raise
        an exception.

        If ``value`` is a base-value, this will do nothing and return it.

        .. note::

            This does not call all composable ``_validate()`` methods.
            It only calls ``_validate()`` methods up to the
            first class in the hierarchy that defines a ``_to_base_type()``
            method, when the MRO is traversed looking for ``_validate()`` and
            ``_to_base_type()`` methods.

        .. note::

            For a repeated property this method should be called
            for each value in the list, not for the list as a whole.

        Args:
            value (Any): The value to be converted / validated.

        Returns:
            Any: The transformed ``value``, possibly modified in an idempotent
            way.
        """
        if isinstance(value, _BaseValue):
            return value

        value = self._call_shallow_validation(value)

        if self._validator is not None:
            new_value = self._validator(self, value)
            if new_value is not None:
                value = new_value

        if self._choices is not None:
            if value not in self._choices:
                raise exceptions.BadValueError(
                    "Value {!r} for property {} is not an allowed "
                    "choice".format(value, self._name)
                )

        return value

    def _fix_up(self, cls, code_name):
        """Internal helper called to tell the property its name.

        This is called by :meth:`_fix_up_properties`, which is called by
        :class:`MetaModel` when finishing the construction of a :class:`Model`
        subclass. The name passed in is the name of the class attribute to
        which the current property is assigned (a.k.a. the code name). Note
        that this means that each property instance must be assigned to (at
        most) one class attribute. E.g. to declare three strings, you must
        call create three :class:`StringProperty` instances:

        .. code-block:: python

            class MyModel(ndb.Model):
                foo = ndb.StringProperty()
                bar = ndb.StringProperty()
                baz = ndb.StringProperty()

        you cannot write:

        .. code-block:: python

            class MyModel(ndb.Model):
                foo = bar = baz = ndb.StringProperty()

        Args:
            cls (type): The class that the property is stored on. This argument
                is unused by this method, but may be used by subclasses.
            code_name (str): The name (on the class) that refers to this
                property.
        """
        self._code_name = code_name
        if self._name is None:
            self._name = code_name

    def _store_value(self, entity, value):
        """Store a value in an entity for this property.

        This assumes validation has already taken place. For a repeated
        property the value should be a list.

        Args:
            entity (Model): An entity to set a value on.
            value (Any): The value to be stored for this property.
        """
        entity._values[self._name] = value

    def _set_value(self, entity, value):
        """Set a value in an entity for a property.

        This performs validation first. For a repeated property the value
        should be a list (or similar container).

        Args:
            entity (Model): An entity to set a value on.
            value (Any): The value to be stored for this property.

        Raises:
            ReadonlyPropertyError: If the ``entity`` is the result of a
                projection query.
            .BadValueError: If the current property is repeated but the
                ``value`` is not a basic container (:class:`list`,
                :class:`tuple`, :class:`set` or :class:`frozenset`).
        """
        if entity._projection:
            raise ReadonlyPropertyError(
                "You cannot set property values of a projection entity"
            )

        if self._repeated:
            if not isinstance(value, (list, tuple, set, frozenset)):
                raise exceptions.BadValueError(
                    "Expected list or tuple, got {!r}".format(value)
                )
            value = [self._do_validate(v) for v in value]
        else:
            if value is not None:
                value = self._do_validate(value)

        self._store_value(entity, value)

    def _has_value(self, entity, unused_rest=None):
        """Determine if the entity has a value for this property.

        Args:
            entity (Model): An entity to check if the current property has
                a value set.
            unused_rest (None): An always unused keyword.
        """
        return self._name in entity._values

    def _retrieve_value(self, entity, default=None):
        """Retrieve the value for this property from an entity.

        This returns :data:`None` if no value is set, or the ``default``
        argument if given. For a repeated property this returns a list if a
        value is set, otherwise :data:`None`. No additional transformations
        are applied.

        Args:
            entity (Model): An entity to get a value from.
            default (Optional[Any]): The default value to use as fallback.
        """
        return entity._values.get(self._name, default)

    def _get_user_value(self, entity):
        """Return the user value for this property of the given entity.

        This implies removing the :class:`_BaseValue` wrapper if present, and
        if it is, calling all ``_from_base_type()`` methods, in the reverse
        method resolution order of the property's class. It also handles
        default values and repeated properties.

        Args:
            entity (Model): An entity to get a value from.

        Returns:
            Any: The original value (if not :class:`_BaseValue`) or the wrapped
            value converted from the base type.
        """
        return self._apply_to_values(entity, self._opt_call_from_base_type)

    def _get_base_value(self, entity):
        """Return the base value for this property of the given entity.

        This implies calling all ``_to_base_type()`` methods, in the method
        resolution order of the property's class, and adding a
        :class:`_BaseValue` wrapper, if one is not already present. (If one
        is present, no work is done.)  It also handles default values and
        repeated properties.

        Args:
            entity (Model): An entity to get a value from.

        Returns:
            Union[_BaseValue, List[_BaseValue]]: The original value
            (if :class:`_BaseValue`) or the value converted to the base type
            and wrapped.
        """
        return self._apply_to_values(entity, self._opt_call_to_base_type)

    def _get_base_value_unwrapped_as_list(self, entity):
        """Like _get_base_value(), but always returns a list.

        Args:
            entity (Model): An entity to get a value from.

        Returns:
            List[Any]: The unwrapped base values. For an unrepeated
            property, if the value is missing or :data:`None`, returns
            ``[None]``; for a repeated property, if the original value is
            missing or :data:`None` or empty, returns ``[]``.
        """
        wrapped = self._get_base_value(entity)
        if self._repeated:
            return [w.b_val for w in wrapped]
        else:
            if wrapped is None:
                return [None]
            return [wrapped.b_val]

    def _opt_call_from_base_type(self, value):
        """Call ``_from_base_type()`` if necessary.

        If ``value`` is a :class:`_BaseValue`, unwrap it and call all
        :math:`_from_base_type` methods. Otherwise, return the value
        unchanged.

        Args:
            value (Any): The value to invoke :meth:`_call_from_base_type`
               for.

        Returns:
            Any: The original value (if not :class:`_BaseValue`) or the value
            converted from the base type.
        """
        if isinstance(value, _BaseValue):
            value = self._call_from_base_type(value.b_val)
        return value

    def _value_to_repr(self, value):
        """Turn a value (base or not) into its repr().

        This exists so that property classes can override it separately.

        This manually applies ``_from_base_type()`` so as not to have a side
        effect on what's contained in the entity. Printing a value should not
        change it.

        Args:
            value (Any): The value to convert to a pretty-print ``repr``.

        Returns:
            str: The ``repr`` of the "true" value.
        """
        val = self._opt_call_from_base_type(value)
        return repr(val)

    def _opt_call_to_base_type(self, value):
        """Call ``_to_base_type()`` if necessary.

        If ``value`` is a :class:`_BaseValue`, return it unchanged.
        Otherwise, call all ``_validate()`` and ``_to_base_type()`` methods
        and wrap it in a :class:`_BaseValue`.

        Args:
            value (Any): The value to invoke :meth:`_call_to_base_type`
               for.

        Returns:
            _BaseValue: The original value (if :class:`_BaseValue`) or the
            value converted to the base type and wrapped.
        """
        if not isinstance(value, _BaseValue):
            value = _BaseValue(self._call_to_base_type(value))
        return value

    def _call_from_base_type(self, value):
        """Call all ``_from_base_type()`` methods on the value.

        This calls the methods in the reverse method resolution order of
        the property's class.

        Args:
            value (Any): The value to be converted.

        Returns:
            Any: The transformed ``value``.
        """
        methods = self._find_methods("_from_base_type", reverse=True)
        call = self._apply_list(methods)
        return call(value)

    def _call_to_base_type(self, value):
        """Call all ``_validate()`` and ``_to_base_type()`` methods on value.

        This calls the methods in the method resolution order of the
        property's class. For example, given the hierarchy

        .. code-block:: python

            class A(Property):
                def _validate(self, value):
                    ...
                def _to_base_type(self, value):
                    ...

            class B(A):
                def _validate(self, value):
                    ...
                def _to_base_type(self, value):
                    ...

            class C(B):
                def _validate(self, value):
                    ...

        the full list of methods (in order) is:

        * ``C._validate()``
        * ``B._validate()``
        * ``B._to_base_type()``
        * ``A._validate()``
        * ``A._to_base_type()``

        Args:
            value (Any): The value to be converted / validated.

        Returns:
            Any: The transformed ``value``.
        """
        methods = self._find_methods("_validate", "_to_base_type")
        call = self._apply_list(methods)
        return call(value)

    def _call_shallow_validation(self, value):
        """Call the "initial" set of ``_validate()`` methods.

        This is similar to :meth:`_call_to_base_type` except it only calls
        those ``_validate()`` methods that can be called without needing to
        call ``_to_base_type()``.

        An example: suppose the class hierarchy is

        .. code-block:: python

            class A(Property):
                def _validate(self, value):
                    ...
                def _to_base_type(self, value):
                    ...

            class B(A):
                def _validate(self, value):
                    ...
                def _to_base_type(self, value):
                    ...

            class C(B):
                def _validate(self, value):
                    ...

        The full list of methods (in order) called by
        :meth:`_call_to_base_type` is:

        * ``C._validate()``
        * ``B._validate()``
        * ``B._to_base_type()``
        * ``A._validate()``
        * ``A._to_base_type()``

        whereas the full list of methods (in order) called here stops once
        a ``_to_base_type()`` method is encountered:

        * ``C._validate()``
        * ``B._validate()``

        Args:
            value (Any): The value to be converted / validated.

        Returns:
            Any: The transformed ``value``.
        """
        methods = []
        for method in self._find_methods("_validate", "_to_base_type"):
            # Stop if ``_to_base_type()`` is encountered.
            if method.__name__ != "_validate":
                break
            methods.append(method)

        call = self._apply_list(methods)
        return call(value)

    @classmethod
    def _find_methods(cls, *names, reverse=False):
        """Compute a list of composable methods.

        Because this is a common operation and the class hierarchy is
        static, the outcome is cached (assuming that for a particular list
        of names the reversed flag is either always on, or always off).

        Args:
            names (Tuple[str, ...]): One or more method names to look up on
                the current class or base classes.
            reverse (bool): Optional flag, default False; if True, the list is
              reversed.

        Returns:
            List[Callable]: Class method objects.
        """
        # Get cache on current class / set cache if it doesn't exist.
        key = "{}.{}".format(cls.__module__, cls.__qualname__)
        cache = cls._FIND_METHODS_CACHE.setdefault(key, {})
        hit = cache.get(names)
        if hit is not None:
            if reverse:
                return list(reversed(hit))
            else:
                return hit

        methods = []
        for klass in cls.__mro__:
            for name in names:
                method = klass.__dict__.get(name)
                if method is not None:
                    methods.append(method)

        cache[names] = methods
        if reverse:
            return list(reversed(methods))
        else:
            return methods

    def _apply_list(self, methods):
        """Chain together a list of callables for transforming a value.

        .. note::

            Each callable in ``methods`` is an unbound instance method, e.g.
            accessed via ``Property.foo`` rather than ``instance.foo``.
            Therefore, calling these methods will require ``self`` as the
            first argument.

        If one of the method returns :data:`None`, the previous value is kept;
        otherwise the last value is replace.

        Exceptions thrown by a method in ``methods`` are not caught, so it
        is up to the caller to catch them.

        Args:
            methods (Iterable[Callable[[Any], Any]]): An iterable of methods
                to apply to a value.

        Returns:
            Callable[[Any], Any]: A callable that takes a single value and
            applies each method in ``methods`` to it.
        """

        def call(value):
            for method in methods:
                new_value = method(self, value)
                if new_value is not None:
                    value = new_value
            return value

        return call

    def _apply_to_values(self, entity, function):
        """Apply a function to the property value / values of a given entity.

        This retrieves the property value, applies the function, and then
        stores the value back. For a repeated property, the function is
        applied separately to each of the values in the list. The
        resulting value or list of values is both stored back in the
        entity and returned from this method.

        Args:
            entity (Model): An entity to get a value from.
            function (Callable[[Any], Any]): A transformation to apply to
                the value.

        Returns:
            Any: The transformed value store on the entity for this property.
        """
        value = self._retrieve_value(entity, self._default)
        if self._repeated:
            if value is None:
                value = []
                self._store_value(entity, value)
            else:
                # NOTE: This assumes, but does not check, that ``value`` is
                #       iterable. This relies on ``_set_value`` having checked
                #       and converted to a ``list`` for a repeated property.
                value[:] = map(function, value)
        else:
            if value is not None:
                new_value = function(value)
                if new_value is not None and new_value is not value:
                    self._store_value(entity, new_value)
                    value = new_value

        return value

    def _get_value(self, entity):
        """Get the value for this property from an entity.

        For a repeated property this initializes the value to an empty
        list if it is not set.

        Args:
            entity (Model): An entity to get a value from.

        Returns:
            Any: The user value stored for the current property.

        Raises:
            UnprojectedPropertyError: If the ``entity`` is the result of a
                projection query and the current property is not one of the
                projected properties.
        """
        if entity._projection:
            if self._name not in entity._projection:
                raise UnprojectedPropertyError(
                    "Property {} is not in the projection".format(self._name)
                )

        return self._get_user_value(entity)

    def _delete_value(self, entity):
        """Delete the value for this property from an entity.

        .. note::

            If no value exists this is a no-op; deleted values will not be
            serialized but requesting their value will return :data:`None` (or
            an empty list in the case of a repeated property).

        Args:
            entity (Model): An entity to get a value from.
        """
        if self._name in entity._values:
            del entity._values[self._name]

    def _is_initialized(self, entity):
        """Ask if the entity has a value for this property.

        This returns :data:`False` if a value is stored but the stored value
        is :data:`None`.

        Args:
            entity (Model): An entity to get a value from.
        """
        return not self._required or (
            (self._has_value(entity) or self._default is not None)
            and self._get_value(entity) is not None
        )

    def __get__(self, entity, unused_cls=None):
        """Descriptor protocol: get the value from the entity.

        Args:
            entity (Model): An entity to get a value from.
            unused_cls (type): The class that owns this instance.
        """
        if entity is None:
            # Handle the case where ``__get__`` is called on the class
            # rather than an instance.
            return self
        return self._get_value(entity)

    def __set__(self, entity, value):
        """Descriptor protocol: set the value on the entity.

        Args:
            entity (Model): An entity to set a value on.
            value (Any): The value to set.
        """
        self._set_value(entity, value)

    def __delete__(self, entity):
        """Descriptor protocol: delete the value from the entity.

        Args:
            entity (Model): An entity to delete a value from.
        """
        self._delete_value(entity)

    def _serialize(
        self, entity, pb, prefix="", parent_repeated=False, projection=None
    ):
        """Serialize this property to a protocol buffer.

        Some subclasses may override this method.

        Args:
            entity (Model): The entity that owns this property.
            pb (google.cloud.datastore_v1.proto.entity_pb2.Entity): An existing
                entity protobuf instance that we'll add a value to.
            prefix (Optional[str]): Name prefix used for
                :class:`StructuredProperty` (if present, must end in ``.``).
            parent_repeated (Optional[bool]): Indicates if the parent (or an
                earlier ancestor) is a repeated property.
            projection (Optional[Union[list, tuple]]): An iterable of strings
                representing the projection for the model instance, or
                :data:`None` if the instance is not a projection.

        Raises:
            NotImplementedError: Always. This method is virtual.
        """
        raise NotImplementedError

    def _deserialize(self, entity, p, unused_depth=1):
        """Deserialize this property from a protocol buffer.

        Raises:
            NotImplementedError: Always. This method is deprecated.
        """
        raise NotImplementedError(_NO_LONGER_IMPLEMENTED)

    def _prepare_for_put(self, entity):
        """Allow this property to define a pre-put hook.

        This base class implementation does nothing, but subclasses may
        provide hooks.

        Args:
            entity (Model): An entity with values.
        """
        pass

    def _check_property(self, rest=None, require_indexed=True):
        """Check this property for specific requirements.

        Called by ``Model._check_properties()``.

        Args:
            rest: Optional subproperty to check, of the form
                ``name1.name2...nameN``.
            required_indexed (bool): Indicates if the current property must
                be indexed.

        Raises:
            InvalidPropertyError: If ``require_indexed`` is :data:`True`
                but the current property is not indexed.
            InvalidPropertyError: If a subproperty is specified via ``rest``
                (:class:`StructuredProperty` overrides this method to handle
                subproperties).
        """
        if require_indexed and not self._indexed:
            raise InvalidPropertyError(
                "Property is unindexed {}".format(self._name)
            )

        if rest:
            raise InvalidPropertyError(
                "Referencing subproperty {}.{} but {} is not a structured "
                "property".format(self._name, rest, self._name)
            )

    def _get_for_dict(self, entity):
        """Retrieve the value like ``_get_value()``.

        This is intended to be processed for ``_to_dict()``.

        Property subclasses can override this if they want the dictionary
        returned by ``entity._to_dict()`` to contain a different value. The
        main use case is allowing :class:`StructuredProperty` and
        :class:`LocalStructuredProperty` to allow the default ``_get_value()``
        behavior.

        * If you override ``_get_for_dict()`` to return a different type, you
          must override ``_validate()`` to accept values of that type and
          convert them back to the original type.

        * If you override ``_get_for_dict()``, you must handle repeated values
          and :data:`None` correctly. However, ``_validate()`` does not need to
          handle these.

        Args:
            entity (Model): An entity to get a value from.

        Returns:
            Any: The user value stored for the current property.
        """
        return self._get_value(entity)


def _validate_key(value, entity=None):
    """Validate a key.

    Args:
        value (.Key): The key to be validated.
        entity (Optional[Model]): The entity that the key is being validated
            for.

    Returns:
        .Key: The passed in ``value``.

    Raises:
        .BadValueError: If ``value`` is not a :class:`.Key`.
        KindError: If ``entity`` is specified, but the kind of the entity
            doesn't match the kind of ``value``.
    """
    if not isinstance(value, Key):
        raise exceptions.BadValueError("Expected Key, got {!r}".format(value))

    if entity and type(entity) not in (Model, Expando):
        if value.kind() != entity._get_kind():
            raise KindError(
                "Expected Key kind to be {}; received "
                "{}".format(entity._get_kind(), value.kind())
            )

    return value


class ModelKey(Property):
    """Special property to store a special "key" for a :class:`Model`.

    This is intended to be used as a psuedo-:class:`Property` on each
    :class:`Model` subclass. It is **not** intended for other usage in
    application code.

    It allows key-only queries to be done for a given kind.

    .. automethod:: _validate
    """

    __slots__ = ()

    def __init__(self):
        super(ModelKey, self).__init__()
        self._name = "__key__"

    def _comparison(self, op, value):
        """Internal helper for comparison operators.

        This uses the base implementation in :class:`Property`, but doesn't
        allow comparison to :data:`None`.

        Args:
            op (str): The comparison operator. One of ``=``, ``!=``, ``<``,
                ``<=``, ``>``, ``>=`` or ``in``.
            value (Any): The value to compare against.

        Returns:
            FilterNode: A FilterNode instance representing the requested
            comparison.

        Raises:
            .BadValueError: If ``value`` is :data:`None`.
        """
        if value is not None:
            return super(ModelKey, self)._comparison(op, value)

        raise exceptions.BadValueError(
            "__key__ filter query can't be compared to None"
        )

    def _validate(self, value):
        """Validate a ``value`` before setting it.

        Args:
            value (.Key): The value to check.

        Returns:
            .Key: The passed-in ``value``.
        """
        return _validate_key(value)

    @staticmethod
    def _set_value(entity, value):
        """Set the entity key on an entity.

        Args:
            entity (Model): An entity to set the entity key on.
            value (.Key): The key to be set on the entity.
        """
        if value is not None:
            value = _validate_key(value, entity=entity)
            value = entity._validate_key(value)

        entity._entity_key = value

    @staticmethod
    def _get_value(entity):
        """Get the entity key from an entity.

        Args:
            entity (Model): An entity to get the entity key from.

        Returns:
            .Key: The entity key stored on ``entity``.
        """
        return entity._entity_key

    @staticmethod
    def _delete_value(entity):
        """Remove / disassociate the entity key from an entity.

        Args:
            entity (Model): An entity to remove the entity key from.
        """
        entity._entity_key = None


class BooleanProperty(Property):
    """A property that contains values of type bool.

    .. automethod:: _validate
    """

    __slots__ = ()

    def _validate(self, value):
        """Validate a ``value`` before setting it.

        Args:
            value (bool): The value to check.

        Returns:
            bool: The passed-in ``value``.

        Raises:
            .BadValueError: If ``value`` is not a :class:`bool`.
        """
        if not isinstance(value, bool):
            raise exceptions.BadValueError(
                "Expected bool, got {!r}".format(value)
            )
        return value

    def _db_set_value(self, v, unused_p, value):
        """Helper for :meth:`_serialize`.

        Raises:
            NotImplementedError: Always. This method is virtual.
        """
        raise NotImplementedError

    def _db_get_value(self, v, unused_p):
        """Helper for :meth:`_deserialize`.

        Raises:
            NotImplementedError: Always. This method is deprecated.
        """
        raise NotImplementedError(_NO_LONGER_IMPLEMENTED)


class IntegerProperty(Property):
    """A property that contains values of type integer.

    .. note::

        If a value is a :class:`bool`, it will be coerced to ``0`` (for
        :data:`False`) or ``1`` (for :data:`True`).

    .. automethod:: _validate
    """

    __slots__ = ()

    def _validate(self, value):
        """Validate a ``value`` before setting it.

        Args:
            value (Union[int, bool]): The value to check.

        Returns:
            int: The passed-in ``value``.

        Raises:
            .BadValueError: If ``value`` is not an :class:`int` or convertible
                to one.
        """
        if not isinstance(value, int):
            raise exceptions.BadValueError(
                "Expected integer, got {!r}".format(value)
            )
        return int(value)

    def _db_set_value(self, v, unused_p, value):
        """Helper for :meth:`_serialize`.

        Raises:
            NotImplementedError: Always. This method is virtual.
        """
        raise NotImplementedError

    def _db_get_value(self, v, unused_p):
        """Helper for :meth:`_deserialize`.

        Raises:
            NotImplementedError: Always. This method is deprecated.
        """
        raise NotImplementedError(_NO_LONGER_IMPLEMENTED)


class FloatProperty(Property):
    """A property that contains values of type float.

    .. note::

        If a value is a :class:`bool` or :class:`int`, it will be
        coerced to a floating point value.

    .. automethod:: _validate
    """

    __slots__ = ()

    def _validate(self, value):
        """Validate a ``value`` before setting it.

        Args:
            value (Union[float, int, bool]): The value to check.

        Returns:
            float: The passed-in ``value``, possibly converted to a
            :class:`float`.

        Raises:
            .BadValueError: If ``value`` is not a :class:`float` or convertible
                to one.
        """
        if not isinstance(value, (float, int)):
            raise exceptions.BadValueError(
                "Expected float, got {!r}".format(value)
            )
        return float(value)

    def _db_set_value(self, v, unused_p, value):
        """Helper for :meth:`_serialize`.

        Raises:
            NotImplementedError: Always. This method is virtual.
        """
        raise NotImplementedError

    def _db_get_value(self, v, unused_p):
        """Helper for :meth:`_deserialize`.

        Raises:
            NotImplementedError: Always. This method is deprecated.
        """
        raise NotImplementedError(_NO_LONGER_IMPLEMENTED)


class _CompressedValue:
    """A marker object wrapping compressed values.

    Args:
        z_val (bytes): A return value of ``zlib.compress``.
    """

    __slots__ = ("z_val",)

    def __init__(self, z_val):
        self.z_val = z_val

    def __repr__(self):
        return "_CompressedValue({!r})".format(self.z_val)

    def __eq__(self, other):
        """Compare two compressed values."""
        if not isinstance(other, _CompressedValue):
            return NotImplemented

        return self.z_val == other.z_val

    def __hash__(self):
        raise TypeError("_CompressedValue is not immutable")


class BlobProperty(Property):
    """A property that contains values that are byte strings.

    .. note::

        Unlike most property types, a :class:`BlobProperty` is **not**
        indexed by default.

    .. automethod:: _to_base_type
    .. automethod:: _from_base_type
    .. automethod:: _validate

    Args:
        name (str): The name of the property.
        compressed (bool): Indicates if the value should be compressed (via
            ``zlib``).
        indexed (bool): Indicates if the value should be indexed.
        repeated (bool): Indicates if this property is repeated, i.e. contains
            multiple values.
        required (bool): Indicates if this property is required on the given
            model type.
        default (bytes): The default value for this property.
        choices (Iterable[bytes]): A container of allowed values for this
            property.
        validator (Callable[[~google.cloud.ndb.model.Property, Any], bool]): A
            validator to be used to check values.
        verbose_name (str): A longer, user-friendly name for this property.
        write_empty_list (bool): Indicates if an empty list should be written
            to the datastore.

    Raises:
        NotImplementedError: If the property is both compressed and indexed.
    """

    _indexed = False
    _compressed = False

    def __init__(
        self,
        name=None,
        *,
        compressed=None,
        indexed=None,
        repeated=None,
        required=None,
        default=None,
        choices=None,
        validator=None,
        verbose_name=None,
        write_empty_list=None
    ):
        super(BlobProperty, self).__init__(
            name=name,
            indexed=indexed,
            repeated=repeated,
            required=required,
            default=default,
            choices=choices,
            validator=validator,
            verbose_name=verbose_name,
            write_empty_list=write_empty_list,
        )
        if compressed is not None:
            self._compressed = compressed
        if self._compressed and self._indexed:
            raise NotImplementedError(
                "BlobProperty {} cannot be compressed and "
                "indexed at the same time.".format(self._name)
            )

    def _value_to_repr(self, value):
        """Turn the value into a user friendly representation.

        .. note::

            This will truncate the value based on the "visual" length, e.g.
            if it contains many ``\\xXX`` or ``\\uUUUU`` sequences, those
            will count against the length as more than one character.

        Args:
            value (Any): The value to convert to a pretty-print ``repr``.

        Returns:
            str: The ``repr`` of the "true" value.
        """
        long_repr = super(BlobProperty, self)._value_to_repr(value)
        if len(long_repr) > _MAX_STRING_LENGTH + 4:
            # Truncate, assuming the final character is the closing quote.
            long_repr = long_repr[:_MAX_STRING_LENGTH] + "..." + long_repr[-1]
        return long_repr

    def _validate(self, value):
        """Validate a ``value`` before setting it.

        Args:
            value (bytes): The value to check.

        Raises:
            .BadValueError: If ``value`` is not a :class:`bytes`.
            .BadValueError: If the current property is indexed but the value
                exceeds the maximum length (1500 bytes).
        """
        if not isinstance(value, bytes):
            raise exceptions.BadValueError(
                "Expected bytes, got {!r}".format(value)
            )

        if self._indexed and len(value) > _MAX_STRING_LENGTH:
            raise exceptions.BadValueError(
                "Indexed value {} must be at most {:d} "
                "bytes".format(self._name, _MAX_STRING_LENGTH)
            )

    def _to_base_type(self, value):
        """Convert a value to the "base" value type for this property.

        Args:
            value (bytes): The value to be converted.

        Returns:
            Optional[bytes]: The converted value. If the current property is
            compressed, this will return a wrapped version of the compressed
            value. Otherwise, it will return :data:`None` to indicate that
            the value didn't need to be converted.
        """
        if self._compressed:
            return _CompressedValue(zlib.compress(value))

    def _from_base_type(self, value):
        """Convert a value from the "base" value type for this property.

        Args:
            value (bytes): The value to be converted.

        Returns:
            Optional[bytes]: The converted value. If the current property is
            a (wrapped) compressed value, this will unwrap the value and return
            the decompressed form. Otherwise, it will return :data:`None` to
            indicate that the value didn't need to be unwrapped and
            decompressed.
        """
        if isinstance(value, _CompressedValue):
            return zlib.decompress(value.z_val)

    def _db_set_value(self, v, unused_p, value):
        """Helper for :meth:`_serialize`.

        Raises:
            NotImplementedError: Always. This method is virtual.
        """
        raise NotImplementedError

    def _db_set_compressed_meaning(self, p):
        """Helper for :meth:`_db_set_value`.

        Raises:
            NotImplementedError: Always. This method is virtual.
        """
        raise NotImplementedError

    def _db_set_uncompressed_meaning(self, p):
        """Helper for :meth:`_db_set_value`.

        Raises:
            NotImplementedError: Always. This method is virtual.
        """
        raise NotImplementedError

    def _db_get_value(self, v, unused_p):
        """Helper for :meth:`_deserialize`.

        Raises:
            NotImplementedError: Always. This method is deprecated.
        """
        raise NotImplementedError(_NO_LONGER_IMPLEMENTED)


class TextProperty(BlobProperty):
    """An unindexed property that contains UTF-8 encoded text values.

    A :class:`TextProperty` is intended for values of unlimited length, hence
    is **not** indexed. Previously, a :class:`TextProperty` could be indexed
    via:

    .. code-block:: python

        class Item(ndb.Model):
            description = ndb.TextProperty(indexed=True)
            ...

    but this usage is no longer supported. If indexed text is desired, a
    :class:`StringProperty` should be used instead.

    .. automethod:: _to_base_type
    .. automethod:: _from_base_type
    .. automethod:: _validate

    Raises:
        NotImplementedError: If ``indexed=True`` is provided.
    """

    __slots__ = ()

    def __init__(self, *args, **kwargs):
        indexed = kwargs.pop("indexed", False)
        if indexed:
            raise NotImplementedError(
                "A TextProperty cannot be indexed. Previously this was "
                "allowed, but this usage is no longer supported."
            )

        super(TextProperty, self).__init__(*args, **kwargs)

    def _constructor_info(self):
        """Helper for :meth:`__repr__`.

        Yields:
            Tuple[str, bool]: Pairs of argument name and a boolean indicating
            if that argument is a keyword.
        """
        parent_init = super(TextProperty, self).__init__
        signature = inspect.signature(parent_init)
        for name, parameter in signature.parameters.items():
            if name == "indexed":
                continue
            is_keyword = parameter.kind == inspect.Parameter.KEYWORD_ONLY
            yield name, is_keyword

    @property
    def _indexed(self):
        """bool: Indicates that the property is not indexed."""
        return False

    def _validate(self, value):
        """Validate a ``value`` before setting it.

        Args:
            value (Union[bytes, str]): The value to check.

        Raises:
            .BadValueError: If ``value`` is :class:`bytes`, but is not a valid
                UTF-8 encoded string.
            .BadValueError: If ``value`` is neither :class:`bytes` nor
                :class:`str`.
            .BadValueError: If the current property is indexed but the UTF-8
                encoded value exceeds the maximum length (1500 bytes).
        """
        if isinstance(value, bytes):
            try:
                encoded_length = len(value)
                value = value.decode("utf-8")
            except UnicodeError:
                raise exceptions.BadValueError(
                    "Expected valid UTF-8, got {!r}".format(value)
                )
        elif isinstance(value, str):
            encoded_length = len(value.encode("utf-8"))
        else:
            raise exceptions.BadValueError(
                "Expected string, got {!r}".format(value)
            )

        if self._indexed and encoded_length > _MAX_STRING_LENGTH:
            raise exceptions.BadValueError(
                "Indexed value {} must be at most {:d} "
                "bytes".format(self._name, _MAX_STRING_LENGTH)
            )

    def _to_base_type(self, value):
        """Convert a value to the "base" value type for this property.

        Args:
            value (Union[bytes, str]): The value to be converted.

        Returns:
            Optional[bytes]: The converted value. If ``value`` is a
            :class:`str`, this will return the UTF-8 encoded bytes for it.
            Otherwise, it will return :data:`None`.
        """
        if isinstance(value, str):
            return value.encode("utf-8")

    def _from_base_type(self, value):
        """Convert a value from the "base" value type for this property.

        .. note::

            Older versions of ``ndb`` could write non-UTF-8 ``TEXT``
            properties. This means that if ``value`` is :class:`bytes`, but is
            not a valid UTF-8 encoded string, it can't (necessarily) be
            rejected. But, :meth:`_validate` now rejects such values, so it's
            not possible to write new non-UTF-8 ``TEXT`` properties.

        Args:
            value (Union[bytes, str]): The value to be converted.

        Returns:
            Optional[str]: The converted value. If ``value`` is a a valid UTF-8
            encoded :class:`bytes` string, this will return the decoded
            :class:`str` corresponding to it. Otherwise, it will return
            :data:`None`.
        """
        if isinstance(value, bytes):
            try:
                return value.decode("utf-8")
            except UnicodeError:
                pass

    def _db_set_uncompressed_meaning(self, p):
        """Helper for :meth:`_db_set_value`.

        Raises:
            NotImplementedError: Always. This method is virtual.
        """
        raise NotImplementedError


class StringProperty(TextProperty):
    """An indexed property that contains UTF-8 encoded text values.

    This is nearly identical to :class:`TextProperty`, but is indexed. Values
    must be at most 1500 bytes (when UTF-8 encoded from :class:`str` to bytes).

    Raises:
        NotImplementedError: If ``indexed=False`` is provided.
    """

    __slots__ = ()

    def __init__(self, *args, **kwargs):
        indexed = kwargs.pop("indexed", True)
        if not indexed:
            raise NotImplementedError(
                "A StringProperty must be indexed. Previously setting "
                "``indexed=False`` was allowed, but this usage is no longer "
                "supported."
            )

        super(StringProperty, self).__init__(*args, **kwargs)

    @property
    def _indexed(self):
        """bool: Indicates that the property is indexed."""
        return True


class GeoPtProperty(Property):
    """A property that contains :attr:`.GeoPt` values.

    .. automethod:: _validate
    """

    __slots__ = ()

    def _validate(self, value):
        """Validate a ``value`` before setting it.

        Args:
            value (~google.cloud.datastore.helpers.GeoPoint): The value to
                check.

        Raises:
            .BadValueError: If ``value`` is not a :attr:`.GeoPt`.
        """
        if not isinstance(value, GeoPt):
            raise exceptions.BadValueError(
                "Expected GeoPt, got {!r}".format(value)
            )

    def _db_set_value(self, v, p, value):
        """Helper for :meth:`_serialize`.

        Raises:
            NotImplementedError: Always. This method is virtual.
        """
        raise NotImplementedError

    def _db_get_value(self, v, unused_p):
        """Helper for :meth:`_deserialize`.

        Raises:
            NotImplementedError: Always. This method is deprecated.
        """
        raise NotImplementedError(_NO_LONGER_IMPLEMENTED)


class PickleProperty(BlobProperty):
    """A property that contains values that are pickle-able.

    .. note::

        Unlike most property types, a :class:`PickleProperty` is **not**
        indexed by default.

    This will use :func:`pickle.dumps` with the highest available pickle
    protocol to convert to bytes and :func:`pickle.loads` to convert **from**
    bytes. The base value stored in the datastore will be the pickled bytes.

    .. automethod:: _to_base_type
    .. automethod:: _from_base_type
    """

    __slots__ = ()

    def _to_base_type(self, value):
        """Convert a value to the "base" value type for this property.

        Args:
            value (Any): The value to be converted.

        Returns:
            bytes: The pickled ``value``.
        """
        return pickle.dumps(value, pickle.HIGHEST_PROTOCOL)

    def _from_base_type(self, value):
        """Convert a value from the "base" value type for this property.

        Args:
            value (bytes): The value to be converted.

        Returns:
            Any: The unpickled ``value``.
        """
        return pickle.loads(value)


class JsonProperty(BlobProperty):
    """A property that contains JSON-encodable values.

    .. note::

        Unlike most property types, a :class:`JsonProperty` is **not**
        indexed by default.

    .. automethod:: _to_base_type
    .. automethod:: _from_base_type
    .. automethod:: _validate

    Args:
        name (str): The name of the property.
        compressed (bool): Indicates if the value should be compressed (via
            ``zlib``).
        json_type (type): The expected type of values that this property can
            hold. If :data:`None`, any type is allowed.
        indexed (bool): Indicates if the value should be indexed.
        repeated (bool): Indicates if this property is repeated, i.e. contains
            multiple values.
        required (bool): Indicates if this property is required on the given
            model type.
        default (Any): The default value for this property.
        choices (Iterable[Any]): A container of allowed values for this
            property.
        validator (Callable[[~google.cloud.ndb.model.Property, Any], bool]): A
            validator to be used to check values.
        verbose_name (str): A longer, user-friendly name for this property.
        write_empty_list (bool): Indicates if an empty list should be written
            to the datastore.
    """

    _json_type = None

    def __init__(
        self,
        name=None,
        *,
        compressed=None,
        json_type=None,
        indexed=None,
        repeated=None,
        required=None,
        default=None,
        choices=None,
        validator=None,
        verbose_name=None,
        write_empty_list=None
    ):
        super(JsonProperty, self).__init__(
            name=name,
            compressed=compressed,
            indexed=indexed,
            repeated=repeated,
            required=required,
            default=default,
            choices=choices,
            validator=validator,
            verbose_name=verbose_name,
            write_empty_list=write_empty_list,
        )
        if json_type is not None:
            self._json_type = json_type

    def _validate(self, value):
        """Validate a ``value`` before setting it.

        Args:
            value (Any): The value to check.

        Raises:
            TypeError: If the current property has a JSON type set and
                ``value`` is not an instance of that type.
        """
        if self._json_type is None:
            return
        if not isinstance(value, self._json_type):
            raise TypeError(
                "JSON property must be a {}".format(self._json_type)
            )

    def _to_base_type(self, value):
        """Convert a value to the "base" value type for this property.

        Args:
            value (Any): The value to be converted.

        Returns:
            bytes: The ``value``, JSON encoded as an ASCII byte string.
        """
        as_str = json.dumps(value, separators=(",", ":"), ensure_ascii=True)
        return as_str.encode("ascii")

    def _from_base_type(self, value):
        """Convert a value from the "base" value type for this property.

        Args:
            value (bytes): The value to be converted.

        Returns:
            Any: The ``value`` (ASCII bytes or string) loaded as JSON.
        """
        return json.loads(value.decode("ascii"))


@functools.total_ordering
class User:
    """Provides the email address, nickname, and ID for a Google Accounts user.

    .. note::

        This class is a port of ``google.appengine.api.users.User``.
        In the (legacy) Google App Engine standard environment, this
        constructor relied on several environment variables to provide a
        fallback for inputs. In particular:

        * ``AUTH_DOMAIN`` for the ``_auth_domain`` argument
        * ``USER_EMAIL`` for the ``email`` argument
        * ``USER_ID`` for the ``_user_id`` argument
        * ``FEDERATED_IDENTITY`` for the (now removed) ``federated_identity``
          argument
        * ``FEDERATED_PROVIDER`` for the (now removed) ``federated_provider``
          argument

        However in the gVisor Google App Engine runtime (e.g. Python 3.7),
        none of these environment variables will be populated.

    .. note::

        Previous versions of the Google Cloud Datastore API had an explicit
        ``UserValue`` field. However, the ``google.datastore.v1`` API returns
        previously stored user values as an ``Entity`` with the meaning set to
        ``ENTITY_USER=20``.

    .. warning::

        The ``federated_identity`` and ``federated_provider`` are
        decommissioned and have been removed from the constructor. Additionally
        ``_strict_mode`` has been removed from the constructor and the
        ``federated_identity()`` and ``federated_provider()`` methods have been
        removed from this class.

    Args:
        email (str): The user's email address.
        _auth_domain (str): The auth domain for the current application.
        _user_id (str): The user ID.

    Raises:
        ValueError: If the ``_auth_domain`` is not passed in.
        UserNotFoundError: If ``email`` is empty.
    """

    __slots__ = ("_auth_domain", "_email", "_user_id")

    def __init__(self, email=None, _auth_domain=None, _user_id=None):
        if _auth_domain is None:
            raise ValueError("_auth_domain is required")

        if not email:
            raise UserNotFoundError

        self._auth_domain = _auth_domain
        self._email = email
        self._user_id = _user_id

    def nickname(self):
        """The nickname for this user.

        A nickname is a human-readable string that uniquely identifies a Google
        user with respect to this application, akin to a username. For some
        users, this nickname is an email address or part of the email address.

        Returns:
            str: The nickname of the user.
        """
        if (
            self._email
            and self._auth_domain
            and self._email.endswith("@" + self._auth_domain)
        ):
            suffix_len = len(self._auth_domain) + 1
            return self._email[:-suffix_len]
        else:
            return self._email

    def email(self):
        """Returns the user's email address."""
        return self._email

    def user_id(self):
        """Obtains the user ID of the user.

        Returns:
            Optional[str]: A permanent unique identifying string or
            :data:`None`. If the email address was set explicity, this will
            return :data:`None`.
        """
        return self._user_id

    def auth_domain(self):
        """Obtains the user's authentication domain.

        Returns:
            str: The authentication domain. This method is internal and
            should not be used by client applications.
        """
        return self._auth_domain

    def add_to_entity(self, entity, name):
        """Add the user value to a datastore entity.

        .. note::

            This assumes, but does not check, that ``name`` is not already
            set on ``entity`` or in the meanings of ``entity``.

        Args:
            entity (~google.cloud.datastore.entity.Entity): An entity that
                contains a user value as the field ``name``.
            name (str): The name of the field containing this user value.
        """
        user_entity = entity_module.Entity()
        entity[name] = user_entity
        entity._meanings[name] = (_MEANING_PREDEFINED_ENTITY_USER, user_entity)

        # Set required fields.
        user_entity["email"] = self._email
        user_entity.exclude_from_indexes.add("email")
        user_entity["auth_domain"] = self._auth_domain
        user_entity.exclude_from_indexes.add("auth_domain")
        # Set optional field.
        if self._user_id:
            user_entity["user_id"] = self._user_id
            user_entity.exclude_from_indexes.add("user_id")

    @classmethod
    def read_from_entity(cls, entity, name):
        """Convert the user value to a datastore entity.

        Args:
            entity (~google.cloud.datastore.entity.Entity): An entity that
                contains a user value as the field ``name``.
            name (str): The name of the field containing this user value.

        Raises:
            ValueError: If the stored meaning for the ``name`` field is not
                equal to ``ENTITY_USER=20``.
            ValueError: If the value stored in the meanings for ``entity``
                is not the actual stored value under ``name``.
        """
        # NOTE: This may fail in a ``KeyError``.
        user_entity = entity[name]
        # NOTE: This may result in a ``ValueError`` for failed unpacking.
        meaning, value = entity._meanings.get(name, (0, None))
        if meaning != _MEANING_PREDEFINED_ENTITY_USER:
            raise ValueError("User values should have meaning=20")
        if user_entity is not value:
            raise ValueError("Unexpected value stored for meaning")

        # NOTE: We do not check ``exclude_from_indexes``.
        kwargs = {
            "email": user_entity["email"],
            "_auth_domain": user_entity["auth_domain"],
        }
        if "user_id" in user_entity:
            kwargs["_user_id"] = user_entity["user_id"]
        return cls(**kwargs)

    def __str__(self):
        return str(self.nickname())

    def __repr__(self):
        values = ["email={!r}".format(self._email)]
        if self._user_id:
            values.append("_user_id={!r}".format(self._user_id))
        return "users.User({})".format(", ".join(values))

    def __hash__(self):
        return hash((self._email, self._auth_domain))

    def __eq__(self, other):
        if not isinstance(other, User):
            return NotImplemented

        return (
            self._email == other._email
            and self._auth_domain == other._auth_domain
        )

    def __lt__(self, other):
        if not isinstance(other, User):
            return NotImplemented

        return (self._email, self._auth_domain) < (
            other._email,
            other._auth_domain,
        )


class UserProperty(Property):
    """A property that contains :class:`.User` values.

    .. warning::

        This exists for backwards compatibility with existing Cloud Datastore
        schemas only; storing :class:`.User` objects directly in Cloud
        Datastore is not recommended.

    .. warning::

        The ``auto_current_user`` and ``auto_current_user_add`` arguments are
        no longer supported.

    .. note::

        On Google App Engine standard, after saving a :class:`User` the user ID
        would automatically be populated by the datastore, even if it wasn't
        set in the :class:`User` value being stored. For example:

        .. code-block:: python

            >>> class Simple(ndb.Model):
            ...     u = ndb.UserProperty()
            ...
            >>> entity = Simple(u=users.User("user@example.com"))
            >>> entity.u.user_id() is None
            True
            >>>
            >>> entity.put()
            >>> # Reload without the cached values
            >>> entity = entity.key.get(use_cache=False, use_memcache=False)
            >>> entity.u.user_id()
            '...9174...'

        However in the gVisor Google App Engine runtime (e.g. Python 3.7),
        this will behave differently. The user ID will only be stored if it
        is manually set in the :class:`User` instance, either by the running
        application or by retrieving a stored :class:`User` that already has
        a user ID set.

    .. automethod:: _validate
    .. automethod:: _prepare_for_put

    Args:
        name (str): The name of the property.
        auto_current_user (bool): Deprecated flag. When supported, if this flag
            was set to :data:`True`, the property value would be set to the
            currently signed-in user whenever the model instance is stored in
            the datastore, overwriting the property's previous value.
            This was useful for tracking which user modifies a model instance.
        auto_current_user_add (bool): Deprecated flag. When supported, if this
            flag was set to :data:`True`, the property value would be set to
            the urrently signed-in user he first time the model instance is
            stored in the datastore, unless the property has already been
            assigned a value. This was useful for tracking which user creates
            a model instance, which may not be the same user that modifies it
            later.
        indexed (bool): Indicates if the value should be indexed.
        repeated (bool): Indicates if this property is repeated, i.e. contains
            multiple values.
        required (bool): Indicates if this property is required on the given
            model type.
        default (bytes): The default value for this property.
        choices (Iterable[bytes]): A container of allowed values for this
            property.
        validator (Callable[[~google.cloud.ndb.model.Property, Any], bool]): A
            validator to be used to check values.
        verbose_name (str): A longer, user-friendly name for this property.
        write_empty_list (bool): Indicates if an empty list should be written
            to the datastore.

    Raises:
        NotImplementedError: If ``auto_current_user`` is provided.
        NotImplementedError: If ``auto_current_user_add`` is provided.
    """

    _auto_current_user = False
    _auto_current_user_add = False

    def __init__(
        self,
        name=None,
        *,
        auto_current_user=None,
        auto_current_user_add=None,
        indexed=None,
        repeated=None,
        required=None,
        default=None,
        choices=None,
        validator=None,
        verbose_name=None,
        write_empty_list=None
    ):
        super(UserProperty, self).__init__(
            name=name,
            indexed=indexed,
            repeated=repeated,
            required=required,
            default=default,
            choices=choices,
            validator=validator,
            verbose_name=verbose_name,
            write_empty_list=write_empty_list,
        )
        if auto_current_user is not None:
            raise NotImplementedError(
                "The auto_current_user argument is no longer supported."
            )
        if auto_current_user_add is not None:
            raise NotImplementedError(
                "The auto_current_user_add argument is no longer supported."
            )

    def _validate(self, value):
        """Validate a ``value`` before setting it.

        Args:
            value (User): The value to check.

        Raises:
            .BadValueError: If ``value`` is not a :class:`User`.
        """
        if not isinstance(value, User):
            raise exceptions.BadValueError(
                "Expected User, got {!r}".format(value)
            )

    def _prepare_for_put(self, entity):
        """Pre-put hook

        This is a no-op. In previous versions of ``ndb``, this method
        populated the value based on ``auto_current_user`` or
        ``auto_current_user_add``, but these flags have been disabled.

        Args:
            entity (Model): An entity with values.
        """

    def _db_set_value(self, v, p, value):
        """Helper for :meth:`_serialize`.

        Raises:
            NotImplementedError: Always. This method is virtual.
        """
        raise NotImplementedError

    def _db_get_value(self, v, unused_p):
        """Helper for :meth:`_deserialize`.

        Raises:
            NotImplementedError: Always. This method is deprecated.
        """
        raise NotImplementedError(_NO_LONGER_IMPLEMENTED)


class KeyProperty(Property):
    """A property that contains :class:`.Key` values.

    The constructor for :class:`KeyProperty` allows at most two positional
    arguments. Any usage of :data:`None` as a positional argument will
    be ignored. Any of the following signatures are allowed:

    .. testsetup:: key-property-constructor

        from google.cloud import ndb


        class SimpleModel(ndb.Model):
            pass

    .. doctest:: key-property-constructor

        >>> name = "my_value"
        >>> ndb.KeyProperty(name)
        KeyProperty('my_value')
        >>> ndb.KeyProperty(SimpleModel)
        KeyProperty(kind='SimpleModel')
        >>> ndb.KeyProperty(name, SimpleModel)
        KeyProperty('my_value', kind='SimpleModel')
        >>> ndb.KeyProperty(SimpleModel, name)
        KeyProperty('my_value', kind='SimpleModel')

    The type of the positional arguments will be used to determine their
    purpose: a string argument is assumed to be the ``name`` and a
    :class:`type` argument is assumed to be the ``kind`` (and checked that
    the type is a subclass of :class:`Model`).

    .. automethod:: _validate

    Args:
        name (str): The name of the property.
        kind (Union[type, str]): The (optional) kind to be stored. If provided
            as a positional argument, this must be a subclass of :class:`Model`
            otherwise the kind name is sufficient.
        indexed (bool): Indicates if the value should be indexed.
        repeated (bool): Indicates if this property is repeated, i.e. contains
            multiple values.
        required (bool): Indicates if this property is required on the given
            model type.
        default (.Key): The default value for this property.
        choices (Iterable[.Key]): A container of allowed values for this
            property.
        validator (Callable[[~google.cloud.ndb.model.Property, .Key], bool]): A
            validator to be used to check values.
        verbose_name (str): A longer, user-friendly name for this property.
        write_empty_list (bool): Indicates if an empty list should be written
            to the datastore.
    """

    _kind = None

    def __init__(
        self,
        *args,
        name=None,
        kind=None,
        indexed=None,
        repeated=None,
        required=None,
        default=None,
        choices=None,
        validator=None,
        verbose_name=None,
        write_empty_list=None
    ):
        name, kind = self._handle_positional(args, name, kind)
        super(KeyProperty, self).__init__(
            name=name,
            indexed=indexed,
            repeated=repeated,
            required=required,
            default=default,
            choices=choices,
            validator=validator,
            verbose_name=verbose_name,
            write_empty_list=write_empty_list,
        )
        if kind is not None:
            self._kind = kind

    @staticmethod
    def _handle_positional(args, name, kind):
        """Handle positional arguments.

        In particular, assign them to the "correct" values and make sure
        they don't collide with the relevant keyword arguments.

        Args:
            args (tuple): The positional arguments provided to the
                constructor.
            name (Optional[str]): The name that was provided as a keyword
                argument to the constructor.
            kind (Optional[Union[type, str]]): The kind that was provided as a
                keyword argument to the constructor.

        Returns:
            Tuple[Optional[str], Optional[str]]: The ``name`` and ``kind``
            inferred from the arguments. Either may be :data:`None`.

        Raises:
            TypeError: If ``args`` has more than 2 elements.
            TypeError: If a valid ``name`` type (i.e. a string) is specified
                twice in ``args``.
            TypeError: If a valid ``kind`` type (i.e. a subclass of
                :class:`Model`) is specified twice in ``args``.
            TypeError: If an element in ``args`` is not a :class:`str` or a
                subclass of :class:`Model`.
            TypeError: If a ``name`` is specified both in ``args`` and via
                the ``name`` keyword.
            TypeError: If a ``kind`` is specified both in ``args`` and via
                the ``kind`` keyword.
            TypeError: If a ``kind`` was provided via ``keyword`` and is
                not a :class:`str` or a subclass of :class:`Model`.
        """
        # Limit positional arguments.
        if len(args) > 2:
            raise TypeError(
                "The KeyProperty constructor accepts at most two "
                "positional arguments."
            )

        # Filter out None
        args = [value for value in args if value is not None]

        # Determine the name / kind inferred from the positional arguments.
        name_via_positional = None
        kind_via_positional = None
        for value in args:
            if isinstance(value, str):
                if name_via_positional is None:
                    name_via_positional = value
                else:
                    raise TypeError("You can only specify one name")
            elif isinstance(value, type) and issubclass(value, Model):
                if kind_via_positional is None:
                    kind_via_positional = value
                else:
                    raise TypeError("You can only specify one kind")
            else:
                raise TypeError(
                    "Unexpected positional argument: {!r}".format(value)
                )

        # Reconcile the two possible ``name``` values.
        if name_via_positional is not None:
            if name is None:
                name = name_via_positional
            else:
                raise TypeError("You can only specify name once")

        # Reconcile the two possible ``kind``` values.
        if kind_via_positional is None:
            if isinstance(kind, type) and issubclass(kind, Model):
                kind = kind._get_kind()
        else:
            if kind is None:
                kind = kind_via_positional._get_kind()
            else:
                raise TypeError("You can only specify kind once")

        # Make sure the ``kind`` is a ``str``.
        if kind is not None and not isinstance(kind, str):
            raise TypeError("kind must be a Model class or a string")

        return name, kind

    def _constructor_info(self):
        """Helper for :meth:`__repr__`.

        Yields:
            Tuple[str, bool]: Pairs of argument name and a boolean indicating
            if that argument is a keyword.
        """
        yield "name", False
        yield "kind", True
        from_inspect = super(KeyProperty, self)._constructor_info()
        for name, is_keyword in from_inspect:
            if name in ("args", "name", "kind"):
                continue
            yield name, is_keyword

    def _validate(self, value):
        """Validate a ``value`` before setting it.

        Args:
            value (.Key): The value to check.

        Raises:
            .BadValueError: If ``value`` is not a :class:`.Key`.
            .BadValueError: If ``value`` is a partial :class:`.Key` (i.e. it
                has no name or ID set).
            .BadValueError: If the current property has an associated ``kind``
                and ``value`` does not match that kind.
        """
        if not isinstance(value, Key):
            raise exceptions.BadValueError(
                "Expected Key, got {!r}".format(value)
            )

        # Reject incomplete keys.
        if not value.id():
            raise exceptions.BadValueError(
                "Expected complete Key, got {!r}".format(value)
            )

        # Verify kind if provided.
        if self._kind is not None:
            if value.kind() != self._kind:
                raise exceptions.BadValueError(
                    "Expected Key with kind={!r}, got "
                    "{!r}".format(self._kind, value)
                )

    def _db_set_value(self, v, unused_p, value):
        """Helper for :meth:`_serialize`.

        Raises:
            NotImplementedError: Always. This method is virtual.
        """
        raise NotImplementedError

    def _db_get_value(self, v, unused_p):
        """Helper for :meth:`_deserialize`.

        Raises:
            NotImplementedError: Always. This method is deprecated.
        """
        raise NotImplementedError(_NO_LONGER_IMPLEMENTED)


class BlobKeyProperty(Property):
    """A property containing :class:`~google.cloud.ndb.model.BlobKey` values.

    .. automethod:: _validate
    """

    __slots__ = ()

    def _validate(self, value):
        """Validate a ``value`` before setting it.

        Args:
            value (~google.cloud.ndb.model.BlobKey): The value to check.

        Raises:
            .BadValueError: If ``value`` is not a
                :class:`~google.cloud.ndb.model.BlobKey`.
        """
        if not isinstance(value, BlobKey):
            raise exceptions.BadValueError(
                "Expected BlobKey, got {!r}".format(value)
            )

    def _db_set_value(self, v, p, value):
        """Helper for :meth:`_serialize`.

        Raises:
            NotImplementedError: Always. This method is virtual.
        """
        raise NotImplementedError

    def _db_get_value(self, v, unused_p):
        """Helper for :meth:`_deserialize`.

        Raises:
            NotImplementedError: Always. This method is deprecated.
        """
        raise NotImplementedError(_NO_LONGER_IMPLEMENTED)


class DateTimeProperty(Property):
    """A property that contains :class:`~datetime.datetime` values.

    This property expects "naive" datetime stamps, i.e. no timezone can
    be set. Furthermore, the assumption is that naive datetime stamps
    represent UTC.

    .. note::

        Unlike Django, ``auto_now_add`` can be overridden by setting the
        value before writing the entity. And unlike the legacy
        ``google.appengine.ext.db``, ``auto_now`` does not supply a default
        value. Also unlike legacy ``db``, when the entity is written, the
        property values are updated to match what was written. Finally, beware
        that this also updates the value in the in-process cache, **and** that
        ``auto_now_add`` may interact weirdly with transaction retries (a retry
        of a property with ``auto_now_add`` set will reuse the value that was
        set on the first try).

    .. automethod:: _validate
    .. automethod:: _prepare_for_put

    Args:
        name (str): The name of the property.
        auto_now (bool): Indicates that the property should be set to the
            current datetime when an entity is created and whenever it is
            updated.
        auto_now_add (bool): Indicates that the property should be set to the
            current datetime when an entity is created.
        indexed (bool): Indicates if the value should be indexed.
        repeated (bool): Indicates if this property is repeated, i.e. contains
            multiple values.
        required (bool): Indicates if this property is required on the given
            model type.
        default (~datetime.datetime): The default value for this property.
        choices (Iterable[~datetime.datetime]): A container of allowed values
            for this property.
        validator (Callable[[~google.cloud.ndb.model.Property, Any], bool]): A
            validator to be used to check values.
        verbose_name (str): A longer, user-friendly name for this property.
        write_empty_list (bool): Indicates if an empty list should be written
            to the datastore.

    Raises:
        ValueError: If ``repeated=True`` and ``auto_now=True``.
        ValueError: If ``repeated=True`` and ``auto_now_add=True``.
    """

    _auto_now = False
    _auto_now_add = False

    def __init__(
        self,
        name=None,
        *,
        auto_now=None,
        auto_now_add=None,
        indexed=None,
        repeated=None,
        required=None,
        default=None,
        choices=None,
        validator=None,
        verbose_name=None,
        write_empty_list=None
    ):
        super(DateTimeProperty, self).__init__(
            name=name,
            indexed=indexed,
            repeated=repeated,
            required=required,
            default=default,
            choices=choices,
            validator=validator,
            verbose_name=verbose_name,
            write_empty_list=write_empty_list,
        )
        if self._repeated:
            if auto_now:
                raise ValueError(
                    "DateTimeProperty {} could use auto_now and be "
                    "repeated, but there would be no point.".format(self._name)
                )
            elif auto_now_add:
                raise ValueError(
                    "DateTimeProperty {} could use auto_now_add and be "
                    "repeated, but there would be no point.".format(self._name)
                )
        if auto_now is not None:
            self._auto_now = auto_now
        if auto_now_add is not None:
            self._auto_now_add = auto_now_add

    def _validate(self, value):
        """Validate a ``value`` before setting it.

        Args:
            value (~datetime.datetime): The value to check.

        Raises:
            .BadValueError: If ``value`` is not a :class:`~datetime.datetime`.
        """
        if not isinstance(value, datetime.datetime):
            raise exceptions.BadValueError(
                "Expected datetime, got {!r}".format(value)
            )

    @staticmethod
    def _now():
        """datetime.datetime: Return current datetime.

        Subclasses will override this to return different forms of "now".
        """
        return datetime.datetime.utcnow()

    def _prepare_for_put(self, entity):
        """Sets the current timestamp when "auto" is set.

        If one of the following scenarios occur

        * ``auto_now=True``
        * ``auto_now_add=True`` and the ``entity`` doesn't have a value set

        then this hook will run before the ``entity`` is ``put()`` into
        the datastore.

        Args:
            entity (Model): An entity with values.
        """
        if self._auto_now or (
            self._auto_now_add and not self._has_value(entity)
        ):
            value = self._now()
            self._store_value(entity, value)

    def _db_set_value(self, v, p, value):
        """Helper for :meth:`_serialize`.

        Raises:
            NotImplementedError: Always. This method is virtual.
        """
        raise NotImplementedError

    def _db_get_value(self, v, unused_p):
        """Helper for :meth:`_deserialize`.

        Raises:
            NotImplementedError: Always. This method is deprecated.
        """
        raise NotImplementedError(_NO_LONGER_IMPLEMENTED)


class DateProperty(DateTimeProperty):
    """A property that contains :class:`~datetime.date` values.

    .. automethod:: _to_base_type
    .. automethod:: _from_base_type
    .. automethod:: _validate
    """

    __slots__ = ()

    def _validate(self, value):
        """Validate a ``value`` before setting it.

        Args:
            value (~datetime.date): The value to check.

        Raises:
            .BadValueError: If ``value`` is not a :class:`~datetime.date`.
        """
        if not isinstance(value, datetime.date):
            raise exceptions.BadValueError(
                "Expected date, got {!r}".format(value)
            )

    def _to_base_type(self, value):
        """Convert a value to the "base" value type for this property.

        Args:
            value (~datetime.date): The value to be converted.

        Returns:
            ~datetime.datetime: The converted value: a datetime object with the
            time set to ``00:00``.

        Raises:
            TypeError: If ``value`` is not a :class:`~datetime.date`.
        """
        if not isinstance(value, datetime.date):
            raise TypeError(
                "Cannot convert to datetime expected date value; "
                "received {}".format(value)
            )
        return datetime.datetime(value.year, value.month, value.day)

    def _from_base_type(self, value):
        """Convert a value from the "base" value type for this property.

        Args:
            value (~datetime.datetime): The value to be converted.

        Returns:
            ~datetime.date: The converted value: the date that ``value``
            occurs on.
        """
        return value.date()

    @staticmethod
    def _now():
        """datetime.datetime: Return current date."""
        return datetime.datetime.utcnow().date()


class TimeProperty(DateTimeProperty):
    """A property that contains :class:`~datetime.time` values.

    .. automethod:: _to_base_type
    .. automethod:: _from_base_type
    .. automethod:: _validate
    """

    __slots__ = ()

    def _validate(self, value):
        """Validate a ``value`` before setting it.

        Args:
            value (~datetime.time): The value to check.

        Raises:
            .BadValueError: If ``value`` is not a :class:`~datetime.time`.
        """
        if not isinstance(value, datetime.time):
            raise exceptions.BadValueError(
                "Expected time, got {!r}".format(value)
            )

    def _to_base_type(self, value):
        """Convert a value to the "base" value type for this property.

        Args:
            value (~datetime.time): The value to be converted.

        Returns:
            ~datetime.datetime: The converted value: a datetime object with the
            date set to ``1970-01-01``.

        Raises:
            TypeError: If ``value`` is not a :class:`~datetime.time`.
        """
        if not isinstance(value, datetime.time):
            raise TypeError(
                "Cannot convert to datetime expected time value; "
                "received {}".format(value)
            )
        return datetime.datetime(
            1970,
            1,
            1,
            value.hour,
            value.minute,
            value.second,
            value.microsecond,
        )

    def _from_base_type(self, value):
        """Convert a value from the "base" value type for this property.

        Args:
            value (~datetime.datetime): The value to be converted.

        Returns:
            ~datetime.time: The converted value: the time that ``value``
            occurs at.
        """
        return value.time()

    @staticmethod
    def _now():
        """datetime.datetime: Return current time."""
        return datetime.datetime.utcnow().time()


class StructuredProperty(Property):
    __slots__ = ()

    def __init__(self, *args, **kwargs):
        raise NotImplementedError


class LocalStructuredProperty(BlobProperty):
    __slots__ = ()

    def __init__(self, *args, **kwargs):
        raise NotImplementedError


class GenericProperty(Property):
    __slots__ = ()

    def __init__(self, *args, **kwargs):
        raise NotImplementedError


class ComputedProperty(GenericProperty):
    __slots__ = ()

    def __init__(self, *args, **kwargs):
        raise NotImplementedError


class MetaModel(type):
    """Metaclass for Model.

    This exists to fix up the properties -- they need to know their name. For
    example, defining a model:

    .. code-block:: python

        class Book(ndb.Model):
            pages = ndb.IntegerProperty()

    the ``Book.pages`` property doesn't have the name ``pages`` assigned.
    This is accomplished by calling the ``_fix_up_properties()`` method on the
    class itself.
    """

    def __init__(cls, name, bases, classdict):
        super(MetaModel, cls).__init__(name, bases, classdict)
        cls._fix_up_properties()

    def __repr__(cls):
        props = []
        for _, prop in sorted(cls._properties.items()):
            props.append("{}={!r}".format(prop._code_name, prop))
        return "{}<{}>".format(cls.__name__, ", ".join(props))


class Model(metaclass=MetaModel):
    """A class describing Cloud Datastore entities.

    Model instances are usually called entities. All model classes
    inheriting from :class:`Model` automatically have :class:`MetaModel` as
    their metaclass, so that the properties are fixed up properly after the
    class is defined.

    Because of this, you cannot use the same :class:`Property` object to
    describe multiple properties -- you must create separate :class:`Property`
    objects for each property. For example, this does not work:

    .. code-block:: python

        reuse_prop = ndb.StringProperty()

        class Wrong(ndb.Model):
            first = reuse_prop
            second = reuse_prop

    instead each class attribute needs to be distinct:

    .. code-block:: python

        class NotWrong(ndb.Model):
            first = ndb.StringProperty()
            second = ndb.StringProperty()

    The "kind" for a given :class:`Model` subclass is normally equal to the
    class name (exclusive of the module name or any other parent scope). To
    override the kind, define :meth:`_get_kind`, as follows:

    .. code-block:: python

        class MyModel(ndb.Model):
            @classmethod
            def _get_kind(cls):
                return "AnotherKind"

    A newly constructed entity will not be persisted to Cloud Datastore without
    an explicit call to :meth:`put`.

    User-defined properties can be passed to the constructor via keyword
    arguments:

    .. doctest:: model-keywords

        >>> class MyModel(ndb.Model):
        ...     value = ndb.FloatProperty()
        ...     description = ndb.StringProperty()
        ...
        >>> MyModel(value=7.34e22, description="Mass of the moon")
        MyModel(description='Mass of the moon', value=7.34e+22)

    In addition to user-defined properties, there are six accepted keyword
    arguments:

    * ``key``
    * ``id``
    * ``app``
    * ``namespace``
    * ``parent``
    * ``projection``

    Of these, ``key`` is a public attribute on :class:`Model` instances:

    .. testsetup:: model-key

        from google.cloud import ndb


        class MyModel(ndb.Model):
            value = ndb.FloatProperty()
            description = ndb.StringProperty()

    .. doctest:: model-key

        >>> entity1 = MyModel(id=11)
        >>> entity1.key
        Key('MyModel', 11)
        >>> entity2 = MyModel(parent=entity1.key)
        >>> entity2.key
        Key('MyModel', 11, 'MyModel', None)
        >>> entity3 = MyModel(key=ndb.Key(MyModel, "e-three"))
        >>> entity3.key
        Key('MyModel', 'e-three')

    However, a user-defined property can be defined on the model with the
    same name as one of those keyword arguments. In this case, the user-defined
    property "wins":

    .. doctest:: model-keyword-id-collision

        >>> class IDCollide(ndb.Model):
        ...     id = ndb.FloatProperty()
        ...
        >>> entity = IDCollide(id=17)
        >>> entity
        IDCollide(id=17.0)
        >>> entity.key is None
        True

    In such cases of argument "collision", an underscore can be used as a
    keyword argument prefix:

    .. doctest:: model-keyword-id-collision

        >>> entity = IDCollide(id=17, _id=2009)
        >>> entity
        IDCollide(key=Key('IDCollide', 2009), id=17.0)

    For the **very** special case of a property named ``key``, the ``key``
    attribute will no longer be the entity's key but instead will be the
    property value. Instead, the entity's key is accessible via ``_key``:

    .. doctest:: model-keyword-key-collision

        >>> class KeyCollide(ndb.Model):
        ...     key = ndb.StringProperty()
        ...
        >>> entity1 = KeyCollide(key="Take fork in road", id=987)
        >>> entity1
        KeyCollide(_key=Key('KeyCollide', 987), key='Take fork in road')
        >>> entity1.key
        'Take fork in road'
        >>> entity1._key
        Key('KeyCollide', 987)
        >>>
        >>> entity2 = KeyCollide(key="Go slow", _key=ndb.Key(KeyCollide, 1))
        >>> entity2
        KeyCollide(_key=Key('KeyCollide', 1), key='Go slow')

    The constructor accepts keyword arguments based on the properties
    defined on model subclass. However, using keywords for nonexistent
    or non-:class:`Property` class attributes will cause a failure:

    .. doctest:: model-keywords-fail

        >>> class Simple(ndb.Model):
        ...     marker = 1001
        ...     some_name = ndb.StringProperty()
        ...
        >>> Simple(some_name="Value set here.")
        Simple(some_name='Value set here.')
        >>> Simple(some_name="Value set here.", marker=29)
        Traceback (most recent call last):
          ...
        TypeError: Cannot set non-property marker
        >>> Simple(some_name="Value set here.", missing=29)
        Traceback (most recent call last):
          ...
        AttributeError: type object 'Simple' has no attribute 'missing'

    .. automethod:: _get_kind

    Args:
        key (Key): Datastore key for this entity (kind must match this model).
            If ``key`` is used, ``id`` and ``parent`` must be unset or
            :data:`None`.
        id (str): Key ID for this model. If ``id`` is used, ``key`` must be
            :data:`None`.
        parent (Key): The parent model or :data:`None` for a top-level model.
            If ``parent`` is used, ``key`` must be :data:`None`.
        namespace (str): Namespace for the entity key.
        app (str): Application ID for the entity key.
        kwargs (Dict[str, Any]): Additional keyword arguments. These should map
            to properties of this model.

    Raises:
        .BadArgumentError: If the constructor is called with ``key`` and one
            of ``id``, ``app``, ``namespace`` or ``parent`` specified.
    """

    # Class variables updated by _fix_up_properties()
    _properties = None
    _has_repeated = False
    _kind_map = {}  # Dict mapping {kind: Model subclass}

    # Defaults for instance variables.
    _entity_key = None
    _values = None
    _projection = ()  # Tuple of names of projected properties.

    # Hardcoded pseudo-property for the key.
    _key = ModelKey()
    key = _key
    """A special pseudo-property for key queries.

    For example:

    .. code-block:: python

        key = ndb.Key(MyModel, 808)
        query = MyModel.query(MyModel.key > key)

    will create a query for the reserved ``__key__`` property.
    """

    def __init__(_self, **kwargs):
        # NOTE: We use ``_self`` rather than ``self`` so users can define a
        #       property named 'self'.
        self = _self
        key = self._get_arg(kwargs, "key")
        id_ = self._get_arg(kwargs, "id")
        app = self._get_arg(kwargs, "app")
        namespace = self._get_arg(kwargs, "namespace")
        parent = self._get_arg(kwargs, "parent")
        projection = self._get_arg(kwargs, "projection")

        key_parts_unspecified = (
            id_ is None
            and parent is None
            and app is None
            and namespace is None
        )
        if key is not None:
            if not key_parts_unspecified:
                raise exceptions.BadArgumentError(
                    "Model constructor given ``key`` does not accept "
                    "``id``, ``app``, ``namespace``, or ``parent``."
                )
            self._key = _validate_key(key, entity=self)
        elif not key_parts_unspecified:
            self._key = Key(
                self._get_kind(),
                id_,
                parent=parent,
                app=app,
                namespace=namespace,
            )

        self._values = {}
        self._set_attributes(kwargs)
        # Set the projection last, otherwise it will prevent _set_attributes().
        if projection:
            self._set_projection(projection)

    @classmethod
    def _get_arg(cls, kwargs, keyword):
        """Parse keywords for fields that aren't user-defined properties.

        This is used to re-map special keyword arguments in the presence
        of name collision. For example if ``id`` is a property on the current
        :class:`Model`, then it may be desirable to pass ``_id`` (instead of
        ``id``) to the constructor.

        If the argument is found as ``_{keyword}`` or ``{keyword}``, it will
        be removed from ``kwargs``.

        Args:
            kwargs (Dict[str, Any]): A keyword arguments dictionary.
            keyword (str): A keyword to be converted.

        Returns:
            Optional[Any]: The ``keyword`` argument, if found.
        """
        alt_keyword = "_" + keyword
        if alt_keyword in kwargs:
            return kwargs.pop(alt_keyword)

        if keyword in kwargs:
            obj = getattr(cls, keyword, None)
            if not isinstance(obj, Property) or isinstance(obj, ModelKey):
                return kwargs.pop(keyword)

        return None

    def _set_attributes(self, kwargs):
        """Set attributes from keyword arguments.

        Args:
            kwargs (Dict[str, Any]): A keyword arguments dictionary.
        """
        cls = type(self)
        for name, value in kwargs.items():
            # NOTE: This raises an ``AttributeError`` for unknown properties
            #       and that is the intended behavior.
            prop = getattr(cls, name)
            if not isinstance(prop, Property):
                raise TypeError("Cannot set non-property {}".format(name))
            prop._set_value(self, value)

    def __repr__(self):
        """Return an unambiguous string representation of an entity."""
        by_args = []
        has_key_property = False
        for prop in self._properties.values():
            if prop._code_name == "key":
                has_key_property = True

            if not prop._has_value(self):
                continue

            value = prop._retrieve_value(self)
            if value is None:
                arg_repr = "None"
            elif prop._repeated:
                arg_reprs = [
                    prop._value_to_repr(sub_value) for sub_value in value
                ]
                arg_repr = "[{}]".format(", ".join(arg_reprs))
            else:
                arg_repr = prop._value_to_repr(value)

            by_args.append("{}={}".format(prop._code_name, arg_repr))

        by_args.sort()

        if self._key is not None:
            if has_key_property:
                entity_key_name = "_key"
            else:
                entity_key_name = "key"
            by_args.insert(0, "{}={!r}".format(entity_key_name, self._key))

        if self._projection:
            by_args.append("_projection={!r}".format(self._projection))

        return "{}({})".format(type(self).__name__, ", ".join(by_args))

    @classmethod
    def _get_kind(cls):
        """str: Return the kind name for this class.

        This defaults to ``cls.__name__``; users may override this to give a
        class a different name when stored in Google Cloud Datastore than the
        name of the class.
        """
        return cls.__name__

    def __hash__(self):
        """Not implemented hash function.

        Raises:
            TypeError: Always, to emphasize that entities are mutable.
        """
        raise TypeError("Model is mutable, so cannot be hashed.")

    def __eq__(self, other):
        """Compare two entities of the same class for equality."""
        if type(other) is not type(self):
            return NotImplemented

        if self._key != other._key:
            return False

        return self._equivalent(other)

    def _equivalent(self, other):
        """Compare two entities of the same class, excluding keys.

        Args:
            other (Model): An entity of the same class. It is assumed that
                the type and the key of ``other`` match the current entity's
                type and key (and the caller is responsible for checking).

        Returns:
            bool: Indicating if the current entity and ``other`` are
            equivalent.
        """
        if set(self._projection) != set(other._projection):
            return False

        prop_names = set(self._properties.keys())
        # Restrict properties to the projection if set.
        if self._projection:
            prop_names = set(self._projection)

        for name in prop_names:
            value = self._properties[name]._get_value(self)
            if value != other._properties[name]._get_value(other):
                return False

        return True

    def __lt__(self, value):
        """The ``<`` comparison is not well-defined."""
        raise TypeError("Model instances are not orderable.")

    def __le__(self, value):
        """The ``<=`` comparison is not well-defined."""
        raise TypeError("Model instances are not orderable.")

    def __gt__(self, value):
        """The ``>`` comparison is not well-defined."""
        raise TypeError("Model instances are not orderable.")

    def __ge__(self, value):
        """The ``>=`` comparison is not well-defined."""
        raise TypeError("Model instances are not orderable.")

    @classmethod
    def _lookup_model(cls, kind, default_model=None):
        """Get the model class for the given kind.

        Args:
            kind (str): The name of the kind to look up.
            default_model (Optional[type]): The model class to return if the
                kind can't be found.

        Returns:
            type: The model class for the requested kind or the default model.

        Raises:
            .KindError: If the kind was not found and no ``default_model`` was
                provided.
        """
        model_class = cls._kind_map.get(kind, default_model)
        if model_class is None:
            raise KindError(
                (
                    "No model class found for the kind '{}'. Did you forget to "
                    "import it?"
                ).format(kind)
            )
        return model_class

    def _set_projection(self, projection):
        """Set the projected properties for this instance.

        Args:
            projection (Union[list, tuple]): An iterable of strings
                representing the projection for the model instance.
        """
        self._projection = tuple(projection)

    @classmethod
    def _fix_up_properties(cls):
        """Fix up the properties by calling their ``_fix_up()`` method.

        .. note::

            This is called by :class:`MetaModel`, but may also be called
            manually after dynamically updating a model class.

        Raises:
            KindError: If the returned kind from ``_get_kind()`` is not a
                :class:`str`.
            TypeError: If a property on this model has a name beginning with
                an underscore.
        """
        kind = cls._get_kind()
        if not isinstance(kind, str):
            raise KindError(
                "Class {} defines a ``_get_kind()`` method that returns "
                "a non-string ({!r})".format(cls.__name__, kind)
            )

        cls._properties = {}

        # Skip the classes in ``ndb.model``.
        if cls.__module__ == __name__:
            return

        for name in dir(cls):
            attr = getattr(cls, name, None)
            if isinstance(attr, ModelAttribute) and not isinstance(
                attr, ModelKey
            ):
                if name.startswith("_"):
                    raise TypeError(
                        "ModelAttribute {} cannot begin with an underscore "
                        "character. ``_`` prefixed attributes are reserved "
                        "for temporary Model instance values.".format(name)
                    )
                attr._fix_up(cls, name)
                if isinstance(attr, Property):
                    if attr._repeated or (
                        isinstance(attr, StructuredProperty)
                        and attr._modelclass._has_repeated
                    ):
                        cls._has_repeated = True
                    cls._properties[attr._name] = attr

        cls._update_kind_map()

    @classmethod
    def _update_kind_map(cls):
        """Update the kind map to include this class."""
        cls._kind_map[cls._get_kind()] = cls

    @staticmethod
    def _validate_key(key):
        """Validation for ``_key`` attribute (designed to be overridden).

        Args:
            key (.Key): Proposed key to use for this entity.

        Returns:
            .Key: The validated ``key``.
        """
        return key

    def _put(self, **ctx_options):
        """Write this entity to Cloud Datastore.

        If the operation creates or completes a key, the entity's key
        attribute is set to the new, complete key.

        Raises:
            NotImplementedError: Always. This is virtual (for now).
        """
        raise NotImplementedError

    put = _put


class Expando(Model):
    __slots__ = ()

    def __init__(self, *args, **kwargs):
        raise NotImplementedError


def transaction(*args, **kwargs):
    raise NotImplementedError


def transaction_async(*args, **kwargs):
    raise NotImplementedError


def in_transaction(*args, **kwargs):
    raise NotImplementedError


def transactional(*args, **kwargs):
    raise NotImplementedError


def transactional_async(*args, **kwargs):
    raise NotImplementedError


def transactional_tasklet(*args, **kwargs):
    raise NotImplementedError


def non_transactional(*args, **kwargs):
    raise NotImplementedError


def get_multi_async(*args, **kwargs):
    raise NotImplementedError


def get_multi(*args, **kwargs):
    raise NotImplementedError


def put_multi_async(*args, **kwargs):
    raise NotImplementedError


def put_multi(*args, **kwargs):
    raise NotImplementedError


def delete_multi_async(*args, **kwargs):
    raise NotImplementedError


def delete_multi(*args, **kwargs):
    raise NotImplementedError


def get_indexes_async(*args, **kwargs):
    raise NotImplementedError


def get_indexes(*args, **kwargs):
    raise NotImplementedError
