##// END OF EJS Templates
Merge pull request #4561 from minrk/for_type_clear...
Min RK -
r13976:3db536e9 merge
parent child Browse files
Show More
@@ -0,0 +1,20 b''
1 DisplayFormatter changes
2 ========================
3
4 There was no official way to query or remove callbacks in the Formatter API.
5 To remedy this, the following methods are added to :class:`BaseFormatter`:
6
7 - ``lookup(instance)`` - return appropriate callback or a given object
8 - ``lookup_by_type(type_or_str)`` - return appropriate callback for a given type or ``'mod.name'`` type string
9 - ``pop(type_or_str)`` - remove a type (by type or string).
10 Pass a second argument to avoid KeyError (like dict).
11
12 All of the above methods raise a KeyError if no match is found.
13
14 And the following methods are changed:
15
16 - ``for_type(type_or_str)`` - behaves the same as before, only adding support for ``'mod.name'``
17 type strings in addition to plain types. This removes the need for ``for_type_by_name()``,
18 but it remains for backward compatibility.
19
20
@@ -34,7 +34,9 b' from IPython.lib import pretty'
34 34 from IPython.utils.traitlets import (
35 35 Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
36 36 )
37 from IPython.utils.py3compat import unicode_to_str, with_metaclass, PY3
37 from IPython.utils.py3compat import (
38 unicode_to_str, with_metaclass, PY3, string_types,
39 )
38 40
39 41 if PY3:
40 42 from io import StringIO
@@ -46,7 +48,6 b' else:'
46 48 # The main DisplayFormatter class
47 49 #-----------------------------------------------------------------------------
48 50
49
50 51 class DisplayFormatter(Configurable):
51 52
52 53 # When set to true only the default plain text formatter will be used.
@@ -206,6 +207,22 b' class FormatterABC(with_metaclass(abc.ABCMeta, object)):'
206 207 return None
207 208
208 209
210 def _mod_name_key(typ):
211 """Return a (__module__, __name__) tuple for a type.
212
213 Used as key in Formatter.deferred_printers.
214 """
215 module = getattr(typ, '__module__', None)
216 name = getattr(typ, '__name__', None)
217 return (module, name)
218
219
220 def _get_type(obj):
221 """Return the type of an instance (old and new-style)"""
222 return getattr(obj, '__class__', None) or type(obj)
223
224 _raise_key_error = object()
225
209 226 class BaseFormatter(Configurable):
210 227 """A base formatter class that is configurable.
211 228
@@ -240,74 +257,142 b' class BaseFormatter(Configurable):'
240 257 # The singleton printers.
241 258 # Maps the IDs of the builtin singleton objects to the format functions.
242 259 singleton_printers = Dict(config=True)
243 def _singleton_printers_default(self):
244 return {}
245 260
246 261 # The type-specific printers.
247 262 # Map type objects to the format functions.
248 263 type_printers = Dict(config=True)
249 def _type_printers_default(self):
250 return {}
251 264
252 265 # The deferred-import type-specific printers.
253 266 # Map (modulename, classname) pairs to the format functions.
254 267 deferred_printers = Dict(config=True)
255 def _deferred_printers_default(self):
256 return {}
257 268
258 269 def __call__(self, obj):
259 270 """Compute the format for an object."""
260 271 if self.enabled:
261 obj_id = id(obj)
262 272 try:
263 obj_class = getattr(obj, '__class__', None) or type(obj)
264 # First try to find registered singleton printers for the type.
273 # lookup registered printer
265 274 try:
266 printer = self.singleton_printers[obj_id]
267 except (TypeError, KeyError):
275 printer = self.lookup(obj)
276 except KeyError:
268 277 pass
269 278 else:
270 279 return printer(obj)
271 # Next look for type_printers.
272 for cls in pretty._get_mro(obj_class):
273 if cls in self.type_printers:
274 return self.type_printers[cls](obj)
275 else:
276 printer = self._in_deferred_types(cls)
277 if printer is not None:
278 return printer(obj)
279 # Finally look for special method names.
280 if hasattr(obj_class, self.print_method):
281 printer = getattr(obj_class, self.print_method)
282 return printer(obj)
280 # Finally look for special method names
281 method = pretty._safe_getattr(obj, self.print_method, None)
282 if method is not None:
283 return method()
283 284 return None
284 285 except Exception:
285 286 pass
286 287 else:
287 288 return None
289
290 def __contains__(self, typ):
291 """map in to lookup_by_type"""
292 try:
293 self.lookup_by_type(typ)
294 except KeyError:
295 return False
296 else:
297 return True
298
299 def lookup(self, obj):
300 """Look up the formatter for a given instance.
301
302 Parameters
303 ----------
304 obj : object instance
288 305
289 def for_type(self, typ, func):
290 """Add a format function for a given type.
306 Returns
307 -------
308 f : callable
309 The registered formatting callable for the type.
310
311 Raises
312 ------
313 KeyError if the type has not been registered.
314 """
315 # look for singleton first
316 obj_id = id(obj)
317 if obj_id in self.singleton_printers:
318 return self.singleton_printers[obj_id]
319 # then lookup by type
320 return self.lookup_by_type(_get_type(obj))
321
322 def lookup_by_type(self, typ):
323 """Look up the registered formatter for a type.
324
325 Parameters
326 ----------
327 typ : type or '__module__.__name__' string for a type
328
329 Returns
330 -------
331 f : callable
332 The registered formatting callable for the type.
333
334 Raises
335 ------
336 KeyError if the type has not been registered.
337 """
338 if isinstance(typ, string_types):
339 typ_key = tuple(typ.rsplit('.',1))
340 if typ_key not in self.deferred_printers:
341 # We may have it cached in the type map. We will have to
342 # iterate over all of the types to check.
343 for cls in self.type_printers:
344 if _mod_name_key(cls) == typ_key:
345 return self.type_printers[cls]
346 else:
347 return self.deferred_printers[typ_key]
348 else:
349 for cls in pretty._get_mro(typ):
350 if cls in self.type_printers or self._in_deferred_types(cls):
351 return self.type_printers[cls]
352
353 # If we have reached here, the lookup failed.
354 raise KeyError("No registered printer for {0!r}".format(typ))
291 355
356 def for_type(self, typ, func=None):
357 """Add a format function for a given type.
358
292 359 Parameters
293 360 -----------
294 typ : class
361 typ : type or '__module__.__name__' string for a type
295 362 The class of the object that will be formatted using `func`.
296 363 func : callable
297 The callable that will be called to compute the format data. The
298 call signature of this function is simple, it must take the
299 object to be formatted and return the raw data for the given
300 format. Subclasses may use a different call signature for the
364 A callable for computing the format data.
365 `func` will be called with the object to be formatted,
366 and will return the raw data in this formatter's format.
367 Subclasses may use a different call signature for the
301 368 `func` argument.
369
370 If `func` is None or not specified, there will be no change,
371 only returning the current value.
372
373 Returns
374 -------
375 oldfunc : callable
376 The currently registered callable.
377 If you are registering a new formatter,
378 this will be the previous value (to enable restoring later).
302 379 """
303 oldfunc = self.type_printers.get(typ, None)
380 # if string given, interpret as 'pkg.module.class_name'
381 if isinstance(typ, string_types):
382 type_module, type_name = typ.rsplit('.', 1)
383 return self.for_type_by_name(type_module, type_name, func)
384
385 try:
386 oldfunc = self.lookup_by_type(typ)
387 except KeyError:
388 oldfunc = None
389
304 390 if func is not None:
305 # To support easy restoration of old printers, we need to ignore
306 # Nones.
307 391 self.type_printers[typ] = func
392
308 393 return oldfunc
309 394
310 def for_type_by_name(self, type_module, type_name, func):
395 def for_type_by_name(self, type_module, type_name, func=None):
311 396 """Add a format function for a type specified by the full dotted
312 397 module and name of the type, rather than the type of the object.
313 398
@@ -319,37 +404,89 b' class BaseFormatter(Configurable):'
319 404 type_name : str
320 405 The name of the type (the class name), like ``dtype``
321 406 func : callable
322 The callable that will be called to compute the format data. The
323 call signature of this function is simple, it must take the
324 object to be formatted and return the raw data for the given
325 format. Subclasses may use a different call signature for the
407 A callable for computing the format data.
408 `func` will be called with the object to be formatted,
409 and will return the raw data in this formatter's format.
410 Subclasses may use a different call signature for the
326 411 `func` argument.
412
413 If `func` is None or unspecified, there will be no change,
414 only returning the current value.
415
416 Returns
417 -------
418 oldfunc : callable
419 The currently registered callable.
420 If you are registering a new formatter,
421 this will be the previous value (to enable restoring later).
327 422 """
328 423 key = (type_module, type_name)
329 oldfunc = self.deferred_printers.get(key, None)
424
425 try:
426 oldfunc = self.lookup_by_type("%s.%s" % key)
427 except KeyError:
428 oldfunc = None
429
330 430 if func is not None:
331 # To support easy restoration of old printers, we need to ignore
332 # Nones.
333 431 self.deferred_printers[key] = func
334 432 return oldfunc
433
434 def pop(self, typ, default=_raise_key_error):
435 """Pop a formatter for the given type.
436
437 Parameters
438 ----------
439 typ : type or '__module__.__name__' string for a type
440 default : object
441 value to be returned if no formatter is registered for typ.
442
443 Returns
444 -------
445 obj : object
446 The last registered object for the type.
447
448 Raises
449 ------
450 KeyError if the type is not registered and default is not specified.
451 """
452
453 if isinstance(typ, string_types):
454 typ_key = tuple(typ.rsplit('.',1))
455 if typ_key not in self.deferred_printers:
456 # We may have it cached in the type map. We will have to
457 # iterate over all of the types to check.
458 for cls in self.type_printers:
459 if _mod_name_key(cls) == typ_key:
460 old = self.type_printers.pop(cls)
461 break
462 else:
463 old = default
464 else:
465 old = self.deferred_printers.pop(typ_key)
466 else:
467 if typ in self.type_printers:
468 old = self.type_printers.pop(typ)
469 else:
470 old = self.deferred_printers.pop(_mod_name_key(typ), default)
471 if old is _raise_key_error:
472 raise KeyError("No registered value for {0!r}".format(typ))
473 return old
335 474
336 475 def _in_deferred_types(self, cls):
337 476 """
338 477 Check if the given class is specified in the deferred type registry.
339 478
340 Returns the printer from the registry if it exists, and None if the
341 class is not in the registry. Successful matches will be moved to the
342 regular type registry for future use.
479 Successful matches will be moved to the regular type registry for future use.
343 480 """
344 481 mod = getattr(cls, '__module__', None)
345 482 name = getattr(cls, '__name__', None)
346 483 key = (mod, name)
347 printer = None
348 484 if key in self.deferred_printers:
349 485 # Move the printer over to the regular registry.
350 486 printer = self.deferred_printers.pop(key)
351 487 self.type_printers[cls] = printer
352 return printer
488 return True
489 return False
353 490
354 491
355 492 class PlainTextFormatter(BaseFormatter):
@@ -175,13 +175,13 b' def select_figure_format(shell, fmt):'
175 175 png_formatter = shell.display_formatter.formatters['image/png']
176 176
177 177 if fmt == 'png':
178 svg_formatter.type_printers.pop(Figure, None)
178 svg_formatter.pop(Figure, None)
179 179 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
180 180 elif fmt in ('png2x', 'retina'):
181 svg_formatter.type_printers.pop(Figure, None)
181 svg_formatter.pop(Figure, None)
182 182 png_formatter.for_type(Figure, retina_figure)
183 183 elif fmt == 'svg':
184 png_formatter.type_printers.pop(Figure, None)
184 png_formatter.pop(Figure, None)
185 185 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
186 186 else:
187 187 raise ValueError("supported formats are: 'png', 'retina', 'svg', not %r" % fmt)
@@ -9,7 +9,7 b' except:'
9 9 numpy = None
10 10 import nose.tools as nt
11 11
12 from IPython.core.formatters import PlainTextFormatter
12 from IPython.core.formatters import PlainTextFormatter, _mod_name_key
13 13
14 14 class A(object):
15 15 def __repr__(self):
@@ -19,6 +19,9 b' class B(A):'
19 19 def __repr__(self):
20 20 return 'B()'
21 21
22 class C:
23 pass
24
22 25 class BadPretty(object):
23 26 _repr_pretty_ = None
24 27
@@ -87,4 +90,145 b' def test_bad_precision():'
87 90 nt.assert_raises(ValueError, set_fp, 'foo')
88 91 nt.assert_raises(ValueError, set_fp, -1)
89 92
93 def test_for_type():
94 f = PlainTextFormatter()
95
96 # initial return, None
97 nt.assert_is(f.for_type(C, foo_printer), None)
98 # no func queries
99 nt.assert_is(f.for_type(C), foo_printer)
100 # shouldn't change anything
101 nt.assert_is(f.for_type(C), foo_printer)
102 # None should do the same
103 nt.assert_is(f.for_type(C, None), foo_printer)
104 nt.assert_is(f.for_type(C, None), foo_printer)
105
106 def test_for_type_string():
107 f = PlainTextFormatter()
108
109 mod = C.__module__
110
111 type_str = '%s.%s' % (C.__module__, 'C')
112
113 # initial return, None
114 nt.assert_is(f.for_type(type_str, foo_printer), None)
115 # no func queries
116 nt.assert_is(f.for_type(type_str), foo_printer)
117 nt.assert_in(_mod_name_key(C), f.deferred_printers)
118 nt.assert_is(f.for_type(C), foo_printer)
119 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
120 nt.assert_in(C, f.type_printers)
121
122 def test_for_type_by_name():
123 f = PlainTextFormatter()
124
125 mod = C.__module__
126
127 # initial return, None
128 nt.assert_is(f.for_type_by_name(mod, 'C', foo_printer), None)
129 # no func queries
130 nt.assert_is(f.for_type_by_name(mod, 'C'), foo_printer)
131 # shouldn't change anything
132 nt.assert_is(f.for_type_by_name(mod, 'C'), foo_printer)
133 # None should do the same
134 nt.assert_is(f.for_type_by_name(mod, 'C', None), foo_printer)
135 nt.assert_is(f.for_type_by_name(mod, 'C', None), foo_printer)
136
137 def test_lookup():
138 f = PlainTextFormatter()
139
140 f.for_type(C, foo_printer)
141 nt.assert_is(f.lookup(C()), foo_printer)
142 with nt.assert_raises(KeyError):
143 f.lookup(A())
144
145 def test_lookup_string():
146 f = PlainTextFormatter()
147 type_str = '%s.%s' % (C.__module__, 'C')
148
149 f.for_type(type_str, foo_printer)
150 nt.assert_is(f.lookup(C()), foo_printer)
151 # should move from deferred to imported dict
152 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
153 nt.assert_in(C, f.type_printers)
154
155 def test_lookup_by_type():
156 f = PlainTextFormatter()
157 f.for_type(C, foo_printer)
158 nt.assert_is(f.lookup_by_type(C), foo_printer)
159 type_str = '%s.%s' % (C.__module__, 'C')
160 with nt.assert_raises(KeyError):
161 f.lookup_by_type(A)
162
163 def test_lookup_by_type_string():
164 f = PlainTextFormatter()
165 type_str = '%s.%s' % (C.__module__, 'C')
166 f.for_type(type_str, foo_printer)
167
168 # verify insertion
169 nt.assert_in(_mod_name_key(C), f.deferred_printers)
170 nt.assert_not_in(C, f.type_printers)
171
172 nt.assert_is(f.lookup_by_type(type_str), foo_printer)
173 # lookup by string doesn't cause import
174 nt.assert_in(_mod_name_key(C), f.deferred_printers)
175 nt.assert_not_in(C, f.type_printers)
176
177 nt.assert_is(f.lookup_by_type(C), foo_printer)
178 # should move from deferred to imported dict
179 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
180 nt.assert_in(C, f.type_printers)
181
182 def test_in_formatter():
183 f = PlainTextFormatter()
184 f.for_type(C, foo_printer)
185 type_str = '%s.%s' % (C.__module__, 'C')
186 nt.assert_in(C, f)
187 nt.assert_in(type_str, f)
188
189 def test_string_in_formatter():
190 f = PlainTextFormatter()
191 type_str = '%s.%s' % (C.__module__, 'C')
192 f.for_type(type_str, foo_printer)
193 nt.assert_in(type_str, f)
194 nt.assert_in(C, f)
195
196 def test_pop():
197 f = PlainTextFormatter()
198 f.for_type(C, foo_printer)
199 nt.assert_is(f.lookup_by_type(C), foo_printer)
200 nt.assert_is(f.pop(C, None), foo_printer)
201 f.for_type(C, foo_printer)
202 nt.assert_is(f.pop(C), foo_printer)
203 with nt.assert_raises(KeyError):
204 f.lookup_by_type(C)
205 with nt.assert_raises(KeyError):
206 f.pop(C)
207 with nt.assert_raises(KeyError):
208 f.pop(A)
209 nt.assert_is(f.pop(A, None), None)
210
211 def test_pop_string():
212 f = PlainTextFormatter()
213 type_str = '%s.%s' % (C.__module__, 'C')
214
215 with nt.assert_raises(KeyError):
216 f.pop(type_str)
217
218 f.for_type(type_str, foo_printer)
219 f.pop(type_str)
220 with nt.assert_raises(KeyError):
221 f.lookup_by_type(C)
222 with nt.assert_raises(KeyError):
223 f.pop(type_str)
224
225 f.for_type(C, foo_printer)
226 nt.assert_is(f.pop(type_str, None), foo_printer)
227 with nt.assert_raises(KeyError):
228 f.lookup_by_type(C)
229 with nt.assert_raises(KeyError):
230 f.pop(type_str)
231 nt.assert_is(f.pop(type_str, None), None)
232
233
90 234
General Comments 0
You need to be logged in to leave comments. Login now