Rev 3789: Use Registry for plugin commands, enable lazy command loading. in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Wed Oct 22 00:18:49 BST 2008


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

------------------------------------------------------------
revno: 3789
revision-id: pqm at pqm.ubuntu.com-20081021231845-k119hl1icewguq50
parent: pqm at pqm.ubuntu.com-20081021060139-fpwr4fxr2oww2x5o
parent: aaron at aaronbentley.com-20081021204954-6ly1dyb9ix92el1a
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Wed 2008-10-22 00:18:45 +0100
message:
  Use Registry for plugin commands, enable lazy command loading.
added:
  bzrlib/tests/fake_command.py   fake_command.py-20081021195002-r9v65tgxx63c25v9-1
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/commands.py             bzr.py-20050309040720-d10f4714595cf8c3
  bzrlib/tests/blackbox/test_command_encoding.py test_command_encoding.py-20060106032110-45431fd2ce9ff21f
  bzrlib/tests/test_commands.py  test_command.py-20051019190109-3b17be0f52eaa7a8
  bzrlib/tests/test_plugins.py   plugins.py-20050622075746-32002b55e5e943e9
  bzrlib/tests/test_store.py     teststore.py-20050826022702-f6caadb647395769
    ------------------------------------------------------------
    revno: 3785.1.5
    revision-id: aaron at aaronbentley.com-20081021204954-6ly1dyb9ix92el1a
    parent: aaron at aaronbentley.com-20081021204305-4yf2n78r6b61yo91
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: command-registry
    timestamp: Tue 2008-10-21 21:49:54 +0100
    message:
      Update NEWS
    modified:
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
    ------------------------------------------------------------
    revno: 3785.1.4
    revision-id: aaron at aaronbentley.com-20081021204305-4yf2n78r6b61yo91
    parent: aaron at aaronbentley.com-20081021184910-kvfby9230xv2mbfz
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: command-registry
    timestamp: Tue 2008-10-21 21:43:05 +0100
    message:
      Enable lazy-loading of commands
    added:
      bzrlib/tests/fake_command.py   fake_command.py-20081021195002-r9v65tgxx63c25v9-1
    modified:
      bzrlib/commands.py             bzr.py-20050309040720-d10f4714595cf8c3
      bzrlib/tests/test_commands.py  test_command.py-20051019190109-3b17be0f52eaa7a8
    ------------------------------------------------------------
    revno: 3785.1.3
    revision-id: aaron at aaronbentley.com-20081021184910-kvfby9230xv2mbfz
    parent: aaron at aaronbentley.com-20081021184829-wb5obp7gst2207ge
    parent: pqm at pqm.ubuntu.com-20081021060139-fpwr4fxr2oww2x5o
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: command-registry
    timestamp: Tue 2008-10-21 19:49:10 +0100
    message:
      merge with bzr.dev
    added:
      doc/developers/cycle.txt       cycle.txt-20081017031739-rw24r0cywm2ok3xu-1
      tools/packaging/lp-upload-release lpuploadrelease-20081020075647-56zdf9z6yav1bx81-1
    modified:
      Makefile                       Makefile-20050805140406-d96e3498bb61c5bb
      bzrlib/errors.py               errors.py-20050309040759-20512168c4e14fbd
      bzrlib/knit.py                 knit.py-20051212171256-f056ac8f0fbe1bd9
      bzrlib/tests/test_knit.py      test_knit.py-20051212171302-95d4c00dd5f11f2b
      doc/developers/index.txt       index.txt-20070508041241-qznziunkg0nffhiw-1
      doc/developers/releasing.txt   releasing.txt-20080502015919-fnrcav8fwy8ccibu-1
    ------------------------------------------------------------
    revno: 3785.1.2
    revision-id: aaron at aaronbentley.com-20081021184829-wb5obp7gst2207ge
    parent: aaron at aaronbentley.com-20081021140831-a8sqdr5sg8y82z4e
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: command-registry
    timestamp: Tue 2008-10-21 19:48:29 +0100
    message:
      Avoid getting plugins unnecessarily
    modified:
      bzrlib/commands.py             bzr.py-20050309040720-d10f4714595cf8c3
    ------------------------------------------------------------
    revno: 3785.1.1
    revision-id: aaron at aaronbentley.com-20081021140831-a8sqdr5sg8y82z4e
    parent: pqm at pqm.ubuntu.com-20081017223605-ais9run1hp476y1c
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: command-registry
    timestamp: Tue 2008-10-21 15:08:31 +0100
    message:
      Switch from dict to Registry for plugin_cmds
    modified:
      bzrlib/commands.py             bzr.py-20050309040720-d10f4714595cf8c3
      bzrlib/tests/blackbox/test_command_encoding.py test_command_encoding.py-20060106032110-45431fd2ce9ff21f
      bzrlib/tests/test_plugins.py   plugins.py-20050622075746-32002b55e5e943e9
      bzrlib/tests/test_store.py     teststore.py-20050826022702-f6caadb647395769
=== modified file 'NEWS'
--- a/NEWS	2008-10-17 22:36:05 +0000
+++ b/NEWS	2008-10-21 20:49:54 +0000
@@ -48,6 +48,8 @@
 
   API CHANGES:
 
+    * commands.plugins_cmds is now a CommandRegistry, not a dict.
+
   TESTING:
 
   INTERNALS:

=== modified file 'bzrlib/commands.py'
--- a/bzrlib/commands.py	2008-10-02 06:18:42 +0000
+++ b/bzrlib/commands.py	2008-10-21 20:43:05 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2006 Canonical Ltd
+# Copyright (C) 2006, 2008 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
@@ -53,37 +53,72 @@
 from bzrlib.option import Option
 
 
-plugin_cmds = {}
+class CommandInfo(object):
+    """Information about a command."""
+
+    def __init__(self, aliases):
+        """The list of aliases for the command."""
+        self.aliases = aliases
+
+    @classmethod
+    def from_command(klass, command):
+        """Factory to construct a CommandInfo from a command."""
+        return klass(command.aliases)
+
+
+class CommandRegistry(registry.Registry):
+
+    @staticmethod
+    def _get_name(command_name):
+        if command_name.startswith("cmd_"):
+            return _unsquish_command_name(command_name)
+        else:
+            return command_name
+
+    def register(self, cmd, decorate=False):
+        """Utility function to help register a command
+
+        :param cmd: Command subclass to register
+        :param decorate: If true, allow overriding an existing command
+            of the same name; the old command is returned by this function.
+            Otherwise it is an error to try to override an existing command.
+        """
+        k = cmd.__name__
+        k_unsquished = self._get_name(k)
+        try:
+            previous = self.get(k_unsquished)
+        except KeyError:
+            previous = _builtin_commands().get(k_unsquished)
+        info = CommandInfo.from_command(cmd)
+        try:
+            registry.Registry.register(self, k_unsquished, cmd,
+                                       override_existing=decorate, info=info)
+        except KeyError:
+            trace.log_error('Two plugins defined the same command: %r' % k)
+            trace.log_error('Not loading the one in %r' %
+                            sys.modules[cmd.__module__])
+            trace.log_error('Previously this command was registered from %r' %
+                            sys.modules[previous.__module__])
+        return previous
+
+    def register_lazy(self, command_name, aliases, module_name):
+        """Register a command without loading its module.
+
+        :param command_name: The primary name of the command.
+        :param aliases: A list of aliases for the command.
+        :module_name: The module that the command lives in.
+        """
+        key = self._get_name(command_name)
+        registry.Registry.register_lazy(self, key, module_name, command_name,
+                                        info=CommandInfo(aliases))
+
+
+plugin_cmds = CommandRegistry()
 
 
 def register_command(cmd, decorate=False):
-    """Utility function to help register a command
-
-    :param cmd: Command subclass to register
-    :param decorate: If true, allow overriding an existing command
-        of the same name; the old command is returned by this function.
-        Otherwise it is an error to try to override an existing command.
-    """
     global plugin_cmds
-    k = cmd.__name__
-    if k.startswith("cmd_"):
-        k_unsquished = _unsquish_command_name(k)
-    else:
-        k_unsquished = k
-    if k_unsquished not in plugin_cmds:
-        plugin_cmds[k_unsquished] = cmd
-        ## trace.mutter('registered plugin command %s', k_unsquished)
-        if decorate and k_unsquished in builtin_command_names():
-            return _builtin_commands()[k_unsquished]
-    elif decorate:
-        result = plugin_cmds[k_unsquished]
-        plugin_cmds[k_unsquished] = cmd
-        return result
-    else:
-        trace.log_error('Two plugins defined the same command: %r' % k)
-        trace.log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
-        trace.log_error('Previously this command was registered from %r' %
-                        sys.modules[plugin_cmds[k_unsquished].__module__])
+    return plugin_cmds.register(cmd, decorate)
 
 
 def _squish_command_name(cmd):
@@ -118,7 +153,7 @@
     """Return name->class mapping for all commands."""
     d = _builtin_commands()
     if plugins_override:
-        d.update(plugin_cmds)
+        d.update(plugin_cmds.iteritems())
     return d
 
     
@@ -150,12 +185,21 @@
     # In the future, we may actually support Unicode command names.
 
     # first look up this command under the specified name
-    cmds = _get_cmd_dict(plugins_override=plugins_override)
+    if plugins_override:
+        try:
+            return plugin_cmds.get(cmd_name)()
+        except KeyError:
+            pass
+    cmds = _get_cmd_dict(plugins_override=False)
     try:
         return cmds[cmd_name]()
     except KeyError:
         pass
-
+    if plugins_override:
+        for key in plugin_cmds.keys():
+            info = plugin_cmds.get_info(key)
+            if cmd_name in info.aliases:
+                return plugin_cmds.get(key)()
     # 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:

=== modified file 'bzrlib/tests/blackbox/test_command_encoding.py'
--- a/bzrlib/tests/blackbox/test_command_encoding.py	2007-09-18 05:47:31 +0000
+++ b/bzrlib/tests/blackbox/test_command_encoding.py	2008-10-21 14:08:31 +0000
@@ -62,7 +62,7 @@
                 bzr,
                 ['echo-exact', u'foo\xb5'])
         finally:
-            plugin_cmds.pop('echo-exact')
+            plugin_cmds.remove('echo-exact')
 
     def test_strict_utf8(self):
         def bzr(*args, **kwargs):
@@ -75,7 +75,7 @@
             self.assertEqual(u'foo\xb5'.encode('utf-8'),
                              bzr(['echo-strict', u'foo\xb5']))
         finally:
-            plugin_cmds.pop('echo-strict')
+            plugin_cmds.remove('echo-strict')
 
     def test_strict_ascii(self):
         def bzr(*args, **kwargs):
@@ -90,7 +90,7 @@
                 bzr,
                 ['echo-strict', u'foo\xb5'])
         finally:
-            plugin_cmds.pop('echo-strict')
+            plugin_cmds.remove('echo-strict')
 
     def test_replace_utf8(self):
         def bzr(*args, **kwargs):
@@ -103,7 +103,7 @@
             self.assertEqual(u'foo\xb5'.encode('utf-8'),
                              bzr(['echo-replace', u'foo\xb5']))
         finally:
-            plugin_cmds.pop('echo-replace')
+            plugin_cmds.remove('echo-replace')
 
     def test_replace_ascii(self):
         def bzr(*args, **kwargs):
@@ -116,6 +116,6 @@
             # ascii can't encode \xb5
             self.assertEqual('foo?', bzr(['echo-replace', u'foo\xb5']))
         finally:
-            plugin_cmds.pop('echo-replace')
+            plugin_cmds.remove('echo-replace')
 
 

=== added file 'bzrlib/tests/fake_command.py'
--- a/bzrlib/tests/fake_command.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/tests/fake_command.py	2008-10-21 20:43:05 +0000
@@ -0,0 +1,23 @@
+# Copyright (C) 2008 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
+
+from bzrlib.tests import test_commands
+test_commands.lazy_command_imported = True
+
+
+class cmd_fake(object):
+
+    pass

=== modified file 'bzrlib/tests/test_commands.py'
--- a/bzrlib/tests/test_commands.py	2007-04-20 03:54:06 +0000
+++ b/bzrlib/tests/test_commands.py	2008-10-21 20:43:05 +0000
@@ -16,6 +16,7 @@
 
 from cStringIO import StringIO
 import errno
+import sys
 
 from bzrlib import (
     commands,
@@ -134,3 +135,43 @@
         self.assertEqual(['bar', 'foo', 'gam'],
             command.get_see_also(['gam', 'bar', 'gam']))
 
+
+class TestRegisterLazy(tests.TestCase):
+
+    def setUp(self):
+        import bzrlib.tests.fake_command
+        del sys.modules['bzrlib.tests.fake_command']
+        global lazy_command_imported
+        lazy_command_imported = False
+
+    @staticmethod
+    def remove_fake():
+        commands.plugin_cmds.remove('fake')
+
+    def assertIsFakeCommand(self, cmd_obj):
+        from bzrlib.tests.fake_command import cmd_fake
+        self.assertIsInstance(cmd_obj, cmd_fake)
+
+    def test_register_lazy(self):
+        """Ensure lazy registration works"""
+        commands.plugin_cmds.register_lazy('cmd_fake', [],
+                                           'bzrlib.tests.fake_command')
+        self.addCleanup(self.remove_fake)
+        self.assertFalse(lazy_command_imported)
+        fake_instance = commands.get_cmd_object('fake')
+        self.assertTrue(lazy_command_imported)
+        self.assertIsFakeCommand(fake_instance)
+
+    def test_get_unrelated_does_not_import(self):
+        commands.plugin_cmds.register_lazy('cmd_fake', [],
+                                           'bzrlib.tests.fake_command')
+        self.addCleanup(self.remove_fake)
+        commands.get_cmd_object('status')
+        self.assertFalse(lazy_command_imported)
+
+    def test_aliases(self):
+        commands.plugin_cmds.register_lazy('cmd_fake', ['fake_alias'],
+                                           'bzrlib.tests.fake_command')
+        self.addCleanup(self.remove_fake)
+        fake_instance = commands.get_cmd_object('fake_alias')
+        self.assertIsFakeCommand(fake_instance)

=== modified file 'bzrlib/tests/test_plugins.py'
--- a/bzrlib/tests/test_plugins.py	2008-10-07 06:41:46 +0000
+++ b/bzrlib/tests/test_plugins.py	2008-10-21 14:08:31 +0000
@@ -412,8 +412,8 @@
             self.assertContainsRe(help, '\[myplug\]')
         finally:
             # unregister command
-            if bzrlib.commands.plugin_cmds.get('myplug', None):
-                del bzrlib.commands.plugin_cmds['myplug']
+            if 'myplug' in bzrlib.commands.plugin_cmds:
+                bzrlib.commands.plugin_cmds.remove('myplug')
             # remove the plugin 'myplug'
             if getattr(bzrlib.plugins, 'myplug', None):
                 delattr(bzrlib.plugins, 'myplug')

=== modified file 'bzrlib/tests/test_store.py'
--- a/bzrlib/tests/test_store.py	2008-05-12 04:20:02 +0000
+++ b/bzrlib/tests/test_store.py	2008-10-21 14:08:31 +0000
@@ -26,6 +26,7 @@
 from bzrlib.store.text import TextStore
 from bzrlib.tests import TestCase, TestCaseInTempDir, TestCaseWithTransport
 import bzrlib.store as store
+import bzrlib.store.versioned
 import bzrlib.transactions as transactions
 import bzrlib.transport as transport
 from bzrlib.transport.memory import MemoryTransport




More information about the bazaar-commits mailing list