-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Expand file tree
/
Copy pathentity.py
More file actions
241 lines (185 loc) · 8.24 KB
/
entity.py
File metadata and controls
241 lines (185 loc) · 8.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
"""Class for representing a single entity in the Cloud Datastore.
Entities are akin to rows in a relational database,
storing the actual instance of data.
Each entity is officially represented with
a :class:`gcloud.datastore.key.Key` class,
however it is possible that you might create
an Entity with only a partial Key
(that is, a Key with a Kind,
and possibly a parent, but without an ID).
Entities in this API act like dictionaries
with extras built in that allow you to
delete or persist the data stored on the entity.
"""
from gcloud.datastore import datastore_v1_pb2 as datastore_pb
from gcloud.datastore.key import Key
class Entity(dict): # pylint: disable=too-many-public-methods
""":type dataset: :class:`gcloud.datastore.dataset.Dataset`
:param dataset: The dataset in which this entity belongs.
:type kind: string
:param kind: The kind of entity this is, akin to a table name in a
relational database.
Entities are mutable and act like a subclass of a dictionary.
This means you could take an existing entity and change the key
to duplicate the object.
This can be used on its own, however it is likely easier to use
the shortcut methods provided by :class:`gcloud.datastore.dataset.Dataset`
such as:
- :func:`gcloud.datastore.dataset.Dataset.entity` to create a new entity.
>>> dataset.entity('MyEntityKind')
<Entity[{'kind': 'MyEntityKind'}] {}>
- :func:`gcloud.datastore.dataset.Dataset.get_entity`
to retrieve an existing entity.
>>> dataset.get_entity(key)
<Entity[{'kind': 'EntityKind', id: 1234}] {'property': 'value'}>
You can the set values on the entity
just like you would on any other dictionary.
>>> entity['age'] = 20
>>> entity['name'] = 'JJ'
>>> entity
<Entity[{'kind': 'EntityKind', id: 1234}] {'age': 20, 'name': 'JJ'}>
And you can cast an entity to a regular Python dictionary
with the `dict` builtin:
>>> dict(entity)
{'age': 20, 'name': 'JJ'}
"""
def __init__(self, dataset=None, kind=None):
super(Entity, self).__init__()
if dataset and kind:
self._key = Key(dataset=dataset).kind(kind)
else:
self._key = None
def dataset(self):
"""Get the :class:`.dataset.Dataset` in which this entity belongs.
:rtype: :class:`gcloud.datastore.dataset.Dataset`
:returns: The Dataset containing the entity if there is a key,
else None.
.. note::
This is based on the :class:`gcloud.datastore.key.Key` set on the
entity. That means that if you have no key set, the dataset might
be `None`. It also means that if you change the key on the entity,
this will refer to that key's dataset.
"""
if self.key():
return self.key().dataset()
def key(self, key=None):
"""Get or set the :class:`.datastore.key.Key` on the current entity.
:type key: :class:`glcouddatastore.key.Key`
:param key: The key you want to set on the entity.
:returns: Either the current key or the :class:`Entity`.
>>> entity.key(my_other_key) # This returns the original entity.
<Entity[{'kind': 'OtherKeyKind', 'id': 1234}] {'property': 'value'}>
>>> entity.key() # This returns the key.
<Key[{'kind': 'OtherKeyKind', 'id': 1234}]>
"""
if key is not None:
self._key = key
return self
else:
return self._key
def kind(self):
"""Get the kind of the current entity.
.. note::
This relies entirely on
the :class:`gcloud.datastore.key.Key`
set on the entity.
That means that we're not storing the kind of the entity at all,
just the properties and a pointer to a Key
which knows its Kind.
"""
if self.key():
return self.key().kind()
@classmethod
def from_key(cls, key):
"""Create entity based on :class:`.datastore.key.Key`.
.. note::
This is a factory method.
:type key: :class:`gcloud.datastore.key.Key`
:param key: The key for the entity.
:returns: The :class:`Entity` derived from the
:class:`gcloud.datastore.key.Key`.
"""
return cls().key(key)
@classmethod
def from_protobuf(cls, pb, dataset=None): # pylint: disable=invalid-name
"""Factory method for creating an entity based on a protobuf.
The protobuf should be one returned from the Cloud Datastore
Protobuf API.
:type pb: :class:`gcloud.datastore.datastore_v1_pb2.Entity`
:param pb: The Protobuf representing the entity.
:returns: The :class:`Entity` derived from the
:class:`gcloud.datastore.datastore_v1_pb2.Entity`.
"""
# This is here to avoid circular imports.
from gcloud.datastore import _helpers
key = Key.from_protobuf(pb.key, dataset=dataset)
entity = cls.from_key(key)
for property_pb in pb.property:
value = _helpers._get_value_from_protobuf(property_pb)
entity[property_pb.name] = value
return entity
def reload(self):
"""Reloads the contents of this entity from the datastore.
This method takes the :class:`gcloud.datastore.key.Key`, loads all
properties from the Cloud Datastore, and sets the updated properties on
the current object.
.. warning::
This will override any existing properties if a different value
exists remotely, however it will *not* override any properties that
exist only locally.
"""
# Note that you must have a valid key, otherwise this makes no sense.
# pylint: disable=maybe-no-member
entity = self.dataset().get_entity(self.key().to_protobuf())
# pylint: enable=maybe-no-member
if entity:
self.update(entity)
return self
def save(self):
"""Save the entity in the Cloud Datastore.
:rtype: :class:`gcloud.datastore.entity.Entity`
:returns: The entity with a possibly updated Key.
"""
# pylint: disable=maybe-no-member
key_pb = self.dataset().connection().save_entity(
dataset_id=self.dataset().id(),
key_pb=self.key().to_protobuf(), properties=dict(self))
# pylint: enable=maybe-no-member
# If we are in a transaction and the current entity needs an
# automatically assigned ID, tell the transaction where to put that.
transaction = self.dataset().connection().transaction()
# pylint: disable=maybe-no-member
if transaction and self.key().is_partial():
transaction.add_auto_id_entity(self)
# pylint: enable=maybe-no-member
if isinstance(key_pb, datastore_pb.Key):
updated_key = Key.from_protobuf(key_pb)
# Update the path (which may have been altered).
# pylint: disable=maybe-no-member
key = self.key().path(updated_key.path())
# pylint: enable=maybe-no-member
self.key(key)
return self
def delete(self):
"""Delete the entity in the Cloud Datastore.
.. note::
This is based entirely off of the :class:`gcloud.datastore.key.Key`
set on the entity. Whatever is stored remotely using the key on the
entity will be deleted.
"""
# NOTE: pylint thinks key() is an Entity, hence key().to_protobuf()
# is not defined. This is because one branch of the return
# in the key() definition returns self.
# pylint: disable=maybe-no-member
self.dataset().connection().delete_entity(
dataset_id=self.dataset().id(), key_pb=self.key().to_protobuf())
# pylint: enable=maybe-no-member
def __repr__(self): # pragma NO COVER
# An entity should have a key all the time (even if it's partial).
if self.key():
# pylint: disable=maybe-no-member
return '<Entity%s %s>' % (self.key().path(),
super(Entity, self).__repr__())
# pylint: enable=maybe-no-member
else:
return '<Entity %s>' % (super(Entity, self).__repr__())