Skip to content

Commit 1305e81

Browse files
committed
Encapsulate factory registry as a singleton object.
Use its methods, rather than free functions.
1 parent 7e92deb commit 1305e81

File tree

8 files changed

+105
-114
lines changed

8 files changed

+105
-114
lines changed

gcloud/datastore/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@
3434
which represents a lookup or search over the rows in the datastore.
3535
"""
3636

37+
# import submodules which register factories.
38+
import gcloud.datastore.connection
39+
import gcloud.datastore.dataset
40+
import gcloud.datastore.entity
41+
import gcloud.datastore.key
42+
import gcloud.datastore.query
43+
import gcloud.datastore.transaction
44+
3745
__version__ = '0.1.2'
3846

3947
SCOPE = ('https://www.googleapis.com/auth/datastore ',

gcloud/datastore/_helpers.py

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,39 @@ class InvalidFactory(Exception):
2020
"""Unknown factory invocation."""
2121

2222

23-
_FACTORIES = {} # registry: name -> callable
23+
class _FactoryRegistry(object):
24+
"""Single registry for named factories.
2425
26+
This registry provides an API for modules to instantiate objects from
27+
other modules without needing to import them directly, allowing us
28+
to break circular import chains.
29+
"""
30+
31+
_MARKER = object()
32+
33+
def __init__(self):
34+
self._registered = {}
2535

26-
def _register_factory(name, factory):
27-
"""Register a factory by name."""
28-
if _FACTORIES.get(name) not in (None, factory):
29-
raise DuplicateFactory(name)
30-
_FACTORIES[name] = factory
36+
def register(self, name, factory):
37+
"""Register a factory by name."""
38+
if self._registered.get(name) not in (None, factory):
39+
raise DuplicateFactory(name)
40+
self._registered[name] = factory
3141

42+
def get(self, name, default=_MARKER):
43+
"""Look up a factory by name."""
44+
found = self._registered.get(name, default)
45+
if found is self._MARKER:
46+
raise InvalidFactory(name)
47+
return found
3248

33-
def _get_factory(name):
34-
"""Look up a factory by name."""
35-
factory = _FACTORIES.get(name)
36-
if factory is None:
37-
raise InvalidFactory(name)
38-
return factory
49+
def invoke(self, name, *args, **kw):
50+
"""Look up and call a factory by name."""
51+
return self.get(name)(*args, **kw)
3952

4053

41-
def _invoke_factory(name, *args, **kw):
42-
"""Look up and call a factory by name."""
43-
return _get_factory(name)(*args, **kw)
54+
_FACTORIES = _FactoryRegistry() # singleton
55+
del _FactoryRegistry
4456

4557

4658
def _get_protobuf_attribute_and_value(val):
@@ -136,7 +148,7 @@ def _get_value_from_value_pb(value_pb):
136148
result = naive.replace(tzinfo=pytz.utc)
137149

138150
elif value_pb.HasField('key_value'):
139-
result = _FACTORIES['Key'].from_protobuf(value_pb.key_value)
151+
result = _FACTORIES.get('Key_pb')(value_pb.key_value)
140152

141153
elif value_pb.HasField('boolean_value'):
142154
result = value_pb.boolean_value
@@ -154,7 +166,7 @@ def _get_value_from_value_pb(value_pb):
154166
result = value_pb.blob_value
155167

156168
elif value_pb.HasField('entity_value'):
157-
result = _FACTORIES['Entity'].from_protobuf(value_pb.entity_value)
169+
result = _FACTORIES.get('Entity_pb')(value_pb.entity_value)
158170

159171
elif value_pb.list_value:
160172
result = [_get_value_from_value_pb(x) for x in value_pb.list_value]

gcloud/datastore/dataset.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def transaction(self, *args, **kwargs):
7373
:returns: a new Transaction instance, bound to this dataset.
7474
"""
7575
kwargs['dataset'] = self
76-
return _helpers._invoke_factory('Transaction', *args, **kwargs)
76+
return _helpers._FACTORIES.invoke('Transaction', *args, **kwargs)
7777

7878
def get_entity(self, key):
7979
"""Retrieves entity from the dataset, along with its attributes.
@@ -104,9 +104,9 @@ def get_entities(self, keys):
104104

105105
entities = []
106106
for entity_pb in entity_pbs:
107-
entities.append(_helpers._invoke_factory('Entity_pb',
108-
entity_pb, dataset=self))
107+
entities.append(_helpers._FACTORIES.invoke(
108+
'Entity_pb', entity_pb, dataset=self))
109109
return entities
110110

111111

112-
_helpers._register_factory('Dataset', Dataset)
112+
_helpers._FACTORIES.register('Dataset', Dataset)

gcloud/datastore/entity.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ class Entity(dict):
6464
def __init__(self, dataset=None, kind=None):
6565
super(Entity, self).__init__()
6666
if dataset and kind:
67-
self._key = _helpers._invoke_factory('Key',
68-
dataset=dataset).kind(kind)
67+
self._key = _helpers._FACTORIES.invoke(
68+
'Key', dataset=dataset).kind(kind)
6969
else:
7070
self._key = None
7171

@@ -151,7 +151,7 @@ def from_protobuf(cls, pb, dataset=None):
151151
:returns: The :class:`Entity` derived from the
152152
:class:`gcloud.datastore.datastore_v1_pb2.Entity`.
153153
"""
154-
key = _helpers._invoke_factory('Key_pb', pb.key, dataset=dataset)
154+
key = _helpers._FACTORIES.invoke('Key_pb', pb.key, dataset=dataset)
155155
entity = cls.from_key(key)
156156

157157
for property_pb in pb.property:
@@ -223,7 +223,7 @@ def save(self):
223223
transaction.add_auto_id_entity(self)
224224

225225
if isinstance(key_pb, datastore_pb.Key):
226-
updated_key = _helpers._invoke_factory('Key_pb', key_pb, dataset)
226+
updated_key = _helpers._FACTORIES.invoke('Key_pb', key_pb, dataset)
227227
# Update the path (which may have been altered).
228228
self._key = key.path(updated_key.path())
229229

@@ -252,5 +252,5 @@ def __repr__(self):
252252
return '<Entity %s>' % (super(Entity, self).__repr__())
253253

254254

255-
_helpers._register_factory('Entity', Entity)
256-
_helpers._register_factory('Entity_pb', Entity.from_protobuf)
255+
_helpers._FACTORIES.register('Entity', Entity)
256+
_helpers._FACTORIES.register('Entity_pb', Entity.from_protobuf)

gcloud/datastore/key.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ def from_protobuf(cls, pb, dataset=None):
7676
path.append(element_dict)
7777

7878
if not dataset:
79-
dataset = _helpers._invoke_factory('Dataset',
80-
id=pb.partition_id.dataset_id)
79+
dataset = _helpers._FACTORIES.invoke('Dataset',
80+
id=pb.partition_id.dataset_id)
8181
namespace = pb.partition_id.namespace
8282
else:
8383
namespace = None
@@ -291,6 +291,6 @@ def __repr__(self):
291291
return '<Key%s>' % self.path()
292292

293293

294-
_helpers._register_factory('Key', Key)
295-
_helpers._register_factory('Key_pb', Key.from_protobuf)
296-
_helpers._register_factory('Key_path', Key.from_path)
294+
_helpers._FACTORIES.register('Key', Key)
295+
_helpers._FACTORIES.register('Key_pb', Key.from_protobuf)
296+
_helpers._FACTORIES.register('Key_path', Key.from_path)

gcloud/datastore/query.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -189,10 +189,10 @@ def ancestor(self, ancestor):
189189

190190
# If a list was provided, turn it into a Key.
191191
if isinstance(ancestor, list):
192-
ancestor = _helpers._invoke_factory('Key_path', *ancestor)
192+
ancestor = _helpers._FACTORIES.invoke('Key_path', *ancestor)
193193

194194
# If we don't have a Key value by now, something is wrong.
195-
if not isinstance(ancestor, _helpers._get_factory('Key')):
195+
if not isinstance(ancestor, _helpers._FACTORIES.get('Key')):
196196
raise TypeError('Expected list or Key, got %s.' % type(ancestor))
197197

198198
# Get the composite filter and add a new property filter.
@@ -322,8 +322,8 @@ def fetch(self, limit=None):
322322
entity_pbs, end_cursor = query_results[:2]
323323

324324
self._cursor = end_cursor
325-
return [_helpers._invoke_factory('Entity_pb', entity,
326-
dataset=self.dataset())
325+
return [_helpers._FACTORIES.invoke('Entity_pb', entity,
326+
dataset=self.dataset())
327327
for entity in entity_pbs]
328328

329329
def cursor(self):
@@ -393,4 +393,4 @@ def order(self, *properties):
393393
return clone
394394

395395

396-
_helpers._register_factory('Query', Query)
396+
_helpers._FACTORIES.register('Query', Query)

gcloud/datastore/test__helpers.py

Lines changed: 46 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,40 @@
11
import unittest2
22

33

4-
class _FactoryBase(object):
4+
class Test_FactoryRegistry(unittest2.TestCase):
55

66
def setUp(self):
7-
from gcloud.datastore._helpers import _FACTORIES
8-
9-
self._before = _FACTORIES.copy()
7+
self._widget = object()
8+
self._called_with = []
109

11-
def tearDown(self):
10+
def _makeOne(self):
1211
from gcloud.datastore._helpers import _FACTORIES
12+
return type(_FACTORIES)()
1313

14-
_FACTORIES.clear()
15-
_FACTORIES.update(self._before)
16-
17-
18-
class Test__register_factory(_FactoryBase, unittest2.TestCase):
19-
20-
def _callFUT(self, name, factory):
21-
from gcloud.datastore._helpers import _register_factory
22-
23-
return _register_factory(name, factory)
14+
def _factory(self, *args, **kw): # pragma: NO COVER
15+
self._called_with.append((args, kw))
16+
return self._widget
2417

25-
def test_it(self):
26-
from gcloud.datastore._helpers import _FACTORIES
18+
def test_register(self):
2719

2820
class _Foo(object):
2921
pass
3022

31-
self._callFUT('Foo', _Foo)
32-
self.assertTrue(_FACTORIES['Foo'] is _Foo)
23+
factories = self._makeOne()
24+
factories.register('Foo', _Foo)
25+
self.assertTrue(factories.get('Foo') is _Foo)
3326

34-
def test_duplicate_exact(self):
35-
from gcloud.datastore._helpers import _FACTORIES
27+
def test_register_duplicate_exact(self):
3628

3729
class _Foo(object):
3830
pass
3931

40-
self._callFUT('Foo', _Foo)
41-
self._callFUT('Foo', _Foo)
42-
self.assertTrue(_FACTORIES['Foo'] is _Foo)
32+
factories = self._makeOne()
33+
factories.register('Foo', _Foo)
34+
factories.register('Foo', _Foo)
35+
self.assertTrue(factories.get('Foo') is _Foo)
4336

44-
def test_duplicate_conflict(self):
37+
def test_register_duplicate_conflict(self):
4538
from gcloud.datastore._helpers import DuplicateFactory
4639

4740
class _Foo(object):
@@ -50,70 +43,48 @@ class _Foo(object):
5043
class _Bar(object):
5144
pass
5245

53-
self._callFUT('Foo', _Foo)
54-
self.assertRaises(DuplicateFactory, self._callFUT, 'Foo', _Bar)
55-
56-
57-
class Test__get_factory(_FactoryBase, unittest2.TestCase):
58-
59-
def setUp(self):
60-
from gcloud.datastore._helpers import _FACTORIES
61-
62-
super(Test__get_factory, self).setUp()
63-
_FACTORIES['Widget'] = self._factory
64-
65-
def _callFUT(self, name, *args, **kw):
66-
from gcloud.datastore._helpers import _get_factory
67-
68-
return _get_factory(name, *args, **kw)
69-
70-
@staticmethod
71-
def _factory(*args, **kw): # pragma: NO COVER
72-
pass
46+
factories = self._makeOne()
47+
factories.register('Foo', _Foo)
48+
self.assertRaises(DuplicateFactory, factories.register, 'Foo', _Bar)
7349

74-
def test_miss(self):
50+
def test_get_miss(self):
7551
from gcloud.datastore._helpers import InvalidFactory
7652

77-
self.assertRaises(InvalidFactory, self._callFUT, 'Nonesuch')
53+
factories = self._makeOne()
54+
self.assertRaises(InvalidFactory, factories.get, 'Nonesuch')
7855

79-
def test_hit(self):
80-
self.assertTrue(self._callFUT('Widget') is self._factory)
56+
def test_get_hit(self):
57+
factories = self._makeOne()
8158

59+
# Use a bare function to avoid method wrappers for testing.
60+
def _bare_factory(): # pragma: NO COVER
61+
pass
8262

83-
class Test__invoke_factory(_FactoryBase, unittest2.TestCase):
84-
85-
def setUp(self):
86-
from gcloud.datastore._helpers import _FACTORIES
87-
88-
super(Test__invoke_factory, self).setUp()
89-
self._called_with = []
90-
self._widget = object()
91-
_FACTORIES['Widget'] = self._factory
92-
93-
def _callFUT(self, name, *args, **kw):
94-
from gcloud.datastore._helpers import _invoke_factory
95-
96-
return _invoke_factory(name, *args, **kw)
97-
98-
def _factory(self, *args, **kw):
99-
self._called_with.append((args, kw))
100-
return self._widget
63+
factories.register('Widget', _bare_factory)
64+
self.assertTrue(factories.get('Widget') is _bare_factory)
10165

102-
def test_missing_registration(self):
66+
def test_invoke_miss(self):
10367
from gcloud.datastore._helpers import InvalidFactory
10468

105-
self.assertRaises(InvalidFactory, self._callFUT, 'Nonesuch')
69+
factories = self._makeOne()
70+
self.assertRaises(InvalidFactory, factories.invoke, 'Nonesuch')
10671

107-
def test_wo_args_or_kw(self):
108-
self.assertTrue(self._callFUT('Widget') is self._widget)
72+
def test_invoke_wo_args_or_kw(self):
73+
factories = self._makeOne()
74+
factories.register('Widget', self._factory)
75+
self.assertTrue(factories.invoke('Widget') is self._widget)
10976
self.assertEqual(self._called_with, [((), {})])
11077

111-
def test_w_args(self):
112-
self.assertTrue(self._callFUT('Widget', 'foo', 42) is self._widget)
78+
def test_invoke_w_args(self):
79+
factories = self._makeOne()
80+
factories.register('Widget', self._factory)
81+
self.assertTrue(factories.invoke('Widget', 'foo', 42) is self._widget)
11382
self.assertEqual(self._called_with, [(('foo', 42), {})])
11483

115-
def test_w_kw(self):
116-
self.assertTrue(self._callFUT('Widget', foo=42) is self._widget)
84+
def test_invoke_w_kw(self):
85+
factories = self._makeOne()
86+
factories.register('Widget', self._factory)
87+
self.assertTrue(factories.invoke('Widget', foo=42) is self._widget)
11788
self.assertEqual(self._called_with, [((), {'foo': 42})])
11889

11990

gcloud/datastore/transaction.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ def commit(self):
237237
key_pb = result.insert_auto_id_key[i]
238238
# We can ignore the 'dataset' parameter ecause we are only
239239
# creating a transient instance in order to copy its path.
240-
key = _helpers._invoke_factory('Key_pb', key_pb)
240+
key = _helpers._FACTORIES.invoke('Key_pb', key_pb)
241241
entity.key(entity.key().path(key.path()))
242242

243243
# Tell the connection that the transaction is over.
@@ -257,4 +257,4 @@ def __exit__(self, exc_type, exc_val, exc_tb):
257257
self.rollback()
258258

259259

260-
_helpers._register_factory('Transaction', Transaction)
260+
_helpers._FACTORIES.register('Transaction', Transaction)

0 commit comments

Comments
 (0)