-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
Copy pathentity.py
215 lines (161 loc) · 6.78 KB
/
entity.py
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
"""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 datetime import datetime
from gcloud.datastore import datastore_v1_pb2 as datastore_pb
from gcloud.datastore.key import Key
class Entity(dict):
"""
: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 retrive 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):
if dataset and kind:
self._key = Key(dataset=dataset).kind(kind)
else:
self._key = None
def dataset(self):
"""Get the :class:`gcloud.datastore.dataset.Dataset` in which this entity belonds.
.. 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:`gcloud.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:
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):
"""Factory method for creating an entity based on the :class:`gcloud.datastore.key.Key`.
: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):
"""Factory method for creating an entity based on a protobuf.
The protobuf should be one returned from the Cloud Datastore Protobuf API.
:type key: :class:`gcloud.datastore.datastore_v1_pb2.Entity`
:param key: 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.
entity = self.dataset().get_entities(self.key().to_protobuf())
# TODO(jjg): Raise an error if something dumb happens.
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.
"""
key_pb = self.dataset().connection().save_entity(
dataset_id=self.dataset().id(), key_pb=self.key().to_protobuf(),
properties=dict(self))
# 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()
if transaction and self.key().is_partial():
transaction.add_auto_id_entity(self)
if isinstance(key_pb, datastore_pb.Key):
updated_key = Key.from_protobuf(key_pb)
# Update the path (which may have been altered).
key = self.key().path(updated_key.path())
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.
"""
self.dataset().connection().delete_entity(
dataset_id=self.dataset().id(), key_pb=self.key().to_protobuf())
def __repr__(self):
# TODO: Make sure that this makes sense.
# An entity should have a key all the time (even if it's partial).
if self.key():
return '<Entity%s %s>' % (self.key().path(), super(Entity, self).__repr__())
else:
return '<Entity %s>' % (super(Entity, self).__repr__())