Skip to content

Commit edac223

Browse files
committed
Fix #260: break import cycles in datastore w/o factories.
Alternative fix to the 'factories' indirection in #268. Note that this requires adding a messy arg to the 'Entity.from_protobuf' classmethod, in order to avoid the need to import '_helpers'. Another way to avoid this cycle would be to make 'Entity.from_protobuf' into a free function in another module (likely also moving 'Key.from_protobuf' for the sake of consistency).
1 parent 1e60e64 commit edac223

File tree

6 files changed

+26
-25
lines changed

6 files changed

+26
-25
lines changed

gcloud/datastore/_helpers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ def _get_value_from_value_pb(value_pb):
123123
result = value_pb.blob_value
124124

125125
elif value_pb.HasField('entity_value'):
126-
result = Entity.from_protobuf(value_pb.entity_value)
126+
result = Entity.from_protobuf(value_pb.entity_value,
127+
_get_value_from_property_pb)
127128

128129
elif value_pb.list_value:
129130
result = [_get_value_from_value_pb(x) for x in value_pb.list_value]

gcloud/datastore/dataset.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
"""Create / interact with gcloud datastore datasets."""
22

3+
from gcloud.datastore.entity import Entity
4+
from gcloud.datastore.query import Query
5+
from gcloud.datastore.transaction import Transaction
6+
from gcloud.datastore import _helpers
7+
38

49
class Dataset(object):
510
"""A dataset in the Cloud Datastore.
@@ -70,8 +75,6 @@ def query(self, *args, **kwargs):
7075
:rtype: :class:`gcloud.datastore.query.Query`
7176
:returns: a new Query instance, bound to this dataset.
7277
"""
73-
# This import is here to avoid circular references.
74-
from gcloud.datastore.query import Query
7578
kwargs['dataset'] = self
7679
return Query(*args, **kwargs)
7780

@@ -84,8 +87,6 @@ def entity(self, kind):
8487
:rtype: :class:`gcloud.datastore.entity.Entity`
8588
:returns: a new Entity instance, bound to this dataset.
8689
"""
87-
# This import is here to avoid circular references.
88-
from gcloud.datastore.entity import Entity
8990
return Entity(dataset=self, kind=kind)
9091

9192
def transaction(self, *args, **kwargs):
@@ -98,8 +99,6 @@ def transaction(self, *args, **kwargs):
9899
:rtype: :class:`gcloud.datastore.transaction.Transaction`
99100
:returns: a new Transaction instance, bound to this dataset.
100101
"""
101-
# This import is here to avoid circular references.
102-
from gcloud.datastore.transaction import Transaction
103102
kwargs['dataset'] = self
104103
return Transaction(*args, **kwargs)
105104

@@ -125,15 +124,11 @@ def get_entities(self, keys):
125124
:rtype: list of :class:`gcloud.datastore.entity.Entity`
126125
:return: The requested entities.
127126
"""
128-
# This import is here to avoid circular references.
129-
from gcloud.datastore.entity import Entity
130-
131127
entity_pbs = self.connection().lookup(
132128
dataset_id=self.id(),
133129
key_pbs=[k.to_protobuf() for k in keys]
134130
)
135131

136-
entities = []
137-
for entity_pb in entity_pbs:
138-
entities.append(Entity.from_protobuf(entity_pb, dataset=self))
139-
return entities
132+
return [Entity.from_protobuf(
133+
entity_pb, _helpers._get_value_from_property_pb,
134+
dataset=self) for entity_pb in entity_pbs]

gcloud/datastore/entity.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def from_key(cls, key, dataset=None):
147147
return cls(dataset).key(key)
148148

149149
@classmethod
150-
def from_protobuf(cls, pb, dataset=None):
150+
def from_protobuf(cls, pb, _get_value, dataset=None):
151151
"""Factory method for creating an entity based on a protobuf.
152152
153153
The protobuf should be one returned from the Cloud Datastore
@@ -156,18 +156,19 @@ def from_protobuf(cls, pb, dataset=None):
156156
:type pb: :class:`gcloud.datastore.datastore_v1_pb2.Entity`
157157
:param pb: The Protobuf representing the entity.
158158
159+
:type _get_value: module
160+
:param _get_value: normally, this will be
161+
:mod:`gcloud.datastore._helpers._get_value_from_property_pb`,
162+
but it could be a shim for testing.
163+
159164
:returns: The :class:`Entity` derived from the
160165
:class:`gcloud.datastore.datastore_v1_pb2.Entity`.
161166
"""
162-
163-
# This is here to avoid circular imports.
164-
from gcloud.datastore import _helpers
165-
166167
key = Key.from_protobuf(pb.key)
167168
entity = cls.from_key(key, dataset)
168169

169170
for property_pb in pb.property:
170-
value = _helpers._get_value_from_property_pb(property_pb)
171+
value = _get_value(property_pb)
171172
entity[property_pb.name] = value
172173

173174
return entity

gcloud/datastore/query.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,8 +343,9 @@ def fetch(self, limit=None):
343343
entity_pbs, end_cursor = query_results[:2]
344344

345345
self._cursor = end_cursor
346-
return [Entity.from_protobuf(entity, dataset=self.dataset())
347-
for entity in entity_pbs]
346+
return [Entity.from_protobuf(
347+
entity, _helpers._get_value_from_property_pb,
348+
dataset=self.dataset()) for entity in entity_pbs]
348349

349350
def cursor(self):
350351
"""Returns cursor ID

gcloud/datastore/test_entity.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def test_from_key_w_dataset(self):
7878

7979
def test_from_protobuf_wo_dataset(self):
8080
from gcloud.datastore import datastore_v1_pb2 as datastore_pb
81+
from gcloud.datastore._helpers import _get_value_from_property_pb
8182

8283
entity_pb = datastore_pb.Entity()
8384
entity_pb.key.partition_id.dataset_id = _DATASET_ID
@@ -87,7 +88,7 @@ def test_from_protobuf_wo_dataset(self):
8788
prop_pb.name = 'foo'
8889
prop_pb.value.string_value = 'Foo'
8990
klass = self._getTargetClass()
90-
entity = klass.from_protobuf(entity_pb)
91+
entity = klass.from_protobuf(entity_pb, _get_value_from_property_pb)
9192
self.assertTrue(entity.dataset() is None)
9293
self.assertEqual(entity.kind(), _KIND)
9394
self.assertEqual(entity['foo'], 'Foo')
@@ -99,6 +100,7 @@ def test_from_protobuf_wo_dataset(self):
99100
def test_from_protobuf_w_dataset(self):
100101
from gcloud.datastore import datastore_v1_pb2 as datastore_pb
101102
from gcloud.datastore.dataset import Dataset
103+
from gcloud.datastore._helpers import _get_value_from_property_pb
102104

103105
entity_pb = datastore_pb.Entity()
104106
entity_pb.key.partition_id.dataset_id = _DATASET_ID
@@ -109,7 +111,8 @@ def test_from_protobuf_w_dataset(self):
109111
prop_pb.value.string_value = 'Foo'
110112
dataset = Dataset(_DATASET_ID)
111113
klass = self._getTargetClass()
112-
entity = klass.from_protobuf(entity_pb, dataset)
114+
entity = klass.from_protobuf(
115+
entity_pb, _get_value_from_property_pb, dataset)
113116
self.assertTrue(entity.dataset() is dataset)
114117
self.assertEqual(entity.kind(), _KIND)
115118
self.assertEqual(entity['foo'], 'Foo')

pylintrc_default

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ ignore = datastore_v1_pb2.py
2424
[MESSAGES CONTROL]
2525
disable = I, protected-access, maybe-no-member, no-member,
2626
redefined-builtin, star-args, missing-format-attribute,
27-
similarities, cyclic-import, arguments-differ,
27+
similarities, arguments-differ,
2828

2929

3030

0 commit comments

Comments
 (0)