Rev 5111: (mbp) allow builtin commands to be lazy loaded; make bundle-info lazy in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Thu Mar 25 06:52:41 GMT 2010


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 5111 [merge]
revision-id: pqm at pqm.ubuntu.com-20100325065238-0mser11okatoq0yz
parent: pqm at pqm.ubuntu.com-20100325000251-bwsv5c5d3l9x3lnn
parent: mbp at canonical.com-20100325060216-mwaldgns6nvrl91m
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Thu 2010-03-25 06:52:38 +0000
message:
  (mbp) allow builtin commands to be lazy loaded; make bundle-info lazy
modified:
  bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
  bzrlib/commands.py             bzr.py-20050309040720-d10f4714595cf8c3
  bzrlib/tests/test_commands.py  test_command.py-20051019190109-3b17be0f52eaa7a8
  bzrlib/tests/test_import_tariff.py test_import_tariff.p-20100207155145-ff9infp7goncs7zh-1
=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2010-03-22 13:59:33 +0000
+++ b/bzrlib/builtins.py	2010-03-25 06:52:38 +0000
@@ -5974,15 +5974,7 @@
             self.outf.write('%s %s\n' % (path, location))
 
 
-# these get imported and then picked up by the scan for cmd_*
-# TODO: Some more consistent way to split command definitions across files;
-# we do need to load at least some information about them to know of
-# aliases.  ideally we would avoid loading the implementation until the
-# details were needed.
 from bzrlib.cmd_version_info import cmd_version_info
 from bzrlib.conflicts import cmd_resolve, cmd_conflicts, restore
-from bzrlib.bundle.commands import (
-    cmd_bundle_info,
-    )
 from bzrlib.foreign import cmd_dpush
 from bzrlib.sign_my_commits import cmd_sign_my_commits

=== modified file 'bzrlib/commands.py'
--- a/bzrlib/commands.py	2010-03-01 09:27:39 +0000
+++ b/bzrlib/commands.py	2010-03-25 06:52:38 +0000
@@ -15,9 +15,6 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
 
-# TODO: probably should say which arguments are candidates for glob
-# expansion on windows and do that at the command level.
-
 # TODO: Define arguments by objects, rather than just using names.
 # Those objects can specify the expected type of the argument, which
 # would help with validation and shell completion.  They could also provide
@@ -25,9 +22,6 @@
 
 # TODO: Specific "examples" property on commands for consistent formatting.
 
-# TODO: "--profile=cum", to change sort order.  Is there any value in leaving
-# the profile output behind so it can be interactively examined?
-
 import os
 import sys
 
@@ -78,6 +72,22 @@
 
 
 class CommandRegistry(registry.Registry):
+    """Special registry mapping command names to command classes.
+    
+    :ivar overridden_registry: Look in this registry for commands being
+        overridden by this registry.  This can be used to tell plugin commands
+        about the builtin they're decorating.
+    """
+
+    def __init__(self):
+        registry.Registry.__init__(self)
+        self.overridden_registry = None
+        # map from aliases to the real command that implements the name
+        self._alias_dict = {}
+
+    def get(self, command_name):
+        real_name = self._alias_dict.get(command_name, command_name)
+        return registry.Registry.get(self, real_name)
 
     @staticmethod
     def _get_name(command_name):
@@ -99,7 +109,12 @@
         try:
             previous = self.get(k_unsquished)
         except KeyError:
-            previous = _builtin_commands().get(k_unsquished)
+            previous = None
+            if self.overridden_registry:
+                try:
+                    previous = self.overridden_registry.get(k_unsquished)
+                except KeyError:
+                    pass
         info = CommandInfo.from_command(cmd)
         try:
             registry.Registry.register(self, k_unsquished, cmd,
@@ -110,6 +125,8 @@
                 sys.modules[cmd.__module__])
             trace.warning('Previously this command was registered from %r' %
                 sys.modules[previous.__module__])
+        for a in cmd.aliases:
+            self._alias_dict[a] = k_unsquished
         return previous
 
     def register_lazy(self, command_name, aliases, module_name):
@@ -122,12 +139,20 @@
         key = self._get_name(command_name)
         registry.Registry.register_lazy(self, key, module_name, command_name,
                                         info=CommandInfo(aliases))
+        for a in aliases:
+            self._alias_dict[a] = key
 
 
 plugin_cmds = CommandRegistry()
+builtin_command_registry = CommandRegistry()
+plugin_cmds.overridden_registry = builtin_command_registry
 
 
 def register_command(cmd, decorate=False):
+    """Register a plugin command.
+
+    Should generally be avoided in favor of lazy registration. 
+    """
     global plugin_cmds
     return plugin_cmds.register(cmd, decorate)
 
@@ -140,9 +165,27 @@
     return cmd[4:].replace('_','-')
 
 
+ at deprecated_function(deprecated_in((2, 2, 0)))
 def _builtin_commands():
+    """Return a dict of {name: cmd_class} for builtin commands.
+
+    :deprecated: Use the builtin_command_registry registry instead
+    """
+    # return dict(name: cmd_class)
+    return dict(builtin_command_registry.items())
+
+
+def _register_builtin_commands():
+    if builtin_command_registry.keys():
+        # only load once
+        return
     import bzrlib.builtins
-    return _scan_module_for_commands(bzrlib.builtins)
+    for cmd_class in _scan_module_for_commands(bzrlib.builtins).values():
+        builtin_command_registry.register(cmd_class)
+    # lazy builtins
+    builtin_command_registry.register_lazy('cmd_bundle_info',
+        [],
+        'bzrlib.bundle.commands')
 
 
 def _scan_module_for_commands(module):
@@ -155,7 +198,10 @@
 
 
 def _list_bzr_commands(names):
-    """Find commands from bzr's core and plugins."""
+    """Find commands from bzr's core and plugins.
+    
+    This is not the public interface, just the default hook called by all_command_names.
+    """
     # to eliminate duplicates
     names.update(builtin_command_names())
     names.update(plugin_command_names())
@@ -179,7 +225,7 @@
     Use of all_command_names() is encouraged rather than builtin_command_names
     and/or plugin_command_names.
     """
-    return _builtin_commands().keys()
+    return builtin_command_registry.keys()
 
 
 def plugin_command_names():
@@ -263,15 +309,12 @@
 
 def _get_bzr_command(cmd_or_None, cmd_name):
     """Get a command from bzr's core."""
-    cmds = _builtin_commands()
     try:
-        return cmds[cmd_name]()
+        cmd_class = builtin_command_registry.get(cmd_name)
     except KeyError:
         pass
-    # look for any command which claims this as an alias
-    for real_cmd_name, cmd_class in cmds.iteritems():
-        if cmd_name in cmd_class.aliases:
-            return cmd_class()
+    else:
+        return cmd_class()
     return cmd_or_None
 
 
@@ -1116,6 +1159,7 @@
     :return: exit code of bzr command.
     """
     argv = _specified_or_unicode_argv(argv)
+    _register_builtin_commands()
     ret = run_bzr_catch_errors(argv)
     bzrlib.ui.ui_factory.log_transport_activity(
         display=('bytes' in debug.debug_flags))

=== modified file 'bzrlib/tests/test_commands.py'
--- a/bzrlib/tests/test_commands.py	2010-03-01 09:27:39 +0000
+++ b/bzrlib/tests/test_commands.py	2010-03-25 06:52:38 +0000
@@ -211,14 +211,13 @@
         commands.Command.hooks.install_named_hook(
             "extend_command", hook_calls.append, None)
         # create a command, should not fire
-        class ACommand(commands.Command):
+        class cmd_test_extend_command_hook(commands.Command):
             """A sample command."""
-        cmd = ACommand()
         self.assertEqual([], hook_calls)
         # -- as a builtin
         # register the command class, should not fire
         try:
-            builtins.cmd_test_extend_command_hook = ACommand
+            commands.builtin_command_registry.register(cmd_test_extend_command_hook)
             self.assertEqual([], hook_calls)
             # and ask for the object, should fire
             cmd = commands.get_cmd_object('test-extend-command-hook')
@@ -228,7 +227,7 @@
             self.assertSubset([cmd], hook_calls)
             del hook_calls[:]
         finally:
-            del builtins.cmd_test_extend_command_hook
+            commands.builtin_command_registry.remove('test-extend-command-hook')
         # -- as a plugin lazy registration
         try:
             # register the command class, should not fire

=== modified file 'bzrlib/tests/test_import_tariff.py'
--- a/bzrlib/tests/test_import_tariff.py	2010-02-10 02:17:15 +0000
+++ b/bzrlib/tests/test_import_tariff.py	2010-03-23 06:03:14 +0000
@@ -36,6 +36,14 @@
     """
 
     def run_command_check_imports(self, args, forbidden_imports):
+        """Run bzr ARGS in a subprocess and check its imports.
+
+        This is fairly expensive because we start a subprocess, so we aim to
+        cover representative rather than exhaustive cases.
+
+        :param forbidden_imports: List of fully-qualified Python module names
+            that should not be loaded while running this command.
+        """
         # We use PYTHON_VERBOSE rather than --profile-importts because in
         # experimentation the profile-imports output seems to not always show
         # the modules you'd expect; this can be debugged but python -v seems
@@ -82,6 +90,7 @@
         # 'st' in a working tree shouldn't need many modules
         self.make_branch_and_tree('.')
         self.run_command_check_imports(['st'], [
+            'bzrlib.bundle.commands',
             'bzrlib.remote',
             'bzrlib.smart',
             'smtplib',




More information about the bazaar-commits mailing list