Merge lp:~thomir-deactivatedaccount/autopilot/trunk-fix-plain-type-reprs into lp:autopilot

Proposed by Thomi Richards
Status: Merged
Approved by: Thomi Richards
Approved revision: 449
Merged at revision: 448
Proposed branch: lp:~thomir-deactivatedaccount/autopilot/trunk-fix-plain-type-reprs
Merge into: lp:autopilot
Diff against target: 531 lines (+331/-61)
2 files modified
autopilot/introspection/types.py (+115/-60)
autopilot/tests/unit/test_types.py (+216/-1)
To merge this branch: bzr merge lp:~thomir-deactivatedaccount/autopilot/trunk-fix-plain-type-reprs
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
Christopher Lee (community) Approve
Review via email: mp+207699@code.launchpad.net

This proposal supersedes a proposal from 2014-02-21.

Commit message

Make proxy object attributes look more pythonic when calling repr(...) or str(...) on them.

Description of the change

Fixes __repr__ and __str__ methods of the marshalled type classes so we hide the underlying dbus type, and make them look more pythonic in test output.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
444. By Thomi Richards

Merged trunk.

445. By Thomi Richards

Added missing test for Color unit tests.

446. By Thomi Richards

Fix flake8 issues.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Christopher Lee (veebers) wrote :

Within the tests you mention UnicodeDecode but catch a UnicodeEncodeError, is this intentional?
278 + # in Python 2.x, str(u'\2603') *should* raise a UnicodeDecode error:
279 + except UnicodeEncodeError:

review: Needs Information
447. By Thomi Richards

Fix failing tests in Python 3.3.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
448. By Thomi Richards

Fix typo in comment.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
449. By Thomi Richards

Fix tests failing.

Revision history for this message
Christopher Lee (veebers) wrote :

LGTM

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
450. By Thomi Richards

Fix failing test.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'autopilot/introspection/types.py'
2--- autopilot/introspection/types.py 2014-01-29 20:48:43 +0000
3+++ autopilot/introspection/types.py 2014-02-27 02:00:36 +0000
4@@ -46,7 +46,7 @@
5 import six
6
7 from autopilot.introspection.utilities import translate_state_keys
8-from autopilot.utilities import sleep
9+from autopilot.utilities import sleep, compatible_repr
10
11
12 logger = logging.getLogger(__name__)
13@@ -217,68 +217,73 @@
14 return _make_plain_type(value, parent=parent, name=name)
15
16
17+def _get_repr_callable_for_value(value):
18+ repr_map = {
19+ dbus.Byte: _integer_repr,
20+ dbus.Int16: _integer_repr,
21+ dbus.Int32: _integer_repr,
22+ dbus.UInt16: _integer_repr,
23+ dbus.UInt32: _integer_repr,
24+ dbus.Int64: _integer_repr,
25+ dbus.UInt64: _integer_repr,
26+ dbus.String: _text_repr,
27+ dbus.ObjectPath: _text_repr,
28+ dbus.Signature: _text_repr,
29+ dbus.ByteArray: _bytes_repr,
30+ dbus.Boolean: _boolean_repr,
31+ dbus.Dictionary: _dict_repr,
32+ dbus.Double: _float_repr,
33+ dbus.Struct: _tuple_repr,
34+ dbus.Array: _list_repr,
35+ }
36+ if not six.PY3:
37+ repr_map[dbus.UTF8String] = _bytes_repr
38+ return repr_map.get(type(value), None)
39+
40+
41+def _get_str_callable_for_value(value):
42+ str_map = {
43+ dbus.Boolean: _boolean_str,
44+ dbus.Byte: _integer_str,
45+ }
46+ return str_map.get(type(value), None)
47+
48+
49+@compatible_repr
50+def _integer_repr(self):
51+ return six.text_type(int(self))
52+
53+
54+def _create_generic_repr(target_type):
55+ return compatible_repr(lambda self: repr(target_type(self)))
56+
57+_bytes_repr = _create_generic_repr(six.binary_type)
58+_text_repr = _create_generic_repr(six.text_type)
59+_dict_repr = _create_generic_repr(dict)
60+_list_repr = _create_generic_repr(list)
61+_tuple_repr = _create_generic_repr(tuple)
62+_float_repr = _create_generic_repr(float)
63+_boolean_repr = _create_generic_repr(bool)
64+
65+
66+def _create_generic_str(target_type):
67+ return compatible_repr(lambda self: str(target_type(self)))
68+
69+
70+_boolean_str = _create_generic_str(bool)
71+_integer_str = _integer_repr
72+
73+
74 def _make_plain_type(value, parent=None, name=None):
75- def repr(self):
76- # Convert our value to the pythonic type,and call __repr__ on it.
77- # At the moment we switch based on our type, which is less than ideal.
78- if six.PY3:
79- long_type = int
80- else:
81- long_type = long
82- dbus_integer_types = (
83- dbus.Byte,
84- dbus.Int16,
85- dbus.Int32,
86- dbus.UInt16,
87- dbus.UInt32,
88- )
89-
90- dbus_string_types = (
91- dbus.String,
92- dbus.ObjectPath,
93- dbus.Signature,
94- )
95-
96- # no UTFString in python 3.
97- if six.PY3:
98- dbus_binary_types = (
99- dbus.ByteArray,
100- )
101- else:
102- dbus_binary_types = (
103- dbus.ByteArray,
104- dbus.UTF8String,
105- )
106-
107- if isinstance(self, dbus_integer_types):
108- return int(self).__repr__()
109- elif isinstance(self, (dbus.Int64, dbus.UInt64)):
110- # Python 2 integer landling is... odd. The maximum integer size
111- # changes depending on platform, so maybe we can get away with
112- # using an int, in which case we should:
113- if not six.PY3 and self <= six.MAXSIZE:
114- return int(self).__repr__()
115- return long_type(self).__repr__()
116- elif isinstance(self, dbus_string_types):
117- return six.text_type(self).__repr__()
118- elif isinstance(self, dbus.Boolean):
119- return bool(self).__repr__()
120- elif isinstance(self, dbus_binary_types):
121- return six.binary_type(self).__repr__()
122- elif isinstance(self, dbus.Dictionary):
123- return dict(self).__repr__()
124- elif isinstance(self, dbus.Double):
125- return float(self).__repr__()
126- elif isinstance(self, dbus.Struct):
127- return tuple(self).__repr__()
128- elif isinstance(self, dbus.Array):
129- return list(self).__repr__()
130- else:
131- return super(type(self), self).__repr__()
132-
133 new_type_name = type(value).__name__
134 new_type_bases = (type(value), PlainType)
135- new_type_dict = dict(parent=parent, name=name, __repr__=repr)
136+ new_type_dict = dict(parent=parent, name=name)
137+ repr_callable = _get_repr_callable_for_value(value)
138+ if repr_callable:
139+ new_type_dict['__repr__'] = repr_callable
140+ str_callable = _get_str_callable_for_value(value)
141+ if str_callable:
142+ new_type_dict['__str__'] = str_callable
143 new_type = type(new_type_name, new_type_bases, new_type_dict)
144 return new_type(value)
145
146@@ -375,6 +380,11 @@
147 def height(self):
148 return self[3]
149
150+ @compatible_repr
151+ def __repr__(self):
152+ coords = u', '.join((str(c) for c in self))
153+ return u'Rectangle(%s)' % (coords)
154+
155
156 class Point(_array_packed_type(2)):
157
158@@ -412,6 +422,10 @@
159 def y(self):
160 return self[1]
161
162+ @compatible_repr
163+ def __repr__(self):
164+ return u'Point(%d, %d)' % (self.x, self.y)
165+
166
167 class Size(_array_packed_type(2)):
168
169@@ -457,6 +471,10 @@
170 def height(self):
171 return self[1]
172
173+ @compatible_repr
174+ def __repr__(self):
175+ return u'Size(%d, %d)' % (self.w, self.h)
176+
177
178 class Color(_array_packed_type(4)):
179
180@@ -506,6 +524,15 @@
181 def alpha(self):
182 return self[3]
183
184+ @compatible_repr
185+ def __repr__(self):
186+ return u'Color(%d, %d, %d, %d)' % (
187+ self.red,
188+ self.green,
189+ self.blue,
190+ self.alpha
191+ )
192+
193
194 class DateTime(_array_packed_type(1)):
195
196@@ -603,6 +630,17 @@
197 return other == self._cached_dt
198 return super(DateTime, self).__eq__(other)
199
200+ @compatible_repr
201+ def __repr__(self):
202+ return u'DateTime(%d-%02d-%02d %02d:%02d:%02d)' % (
203+ self.year,
204+ self.month,
205+ self.day,
206+ self.hour,
207+ self.minute,
208+ self.second
209+ )
210+
211
212 class Time(_array_packed_type(4)):
213
214@@ -683,6 +721,15 @@
215 return other == self._cached_time
216 return super(Time, self).__eq__(other)
217
218+ @compatible_repr
219+ def __repr__(self):
220+ return u'Time(%02d:%02d:%02d.%03d)' % (
221+ self.hour,
222+ self.minute,
223+ self.second,
224+ self.millisecond
225+ )
226+
227
228 class Point3D(_array_packed_type(3)):
229
230@@ -725,3 +772,11 @@
231 @property
232 def z(self):
233 return self[2]
234+
235+ @compatible_repr
236+ def __repr__(self):
237+ return u'Point3D(%d, %d, %d)' % (
238+ self.x,
239+ self.y,
240+ self.z,
241+ )
242
243=== modified file 'autopilot/tests/unit/test_types.py'
244--- autopilot/tests/unit/test_types.py 2013-12-10 20:05:38 +0000
245+++ autopilot/tests/unit/test_types.py 2014-02-27 02:00:36 +0000
246@@ -38,8 +38,20 @@
247 Size,
248 Time,
249 ValueType,
250+ _integer_repr,
251+ _boolean_repr,
252+ _text_repr,
253+ _bytes_repr,
254+ _dict_repr,
255+ _list_repr,
256+ _float_repr,
257+ _tuple_repr,
258+ _get_repr_callable_for_value,
259+ _boolean_str,
260+ _integer_str,
261 )
262 from autopilot.introspection.dbus import DBusIntrospectionObject
263+from autopilot.utilities import compatible_repr
264
265
266 class PlainTypeTests(TestWithScenarios, TestCase):
267@@ -81,7 +93,21 @@
268 """repr for PlainType must be the same as the pythonic type."""
269 p = PlainType(self.t(self.v))
270
271- self.assertThat(repr(p), Equals(repr(self.v)))
272+ expected = repr(self.v)
273+ expected = expected.rstrip('L')
274+ self.assertThat(repr(p), Equals(expected))
275+
276+ def test_str(self):
277+ """str(p) for PlainType must be the same as the pythonic type."""
278+ p = PlainType(self.t(self.v))
279+ try:
280+ expected = str(self.v)
281+ observed = str(p)
282+ self.assertEqual(expected, observed)
283+ # in Python 2.x, str(u'\2603') *should* raise a UnicodeEncode error:
284+ except UnicodeEncodeError:
285+ if not six.PY2:
286+ raise
287
288 def test_wait_for_raises_RuntimeError(self):
289 """The wait_for method must raise a RuntimeError if it's called."""
290@@ -131,6 +157,15 @@
291
292 self.assertThat(r1, Equals(r2))
293
294+ def test_repr(self):
295+ expected = repr_type("Rectangle(1, 2, 3, 4)")
296+ observed = repr(Rectangle(1, 2, 3, 4))
297+ self.assertEqual(expected, observed)
298+
299+ def test_repr_equals_str(self):
300+ r = Rectangle(1, 2, 3, 4)
301+ self.assertEqual(repr(r), str(r))
302+
303
304 class PointTypeTests(TestCase):
305
306@@ -162,6 +197,15 @@
307
308 self.assertThat(p1, Equals(p2))
309
310+ def test_repr(self):
311+ expected = repr_type('Point(1, 2)')
312+ observed = repr(Point(1, 2))
313+ self.assertEqual(expected, observed)
314+
315+ def test_repr_equals_str(self):
316+ p = Point(1, 2)
317+ self.assertEqual(repr(p), str(p))
318+
319
320 class SizeTypeTests(TestCase):
321
322@@ -195,6 +239,15 @@
323
324 self.assertThat(s1, Equals(s2))
325
326+ def test_repr(self):
327+ expected = repr_type('Size(1, 2)')
328+ observed = repr(Size(1, 2))
329+ self.assertEqual(expected, observed)
330+
331+ def test_repr_equals_str(self):
332+ s = Size(3, 4)
333+ self.assertEqual(repr(s), str(s))
334+
335
336 class ColorTypeTests(TestCase):
337
338@@ -230,6 +283,15 @@
339
340 self.assertThat(c1, Equals(c2))
341
342+ def test_repr(self):
343+ expected = repr_type('Color(1, 2, 3, 4)')
344+ observed = repr(Color(1, 2, 3, 4))
345+ self.assertEqual(expected, observed)
346+
347+ def test_repr_equals_str(self):
348+ c = Color(255, 255, 255, 0)
349+ self.assertEqual(repr(c), str(c))
350+
351
352 class DateTimeTests(TestCase):
353
354@@ -278,6 +340,16 @@
355
356 self.assertThat(dt1.datetime, IsInstance(datetime))
357
358+ def test_repr(self):
359+ dt = DateTime(1377209927)
360+ expected = repr_type('DateTime(2013-08-22 22:18:47)')
361+ observed = repr(dt)
362+ self.assertEqual(expected, observed)
363+
364+ def test_repr_equals_str(self):
365+ dt = DateTime(1377209927)
366+ self.assertEqual(repr(dt), str(dt))
367+
368
369 class TimeTests(TestCase):
370
371@@ -322,6 +394,15 @@
372
373 self.assertThat(dt1.time, IsInstance(time))
374
375+ def test_repr(self):
376+ expected = repr_type('Time(01:02:03.004)')
377+ observed = repr(Time(1, 2, 3, 4))
378+ self.assertEqual(expected, observed)
379+
380+ def test_repr_equals_str(self):
381+ t = Time(2, 3, 4, 5)
382+ self.assertEqual(repr(t), str(t))
383+
384
385 class Point3DTypeTests(TestCase):
386
387@@ -367,6 +448,15 @@
388
389 self.assertThat(p1, NotEquals(p2))
390
391+ def test_repr(self):
392+ expected = repr_type('Point3D(1, 2, 3)')
393+ observed = repr(Point3D(1, 2, 3))
394+ self.assertEqual(expected, observed)
395+
396+ def test_repr_equals_str(self):
397+ p3d = Point3D(1, 2, 3)
398+ self.assertEqual(repr(p3d), str(p3d))
399+
400
401 class CreateValueInstanceTests(TestCase):
402
403@@ -693,3 +783,128 @@
404 "foo",
405 "Cannot create attribute, no data supplied"
406 )
407+
408+
409+class TypeReprTests(TestCase):
410+
411+ def test_integer_repr(self):
412+ expected = repr_type('42')
413+ observed = _integer_repr(42)
414+ self.assertEqual(expected, observed)
415+
416+ def test_dbus_int_types_all_work(self):
417+ expected = repr_type('42')
418+ int_types = (
419+ dbus.Byte,
420+ dbus.Int16,
421+ dbus.Int32,
422+ dbus.UInt16,
423+ dbus.UInt32,
424+ dbus.Int64,
425+ dbus.UInt64,
426+ )
427+ for t in int_types:
428+ observed = _integer_repr(t(42))
429+ self.assertEqual(expected, observed)
430+
431+ def test_get_repr_gets_integer_repr_for_all_integer_types(self):
432+ int_types = (
433+ dbus.Byte,
434+ dbus.Int16,
435+ dbus.Int32,
436+ dbus.UInt16,
437+ dbus.UInt32,
438+ dbus.Int64,
439+ dbus.UInt64,
440+ )
441+ for t in int_types:
442+ observed = _get_repr_callable_for_value(t(42))
443+ self.assertEqual(_integer_repr, observed)
444+
445+ def test_boolean_repr_true(self):
446+ expected = repr_type('True')
447+ for values in (True, dbus.Boolean(True)):
448+ observed = _boolean_repr(True)
449+ self.assertEqual(expected, observed)
450+
451+ def test_boolean_repr_false(self):
452+ expected = repr_type('False')
453+ for values in (False, dbus.Boolean(False)):
454+ observed = _boolean_repr(False)
455+ self.assertEqual(expected, observed)
456+
457+ def test_get_repr_gets_boolean_repr_for_dbus_boolean_type(self):
458+ observed = _get_repr_callable_for_value(dbus.Boolean(False))
459+ self.assertEqual(_boolean_repr, observed)
460+
461+ def test_text_repr_handles_dbus_string(self):
462+ unicode_text = u"plɹoʍ ollǝɥ"
463+ observed = _text_repr(dbus.String(unicode_text))
464+ self.assertEqual(repr(unicode_text), observed)
465+
466+ def test_text_repr_handles_dbus_object_path(self):
467+ path = u"/path/to/some/object"
468+ observed = _text_repr(dbus.ObjectPath(path))
469+ self.assertEqual(repr(path), observed)
470+
471+ def test_binry_repr_handles_dbys_byte_array(self):
472+ data = b'Some bytes'
473+ observed = _bytes_repr(dbus.ByteArray(data))
474+ self.assertEqual(repr(data), observed)
475+
476+ def test_get_repr_gets_bytes_repr_for_dbus_byte_array(self):
477+ observed = _get_repr_callable_for_value(dbus.ByteArray(b''))
478+ self.assertEqual(_bytes_repr, observed)
479+
480+ def test_dict_repr_handles_dbus_dictionary(self):
481+ token = dict(foo='bar')
482+ observed = _dict_repr(dbus.Dictionary(token))
483+ self.assertEqual(repr(token), observed)
484+
485+ def test_get_repr_gets_dict_repr_on_dbus_dictionary(self):
486+ observed = _get_repr_callable_for_value(dbus.Dictionary(dict()))
487+ self.assertEqual(_dict_repr, observed)
488+
489+ def test_float_repr_handles_dbus_double(self):
490+ token = 1.2345
491+ observed = _float_repr(token)
492+ self.assertEqual(repr(token), observed)
493+
494+ def test_get_repr_gets_float_repr_on_dbus_double(self):
495+ observed = _get_repr_callable_for_value(dbus.Double(0.0))
496+ self.assertEqual(_float_repr, observed)
497+
498+ def test_tuple_repr_handles_dbus_struct(self):
499+ data = (1, 2, 3)
500+ observed = _tuple_repr(dbus.Struct(data))
501+ self.assertEqual(repr(data), observed)
502+
503+ def test_get_repr_gets_tuple_repr_on_dbus_struct(self):
504+ observed = _get_repr_callable_for_value(dbus.Struct([1]))
505+ self.assertEqual(_tuple_repr, observed)
506+
507+ def test_list_repr_handles_dbus_array(self):
508+ data = [1, 2, 3]
509+ observed = _list_repr(dbus.Array(data))
510+ self.assertEqual(repr(data), observed)
511+
512+ def test_get_repr_gets_list_repr_on_dbus_array(self):
513+ observed = _get_repr_callable_for_value(dbus.Array([1]))
514+ self.assertEqual(_list_repr, observed)
515+
516+
517+class TypeStrTests(TestCase):
518+
519+ def test_boolean_str_handles_dbus_boolean(self):
520+ observed = _boolean_str(dbus.Boolean(False))
521+ self.assertEqual(str(False), observed)
522+
523+ def test_integer_str_handles_dbus_byte(self):
524+ observed = _integer_str(dbus.Byte(14))
525+ self.assertEqual(str(14), observed)
526+
527+
528+def repr_type(value):
529+ """Convert a text or bytes object into the appropriate return type for
530+ the __repr__ method."""
531+ return compatible_repr(lambda: value)()

Subscribers

People subscribed via source and target branches