1# Copyright 2014 Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Class for representing a single entity in the Cloud Datastore."""
16
17
18from google.cloud._helpers import _ensure_tuple_or_list
19
20
21class Entity(dict):
22    """Entities are akin to rows in a relational database
23
24    An entity storing the actual instance of data.
25
26    Each entity is officially represented with a
27    :class:`~google.cloud.datastore.key.Key`, however it is possible that
28    you might create an entity with only a partial key (that is, a key
29    with a kind, and possibly a parent, but without an ID).  In such a
30    case, the datastore service will automatically assign an ID to the
31    partial key.
32
33    Entities in this API act like dictionaries with extras built in that
34    allow you to delete or persist the data stored on the entity.
35
36    Entities are mutable and act like a subclass of a dictionary.
37    This means you could take an existing entity and change the key
38    to duplicate the object.
39
40    Use :meth:`~google.cloud.datastore.client.Client.get` to retrieve an
41    existing entity:
42
43    .. testsetup:: entity-ctor
44
45        import uuid
46
47        from google.cloud import datastore
48        from google.cloud import datastore
49
50        unique = str(uuid.uuid4())[0:8]
51        client = datastore.Client(namespace='ns{}'.format(unique))
52
53        entity = datastore.Entity(client.key('EntityKind', 1234))
54        entity['property'] = 'value'
55        client.put(entity)
56
57    .. doctest:: entity-ctor
58
59        >>> key = client.key('EntityKind', 1234)
60        >>> client.get(key)
61        <Entity('EntityKind', 1234) {'property': 'value'}>
62
63    You can the set values on the entity just like you would on any
64    other dictionary.
65
66    .. doctest:: entity-ctor
67
68        >>> entity['age'] = 20
69        >>> entity['name'] = 'JJ'
70
71    .. testcleanup:: entity-ctor
72
73        client.delete(entity.key)
74
75    However, not all types are allowed as a value for a Google Cloud Datastore
76    entity. The following basic types are supported by the API:
77
78    * :class:`datetime.datetime`
79    * :class:`~google.cloud.datastore.key.Key`
80    * :class:`bool`
81    * :class:`float`
82    * :class:`int` (as well as :class:`long` in Python 2)
83    * ``unicode`` (called ``str`` in Python 3)
84    * ``bytes`` (called ``str`` in Python 2)
85    * :class:`~google.cloud.datastore.helpers.GeoPoint`
86    * :data:`None`
87
88    In addition, three container types are supported:
89
90    * :class:`list`
91    * :class:`~google.cloud.datastore.entity.Entity`
92    * :class:`dict` (will just be treated like an ``Entity`` without
93      a key or ``exclude_from_indexes``)
94
95    Each entry in a list must be one of the value types (basic or
96    container) and each value in an
97    :class:`~google.cloud.datastore.entity.Entity` must as well. In
98    this case an :class:`~google.cloud.datastore.entity.Entity` **as a
99    container** acts as a :class:`dict`, but also has the special annotations
100    of ``key`` and ``exclude_from_indexes``.
101
102    And you can treat an entity like a regular Python dictionary:
103
104    .. testsetup:: entity-dict
105
106        from google.cloud import datastore
107
108        entity = datastore.Entity()
109        entity['age'] = 20
110        entity['name'] = 'JJ'
111
112    .. doctest:: entity-dict
113
114        >>> sorted(entity.keys())
115        ['age', 'name']
116        >>> sorted(entity.items())
117        [('age', 20), ('name', 'JJ')]
118
119    .. note::
120
121        When saving an entity to the backend, values which are "text"
122        (``unicode`` in Python2, ``str`` in Python3) will be saved using
123        the 'text_value' field, after being encoded to UTF-8.  When
124        retrieved from the back-end, such values will be decoded to "text"
125        again.  Values which are "bytes" (``str`` in Python2, ``bytes`` in
126        Python3), will be saved using the 'blob_value' field, without
127        any decoding / encoding step.
128
129    :type key: :class:`google.cloud.datastore.key.Key`
130    :param key: Optional key to be set on entity.
131
132    :type exclude_from_indexes: tuple of string
133    :param exclude_from_indexes: Names of fields whose values are not to be
134                                 indexed for this entity.
135    """
136
137    def __init__(self, key=None, exclude_from_indexes=()):
138        super(Entity, self).__init__()
139        self.key = key
140        self.exclude_from_indexes = set(
141            _ensure_tuple_or_list("exclude_from_indexes", exclude_from_indexes)
142        )
143        """Names of fields which are *not* to be indexed for this entity."""
144        # NOTE: This will be populated when parsing a protobuf in
145        #       google.cloud.datastore.helpers.entity_from_protobuf.
146        self._meanings = {}
147
148    def __eq__(self, other):
149        """Compare two entities for equality.
150
151        Entities compare equal if their keys compare equal and their
152        properties compare equal.
153
154        :rtype: bool
155        :returns: True if the entities compare equal, else False.
156        """
157        if not isinstance(other, Entity):
158            return NotImplemented
159
160        return (
161            self.key == other.key
162            and self.exclude_from_indexes == other.exclude_from_indexes
163            and self._meanings == other._meanings
164            and super(Entity, self).__eq__(other)
165        )
166
167    def __ne__(self, other):
168        """Compare two entities for inequality.
169
170        Entities compare equal if their keys compare equal and their
171        properties compare equal.
172
173        :rtype: bool
174        :returns: False if the entities compare equal, else True.
175        """
176        return not self == other
177
178    @property
179    def kind(self):
180        """Get the kind of the current entity.
181
182        .. note::
183
184            This relies entirely on the :class:`google.cloud.datastore.key.Key`
185            set on the entity.  That means that we're not storing the kind
186            of the entity at all, just the properties and a pointer to a
187            Key which knows its Kind.
188        """
189        if self.key:
190            return self.key.kind
191
192    @property
193    def id(self):
194        """Get the ID of the current entity.
195
196        .. note::
197
198            This relies entirely on the :class:`google.cloud.datastore.key.Key`
199            set on the entity.  That means that we're not storing the ID
200            of the entity at all, just the properties and a pointer to a
201            Key which knows its ID.
202        """
203        if self.key is None:
204            return None
205        else:
206            return self.key.id
207
208    def __repr__(self):
209        if self.key:
210            return "<Entity%s %s>" % (
211                self.key._flat_path,
212                super(Entity, self).__repr__(),
213            )
214        else:
215            return "<Entity %s>" % (super(Entity, self).__repr__(),)
216