##// END OF EJS Templates
add lookup logic based on apptools.type_registry...
MinRK -
Show More
@@ -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,8 +48,6 b' else:'
46 # The main DisplayFormatter class
48 # The main DisplayFormatter class
47 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
48
50
49 _current = object()
50
51 class DisplayFormatter(Configurable):
51 class DisplayFormatter(Configurable):
52
52
53 # 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.
@@ -207,6 +207,18 b' class FormatterABC(with_metaclass(abc.ABCMeta, object)):'
207 return None
207 return None
208
208
209
209
210 def _mod_name_key(typ):
211 """Return a '__module__.__name__' string key for a type."""
212 module = getattr(typ, '__module__', None)
213 name = getattr(typ, '__name__', None)
214 return (module, name)
215
216
217 def _get_type(obj):
218 """Return the type of an instance (old and new-style)"""
219 return getattr(obj, '__class__', None) or type(obj)
220
221
210 class BaseFormatter(Configurable):
222 class BaseFormatter(Configurable):
211 """A base formatter class that is configurable.
223 """A base formatter class that is configurable.
212
224
@@ -241,53 +253,83 b' class BaseFormatter(Configurable):'
241 # The singleton printers.
253 # The singleton printers.
242 # Maps the IDs of the builtin singleton objects to the format functions.
254 # Maps the IDs of the builtin singleton objects to the format functions.
243 singleton_printers = Dict(config=True)
255 singleton_printers = Dict(config=True)
244 def _singleton_printers_default(self):
245 return {}
246
256
247 # The type-specific printers.
257 # The type-specific printers.
248 # Map type objects to the format functions.
258 # Map type objects to the format functions.
249 type_printers = Dict(config=True)
259 type_printers = Dict(config=True)
250 def _type_printers_default(self):
251 return {}
252
260
253 # The deferred-import type-specific printers.
261 # The deferred-import type-specific printers.
254 # Map (modulename, classname) pairs to the format functions.
262 # Map (modulename, classname) pairs to the format functions.
255 deferred_printers = Dict(config=True)
263 deferred_printers = Dict(config=True)
256 def _deferred_printers_default(self):
257 return {}
258
264
259 def __call__(self, obj):
265 def __call__(self, obj):
260 """Compute the format for an object."""
266 """Compute the format for an object."""
261 if self.enabled:
267 if self.enabled:
262 obj_id = id(obj)
263 try:
268 try:
264 obj_class = getattr(obj, '__class__', None) or type(obj)
269 # lookup registered printer
265 # First try to find registered singleton printers for the type.
266 try:
270 try:
267 printer = self.singleton_printers[obj_id]
271 printer = self.lookup(obj)
268 except (TypeError, KeyError):
272 except KeyError:
269 pass
273 pass
270 else:
274 else:
271 return printer(obj)
275 return printer(obj)
272 # Next look for type_printers.
276 # Finally look for special method names
273 for cls in pretty._get_mro(obj_class):
277 method = pretty._safe_getattr(obj, self.print_method, None)
274 if cls in self.type_printers:
278 if method is not None:
275 return self.type_printers[cls](obj)
279 return method()
276 else:
277 printer = self._in_deferred_types(cls)
278 if printer is not None:
279 return printer(obj)
280 # Finally look for special method names.
281 if hasattr(obj_class, self.print_method):
282 printer = getattr(obj_class, self.print_method)
283 return printer(obj)
284 return None
280 return None
285 except Exception:
281 except Exception:
286 pass
282 pass
287 else:
283 else:
288 return None
284 return None
285
286 def lookup(self, obj):
287 """Look up the formatter for a given instance.
288
289 Parameters
290 ----------
291 obj : object instance
292
293 Returns
294 -------
295 f : callable
296 The registered fromatting callable for the type.
297
298 Raises
299 ------
300 KeyError if the type has not been registered.
301 """
302 # look for singleton first
303 obj_id = id(obj)
304 if obj_id in self.singleton_printers:
305 return self.singleton_printers[obj_id]
306 # then lookup by type
307 return self.lookup_by_type(_get_type(obj))
308
309 def lookup_by_type(self, typ):
310 """ Look up all the registered formatters for a type.
311
312 Parameters
313 ----------
314 typ : type
315
316 Returns
317 -------
318 f : callable
319 The registered fromatting callable for the type.
320
321 Raises
322 ------
323 KeyError if the type has not been registered.
324 """
325 for cls in pretty._get_mro(typ):
326 if cls in self.type_printers or self._in_deferred_types(cls):
327 return self.type_printers[cls]
328
329 # If we have reached here, the lookup failed.
330 raise KeyError("No registered printer for {0!r}".format(typ))
289
331
290 def for_type(self, typ, func=_current):
332 def for_type(self, typ, func=None):
291 """Add a format function for a given type.
333 """Add a format function for a given type.
292
334
293 Parameters
335 Parameters
@@ -301,10 +343,7 b' class BaseFormatter(Configurable):'
301 Subclasses may use a different call signature for the
343 Subclasses may use a different call signature for the
302 `func` argument.
344 `func` argument.
303
345
304 If None is given, the current formatter for the type, if any,
346 If `func` is None or not specified, there will be no change,
305 will be cleared.
306
307 If `func` is not specified, there will be no change,
308 only returning the current value.
347 only returning the current value.
309
348
310 Returns
349 Returns
@@ -314,14 +353,22 b' class BaseFormatter(Configurable):'
314 If you are registering a new formatter,
353 If you are registering a new formatter,
315 this will be the previous value (to enable restoring later).
354 this will be the previous value (to enable restoring later).
316 """
355 """
317 oldfunc = self.type_printers.get(typ, None)
356 # if string given, interpret as 'pkg.module.class_name'
318 if func is None:
357 if isinstance(typ, string_types):
319 self.type_printers.pop(typ, None)
358 type_module, type_name = typ.rsplit('.', 1)
320 elif func is not _current:
359 return self.for_type_by_name(type_module, type_name, func)
360
361 try:
362 oldfunc = self.lookup_by_type(typ)
363 except KeyError:
364 oldfunc = None
365
366 if func is not None:
321 self.type_printers[typ] = func
367 self.type_printers[typ] = func
368
322 return oldfunc
369 return oldfunc
323
370
324 def for_type_by_name(self, type_module, type_name, func=_current):
371 def for_type_by_name(self, type_module, type_name, func=None):
325 """Add a format function for a type specified by the full dotted
372 """Add a format function for a type specified by the full dotted
326 module and name of the type, rather than the type of the object.
373 module and name of the type, rather than the type of the object.
327
374
@@ -339,10 +386,7 b' class BaseFormatter(Configurable):'
339 Subclasses may use a different call signature for the
386 Subclasses may use a different call signature for the
340 `func` argument.
387 `func` argument.
341
388
342 If None is given, the current formatter for the type, if any,
389 If `func` is None or unspecified, there will be no change,
343 will be cleared.
344
345 If `func` is not specified, there will be no change,
346 only returning the current value.
390 only returning the current value.
347
391
348 Returns
392 Returns
@@ -353,30 +397,63 b' class BaseFormatter(Configurable):'
353 this will be the previous value (to enable restoring later).
397 this will be the previous value (to enable restoring later).
354 """
398 """
355 key = (type_module, type_name)
399 key = (type_module, type_name)
400
356 oldfunc = self.deferred_printers.get(key, None)
401 oldfunc = self.deferred_printers.get(key, None)
357 if func is None:
402 if func is not None:
358 self.deferred_printers.pop(key, None)
359 elif func is not _current:
360 self.deferred_printers[key] = func
403 self.deferred_printers[key] = func
361 return oldfunc
404 return oldfunc
405
406 def pop(self, typ):
407 """ Pop a registered object for the given type.
408
409 Parameters
410 ----------
411 typ : type or '__module__.__name__' string for a type
412
413 Returns
414 -------
415 obj : object
416 The last registered object for the type.
417
418 Raises
419 ------
420 KeyError if the type is not registered.
421 """
422 if isinstance(typ, string_types):
423 typ_key = tuple(typ.rsplit('.',1))
424 if typ_key not in self.deferred_printers:
425 # We may have it cached in the type map. We will have to
426 # iterate over all of the types to check.
427 for cls in self.type_printers:
428 if _mod_name_key(cls) == typ_key:
429 old = self.type_printers.pop(cls)
430 break
431 else:
432 raise KeyError("No registered value for {0!r}".format(typ_key))
433 else:
434 old = self.deferred_printers.pop(typ)
435 else:
436 if typ in self.type_printers:
437 old = self.type_printers.pop(typ)
438 else:
439 old = self.deferred_printers.pop(_mod_name_key(typ))
440 return old
362
441
363 def _in_deferred_types(self, cls):
442 def _in_deferred_types(self, cls):
364 """
443 """
365 Check if the given class is specified in the deferred type registry.
444 Check if the given class is specified in the deferred type registry.
366
445
367 Returns the printer from the registry if it exists, and None if the
446 Successful matches will be moved to the regular type registry for future use.
368 class is not in the registry. Successful matches will be moved to the
369 regular type registry for future use.
370 """
447 """
371 mod = getattr(cls, '__module__', None)
448 mod = getattr(cls, '__module__', None)
372 name = getattr(cls, '__name__', None)
449 name = getattr(cls, '__name__', None)
373 key = (mod, name)
450 key = (mod, name)
374 printer = None
375 if key in self.deferred_printers:
451 if key in self.deferred_printers:
376 # Move the printer over to the regular registry.
452 # Move the printer over to the regular registry.
377 printer = self.deferred_printers.pop(key)
453 printer = self.deferred_printers.pop(key)
378 self.type_printers[cls] = printer
454 self.type_printers[cls] = printer
379 return printer
455 return True
456 return False
380
457
381
458
382 class PlainTextFormatter(BaseFormatter):
459 class PlainTextFormatter(BaseFormatter):
@@ -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
@@ -90,45 +93,61 b' def test_bad_precision():'
90
93
91 def test_for_type():
94 def test_for_type():
92 f = PlainTextFormatter()
95 f = PlainTextFormatter()
93 class C():
94 pass
95
96 def foo(c, p, cycle):
97 p.text('C')
98
96
99 # initial return is None
97 # initial return is None
100 assert f.for_type(C, foo) is None
98 assert f.for_type(C, foo_printer) is None
101 # no func queries
99 # no func queries
102 assert f.for_type(C) is foo
100 assert f.for_type(C) is foo_printer
103 # shouldn't change anything
101 # shouldn't change anything
104 assert f.for_type(C) is foo
102 assert f.for_type(C) is foo_printer
105 # None should clear and return foo
103 # None should do the same
106 assert f.for_type(C, None) is foo
104 assert f.for_type(C, None) is foo_printer
107 # verify clear
105 assert f.for_type(C, None) is foo_printer
108 assert C not in f.type_printers
109 assert f.for_type(C) is None
110
106
111
107
112 def test_for_type_by_name():
108 def test_for_type_string():
113 f = PlainTextFormatter()
109 f = PlainTextFormatter()
114 class C():
115 pass
116
110
117 mod = C.__module__
111 mod = C.__module__
118
112
119 def foo(c, p, cycle):
113 type_str = '%s.%s' % (C.__module__, 'C')
120 p.text('C')
114
115 # initial return is None
116 assert f.for_type(type_str, foo_printer) is None
117 # no func queries
118 assert f.for_type(type_str) is foo_printer
119 assert _mod_name_key(C) in f.deferred_printers
120 assert f.for_type(C) is foo_printer
121 assert _mod_name_key(C) not in f.deferred_printers
122 assert C in f.type_printers
123
124
125 def test_for_type_by_name():
126 f = PlainTextFormatter()
127
128 mod = C.__module__
121
129
122 # initial return is None
130 # initial return is None
123 assert f.for_type_by_name(mod, 'C', foo) is None
131 assert f.for_type_by_name(mod, 'C', foo_printer) is None
124 # no func queries
132 # no func queries
125 assert f.for_type_by_name(mod, 'C') is foo
133 assert f.for_type_by_name(mod, 'C') is foo_printer
126 # shouldn't change anything
134 # shouldn't change anything
127 assert f.for_type_by_name(mod, 'C') is foo
135 assert f.for_type_by_name(mod, 'C') is foo_printer
128 # None should clear and return foo
136 # None should do the same
129 assert f.for_type_by_name(mod, 'C', None) is foo
137 assert f.for_type_by_name(mod, 'C', None) is foo_printer
130 # verify clear
138 assert f.for_type_by_name(mod, 'C', None) is foo_printer
131 assert (mod, 'C') not in f.deferred_printers
139
132 assert f.for_type_by_name(mod, 'C') is None
133
140
141 def test_lookup():
142 f = PlainTextFormatter()
143
144 mod = C.__module__
145 c = C()
146
147 type_str = '%s.%s' % (C.__module__, 'C')
148
149 # initial return is None
150 assert f.for_type(type_str, foo_printer) is None
151 # no func queries
152 assert f.lookup(c) is foo_printer
134
153
General Comments 0
You need to be logged in to leave comments. Login now