Merge lp:~spiv/bzr/pretty-decorator-vs-string-identity-718569 into lp:bzr

Proposed by Andrew Bennetts
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: no longer in the source branch.
Merged at revision: 5664
Proposed branch: lp:~spiv/bzr/pretty-decorator-vs-string-identity-718569
Merge into: lp:bzr
Diff against target: 153 lines (+47/-9)
3 files modified
bzrlib/decorators.py (+22/-8)
bzrlib/tests/test_decorators.py (+21/-1)
doc/en/release-notes/bzr-2.4.txt (+4/-0)
To merge this branch: bzr merge lp:~spiv/bzr/pretty-decorator-vs-string-identity-718569
Reviewer Review Type Date Requested Status
Vincent Ladeuil Approve
Review via email: mp+49614@code.launchpad.net

Commit message

Preserve identity of default values in the pretty decorators. (#718569)

Description of the change

This fixes bug 718569. Instead of relying on the repr() of default parameter values preserving the semantics of those values (which isn't the case of at least symbol_versioning.DEPRECATED_PARAMETER), it runs the exec statement with a scope that provides the original objects, and uses those instead.

All the existing tests for prettiness of the "pretty" decorators pass, so presumably this doesn't uglify them too much :) Correctness trumps prettiness anyway. I added new tests to catch this problem.

To post a comment you must log in.
Revision history for this message
Vincent Ladeuil (vila) wrote :

Sounds reasonable and tested. A bit scary in retrospect, nice catch !

review: Approve
Revision history for this message
Andrew Bennetts (spiv) wrote :

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzrlib/decorators.py'
2--- bzrlib/decorators.py 2010-02-17 17:11:16 +0000
3+++ bzrlib/decorators.py 2011-02-14 12:09:20 +0000
4@@ -30,14 +30,16 @@
5 def _get_parameters(func):
6 """Recreate the parameters for a function using introspection.
7
8- :return: (function_params, calling_params)
9+ :return: (function_params, calling_params, default_values)
10 function_params: is a string representing the parameters of the
11 function. (such as "a, b, c=None, d=1")
12 This is used in the function declaration.
13 calling_params: is another string representing how you would call the
14 function with the correct parameters. (such as "a, b, c=c, d=d")
15- Assuming you sued function_params in the function declaration, this
16+ Assuming you used function_params in the function declaration, this
17 is the parameters to put in the function call.
18+ default_values_block: a dict with the default values to be passed as
19+ the scope for the 'exec' statement.
20
21 For example:
22
23@@ -49,9 +51,15 @@
24 # it globally.
25 import inspect
26 args, varargs, varkw, defaults = inspect.getargspec(func)
27+ defaults_dict = {}
28+ def formatvalue(value):
29+ default_name = '__default_%d' % len(defaults_dict)
30+ defaults_dict[default_name] = value
31+ return '=' + default_name
32 formatted = inspect.formatargspec(args, varargs=varargs,
33 varkw=varkw,
34- defaults=defaults)
35+ defaults=defaults,
36+ formatvalue=formatvalue)
37 if defaults is None:
38 args_passed = args
39 else:
40@@ -65,7 +73,7 @@
41 args_passed.append('**' + varkw)
42 args_passed = ', '.join(args_passed)
43
44- return formatted[1:-1], args_passed
45+ return formatted[1:-1], args_passed, defaults_dict
46
47
48 def _pretty_needs_read_lock(unbound):
49@@ -107,14 +115,17 @@
50 return result
51 read_locked = %(name)s_read_locked
52 """
53- params, passed_params = _get_parameters(unbound)
54+ params, passed_params, defaults_dict = _get_parameters(unbound)
55 variables = {'name':unbound.__name__,
56 'params':params,
57 'passed_params':passed_params,
58 }
59 func_def = template % variables
60
61- exec func_def in locals()
62+ scope = dict(defaults_dict)
63+ scope['unbound'] = unbound
64+ exec func_def in scope
65+ read_locked = scope['read_locked']
66
67 read_locked.__doc__ = unbound.__doc__
68 read_locked.__name__ = unbound.__name__
69@@ -172,14 +183,17 @@
70 return result
71 write_locked = %(name)s_write_locked
72 """
73- params, passed_params = _get_parameters(unbound)
74+ params, passed_params, defaults_dict = _get_parameters(unbound)
75 variables = {'name':unbound.__name__,
76 'params':params,
77 'passed_params':passed_params,
78 }
79 func_def = template % variables
80
81- exec func_def in locals()
82+ scope = dict(defaults_dict)
83+ scope['unbound'] = unbound
84+ exec func_def in scope
85+ write_locked = scope['write_locked']
86
87 write_locked.__doc__ = unbound.__doc__
88 write_locked.__name__ = unbound.__name__
89
90=== modified file 'bzrlib/tests/test_decorators.py'
91--- bzrlib/tests/test_decorators.py 2010-04-23 08:51:52 +0000
92+++ bzrlib/tests/test_decorators.py 2011-02-14 12:09:20 +0000
93@@ -27,11 +27,13 @@
94 pass
95
96
97-def create_decorator_sample(style, unlock_error=None):
98+def create_decorator_sample(style, unlock_error=None, meth=None):
99 """Create a DecoratorSample object, using specific lock operators.
100
101 :param style: The type of lock decorators to use (fast/pretty/None)
102 :param unlock_error: If specified, an error to raise from unlock.
103+ :param meth: a function to be decorated and added as a 'meth_read' and
104+ 'meth_write' to the object.
105 :return: An instantiated DecoratorSample object.
106 """
107
108@@ -92,6 +94,10 @@
109 self.actions.append('fail_during_write')
110 raise TypeError('during write')
111
112+ if meth is not None:
113+ meth_read = needs_read_lock(meth)
114+ meth_write = needs_write_lock(meth)
115+
116 return DecoratorSample()
117
118
119@@ -149,6 +155,20 @@
120 self.assertEqual(['lock_write', ('bank', 'bar', 'bing'),
121 'unlock_fail'], sam.actions)
122
123+ def test_read_lock_preserves_default_str_kwarg_identity(self):
124+ a_constant = 'A str used as a constant'
125+ def meth(self, param=a_constant):
126+ return param
127+ sam = create_decorator_sample(self._decorator_style, meth=meth)
128+ self.assertIs(a_constant, sam.meth_read())
129+
130+ def test_write_lock_preserves_default_str_kwarg_identity(self):
131+ a_constant = 'A str used as a constant'
132+ def meth(self, param=a_constant):
133+ return param
134+ sam = create_decorator_sample(self._decorator_style, meth=meth)
135+ self.assertIs(a_constant, sam.meth_write())
136+
137
138 class TestFastDecoratorActions(TestDecoratorActions):
139
140
141=== modified file 'doc/en/release-notes/bzr-2.4.txt'
142--- doc/en/release-notes/bzr-2.4.txt 2011-02-11 17:12:35 +0000
143+++ doc/en/release-notes/bzr-2.4.txt 2011-02-14 12:09:20 +0000
144@@ -90,6 +90,10 @@
145 ``bzr plugins`` and in crash reports.
146 (#704195, Martin Pool)
147
148+* The "pretty" version of ``needs_read_lock`` and ``needs_write_lock`` now
149+ preserves the identity of default parameter values.
150+ (Andrew Bennetts, #718569)
151+
152 * ``bzr dump-btree --raw`` no longer tracebacks on a B-Tree file
153 containing no rows. (Eric Siegerman, #715508)
154