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
1=== modified file 'NEWS'
2--- NEWS 2009-08-30 23:51:10 +0000
3+++ NEWS 2009-08-31 04:37:26 +0000
4@@ -767,6 +767,10 @@
5 improve memory overhead and performance of committing large files.
6 (Currently a private api, used only by commit). (John Arbash Meinel)
7
8+* ``commands.CommandRegistry.register_lazy`` now has a decorated parameter
9+ to make it possible to register decorate commands lazily.
10+ (Gary van der Merwe, #304154)
11+
12
13 Improvements
14 ************
15
16=== modified file 'bzrlib/commands.py'
17--- bzrlib/commands.py 2009-06-19 09:06:56 +0000
18+++ bzrlib/commands.py 2009-08-31 04:37:26 +0000
19@@ -81,6 +81,14 @@
20 return _unsquish_command_name(command_name)
21 else:
22 return command_name
23+
24+ def _report_overwrite(self, command_name, module_name, prev_module_name):
25+ trace.log_error('Two plugins defined the same command: %r' %
26+ command_name)
27+ trace.log_error('Not loading the one in %r' %
28+ module_name)
29+ trace.log_error('Previously this command was registered from %r' %
30+ prev_module_name)
31
32 def register(self, cmd, decorate=False):
33 """Utility function to help register a command
34@@ -101,23 +109,34 @@
35 registry.Registry.register(self, k_unsquished, cmd,
36 override_existing=decorate, info=info)
37 except KeyError:
38- trace.log_error('Two plugins defined the same command: %r' % k)
39- trace.log_error('Not loading the one in %r' %
40- sys.modules[cmd.__module__])
41- trace.log_error('Previously this command was registered from %r' %
42- sys.modules[previous.__module__])
43+ self._report_overwrite(k, sys.modules[cmd.__module__],
44+ sys.modules[previous.__module__])
45 return previous
46
47- def register_lazy(self, command_name, aliases, module_name):
48+ def register_lazy(self, command_name, aliases, module_name, decorate=False):
49 """Register a command without loading its module.
50
51 :param command_name: The primary name of the command.
52 :param aliases: A list of aliases for the command.
53- :module_name: The module that the command lives in.
54+ :param module_name: The module that the command lives in.
55+ :param decorate: If true, allow overriding an existing command
56+ of the same name; the old command is returned by this function.
57+ Otherwise it is an error to try to override an existing command.
58 """
59 key = self._get_name(command_name)
60- registry.Registry.register_lazy(self, key, module_name, command_name,
61- info=CommandInfo(aliases))
62+ try:
63+ previous = self.get(key)
64+ except KeyError:
65+ previous = _builtin_commands().get(key)
66+
67+ try:
68+ registry.Registry.register_lazy(self, key, module_name, command_name,
69+ info=CommandInfo(aliases),
70+ override_existing=decorate)
71+ except KeyError:
72+ self._report_overwrite(command_name, module_name,
73+ sys.modules[previous.__module__])
74+ return previous
75
76
77 plugin_cmds = CommandRegistry()
78
79=== added file 'bzrlib/tests/decorated_command.py'
80--- bzrlib/tests/decorated_command.py 1970-01-01 00:00:00 +0000
81+++ bzrlib/tests/decorated_command.py 2009-08-31 04:37:26 +0000
82@@ -0,0 +1,24 @@
83+# Copyright (C) 2009 Canonical Ltd
84+#
85+# This program is free software; you can redistribute it and/or modify
86+# it under the terms of the GNU General Public License as published by
87+# the Free Software Foundation; either version 2 of the License, or
88+# (at your option) any later version.
89+#
90+# This program is distributed in the hope that it will be useful,
91+# but WITHOUT ANY WARRANTY; without even the implied warranty of
92+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
93+# GNU General Public License for more details.
94+#
95+# You should have received a copy of the GNU General Public License
96+# along with this program; if not, write to the Free Software
97+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
98+
99+
100+# used by bzrlib.tests.test_commands.TestRegisterDecorated
101+
102+from bzrlib.commands import Command
103+
104+class cmd_fake(Command):
105+
106+ decorated = True
107
108=== modified file 'bzrlib/tests/fake_command.py'
109--- bzrlib/tests/fake_command.py 2009-03-23 14:59:43 +0000
110+++ bzrlib/tests/fake_command.py 2009-08-31 04:37:26 +0000
111@@ -17,7 +17,8 @@
112 from bzrlib.tests import test_commands
113 test_commands.lazy_command_imported = True
114
115-
116-class cmd_fake(object):
117-
118- pass
119+from bzrlib.commands import Command
120+
121+class cmd_fake(Command):
122+
123+ decorated = False
124
125=== modified file 'bzrlib/tests/test_commands.py'
126--- bzrlib/tests/test_commands.py 2009-05-23 21:01:51 +0000
127+++ bzrlib/tests/test_commands.py 2009-08-31 04:37:26 +0000
128@@ -323,3 +323,40 @@
129 cmds = list(commands.all_command_names())
130 self.assertEqual(['called'], hook_calls)
131 self.assertSubset(['foo', 'bar'], cmds)
132+
133+class TestRegisterDecorated(tests.TestCase):
134+
135+ def setUp(self):
136+ tests.TestCase.setUp(self)
137+ commands.install_bzr_command_hooks()
138+ self.addCleanup(self.remove_fake)
139+
140+ @staticmethod
141+ def remove_fake():
142+ commands.plugin_cmds.remove('fake')
143+
144+ def _test_register_decorated(self, register_func,
145+ base_args, decorated_args):
146+
147+ register_func(*base_args)
148+ self.assertFalse(commands.get_cmd_object("fake").decorated)
149+
150+ register_func(*decorated_args)
151+ # Should not have accepcted the decorated command.
152+ self.assertFalse(commands.get_cmd_object("fake").decorated)
153+
154+ register_func(*decorated_args, decorate=True)
155+ self.assertTrue(commands.get_cmd_object("fake").decorated)
156+
157+ def test_register_decorated(self):
158+ from bzrlib.tests import fake_command, decorated_command
159+
160+ self._test_register_decorated(commands.plugin_cmds.register,
161+ [fake_command.cmd_fake],
162+ [decorated_command.cmd_fake])
163+
164+ def test_register_lazy_decorated(self):
165+ self._test_register_decorated(commands.plugin_cmds.register_lazy,
166+ ["cmd_fake", [], "bzrlib.tests.fake_command"],
167+ ["cmd_fake", [], "bzrlib.tests.decorated_command"])
168+