Skip to content

Commit fe6e4fb

Browse files
committed
Merge pull request #276 from tseaver/121-dont_copy_key_datasetid_to_pb
Fix #121: don't copy project ID to the Key protobuf's dataset_id.
2 parents c2a38fb + c769cc7 commit fe6e4fb

7 files changed

Lines changed: 159 additions & 250 deletions

File tree

gcloud/datastore/entity.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ class NoKey(RuntimeError):
2323
"""Exception raised by Entity methods which require a key."""
2424

2525

26+
class NoDataset(RuntimeError):
27+
"""Exception raised by Entity methods which require a dataset."""
28+
29+
2630
class Entity(dict):
2731
""":type dataset: :class:`gcloud.datastore.dataset.Dataset`
2832
:param dataset: The dataset in which this entity belongs.
@@ -68,8 +72,9 @@ class Entity(dict):
6872

6973
def __init__(self, dataset=None, kind=None):
7074
super(Entity, self).__init__()
71-
if dataset and kind:
72-
self._key = Key(dataset=dataset).kind(kind)
75+
self._dataset = dataset
76+
if kind:
77+
self._key = Key().kind(kind)
7378
else:
7479
self._key = None
7580

@@ -86,8 +91,7 @@ def dataset(self):
8691
be `None`. It also means that if you change the key on the entity,
8792
this will refer to that key's dataset.
8893
"""
89-
if self._key:
90-
return self._key.dataset()
94+
return self._dataset
9195

9296
def key(self, key=None):
9397
"""Get or set the :class:`.datastore.key.Key` on the current entity.
@@ -127,7 +131,7 @@ def kind(self):
127131
return self._key.kind()
128132

129133
@classmethod
130-
def from_key(cls, key):
134+
def from_key(cls, key, dataset=None):
131135
"""Create entity based on :class:`.datastore.key.Key`.
132136
133137
.. note::
@@ -140,7 +144,7 @@ def from_key(cls, key):
140144
:class:`gcloud.datastore.key.Key`.
141145
"""
142146

143-
return cls().key(key)
147+
return cls(dataset).key(key)
144148

145149
@classmethod
146150
def from_protobuf(cls, pb, dataset=None):
@@ -159,8 +163,8 @@ def from_protobuf(cls, pb, dataset=None):
159163
# This is here to avoid circular imports.
160164
from gcloud.datastore import _helpers
161165

162-
key = Key.from_protobuf(pb.key, dataset=dataset)
163-
entity = cls.from_key(key)
166+
key = Key.from_protobuf(pb.key)
167+
entity = cls.from_key(key, dataset)
164168

165169
for property_pb in pb.property:
166170
value = _helpers._get_value_from_property_pb(property_pb)
@@ -177,9 +181,21 @@ def _must_key(self):
177181
:raises: NoKey if key is None
178182
"""
179183
if self._key is None:
180-
raise NoKey('no key')
184+
raise NoKey()
181185
return self._key
182186

187+
@property
188+
def _must_dataset(self):
189+
"""Return our dataset, or raise NoDataset if not set.
190+
191+
:rtype: :class:`gcloud.datastore.key.Key`.
192+
:returns: our key
193+
:raises: NoDataset if key is None
194+
"""
195+
if self._dataset is None:
196+
raise NoDataset()
197+
return self._dataset
198+
183199
def reload(self):
184200
"""Reloads the contents of this entity from the datastore.
185201
@@ -193,7 +209,8 @@ def reload(self):
193209
exist only locally.
194210
"""
195211
key = self._must_key
196-
entity = key.dataset().get_entity(key.to_protobuf())
212+
dataset = self._must_dataset
213+
entity = dataset.get_entity(key.to_protobuf())
197214

198215
if entity:
199216
self.update(entity)
@@ -217,7 +234,7 @@ def save(self):
217234
:returns: The entity with a possibly updated Key.
218235
"""
219236
key = self._must_key
220-
dataset = key.dataset()
237+
dataset = self._must_dataset
221238
connection = dataset.connection()
222239
key_pb = connection.save_entity(
223240
dataset_id=dataset.id(),
@@ -246,7 +263,7 @@ def delete(self):
246263
entity will be deleted.
247264
"""
248265
key = self._must_key
249-
dataset = key.dataset()
266+
dataset = self._must_dataset
250267
dataset.connection().delete_entities(
251268
dataset_id=dataset.id(),
252269
key_pbs=[key.to_protobuf()],

gcloud/datastore/key.py

Lines changed: 14 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from itertools import izip
55

66
from gcloud.datastore import datastore_v1_pb2 as datastore_pb
7-
from gcloud.datastore.dataset import Dataset
87

98

109
class Key(object):
@@ -13,22 +12,23 @@ class Key(object):
1312
.. automethod:: __init__
1413
"""
1514

16-
def __init__(self, dataset=None, namespace=None, path=None):
15+
def __init__(self, path=None, namespace=None, dataset_id=None):
1716
"""Constructor / initializer for a key.
1817
19-
:type dataset: :class:`gcloud.datastore.dataset.Dataset`
20-
:param dataset: A dataset instance for the key.
21-
2218
:type namespace: :class:`str`
2319
:param namespace: A namespace identifier for the key.
2420
2521
:type path: sequence of dicts
2622
:param path: Each dict must have keys 'kind' (a string) and optionally
2723
'name' (a string) or 'id' (an integer).
24+
25+
:type dataset_id: string
26+
:param dataset: The dataset ID assigned by back-end for the key.
27+
Leave as None for newly-created keys.
2828
"""
29-
self._dataset = dataset
30-
self._namespace = namespace
3129
self._path = path or [{'kind': ''}]
30+
self._namespace = namespace
31+
self._dataset_id = dataset_id
3232

3333
def _clone(self):
3434
"""Duplicates the Key.
@@ -40,12 +40,10 @@ def _clone(self):
4040
:rtype: :class:`gcloud.datastore.key.Key`
4141
:returns: a new `Key` instance
4242
"""
43-
clone = copy.deepcopy(self)
44-
clone._dataset = self._dataset # Make a shallow copy of the Dataset.
45-
return clone
43+
return copy.deepcopy(self)
4644

4745
@classmethod
48-
def from_protobuf(cls, pb, dataset=None):
46+
def from_protobuf(cls, pb):
4947
"""Factory method for creating a key based on a protobuf.
5048
5149
The protobuf should be one returned from the Cloud Datastore
@@ -54,10 +52,6 @@ def from_protobuf(cls, pb, dataset=None):
5452
:type pb: :class:`gcloud.datastore.datastore_v1_pb2.Key`
5553
:param pb: The Protobuf representing the key.
5654
57-
:type dataset: :class:`gcloud.datastore.dataset.Dataset`
58-
:param dataset: A dataset instance. If not passed, defaults to an
59-
instance whose ID is derived from pb.
60-
6155
:rtype: :class:`gcloud.datastore.key.Key`
6256
:returns: a new `Key` instance
6357
"""
@@ -75,13 +69,10 @@ def from_protobuf(cls, pb, dataset=None):
7569

7670
path.append(element_dict)
7771

78-
if not dataset:
79-
dataset = Dataset(id=pb.partition_id.dataset_id)
80-
namespace = pb.partition_id.namespace
81-
else:
82-
namespace = None
72+
dataset_id = pb.partition_id.dataset_id or None
73+
namespace = pb.partition_id.namespace
8374

84-
return cls(dataset, namespace, path)
75+
return cls(path, namespace, dataset_id)
8576

8677
def to_protobuf(self):
8778
"""Return a protobuf corresponding to the key.
@@ -91,18 +82,8 @@ def to_protobuf(self):
9182
"""
9283
key = datastore_pb.Key()
9384

94-
# Technically a dataset is required to do anything with the key,
95-
# but we shouldn't throw a cryptic error if one isn't provided
96-
# in the initializer.
97-
if self.dataset():
98-
# Apparently 's~' is a prefix for High-Replication and is necessary
99-
# here. Another valid preflix is 'e~' indicating EU datacenters.
100-
dataset_id = self.dataset().id()
101-
if dataset_id:
102-
if dataset_id[:2] not in ['s~', 'e~']:
103-
dataset_id = 's~' + dataset_id
104-
105-
key.partition_id.dataset_id = dataset_id
85+
if self._dataset_id is not None:
86+
key.partition_id.dataset_id = self._dataset_id
10687

10788
if self._namespace:
10889
key.partition_id.namespace = self._namespace
@@ -161,24 +142,6 @@ def is_partial(self):
161142
"""
162143
return self.id_or_name() is None
163144

164-
def dataset(self, dataset=None):
165-
"""Dataset setter / getter.
166-
167-
:type dataset: :class:`gcloud.datastore.dataset.Dataset`
168-
:param dataset: A dataset instance for the key.
169-
170-
:rtype: :class:`Key` (for setter); or
171-
:class:`gcloud.datastore.dataset.Dataset` (for getter)
172-
:returns: a new key, cloned from self., with the given dataset
173-
(setter); or self's dataset (getter).
174-
"""
175-
if dataset:
176-
clone = self._clone()
177-
clone._dataset = dataset
178-
return clone
179-
else:
180-
return self._dataset
181-
182145
def namespace(self, namespace=None):
183146
"""Namespace setter / getter.
184147

gcloud/datastore/test__helpers.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,13 @@ def test_datetime_w_zone(self):
3232
self.assertEqual(value % 1000000, 4375)
3333

3434
def test_key(self):
35-
from gcloud.datastore.dataset import Dataset
3635
from gcloud.datastore.key import Key
3736

3837
_DATASET = 'DATASET'
3938
_KIND = 'KIND'
4039
_ID = 1234
4140
_PATH = [{'kind': _KIND, 'id': _ID}]
42-
key = Key(dataset=Dataset(_DATASET), path=_PATH)
41+
key = Key(dataset_id=_DATASET, path=_PATH)
4342
name, value = self._callFUT(key)
4443
self.assertEqual(name, 'key_value')
4544
self.assertEqual(value, key.to_protobuf())
@@ -131,15 +130,14 @@ def test_datetime(self):
131130

132131
def test_key(self):
133132
from gcloud.datastore.datastore_v1_pb2 import Value
134-
from gcloud.datastore.dataset import Dataset
135133
from gcloud.datastore.key import Key
136134

137135
_DATASET = 'DATASET'
138136
_KIND = 'KIND'
139137
_ID = 1234
140138
_PATH = [{'kind': _KIND, 'id': _ID}]
141139
pb = Value()
142-
expected = Key(dataset=Dataset(_DATASET), path=_PATH).to_protobuf()
140+
expected = Key(dataset_id=_DATASET, path=_PATH).to_protobuf()
143141
pb.key_value.CopyFrom(expected)
144142
found = self._callFUT(pb)
145143
self.assertEqual(found.to_protobuf(), expected)
@@ -236,15 +234,14 @@ def test_datetime(self):
236234
self.assertEqual(value % 1000000, 4375)
237235

238236
def test_key(self):
239-
from gcloud.datastore.dataset import Dataset
240237
from gcloud.datastore.key import Key
241238

242239
_DATASET = 'DATASET'
243240
_KIND = 'KIND'
244241
_ID = 1234
245242
_PATH = [{'kind': _KIND, 'id': _ID}]
246243
pb = self._makePB()
247-
key = Key(dataset=Dataset(_DATASET), path=_PATH)
244+
key = Key(dataset_id=_DATASET, path=_PATH)
248245
self._callFUT(pb, key)
249246
value = pb.key_value
250247
self.assertEqual(value, key.to_protobuf())

0 commit comments

Comments
 (0)