Merge lp:~garyvdm/bzr/register_lazy_decorated into lp:~bzr/bzr/trunk-old

Proposed by Gary van der Merwe
Status: Work in progress
Proposed branch: lp:~garyvdm/bzr/register_lazy_decorated
Merge into: lp:~bzr/bzr/trunk-old
Diff against target: 168 lines
To merge this branch: bzr merge lp:~garyvdm/bzr/register_lazy_decorated
Reviewer Review Type Date Requested Status
Robert Collins (community) Needs Fixing
Review via email: mp+8430@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Gary van der Merwe (garyvdm) wrote :

Make it possible to register decorate commands lazily.

Fixes Bug 304154.

Revision history for this message
Marius Kruger (amanica) wrote :

thanks for working on this.

some minor notes:
I'd prefer if this doesn't use exception handling to decide
what the previous command is, rather check it:

- try:
+ if key in self:
             previous = self.get(key)
- except KeyError:
+ else:
             previous = _builtin_commands().get(key

There are some lines longer than 79 chars:
commands.py line 116 and 138.
There are also a bunch of blank lines with whitespace, which can be cleaned up.

other than that it looks fine to me.
(I haven't actually tried out this functionality yet, but I'd like to soon.)
bb:tweak

Revision history for this message
Robert Collins (lifeless) wrote :

On Sun, 2009-07-12 at 21:08 +0000, Marius Kruger wrote:
> thanks for working on this.
>
> some minor notes:
> I'd prefer if this doesn't use exception handling to decide
> what the previous command is, rather check it:
>
> - try:
> + if key in self:
> previous = self.get(key)
> - except KeyError:
> + else:
> previous = _builtin_commands().get(key

This requires two lookups ('key in self' and 'self.get') vs one. Whats
the objects to an exception?

(Another way to write it that has neither an exception nor double
lookups is;

previous = self.get(key, None)
if previous is None:
    previous = _builtin_commands().get(key)

A further issue is that _builtin_cmds + self.get isn't a full set of
commands. It would better to call
try:
    previous = get_cmd_object(cmd_name)
except errors.BzrCommandError:
    previous = None

except that this will trigger the missing command lookup hook. I'll look
at fixing that hook to support this use case (though I'm rather of the
opinion that its overly protective these days).

For now though, to be robust, you should say 'if name in
all_command_names()'.

-Rob

Revision history for this message
Robert Collins (lifeless) wrote :

Is it possible to put your test helpers 'decorated_command' and
'fake_command' into the test_commands python module? There is no need to
have them separate as far as I can tell, and if you are relying on them
not being imported when the test runs - well python won't permit you to
rely on that for a number of reasons.

'fake' as a command name is one that might actually get used someday.
How about using one of the already used test command names rather than
increasing the overlap between testing namespace and real?

I commented in another mail about the use of _builtin_cmds(); I see that
you're actually copying another use of it. How about turning the
existing use into a helper call? This will prevent code duplicatin.

I think I'd rather see *both* uses of it go away, but thats a different
proposition and I'm writing mail to the list about it now.

-Rob

Revision history for this message
Marius Kruger (amanica) wrote :

2009/7/12 Robert Collins <email address hidden>:
> On Sun, 2009-07-12 at 21:08 +0000, Marius Kruger wrote:
...
>> I'd prefer if this doesn't use exception handling to decide
>> what the previous command is, rather check it:
>>
>> -        try:
>> +        if key in self:
>>              previous = self.get(key)
>> -        except KeyError:
>> +        else:
>>              previous = _builtin_commands().get(key
>
> This requires two lookups ('key in self' and 'self.get') vs one. Whats
> the objects to an exception?

Maybe its just some of my java baggage talking, but in general I don't think
its good practice to use exception handling to determine normal code flow.
It can have hidden side effects, like catching unrelated KeyErrors
or have additional exception-creating-overhead, although I do agree
that it is sometimes more efficient just to try something in stead of
checking if you can.
Anyway if you're ok with this, its fine.

Revision history for this message
Robert Collins (lifeless) wrote :

In python its considered better to ask forgiveness rather than
permission. The pattern of checking first, 'look before you leap' is
often associated with poor performance and/or complex code.

-Rob

Revision history for this message
Robert Collins (lifeless) wrote :

Based on previous discussion

review: Needs Fixing

Unmerged revisions

4517. By Gary van der Merwe

Make it possible to register decorate commands lazily.

4516. By Canonical.com Patch Queue Manager <email address hidden>

(mbp) add xfail tests for bug 395556

4515. By Canonical.com Patch Queue Manager <email address hidden>

(mbp) integrated community patches to docs and OS locks

4514. By Canonical.com Patch Queue Manager <email address hidden>

(bialix) launchpad plugin: import webbrowse should be explicit and
 never lazy, otherwise bzr.exe lacks this module.

4513. By Canonical.com Patch Queue Manager <email address hidden>

(mbp) move hpss vfs tracebacks to -Dhpssvfs

4512. By Canonical.com Patch Queue Manager <email address hidden>

(Russel Winder) Add python2.6 to the list of KNOWN_PYTHONS for
 reinvoking.

4511. By Canonical.com Patch Queue Manager <email address hidden>

Mark the known failures as such on OSX

4510. By Canonical.com Patch Queue Manager <email address hidden>

(mbp) Fix for unbound variable in reconfiguring lightweight checkout

4509. By Canonical.com Patch Queue Manager <email address hidden>

(jml) Trap gaierror and re-raise ConnectionError in Launchpad
 directory service,
 giving a nicer error message when DNS lookup fails. Fixes bug 247958.

4508. By Canonical.com Patch Queue Manager <email address hidden>

(Andy Kilner) Leave tildes as they are in transport URLs.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2009-08-30 23:51:10 +0000
+++ NEWS 2009-08-31 04:37:26 +0000
@@ -767,6 +767,10 @@
767 improve memory overhead and performance of committing large files.767 improve memory overhead and performance of committing large files.
768 (Currently a private api, used only by commit). (John Arbash Meinel)768 (Currently a private api, used only by commit). (John Arbash Meinel)
769769
770* ``commands.CommandRegistry.register_lazy`` now has a decorated parameter
771 to make it possible to register decorate commands lazily.
772 (Gary van der Merwe, #304154)
773
770774
771Improvements775Improvements
772************776************
773777
=== modified file 'bzrlib/commands.py'
--- bzrlib/commands.py 2009-06-19 09:06:56 +0000
+++ bzrlib/commands.py 2009-08-31 04:37:26 +0000
@@ -81,6 +81,14 @@
81 return _unsquish_command_name(command_name)81 return _unsquish_command_name(command_name)
82 else:82 else:
83 return command_name83 return command_name
84
85 def _report_overwrite(self, command_name, module_name, prev_module_name):
86 trace.log_error('Two plugins defined the same command: %r' %
87 command_name)
88 trace.log_error('Not loading the one in %r' %
89 module_name)
90 trace.log_error('Previously this command was registered from %r' %
91 prev_module_name)
8492
85 def register(self, cmd, decorate=False):93 def register(self, cmd, decorate=False):
86 """Utility function to help register a command94 """Utility function to help register a command
@@ -101,23 +109,34 @@
101 registry.Registry.register(self, k_unsquished, cmd,109 registry.Registry.register(self, k_unsquished, cmd,
102 override_existing=decorate, info=info)110 override_existing=decorate, info=info)
103 except KeyError:111 except KeyError:
104 trace.log_error('Two plugins defined the same command: %r' % k)112 self._report_overwrite(k, sys.modules[cmd.__module__],
105 trace.log_error('Not loading the one in %r' %113 sys.modules[previous.__module__])
106 sys.modules[cmd.__module__])
107 trace.log_error('Previously this command was registered from %r' %
108 sys.modules[previous.__module__])
109 return previous114 return previous
110115
111 def register_lazy(self, command_name, aliases, module_name):116 def register_lazy(self, command_name, aliases, module_name, decorate=False):
112 """Register a command without loading its module.117 """Register a command without loading its module.
113118
114 :param command_name: The primary name of the command.119 :param command_name: The primary name of the command.
115 :param aliases: A list of aliases for the command.120 :param aliases: A list of aliases for the command.
116 :module_name: The module that the command lives in.121 :param module_name: The module that the command lives in.
122 :param decorate: If true, allow overriding an existing command
123 of the same name; the old command is returned by this function.
124 Otherwise it is an error to try to override an existing command.
117 """125 """
118 key = self._get_name(command_name)126 key = self._get_name(command_name)
119 registry.Registry.register_lazy(self, key, module_name, command_name,127 try:
120 info=CommandInfo(aliases))128 previous = self.get(key)
129 except KeyError:
130 previous = _builtin_commands().get(key)
131
132 try:
133 registry.Registry.register_lazy(self, key, module_name, command_name,
134 info=CommandInfo(aliases),
135 override_existing=decorate)
136 except KeyError:
137 self._report_overwrite(command_name, module_name,
138 sys.modules[previous.__module__])
139 return previous
121140
122141
123plugin_cmds = CommandRegistry()142plugin_cmds = CommandRegistry()
124143
=== added file 'bzrlib/tests/decorated_command.py'
--- bzrlib/tests/decorated_command.py 1970-01-01 00:00:00 +0000
+++ bzrlib/tests/decorated_command.py 2009-08-31 04:37:26 +0000
@@ -0,0 +1,24 @@
1# Copyright (C) 2009 Canonical Ltd
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17
18# used by bzrlib.tests.test_commands.TestRegisterDecorated
19
20from bzrlib.commands import Command
21
22class cmd_fake(Command):
23
24 decorated = True
025
=== modified file 'bzrlib/tests/fake_command.py'
--- bzrlib/tests/fake_command.py 2009-03-23 14:59:43 +0000
+++ bzrlib/tests/fake_command.py 2009-08-31 04:37:26 +0000
@@ -17,7 +17,8 @@
17from bzrlib.tests import test_commands17from bzrlib.tests import test_commands
18test_commands.lazy_command_imported = True18test_commands.lazy_command_imported = True
1919
2020from bzrlib.commands import Command
21class cmd_fake(object):21
2222class cmd_fake(Command):
23 pass23
24 decorated = False
2425
=== modified file 'bzrlib/tests/test_commands.py'
--- bzrlib/tests/test_commands.py 2009-05-23 21:01:51 +0000
+++ bzrlib/tests/test_commands.py 2009-08-31 04:37:26 +0000
@@ -323,3 +323,40 @@
323 cmds = list(commands.all_command_names())323 cmds = list(commands.all_command_names())
324 self.assertEqual(['called'], hook_calls)324 self.assertEqual(['called'], hook_calls)
325 self.assertSubset(['foo', 'bar'], cmds)325 self.assertSubset(['foo', 'bar'], cmds)
326
327class TestRegisterDecorated(tests.TestCase):
328
329 def setUp(self):
330 tests.TestCase.setUp(self)
331 commands.install_bzr_command_hooks()
332 self.addCleanup(self.remove_fake)
333
334 @staticmethod
335 def remove_fake():
336 commands.plugin_cmds.remove('fake')
337
338 def _test_register_decorated(self, register_func,
339 base_args, decorated_args):
340
341 register_func(*base_args)
342 self.assertFalse(commands.get_cmd_object("fake").decorated)
343
344 register_func(*decorated_args)
345 # Should not have accepcted the decorated command.
346 self.assertFalse(commands.get_cmd_object("fake").decorated)
347
348 register_func(*decorated_args, decorate=True)
349 self.assertTrue(commands.get_cmd_object("fake").decorated)
350
351 def test_register_decorated(self):
352 from bzrlib.tests import fake_command, decorated_command
353
354 self._test_register_decorated(commands.plugin_cmds.register,
355 [fake_command.cmd_fake],
356 [decorated_command.cmd_fake])
357
358 def test_register_lazy_decorated(self):
359 self._test_register_decorated(commands.plugin_cmds.register_lazy,
360 ["cmd_fake", [], "bzrlib.tests.fake_command"],
361 ["cmd_fake", [], "bzrlib.tests.decorated_command"])
362