##// 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 from IPython.utils.traitlets import (
34 from IPython.utils.traitlets import (
35 Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
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 if PY3:
41 if PY3:
40 from io import StringIO
42 from io import StringIO
@@ -46,7 +48,6 b' else:'
46 # The main DisplayFormatter class
48 # The main DisplayFormatter class
47 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
48
50
49
50 class DisplayFormatter(Configurable):
51 class DisplayFormatter(Configurable):
51
52
52 # When set to true only the default plain text formatter will be used.
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 return None
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 class BaseFormatter(Configurable):
226 class BaseFormatter(Configurable):
210 """A base formatter class that is configurable.
227 """A base formatter class that is configurable.
211
228
@@ -240,74 +257,142 b' class BaseFormatter(Configurable):'
240 # The singleton printers.
257 # The singleton printers.
241 # Maps the IDs of the builtin singleton objects to the format functions.
258 # Maps the IDs of the builtin singleton objects to the format functions.
242 singleton_printers = Dict(config=True)
259 singleton_printers = Dict(config=True)
243 def _singleton_printers_default(self):
244 return {}
245
260
246 # The type-specific printers.
261 # The type-specific printers.
247 # Map type objects to the format functions.
262 # Map type objects to the format functions.
248 type_printers = Dict(config=True)
263 type_printers = Dict(config=True)
249 def _type_printers_default(self):
250 return {}
251
264
252 # The deferred-import type-specific printers.
265 # The deferred-import type-specific printers.
253 # Map (modulename, classname) pairs to the format functions.
266 # Map (modulename, classname) pairs to the format functions.
254 deferred_printers = Dict(config=True)
267 deferred_printers = Dict(config=True)
255 def _deferred_printers_default(self):
256 return {}
257
268
258 def __call__(self, obj):
269 def __call__(self, obj):
259 """Compute the format for an object."""
270 """Compute the format for an object."""
260 if self.enabled:
271 if self.enabled:
261 obj_id = id(obj)
262 try:
272 try:
263 obj_class = getattr(obj, '__class__', None) or type(obj)
273 # lookup registered printer
264 # First try to find registered singleton printers for the type.
265 try:
274 try:
266 printer = self.singleton_printers[obj_id]
275 printer = self.lookup(obj)
267 except (TypeError, KeyError):
276 except KeyError:
268 pass
277 pass
269 else:
278 else:
270 return printer(obj)
279 return printer(obj)
271 # Next look for type_printers.
280 # Finally look for special method names
272 for cls in pretty._get_mro(obj_class):
281 method = pretty._safe_getattr(obj, self.print_method, None)
273 if cls in self.type_printers:
282 if method is not None:
274 return self.type_printers[cls](obj)
283 return method()
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)
283 return None
284 return None
284 except Exception:
285 except Exception:
285 pass
286 pass
286 else:
287 else:
287 return None
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):
306 Returns
290 """Add a format function for a given type.
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 Parameters
359 Parameters
293 -----------
360 -----------
294 typ : class
361 typ : type or '__module__.__name__' string for a type
295 The class of the object that will be formatted using `func`.
362 The class of the object that will be formatted using `func`.
296 func : callable
363 func : callable
297 The callable that will be called to compute the format data. The
364 A callable for computing the format data.
298 call signature of this function is simple, it must take the
365 `func` will be called with the object to be formatted,
299 object to be formatted and return the raw data for the given
366 and will return the raw data in this formatter's format.
300 format. Subclasses may use a different call signature for the
367 Subclasses may use a different call signature for the
301 `func` argument.
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 if func is not None:
390 if func is not None:
305 # To support easy restoration of old printers, we need to ignore
306 # Nones.
307 self.type_printers[typ] = func
391 self.type_printers[typ] = func
392
308 return oldfunc
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 """Add a format function for a type specified by the full dotted
396 """Add a format function for a type specified by the full dotted
312 module and name of the type, rather than the type of the object.
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 type_name : str
404 type_name : str
320 The name of the type (the class name), like ``dtype``
405 The name of the type (the class name), like ``dtype``
321 func : callable
406 func : callable
322 The callable that will be called to compute the format data. The
407 A callable for computing the format data.
323 call signature of this function is simple, it must take the
408 `func` will be called with the object to be formatted,
324 object to be formatted and return the raw data for the given
409 and will return the raw data in this formatter's format.
325 format. Subclasses may use a different call signature for the
410 Subclasses may use a different call signature for the
326 `func` argument.
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 key = (type_module, type_name)
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 if func is not None:
430 if func is not None:
331 # To support easy restoration of old printers, we need to ignore
332 # Nones.
333 self.deferred_printers[key] = func
431 self.deferred_printers[key] = func
334 return oldfunc
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 def _in_deferred_types(self, cls):
475 def _in_deferred_types(self, cls):
337 """
476 """
338 Check if the given class is specified in the deferred type registry.
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
479 Successful matches will be moved to the regular type registry for future use.
341 class is not in the registry. Successful matches will be moved to the
342 regular type registry for future use.
343 """
480 """
344 mod = getattr(cls, '__module__', None)
481 mod = getattr(cls, '__module__', None)
345 name = getattr(cls, '__name__', None)
482 name = getattr(cls, '__name__', None)
346 key = (mod, name)
483 key = (mod, name)
347 printer = None
348 if key in self.deferred_printers:
484 if key in self.deferred_printers:
349 # Move the printer over to the regular registry.
485 # Move the printer over to the regular registry.
350 printer = self.deferred_printers.pop(key)
486 printer = self.deferred_printers.pop(key)
351 self.type_printers[cls] = printer
487 self.type_printers[cls] = printer
352 return printer
488 return True
489 return False
353
490
354
491
355 class PlainTextFormatter(BaseFormatter):
492 class PlainTextFormatter(BaseFormatter):
@@ -175,13 +175,13 b' def select_figure_format(shell, fmt):'
175 png_formatter = shell.display_formatter.formatters['image/png']
175 png_formatter = shell.display_formatter.formatters['image/png']
176
176
177 if fmt == 'png':
177 if fmt == 'png':
178 svg_formatter.type_printers.pop(Figure, None)
178 svg_formatter.pop(Figure, None)
179 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
179 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
180 elif fmt in ('png2x', 'retina'):
180 elif fmt in ('png2x', 'retina'):
181 svg_formatter.type_printers.pop(Figure, None)
181 svg_formatter.pop(Figure, None)
182 png_formatter.for_type(Figure, retina_figure)
182 png_formatter.for_type(Figure, retina_figure)
183 elif fmt == 'svg':
183 elif fmt == 'svg':
184 png_formatter.type_printers.pop(Figure, None)
184 png_formatter.pop(Figure, None)
185 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
185 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
186 else:
186 else:
187 raise ValueError("supported formats are: 'png', 'retina', 'svg', not %r" % fmt)
187 raise ValueError("supported formats are: 'png', 'retina', 'svg', not %r" % fmt)
@@ -9,7 +9,7 b' except:'
9 numpy = None
9 numpy = None
10 import nose.tools as nt
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 class A(object):
14 class A(object):
15 def __repr__(self):
15 def __repr__(self):
@@ -19,6 +19,9 b' class B(A):'
19 def __repr__(self):
19 def __repr__(self):
20 return 'B()'
20 return 'B()'
21
21
22 class C:
23 pass
24
22 class BadPretty(object):
25 class BadPretty(object):
23 _repr_pretty_ = None
26 _repr_pretty_ = None
24
27
@@ -87,4 +90,145 b' def test_bad_precision():'
87 nt.assert_raises(ValueError, set_fp, 'foo')
90 nt.assert_raises(ValueError, set_fp, 'foo')
88 nt.assert_raises(ValueError, set_fp, -1)
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