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