[merge] LazyFactory
Adeodato Simó
dato at net.com.org.es
Thu Aug 10 23:03:25 BST 2006
* John Arbash Meinel [Wed, 09 Aug 2006 16:57:40 -0500]:
> I think we have quite a few use cases for this sort of ability, and it
> is best if we create a real factory class, and build up our lazy work
> around it, rather than re-inventing the wheel each time.
As I said on my message in the [RFC] Optparse thread [1], I think it
would be good to generalize this LazyFactory idea into a registry-like
class, and use subclasses for specialized stuff, like lazy imports.
[1] https://lists.ubuntu.com/archives/bazaar-ng/2006q3/015765.html
The attached patch (with tests) against John's initial version does
that. Also attached for convenience is a diff for registry.py against
bzr.dev.
A couple comments:
- John, I took the liberty to rename "Factory" into "Registry". The
rationale is that it may not be a factory in some cases: eg., for my
commit_template branch I'll be registering objects, not eg. classes.
If you feel strong that is named "Factory", I guess it can be changed. ;-)
- The get() method is a bit overloaded, but I wanted to support all
possible use cases. Suggestions to improve it welcome.
- [Minor? implementation detail] I moved from storing the default
object in the dict under key None to store the default *key* (not
object) in a separate property(). I really think that the default
should be a key, not an object.
I'd be seeking +1 on this, unless John doesn't like what I did with his
code. If this goes into bzr.dev, I'll look into a OptionValuesRegistry
and OptionFromRegistry, and maybe other useful ones (eg. one that raises
BzrCommandError if the key is not found).
Mergeable URL:
http://people.debian.org/~adeodato/code/branches/bzr/registry_class/
Cheers,
--
Adeodato Simó dato at net.com.org.es
Debian Developer adeodato at debian.org
There is no man so good who, were he to submit all his thoughts to the
laws, would not deserve hanging ten times in his life.
-- Michel de Montaigne
-------------- next part --------------
=== renamed file 'bzrlib/lazy_factory.py' => 'bzrlib/registry.py'
--- bzrlib/lazy_factory.py 2006-08-09 21:37:43 +0000
+++ bzrlib/registry.py 2006-08-10 21:47:27 +0000
@@ -14,61 +14,95 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-"""LazyFactory lets you register objects to be loaded by request
-
-Basically, formats are registered by some sort of key. In the future
-that object can be requested, and the factory will import the appropriate
-module, and return the requested member object.
-"""
-
-
-class LazyFactory(object):
- """A factory which registers objects which will be load on request."""
+"""Classes to provide name-to-object registry-like support."""
+
+
+_marker = object()
+
+
+class Registry(object):
+ """A class that registers objects to a name."""
def __init__(self, first_is_default=False):
- """Create a new Lazy Factory.
+ """Create a new Registry.
- :param first_is_default: If True, then the first object to be registered
- will also be registered against the key None, and will be returned
- by default from .get()
+ :param first_is_default: If True, then the first key to be registered
+ will be set as the default key for get() to use.
"""
self._first_is_default = first_is_default
+ self._default_key = None
self._dict = {}
+ def register(self, key, object):
+ """Register a new object to a name.
+
+ :param key: This is the key to use to request the object later.
+ :param object: The object to register.
+ """
+ if self._first_is_default and not self._dict:
+ self._default_key = key
+ self._dict[key] = object
+
+ def get(self, key=_marker, default_key=_marker):
+ """Return the object register()'ed by the given key.
+
+ This may raise KeyError if the key is not present.
+
+ :param key: The key to obtain the object for; if not given, :param
+ default_key: will be used.
+ :param default_key: Key to use if an object for :param key: can't be
+ found; defaults to self.default_key. Set it to None if you'd like
+ to ensure an exception is raised for non-found keys.
+ :return: The previously registered object.
+ """
+ if default_key is _marker:
+ default_key = self.default_key
+
+ if key is _marker:
+ return self._dict[default_key]
+ else:
+ try:
+ return self._dict[key]
+ except KeyError:
+ return self._dict[default_key]
+
+ def keys(self):
+ """Get a list of registered entries"""
+ return sorted(self._dict.keys())
+
+ def _set_default_key(self, key):
+ if not self._dict.has_key(key):
+ raise KeyError('No object registered under key %s.' % key)
+ else:
+ self._default_key = key
+
+ def _get_default_key(self):
+ return self._default_key
+
+ default_key = property(_get_default_key, _set_default_key)
+ """Current value of the default key. Can be set to any existing key."""
+
+
+class LazyImportRegistry(Registry):
+ """A class to register modules/members to be loaded on request."""
+
def register(self, key, module_name, member_name):
"""Register a new object to be loaded on request.
- :param key: This is the key to use to request the object later.
- :param module_name: The python path to the module. Such as 'os.path'
+ :param module_name: The python path to the module. Such as 'os.path'.
:param member_name: The member of the module to return, if empty or None
get() will return the module itself.
- :return: if something used to be registered, its information will be
- returned.
"""
- old_info = self._dict.get(key)
- if self._first_is_default and not self._dict:
- self._dict[None] = (module_name, member_name)
- self._dict[key] = (module_name, member_name)
+ Registry.register(self, key, (module_name, member_name))
- def get(self, key=None):
+ def get(self, key=None, default_key=_marker):
"""Load the module and return the object specified by the given key.
- This may raise KeyError if the key is not present.
- May also raise ImportError if there are any problems
- Or AttributeError if the module does not have the supplied member
-
- :param key: The key registered by register()
- :return: The module/klass/function/object specified earlier
+ May raise ImportError if there are any problems, or AttributeError if
+ the module does not have the supplied member.
"""
- module_name, member_name = self._dict[key]
+ module_name, member_name = Registry.get(self, key, default_key)
module = __import__(module_name, globals(), locals(), [member_name])
if member_name:
return getattr(module, member_name)
return module
-
- def keys(self, include_none=False):
- """Get a list of registered entries"""
- keys = self._dict.keys()
- if not include_none and None in self._dict:
- keys.remove(None)
- return keys
=== renamed file 'bzrlib/tests/test_lazy_factory.py' => 'bzrlib/tests/test_registry.py'
--- bzrlib/tests/test_lazy_factory.py 2006-08-09 21:37:43 +0000
+++ bzrlib/tests/test_registry.py 2006-08-10 21:47:27 +0000
@@ -14,20 +14,68 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-"""Tests for the LazyFactory class"""
+"""Tests for the Registry classes"""
import os
import sys
from bzrlib import (
errors,
- lazy_factory,
+ registry,
osutils,
)
-from bzrlib.tests import TestCaseInTempDir
-
-
-class TestLazyFactory(TestCaseInTempDir):
+from bzrlib.tests import TestCase, TestCaseInTempDir
+
+
+class TestRegistry(TestCase):
+ def register_stuff(self, registry):
+ registry.register('one', 1)
+ registry.register('two', 2)
+ registry.register('four', 4)
+ registry.register('five', 5)
+
+ def test_registry(self):
+ registry_ = registry.Registry()
+ self.register_stuff(registry_)
+
+ self.failUnless(registry_.default_key is None)
+
+ # test get() (self.default_key == None)
+ self.assertRaises(KeyError, registry_.get)
+ self.assertEqual(2, registry_.get('two'))
+ self.assertRaises(KeyError, registry_.get, 'three')
+ self.assertEqual(4, registry_.get('three', 'four'))
+ self.assertRaises(KeyError, registry_.get, 'three', 'no-such-key')
+
+ # test _set_default_key
+ registry_.default_key = 'five'
+ self.failUnless(registry_.default_key == 'five')
+ self.assertEqual(5, registry_.get('six'))
+ self.assertRaises(KeyError, registry_._set_default_key, 'six')
+
+ # test keys()
+ self.assertEqual(['five', 'four', 'one', 'two'], registry_.keys())
+
+ def test_registry_with_first_is_default(self):
+ registry_ = registry.Registry(True)
+ self.register_stuff(registry_)
+
+ self.failUnless(registry_.default_key == 'one')
+
+ # test get() (self.default_key == 'one')
+ self.assertEqual(1, registry_.get())
+ self.assertEqual(2, registry_.get('two'))
+ self.assertEqual(1, registry_.get('three'))
+ self.assertEqual(4, registry_.get('three', 'four'))
+ self.assertRaises(KeyError, registry_.get, 'three', 'no-such-key')
+
+ # test _set_default_key
+ registry_.default_key = 'five'
+ self.failUnless(registry_.default_key == 'five')
+ self.assertEqual(5, registry_.get('six'))
+ self.assertRaises(KeyError, registry_._set_default_key, 'six')
+
+class TestLazyImportRegistry(TestCaseInTempDir):
def create_plugin_file(self, contents):
plugin_name = 'bzr_plugin_a_%s' % (osutils.rand_chars(4),)
@@ -47,9 +95,9 @@
'\n\n'
)
- def test_lazy_factory(self):
+ def test_lazy_import_registry(self):
plugin_name = self.create_simple_plugin()
- factory = lazy_factory.LazyFactory()
+ factory = registry.LazyImportRegistry()
factory.register('obj', plugin_name, 'object1')
factory.register('function', plugin_name, 'function')
factory.register('klass', plugin_name, 'MyClass')
=== modified file 'bzrlib/tests/__init__.py'
--- bzrlib/tests/__init__.py 2006-08-10 19:46:14 +0000
+++ bzrlib/tests/__init__.py 2006-08-10 21:47:27 +0000
@@ -1287,7 +1287,7 @@
'bzrlib.tests.test_ignores',
'bzrlib.tests.test_inv',
'bzrlib.tests.test_knit',
- 'bzrlib.tests.test_lazy_factory',
+ 'bzrlib.tests.test_registry',
'bzrlib.tests.test_lockdir',
'bzrlib.tests.test_lockable_files',
'bzrlib.tests.test_log',
-------------- next part --------------
=== added file 'bzrlib/registry.py'
--- bzrlib/registry.py 1970-01-01 00:00:00 +0000
+++ bzrlib/registry.py 2006-08-10 21:45:29 +0000
@@ -0,0 +1,108 @@
+# Copyright (C) 2006 by Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""Classes to provide name-to-object registry-like support."""
+
+
+_marker = object()
+
+
+class Registry(object):
+ """A class that registers objects to a name."""
+
+ def __init__(self, first_is_default=False):
+ """Create a new Registry.
+
+ :param first_is_default: If True, then the first key to be registered
+ will be set as the default key for get() to use.
+ """
+ self._first_is_default = first_is_default
+ self._default_key = None
+ self._dict = {}
+
+ def register(self, key, object):
+ """Register a new object to a name.
+
+ :param key: This is the key to use to request the object later.
+ :param object: The object to register.
+ """
+ if self._first_is_default and not self._dict:
+ self._default_key = key
+ self._dict[key] = object
+
+ def get(self, key=_marker, default_key=_marker):
+ """Return the object register()'ed by the given key.
+
+ This may raise KeyError if the key is not present.
+
+ :param key: The key to obtain the object for; if not given, :param
+ default_key: will be used.
+ :param default_key: Key to use if an object for :param key: can't be
+ found; defaults to self.default_key. Set it to None if you'd like
+ to ensure an exception is raised for non-found keys.
+ :return: The previously registered object.
+ """
+ if default_key is _marker:
+ default_key = self.default_key
+
+ if key is _marker:
+ return self._dict[default_key]
+ else:
+ try:
+ return self._dict[key]
+ except KeyError:
+ return self._dict[default_key]
+
+ def keys(self):
+ """Get a list of registered entries"""
+ return sorted(self._dict.keys())
+
+ def _set_default_key(self, key):
+ if not self._dict.has_key(key):
+ raise KeyError('No object registered under key %s.' % key)
+ else:
+ self._default_key = key
+
+ def _get_default_key(self):
+ return self._default_key
+
+ default_key = property(_get_default_key, _set_default_key)
+ """Current value of the default key. Can be set to any existing key."""
+
+
+class LazyImportRegistry(Registry):
+ """A class to register modules/members to be loaded on request."""
+
+ def register(self, key, module_name, member_name):
+ """Register a new object to be loaded on request.
+
+ :param module_name: The python path to the module. Such as 'os.path'.
+ :param member_name: The member of the module to return, if empty or None
+ get() will return the module itself.
+ """
+ Registry.register(self, key, (module_name, member_name))
+
+ def get(self, key=None, default_key=_marker):
+ """Load the module and return the object specified by the given key.
+
+ May raise ImportError if there are any problems, or AttributeError if
+ the module does not have the supplied member.
+ """
+ module_name, member_name = Registry.get(self, key, default_key)
+ module = __import__(module_name, globals(), locals(), [member_name])
+ if member_name:
+ return getattr(module, member_name)
+ return module
More information about the bazaar
mailing list