Skip to content

Commit 13f8e45

Browse files
committed
Making all attributes on Key read-only and sorts out use of setters.
Addresses second part of #451. In some cases, since no valid replacements for setters are implemented here, the functionality is replaced with hacks using protected methods. Also added a getter for Key._dataset_id and made the Key.path property return a copy since a `list` of `dict`s is very much mutable.
1 parent fe7e7bf commit 13f8e45

File tree

13 files changed

+194
-274
lines changed

13 files changed

+194
-274
lines changed

gcloud/datastore/dataset.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ def allocate_ids(self, incomplete_key, num_ids):
186186
:returns: The (complete) keys allocated with `incomplete_key` as root.
187187
:raises: `ValueError` if `incomplete_key` is not a partial key.
188188
"""
189-
if not incomplete_key.is_partial():
189+
if not incomplete_key.is_partial:
190190
raise ValueError(('Key is not partial.', incomplete_key))
191191

192192
incomplete_key_pb = incomplete_key.to_protobuf()
@@ -196,5 +196,17 @@ def allocate_ids(self, incomplete_key, num_ids):
196196
self.id(), incomplete_key_pbs)
197197
allocated_ids = [allocated_key_pb.path_element[-1].id
198198
for allocated_key_pb in allocated_key_pbs]
199-
return [incomplete_key.id(allocated_id)
199+
200+
# This method is temporary and will move over to Key in
201+
# part 5 of #451.
202+
def create_new_key(new_id):
203+
"""Temporary method to complete `incomplete_key`.
204+
205+
Uses `incomplete_key` from outside scope.
206+
"""
207+
clone = incomplete_key._clone()
208+
clone._path[-1]['id'] = new_id
209+
return clone
210+
211+
return [create_new_key(allocated_id)
200212
for allocated_id in allocated_ids]

gcloud/datastore/entity.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def __init__(self, dataset=None, kind=None, exclude_from_indexes=()):
100100
# _implicit_environ._DatastoreBase to avoid split MRO.
101101
self._dataset = dataset or _implicit_environ.DATASET
102102
if kind:
103-
self._key = Key().kind(kind)
103+
self._key = Key(path=[{'kind': kind}])
104104
else:
105105
self._key = None
106106
self._exclude_from_indexes = set(exclude_from_indexes)
@@ -153,7 +153,7 @@ def kind(self):
153153
"""
154154

155155
if self._key:
156-
return self._key.kind()
156+
return self._key.kind
157157

158158
def exclude_from_indexes(self):
159159
"""Names of fields which are *not* to be indexed for this entity.
@@ -250,7 +250,7 @@ def save(self):
250250
# If we are in a transaction and the current entity needs an
251251
# automatically assigned ID, tell the transaction where to put that.
252252
transaction = connection.transaction()
253-
if transaction and key.is_partial():
253+
if transaction and key.is_partial:
254254
transaction.add_auto_id_entity(self)
255255

256256
if isinstance(key_pb, datastore_pb.Key):
@@ -266,7 +266,10 @@ def save(self):
266266
for descriptor, value in element._fields.items():
267267
key_part[descriptor.name] = value
268268
path.append(key_part)
269-
self._key = key.path(path)
269+
# This is temporary. Will be addressed throughout #451.
270+
clone = key._clone()
271+
clone._path = path
272+
self._key = clone
270273

271274
return self
272275

@@ -287,7 +290,7 @@ def delete(self):
287290

288291
def __repr__(self):
289292
if self._key:
290-
return '<Entity%s %s>' % (self._key.path(),
293+
return '<Entity%s %s>' % (self._key.path,
291294
super(Entity, self).__repr__())
292295
else:
293296
return '<Entity %s>' % (super(Entity, self).__repr__())

gcloud/datastore/key.py

Lines changed: 67 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,13 @@ def to_protobuf(self):
7070
"""
7171
key = datastore_pb.Key()
7272

73-
if self._dataset_id is not None:
74-
key.partition_id.dataset_id = self._dataset_id
73+
if self.dataset_id is not None:
74+
key.partition_id.dataset_id = self.dataset_id
7575

7676
if self._namespace:
7777
key.partition_id.namespace = self._namespace
7878

79-
for item in self.path():
79+
for item in self.path:
8080
element = key.path_element.add()
8181
if 'kind' in item:
8282
element.kind = item['kind']
@@ -87,121 +87,100 @@ def to_protobuf(self):
8787

8888
return key
8989

90+
@property
9091
def is_partial(self):
91-
"""Boolean test: is the key fully mapped onto a backend entity?
92+
"""Boolean indicating if the key has an ID (or name).
9293
9394
:rtype: :class:`bool`
9495
:returns: True if the last element of the key's path does not have
9596
an 'id' or a 'name'.
9697
"""
97-
return self.id_or_name() is None
98+
return self.id_or_name is None
9899

99-
def namespace(self, namespace=None):
100-
"""Namespace setter / getter.
100+
@property
101+
def namespace(self):
102+
"""Namespace getter.
101103
102-
:type namespace: :class:`str`
103-
:param namespace: A namespace identifier for the key.
104-
105-
:rtype: :class:`Key` (for setter); or :class:`str` (for getter)
106-
:returns: a new key, cloned from self., with the given namespace
107-
(setter); or self's namespace (getter).
104+
:rtype: :class:`str`
105+
:returns: The namespace of the current key.
108106
"""
109-
if namespace:
110-
clone = self._clone()
111-
clone._namespace = namespace
112-
return clone
113-
else:
114-
return self._namespace
107+
return self._namespace
115108

116-
def path(self, path=None):
117-
"""Path setter / getter.
109+
@property
110+
def path(self):
111+
"""Path getter.
118112
119-
:type path: sequence of dicts
120-
:param path: Each dict must have keys 'kind' (a string) and optionally
121-
'name' (a string) or 'id' (an integer).
113+
Returns a copy so that the key remains immutable.
122114
123-
:rtype: :class:`Key` (for setter); or :class:`str` (for getter)
124-
:returns: a new key, cloned from self., with the given path (setter);
125-
or self's path (getter).
115+
:rtype: :class:`str`
116+
:returns: The (key) path of the current key.
126117
"""
127-
if path:
128-
clone = self._clone()
129-
clone._path = path
130-
return clone
131-
else:
132-
return self._path
133-
134-
def kind(self, kind=None):
135-
"""Kind setter / getter. Based on the last element of path.
136-
137-
:type kind: :class:`str`
138-
:param kind: The new kind for the key.
139-
140-
:rtype: :class:`Key` (for setter); or :class:`str` (for getter)
141-
:returns: a new key, cloned from self., with the given kind (setter);
142-
or self's kind (getter).
118+
return copy.deepcopy(self._path)
119+
120+
@property
121+
def kind(self):
122+
"""Kind getter. Based on the last element of path.
123+
124+
:rtype: :class:`str`
125+
:returns: The kind of the current key.
143126
"""
144-
if kind:
145-
clone = self._clone()
146-
clone._path[-1]['kind'] = kind
147-
return clone
148-
elif self.path():
149-
return self._path[-1]['kind']
150-
151-
def id(self, id_to_set=None):
152-
"""ID setter / getter. Based on the last element of path.
153-
154-
:type id_to_set: :class:`int`
155-
:param id_to_set: The new ID for the key.
156-
157-
:rtype: :class:`Key` (for setter); or :class:`int` (for getter)
158-
:returns: a new key, cloned from self., with the given id (setter);
159-
or self's id (getter).
127+
if self.path:
128+
return self.path[-1].get('kind')
129+
130+
@property
131+
def id(self):
132+
"""ID getter. Based on the last element of path.
133+
134+
:rtype: :class:`int`
135+
:returns: The (integer) ID of the key.
160136
"""
161-
if id_to_set:
162-
clone = self._clone()
163-
clone._path[-1]['id'] = id_to_set
164-
return clone
165-
elif self.path():
166-
return self._path[-1].get('id')
167-
168-
def name(self, name=None):
169-
"""Name setter / getter. Based on the last element of path.
170-
171-
:type kind: :class:`str`
172-
:param kind: The new name for the key.
173-
174-
:rtype: :class:`Key` (for setter); or :class:`str` (for getter)
175-
:returns: a new key, cloned from self., with the given name (setter);
176-
or self's name (getter).
137+
if self.path:
138+
return self.path[-1].get('id')
139+
140+
@property
141+
def name(self):
142+
"""Name getter. Based on the last element of path.
143+
144+
:rtype: :class:`str`
145+
:returns: The (string) name of the key.
177146
"""
178-
if name:
179-
clone = self._clone()
180-
clone._path[-1]['name'] = name
181-
return clone
182-
elif self.path():
183-
return self._path[-1].get('name')
147+
if self.path:
148+
return self.path[-1].get('name')
184149

150+
@property
185151
def id_or_name(self):
186-
"""Getter. Based on the last element of path.
152+
"""Getter. Based on the last element of path.
187153
188-
:rtype: :class:`int` (if 'id' is set); or :class:`str` (the 'name')
189-
:returns: True if the last element of the key's path has either an 'id'
154+
:rtype: :class:`int` (if 'id') or :class:`str` (if 'name')
155+
:returns: The last element of the key's path if it is either an 'id'
190156
or a 'name'.
191157
"""
192-
return self.id() or self.name()
158+
return self.id or self.name
159+
160+
@property
161+
def dataset_id(self):
162+
"""Dataset ID getter.
163+
164+
:rtype: :class:`str`
165+
:returns: The key's dataset.
166+
"""
167+
return self._dataset_id
193168

169+
@property
194170
def parent(self):
195171
"""Getter: return a new key for the next highest element in path.
196172
197173
:rtype: :class:`gcloud.datastore.key.Key`
198174
:returns: a new `Key` instance, whose path consists of all but the last
199175
element of self's path. If self has only one path element,
200-
return None.
176+
returns None.
201177
"""
202178
if len(self._path) <= 1:
203179
return None
204-
return self.path(self.path()[:-1])
180+
# This is temporary. Will be addressed throughout #451.
181+
clone = self._clone()
182+
clone._path = self.path[:-1]
183+
return clone
205184

206185
def __repr__(self):
207-
return '<Key%s>' % self.path()
186+
return '<Key%s>' % self.path

gcloud/datastore/test___init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,4 @@ def test_allocate_ids(self):
173173
result = gcloud.datastore.allocate_ids(INCOMPLETE_KEY, NUM_IDS)
174174

175175
# Check the IDs returned.
176-
self.assertEqual([key.id() for key in result], range(1, NUM_IDS + 1))
176+
self.assertEqual([key.id for key in result], range(1, NUM_IDS + 1))

gcloud/datastore/test_dataset.py

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ def test_get_entity_hit(self):
104104
key = Key(path=PATH)
105105
result = dataset.get_entity(key)
106106
key = result.key()
107-
self.assertEqual(key._dataset_id, DATASET_ID)
108-
self.assertEqual(key.path(), PATH)
107+
self.assertEqual(key.dataset_id, DATASET_ID)
108+
self.assertEqual(key.path, PATH)
109109
self.assertEqual(list(result), ['foo'])
110110
self.assertEqual(result['foo'], 'Foo')
111111

@@ -175,37 +175,28 @@ def test_get_entities_hit(self):
175175
key = Key(path=PATH)
176176
result, = dataset.get_entities([key])
177177
key = result.key()
178-
self.assertEqual(key._dataset_id, DATASET_ID)
179-
self.assertEqual(key.path(), PATH)
178+
self.assertEqual(key.dataset_id, DATASET_ID)
179+
self.assertEqual(key.path, PATH)
180180
self.assertEqual(list(result), ['foo'])
181181
self.assertEqual(result['foo'], 'Foo')
182182

183183
def test_allocate_ids(self):
184-
from gcloud.datastore.test_entity import _Key
185-
186-
INCOMPLETE_KEY = _Key()
187-
PROTO_ID = object()
188-
INCOMPLETE_KEY._key = _KeyProto(PROTO_ID)
189-
INCOMPLETE_KEY._partial = True
184+
from gcloud.datastore.key import Key
190185

186+
INCOMPLETE_KEY = Key(path=[{'kind': 'foo'}])
191187
CONNECTION = _Connection()
192188
NUM_IDS = 2
193189
DATASET_ID = 'foo'
194190
DATASET = self._makeOne(DATASET_ID, connection=CONNECTION)
195191
result = DATASET.allocate_ids(INCOMPLETE_KEY, NUM_IDS)
196192

197193
# Check the IDs returned match.
198-
self.assertEqual([key._id for key in result], range(NUM_IDS))
194+
self.assertEqual([key.id for key in result], range(NUM_IDS))
199195

200196
# Check connection is called correctly.
201197
self.assertEqual(CONNECTION._called_dataset_id, DATASET_ID)
202198
self.assertEqual(len(CONNECTION._called_key_pbs), NUM_IDS)
203199

204-
# Check the IDs passed to Connection.allocate_ids.
205-
key_paths = [key_pb.path_element[-1].id
206-
for key_pb in CONNECTION._called_key_pbs]
207-
self.assertEqual(key_paths, [PROTO_ID] * NUM_IDS)
208-
209200
def test_allocate_ids_with_complete(self):
210201
from gcloud.datastore.test_entity import _Key
211202

0 commit comments

Comments
 (0)