Rev 5500: Merge lp:~spiv/bzr/hooks-refactoring with tweaks in http://bazaar.launchpad.net/~vila/bzr/integration/
Vincent Ladeuil
v.ladeuil+lp at free.fr
Fri Oct 15 12:25:40 BST 2010
At http://bazaar.launchpad.net/~vila/bzr/integration/
------------------------------------------------------------
revno: 5500 [merge]
revision-id: v.ladeuil+lp at free.fr-20101015112540-vhgyone6ou1g0foo
parent: pqm at pqm.ubuntu.com-20101015101453-ran88oqq3a5qb7jw
parent: v.ladeuil+lp at free.fr-20101015112045-5133zwua3wu4k4xk
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: trunk
timestamp: Fri 2010-10-15 13:25:40 +0200
message:
Merge lp:~spiv/bzr/hooks-refactoring with tweaks
added:
bzrlib/pyutils.py pyutils.py-20100921013316-xm9ajurjifgh7zmd-1
bzrlib/tests/test_pyutils.py test_pyutils.py-20100921032001-66iy9gigf95a2ze8-1
modified:
bzrlib/bundle/serializer/__init__.py __init__.py-20051118175413-86b97db0b618feef
bzrlib/bzrdir.py bzrdir.py-20060131065624-156dfea39c4387cb
bzrlib/export/__init__.py __init__.py-20051114235828-1ba62cb4062304e6
bzrlib/hooks.py hooks.py-20070325015548-ix4np2q0kd8452au-1
bzrlib/registry.py lazy_factory.py-20060809213415-2gfvqadtvdn0phtg-1
bzrlib/repository.py rev_storage.py-20051111201905-119e9401e46257e3
bzrlib/tests/TestUtil.py TestUtil.py-20050824080200-5f70140a2d938694
bzrlib/tests/__init__.py selftest.py-20050531073622-8d0e3c8845c97a64
bzrlib/tests/per_transport.py test_transport_implementations.py-20051227111451-f97c5c7d5c49fce7
bzrlib/tests/test_hooks.py test_hooks.py-20070628030849-89rtsbe5dmer5npz-1
bzrlib/tests/test_registry.py test_lazy_factory.py-20060809213415-2gfvqadtvdn0phtg-2
bzrlib/tests/test_selftest.py test_selftest.py-20051202044319-c110a115d8c0456a
doc/developers/code-style.txt codestyle.txt-20100515105711-133ealf7ereiq2eq-1
doc/en/release-notes/bzr-2.3.txt NEWS-20050323055033-4e00b5db738777ff
-------------- next part --------------
=== modified file 'bzrlib/bundle/serializer/__init__.py'
--- a/bzrlib/bundle/serializer/__init__.py 2010-07-15 12:53:00 +0000
+++ b/bzrlib/bundle/serializer/__init__.py 2010-09-21 03:20:09 +0000
@@ -21,7 +21,10 @@
from StringIO import StringIO
import re
-import bzrlib.errors as errors
+from bzrlib import (
+ errors,
+ pyutils,
+ )
from bzrlib.diff import internal_diff
from bzrlib.revision import NULL_REVISION
# For backwards-compatibility
@@ -191,8 +194,7 @@
:param overwrite: Should this version override a default
"""
def _loader(version):
- mod = __import__(module, globals(), locals(), [classname])
- klass = getattr(mod, classname)
+ klass = pyutils.get_named_object(module, classname)
return klass(version)
register(version, _loader, overwrite=overwrite)
=== modified file 'bzrlib/bzrdir.py'
--- a/bzrlib/bzrdir.py 2010-10-12 09:46:37 +0000
+++ b/bzrlib/bzrdir.py 2010-10-15 11:20:45 +0000
@@ -45,6 +45,7 @@
lockable_files,
lockdir,
osutils,
+ pyutils,
remote,
repository,
revision as _mod_revision,
@@ -3092,15 +3093,12 @@
def _load(full_name):
mod_name, factory_name = full_name.rsplit('.', 1)
try:
- mod = __import__(mod_name, globals(), locals(),
- [factory_name])
+ factory = pyutils.get_named_object(mod_name, factory_name)
except ImportError, e:
raise ImportError('failed to load %s: %s' % (full_name, e))
- try:
- factory = getattr(mod, factory_name)
except AttributeError:
raise AttributeError('no factory %s in module %r'
- % (full_name, mod))
+ % (full_name, sys.modules[mod_name]))
return factory()
def helper():
=== modified file 'bzrlib/export/__init__.py'
--- a/bzrlib/export/__init__.py 2010-03-25 09:39:03 +0000
+++ b/bzrlib/export/__init__.py 2010-09-21 03:20:09 +0000
@@ -20,7 +20,10 @@
"""
import os
-import bzrlib.errors as errors
+from bzrlib import (
+ errors,
+ pyutils,
+ )
# Maps format name => export function
_exporters = {}
@@ -55,8 +58,7 @@
When requesting a specific type of export, load the respective path.
"""
def _loader(tree, dest, root, subdir, filtered, per_file_timestamps):
- mod = __import__(module, globals(), locals(), [funcname])
- func = getattr(mod, funcname)
+ func = pyutils.get_named_object(module, funcname)
return func(tree, dest, root, subdir, filtered=filtered,
per_file_timestamps=per_file_timestamps)
register_exporter(scheme, extensions, _loader)
=== modified file 'bzrlib/hooks.py'
--- a/bzrlib/hooks.py 2010-08-28 06:13:48 +0000
+++ b/bzrlib/hooks.py 2010-09-28 07:34:29 +0000
@@ -16,7 +16,11 @@
"""Support for plugin hooking logic."""
-from bzrlib import registry
+from bzrlib import (
+ pyutils,
+ registry,
+ symbol_versioning,
+ )
from bzrlib.lazy_import import lazy_import
lazy_import(globals(), """
import textwrap
@@ -29,39 +33,63 @@
""")
-known_hooks = registry.Registry()
-# known_hooks registry contains
-# tuple of (module, member name) which is the hook point
-# module where the specific hooks are defined
-# callable to get the empty specific Hooks for that attribute
-known_hooks.register_lazy(('bzrlib.branch', 'Branch.hooks'), 'bzrlib.branch',
- 'BranchHooks')
-known_hooks.register_lazy(('bzrlib.bzrdir', 'BzrDir.hooks'), 'bzrlib.bzrdir',
- 'BzrDirHooks')
-known_hooks.register_lazy(('bzrlib.commands', 'Command.hooks'),
- 'bzrlib.commands', 'CommandHooks')
-known_hooks.register_lazy(('bzrlib.info', 'hooks'),
- 'bzrlib.info', 'InfoHooks')
-known_hooks.register_lazy(('bzrlib.lock', 'Lock.hooks'), 'bzrlib.lock',
- 'LockHooks')
-known_hooks.register_lazy(('bzrlib.merge', 'Merger.hooks'), 'bzrlib.merge',
- 'MergeHooks')
-known_hooks.register_lazy(('bzrlib.msgeditor', 'hooks'), 'bzrlib.msgeditor',
- 'MessageEditorHooks')
-known_hooks.register_lazy(('bzrlib.mutabletree', 'MutableTree.hooks'),
- 'bzrlib.mutabletree', 'MutableTreeHooks')
-known_hooks.register_lazy(('bzrlib.smart.client', '_SmartClient.hooks'),
- 'bzrlib.smart.client', 'SmartClientHooks')
-known_hooks.register_lazy(('bzrlib.smart.server', 'SmartTCPServer.hooks'),
- 'bzrlib.smart.server', 'SmartServerHooks')
-known_hooks.register_lazy(('bzrlib.status', 'hooks'),
- 'bzrlib.status', 'StatusHooks')
-known_hooks.register_lazy(
- ('bzrlib.version_info_formats.format_rio', 'RioVersionInfoBuilder.hooks'),
- 'bzrlib.version_info_formats.format_rio', 'RioVersionInfoBuilderHooks')
-known_hooks.register_lazy(
- ('bzrlib.merge_directive', 'BaseMergeDirective.hooks'),
- 'bzrlib.merge_directive', 'MergeDirectiveHooks')
+class KnownHooksRegistry(registry.Registry):
+ # known_hooks registry contains
+ # tuple of (module, member name) which is the hook point
+ # module where the specific hooks are defined
+ # callable to get the empty specific Hooks for that attribute
+
+ def register_lazy_hook(self, hook_module_name, hook_member_name,
+ hook_factory_member_name):
+ self.register_lazy((hook_module_name, hook_member_name),
+ hook_module_name, hook_factory_member_name)
+
+ def iter_parent_objects(self):
+ """Yield (hook_key, (parent_object, attr)) tuples for every registered
+ hook, where 'parent_object' is the object that holds the hook
+ instance.
+
+ This is useful for resetting/restoring all the hooks to a known state,
+ as is done in bzrlib.tests.TestCase._clear_hooks.
+ """
+ for key in self.keys():
+ yield key, self.key_to_parent_and_attribute(key)
+
+ def key_to_parent_and_attribute(self, (module_name, member_name)):
+ """Convert a known_hooks key to a (parent_obj, attr) pair.
+
+ :param key: A tuple (module_name, member_name) as found in the keys of
+ the known_hooks registry.
+ :return: The parent_object of the hook and the name of the attribute on
+ that parent object where the hook is kept.
+ """
+ parent_mod, parent_member, attr = pyutils.calc_parent_name(module_name,
+ member_name)
+ return pyutils.get_named_object(parent_mod, parent_member), attr
+
+
+_builtin_known_hooks = (
+ ('bzrlib.branch', 'Branch.hooks', 'BranchHooks'),
+ ('bzrlib.bzrdir', 'BzrDir.hooks', 'BzrDirHooks'),
+ ('bzrlib.commands', 'Command.hooks', 'CommandHooks'),
+ ('bzrlib.info', 'hooks', 'InfoHooks'),
+ ('bzrlib.lock', 'Lock.hooks', 'LockHooks'),
+ ('bzrlib.merge', 'Merger.hooks', 'MergeHooks'),
+ ('bzrlib.msgeditor', 'hooks', 'MessageEditorHooks'),
+ ('bzrlib.mutabletree', 'MutableTree.hooks', 'MutableTreeHooks'),
+ ('bzrlib.smart.client', '_SmartClient.hooks', 'SmartClientHooks'),
+ ('bzrlib.smart.server', 'SmartTCPServer.hooks', 'SmartServerHooks'),
+ ('bzrlib.status', 'hooks', 'StatusHooks'),
+ ('bzrlib.version_info_formats.format_rio', 'RioVersionInfoBuilder.hooks',
+ 'RioVersionInfoBuilderHooks'),
+ ('bzrlib.merge_directive', 'BaseMergeDirective.hooks',
+ 'MergeDirectiveHooks'),
+ )
+
+known_hooks = KnownHooksRegistry()
+for (_hook_module, _hook_attribute, _hook_class) in _builtin_known_hooks:
+ known_hooks.register_lazy_hook(_hook_module, _hook_attribute, _hook_class)
+del _builtin_known_hooks, _hook_module, _hook_attribute, _hook_class
def known_hooks_key_to_object((module_name, member_name)):
@@ -71,24 +99,13 @@
the known_hooks registry.
:return: The object this specifies.
"""
- return registry._LazyObjectGetter(module_name, member_name).get_obj()
-
-
-def known_hooks_key_to_parent_and_attribute((module_name, member_name)):
- """Convert a known_hooks key to a object.
-
- :param key: A tuple (module_name, member_name) as found in the keys of
- the known_hooks registry.
- :return: The object this specifies.
- """
- member_list = member_name.rsplit('.', 1)
- if len(member_list) == 2:
- parent_name, attribute = member_list
- else:
- parent_name = None
- attribute = member_name
- parent = known_hooks_key_to_object((module_name, parent_name))
- return parent, attribute
+ return pyutils.get_named_object(module_name, member_name)
+
+
+ at symbol_versioning.deprecated_function(symbol_versioning.deprecated_in((2, 3)))
+def known_hooks_key_to_parent_and_attribute(key):
+ """See KnownHooksRegistry.key_to_parent_and_attribute."""
+ return known_hooks.key_to_parent_and_attribute(key)
class Hooks(dict):
=== added file 'bzrlib/pyutils.py'
--- a/bzrlib/pyutils.py 1970-01-01 00:00:00 +0000
+++ b/bzrlib/pyutils.py 2010-10-15 11:15:52 +0000
@@ -0,0 +1,90 @@
+# Copyright (C) 2010 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""General Python convenience functions."""
+
+
+import sys
+
+
+def get_named_object(module_name, member_name=None):
+ """Get the Python object named by a given module and member name.
+
+ This is usually much more convenient than dealing with ``__import__``
+ directly::
+
+ >>> doc = get_named_object('bzrlib.pyutils', 'get_named_object.__doc__')
+ >>> doc.splitlines()[0]
+ 'Get the Python object named by a given module and member name.'
+
+ :param module_name: a module name, as would be found in sys.modules if
+ the module is already imported. It may contain dots. e.g. 'sys' or
+ 'os.path'.
+ :param member_name: (optional) a name of an attribute in that module to
+ return. It may contain dots. e.g. 'MyClass.some_method'. If not
+ given, the named module will be returned instead.
+ :raises: ImportError or AttributeError.
+ """
+ # We may have just a module name, or a module name and a member name,
+ # and either may contain dots. __import__'s return value is a bit
+ # unintuitive, so we need to take care to always return the object
+ # specified by the full combination of module name + member name.
+ if member_name:
+ # Give __import__ a from_list. It will return the last module in
+ # the dotted module name.
+ attr_chain = member_name.split('.')
+ from_list = attr_chain[:1]
+ obj = __import__(module_name, {}, {}, from_list)
+ for attr in attr_chain:
+ obj = getattr(obj, attr)
+ else:
+ # We're just importing a module, no attributes, so we have no
+ # from_list. __import__ will return the first module in the dotted
+ # module name, so we look up the module from sys.modules.
+ __import__(module_name, globals(), locals(), [])
+ obj = sys.modules[module_name]
+ return obj
+
+
+def calc_parent_name(module_name, member_name=None):
+ """Determine the 'parent' of a given dotted module name and (optional)
+ member name.
+
+ Typical use is::
+
+ >>> parent_mod, parent_member, final_attr = calc_parent_name(
+ ... module_name, member_name) # doctest: +SKIP
+ >>> parent_obj = get_named_object(parent_mod, parent_member)
+ ... # doctest: +SKIP
+
+ The idea is that ``getattr(parent_obj, final_attr)`` will equal
+ get_named_object(module_name, member_name).
+
+ :return: (module_name, member_name, final_attr) tuple.
+ """
+ if member_name is not None:
+ split_name = member_name.rsplit('.', 1)
+ if len(split_name) == 1:
+ return (module_name, None, member_name)
+ else:
+ return (module_name, split_name[0], split_name[1])
+ else:
+ split_name = module_name.rsplit('.', 1)
+ if len(split_name) == 1:
+ raise AssertionError(
+ 'No parent object for top-level module %r' % (module_name,))
+ else:
+ return (split_name[0], None, split_name[1])
=== modified file 'bzrlib/registry.py'
--- a/bzrlib/registry.py 2009-09-16 10:37:29 +0000
+++ b/bzrlib/registry.py 2010-09-21 03:20:09 +0000
@@ -17,6 +17,9 @@
"""Classes to provide name-to-object registry-like support."""
+from bzrlib.pyutils import get_named_object
+
+
class _ObjectGetter(object):
"""Maintain a reference to an object, and return the object on request.
@@ -58,26 +61,14 @@
return the imported object.
"""
if not self._imported:
- self._do_import()
+ self._obj = get_named_object(self._module_name, self._member_name)
+ self._imported = True
return super(_LazyObjectGetter, self).get_obj()
- def _do_import(self):
- if self._member_name:
- segments = self._member_name.split('.')
- names = segments[0:1]
- else:
- names = [self._member_name]
- obj = __import__(self._module_name, globals(), locals(), names)
- if self._member_name:
- for segment in segments:
- obj = getattr(obj, segment)
- self._obj = obj
- self._imported = True
-
def __repr__(self):
- return "<%s.%s object at %x, module=%r attribute=%r>" % (
+ return "<%s.%s object at %x, module=%r attribute=%r imported=%r>" % (
self.__class__.__module__, self.__class__.__name__, id(self),
- self._module_name, self._member_name)
+ self._module_name, self._member_name, self._imported)
class Registry(object):
=== modified file 'bzrlib/repository.py'
--- a/bzrlib/repository.py 2010-09-28 18:51:47 +0000
+++ b/bzrlib/repository.py 2010-10-15 11:20:45 +0000
@@ -39,6 +39,7 @@
lockdir,
lru_cache,
osutils,
+ pyutils,
revision as _mod_revision,
static_tuple,
symbol_versioning,
@@ -52,6 +53,7 @@
from bzrlib.testament import Testament
""")
+import sys
from bzrlib import (
errors,
registry,
@@ -2825,12 +2827,11 @@
% (name, from_module),
DeprecationWarning,
stacklevel=2)
- m = __import__(from_module, globals(), locals(), [name])
try:
- return getattr(m, name)
+ return pyutils.get_named_object(from_module, name)
except AttributeError:
raise AttributeError('module %s has no name %s'
- % (m, name))
+ % (sys.modules[from_module], name))
globals()[name] = _deprecated_repository_forwarder
for _name in [
=== modified file 'bzrlib/tests/TestUtil.py'
--- a/bzrlib/tests/TestUtil.py 2010-08-05 18:13:23 +0000
+++ b/bzrlib/tests/TestUtil.py 2010-09-21 03:20:09 +0000
@@ -20,6 +20,8 @@
import logging
import unittest
+from bzrlib import pyutils
+
# Mark this python module as being part of the implementation
# of unittest: this gives us better tracebacks where the last
# shown frame is the test code, not our assertXYZ.
@@ -106,7 +108,7 @@
def loadTestsFromModuleName(self, name):
result = self.suiteClass()
- module = _load_module_by_name(name)
+ module = pyutils.get_named_object(name)
result.addTests(self.loadTestsFromModule(module))
return result
@@ -179,17 +181,6 @@
return self.suiteClass()
-def _load_module_by_name(mod_name):
- parts = mod_name.split('.')
- module = __import__(mod_name)
- del parts[0]
- # for historical reasons python returns the top-level module even though
- # it loads the submodule; we need to walk down to get the one we want.
- while parts:
- module = getattr(module, parts.pop(0))
- return module
-
-
class TestVisitor(object):
"""A visitor for Tests"""
def visitSuite(self, aTestSuite):
=== modified file 'bzrlib/tests/__init__.py'
--- a/bzrlib/tests/__init__.py 2010-10-15 02:13:13 +0000
+++ b/bzrlib/tests/__init__.py 2010-10-15 11:20:45 +0000
@@ -71,6 +71,7 @@
lock as _mod_lock,
memorytree,
osutils,
+ pyutils,
ui,
urlutils,
registry,
@@ -897,14 +898,14 @@
def _clear_hooks(self):
# prevent hooks affecting tests
+ known_hooks = hooks.known_hooks
self._preserved_hooks = {}
- for key, factory in hooks.known_hooks.items():
- parent, name = hooks.known_hooks_key_to_parent_and_attribute(key)
- current_hooks = hooks.known_hooks_key_to_object(key)
+ for key, (parent, name) in known_hooks.iter_parent_objects():
+ current_hooks = getattr(parent, name)
self._preserved_hooks[parent] = (name, current_hooks)
self.addCleanup(self._restoreHooks)
- for key, factory in hooks.known_hooks.items():
- parent, name = hooks.known_hooks_key_to_parent_and_attribute(key)
+ for key, (parent, name) in known_hooks.iter_parent_objects():
+ factory = known_hooks.get(key)
setattr(parent, name, factory())
# this hook should always be installed
request._install_hook()
@@ -3761,6 +3762,7 @@
'bzrlib.tests.test_permissions',
'bzrlib.tests.test_plugins',
'bzrlib.tests.test_progress',
+ 'bzrlib.tests.test_pyutils',
'bzrlib.tests.test_read_bundle',
'bzrlib.tests.test_reconcile',
'bzrlib.tests.test_reconfigure',
@@ -3845,6 +3847,7 @@
'bzrlib.lockdir',
'bzrlib.merge3',
'bzrlib.option',
+ 'bzrlib.pyutils',
'bzrlib.symbol_versioning',
'bzrlib.tests',
'bzrlib.tests.fixtures',
@@ -4100,7 +4103,7 @@
the module is available.
"""
- py_module = __import__(py_module_name, {}, {}, ['NO_SUCH_ATTRIB'])
+ py_module = pyutils.get_named_object(py_module_name)
scenarios = [
('python', {'module': py_module}),
]
@@ -4259,9 +4262,8 @@
symbol_versioning.warn(depr_msg + use_msg, DeprecationWarning)
# Import the new feature and use it as a replacement for the
# deprecated one.
- mod = __import__(self._replacement_module, {}, {},
- [self._replacement_name])
- self._feature = getattr(mod, self._replacement_name)
+ self._feature = pyutils.get_named_object(
+ self._replacement_module, self._replacement_name)
def _probe(self):
self._ensure()
=== modified file 'bzrlib/tests/per_transport.py'
--- a/bzrlib/tests/per_transport.py 2010-08-24 13:03:18 +0000
+++ b/bzrlib/tests/per_transport.py 2010-09-21 03:20:09 +0000
@@ -26,28 +26,24 @@
from StringIO import StringIO as pyStringIO
import stat
import sys
-import unittest
from bzrlib import (
errors,
osutils,
+ pyutils,
tests,
urlutils,
)
from bzrlib.errors import (ConnectionError,
- DirectoryNotEmpty,
FileExists,
InvalidURL,
- LockError,
NoSuchFile,
- NotLocalUrl,
PathError,
TransportNotPossible,
)
from bzrlib.osutils import getcwd
from bzrlib.smart import medium
from bzrlib.tests import (
- TestCaseInTempDir,
TestSkipped,
TestNotApplicable,
multiply_tests,
@@ -78,7 +74,7 @@
for module in _get_transport_modules():
try:
permutations = get_transport_test_permutations(
- reduce(getattr, (module).split('.')[1:], __import__(module)))
+ pyutils.get_named_object(module))
for (klass, server_factory) in permutations:
scenario = ('%s,%s' % (klass.__name__, server_factory.__name__),
{"transport_class":klass,
=== modified file 'bzrlib/tests/test_hooks.py'
--- a/bzrlib/tests/test_hooks.py 2010-02-17 17:11:16 +0000
+++ b/bzrlib/tests/test_hooks.py 2010-09-21 03:20:09 +0000
@@ -28,6 +28,9 @@
known_hooks_key_to_object,
known_hooks_key_to_parent_and_attribute,
)
+from bzrlib.symbol_versioning import (
+ deprecated_in,
+ )
class TestHooks(tests.TestCase):
@@ -175,10 +178,20 @@
self.assertIs(branch.Branch.hooks,
known_hooks_key_to_object(('bzrlib.branch', 'Branch.hooks')))
+ def test_known_hooks_key_to_parent_and_attribute_deprecated(self):
+ self.assertEqual((branch.Branch, 'hooks'),
+ self.applyDeprecated(deprecated_in((2,3)),
+ known_hooks_key_to_parent_and_attribute,
+ ('bzrlib.branch', 'Branch.hooks')))
+ self.assertEqual((branch, 'Branch'),
+ self.applyDeprecated(deprecated_in((2,3)),
+ known_hooks_key_to_parent_and_attribute,
+ ('bzrlib.branch', 'Branch')))
+
def test_known_hooks_key_to_parent_and_attribute(self):
self.assertEqual((branch.Branch, 'hooks'),
- known_hooks_key_to_parent_and_attribute(
+ known_hooks.key_to_parent_and_attribute(
('bzrlib.branch', 'Branch.hooks')))
self.assertEqual((branch, 'Branch'),
- known_hooks_key_to_parent_and_attribute(
+ known_hooks.key_to_parent_and_attribute(
('bzrlib.branch', 'Branch')))
=== added file 'bzrlib/tests/test_pyutils.py'
--- a/bzrlib/tests/test_pyutils.py 1970-01-01 00:00:00 +0000
+++ b/bzrlib/tests/test_pyutils.py 2010-09-21 03:20:09 +0000
@@ -0,0 +1,88 @@
+# Copyright (C) 2010 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""Tests for bzrlib.pyutils."""
+
+from bzrlib import (
+ branch,
+ tests,
+ )
+from bzrlib.pyutils import (
+ calc_parent_name,
+ get_named_object,
+ )
+
+
+class TestGetNamedObject(tests.TestCase):
+ """Tests for get_named_object."""
+
+ def test_module_only(self):
+ import sys
+ self.assertIs(sys, get_named_object('sys'))
+
+ def test_dotted_module(self):
+ self.assertIs(branch, get_named_object('bzrlib.branch'))
+
+ def test_module_attr(self):
+ self.assertIs(
+ branch.Branch, get_named_object('bzrlib.branch', 'Branch'))
+
+ def test_dotted_attr(self):
+ self.assertIs(
+ branch.Branch.hooks,
+ get_named_object('bzrlib.branch', 'Branch.hooks'))
+
+ def test_package(self):
+ # bzrlib.tests is a package, not simply a module
+ self.assertIs(tests, get_named_object('bzrlib.tests'))
+
+ def test_package_attr(self):
+ # bzrlib.tests is a package, not simply a module
+ self.assertIs(
+ tests.TestCase, get_named_object('bzrlib.tests', 'TestCase'))
+
+ def test_import_error(self):
+ self.assertRaises(ImportError, get_named_object, 'NO_SUCH_MODULE')
+
+ def test_attribute_error(self):
+ self.assertRaises(
+ AttributeError, get_named_object, 'sys', 'NO_SUCH_ATTR')
+
+
+
+class TestCalcParent_name(tests.TestCase):
+ """Tests for calc_parent_name."""
+
+ def test_dotted_member(self):
+ self.assertEqual(
+ ('mod_name', 'attr1', 'attr2'),
+ calc_parent_name('mod_name', 'attr1.attr2'))
+
+ def test_undotted_member(self):
+ self.assertEqual(
+ ('mod_name', None, 'attr1'),
+ calc_parent_name('mod_name', 'attr1'))
+
+ def test_dotted_module_no_member(self):
+ self.assertEqual(
+ ('mod', None, 'sub_mod'),
+ calc_parent_name('mod.sub_mod'))
+
+ def test_undotted_module_no_member(self):
+ err = self.assertRaises(AssertionError, calc_parent_name, 'mod_name')
+ self.assertEqual(
+ "No parent object for top-level module 'mod_name'", err.args[0])
+
=== modified file 'bzrlib/tests/test_registry.py'
--- a/bzrlib/tests/test_registry.py 2009-09-16 10:37:29 +0000
+++ b/bzrlib/tests/test_registry.py 2010-09-21 03:20:09 +0000
@@ -20,6 +20,7 @@
import sys
from bzrlib import (
+ branch,
errors,
osutils,
registry,
@@ -286,6 +287,13 @@
'\n\n'
)
+ def test_lazy_import_registry_foo(self):
+ a_registry = registry.Registry()
+ a_registry.register_lazy('foo', 'bzrlib.branch', 'Branch')
+ a_registry.register_lazy('bar', 'bzrlib.branch', 'Branch.hooks')
+ self.assertEqual(branch.Branch, a_registry.get('foo'))
+ self.assertEqual(branch.Branch.hooks, a_registry.get('bar'))
+
def test_lazy_import_registry(self):
plugin_name = self.create_simple_plugin()
a_registry = registry.Registry()
=== modified file 'bzrlib/tests/test_selftest.py'
--- a/bzrlib/tests/test_selftest.py 2010-10-15 02:13:13 +0000
+++ b/bzrlib/tests/test_selftest.py 2010-10-15 11:20:45 +0000
@@ -81,18 +81,6 @@
return [t.id() for t in tests.iter_suite_tests(test_suite)]
-class SelftestTests(tests.TestCase):
-
- def test_import_tests(self):
- mod = TestUtil._load_module_by_name('bzrlib.tests.test_selftest')
- self.assertEqual(mod.SelftestTests, SelftestTests)
-
- def test_import_test_failure(self):
- self.assertRaises(ImportError,
- TestUtil._load_module_by_name,
- 'bzrlib.no-name-yet')
-
-
class MetaTestLog(tests.TestCase):
def test_logging(self):
=== modified file 'doc/developers/code-style.txt'
--- a/doc/developers/code-style.txt 2010-09-24 08:42:02 +0000
+++ b/doc/developers/code-style.txt 2010-09-28 07:20:09 +0000
@@ -485,5 +485,19 @@
* Don't say "open source" when you mean "free software".
+
+Dynamic imports
+===============
+
+If you need to import a module (or attribute of a module) named in a
+variable:
+
+ * If importing a module, not an attribute, and the module is a top-level
+ module (i.e. has no dots in the name), then it's ok to use the builtin
+ ``__import__``, e.g. ``__import__(module_name)``.
+ * In all other cases, prefer ``bzrlib.pyutils.get_named_object`` to the
+ built-in ``__import__``. ``__import__`` has some subtleties and
+ unintuitive behaviours that make it hard to use correctly.
+
..
vim: ft=rst tw=74 ai
=== modified file 'doc/en/release-notes/bzr-2.3.txt'
--- a/doc/en/release-notes/bzr-2.3.txt 2010-10-15 09:34:33 +0000
+++ b/doc/en/release-notes/bzr-2.3.txt 2010-10-15 11:20:45 +0000
@@ -61,9 +61,17 @@
API Changes
***********
+* Add ``bzrlib.pyutils`` module with helper functions for some Python
+ tasks such as resolving a dotted name to a Python object
+ (``get_named_object``). (Andrew Bennetts)
+
* ``bzrlib.tests.ForwardingResult`` no longer exists. Use
``testtools.ExtendedToOriginalDecorator`` instead. (Andrew Bennetts)
+* ``known_hooks_key_to_parent_and_attribute`` in ``bzrlib.hooks`` has been
+ deprecated in favour of ``known_hooks.key_to_parent_and_attribute`` in
+ the same module. (Andrew Bennetts)
+
Internals
*********
More information about the bazaar-commits
mailing list