Show More
@@ -1,27 +1,16 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """Top-level display functions for displaying object in different formats. |
|
2 | """Top-level display functions for displaying object in different formats.""" | |
3 |
|
3 | |||
4 | Authors: |
|
4 | # Copyright (c) IPython Development Team. | |
5 |
|
5 | # Distributed under the terms of the Modified BSD License. | ||
6 | * Brian Granger |
|
|||
7 | """ |
|
|||
8 |
|
||||
9 | #----------------------------------------------------------------------------- |
|
|||
10 | # Copyright (C) 2013 The IPython Development Team |
|
|||
11 | # |
|
|||
12 | # Distributed under the terms of the BSD License. The full license is in |
|
|||
13 | # the file COPYING, distributed as part of this software. |
|
|||
14 | #----------------------------------------------------------------------------- |
|
|||
15 |
|
||||
16 | #----------------------------------------------------------------------------- |
|
|||
17 | # Imports |
|
|||
18 | #----------------------------------------------------------------------------- |
|
|||
19 |
|
6 | |||
20 | from __future__ import print_function |
|
7 | from __future__ import print_function | |
21 |
|
8 | |||
|
9 | import json | |||
|
10 | import mimetypes | |||
22 | import os |
|
11 | import os | |
23 | import struct |
|
12 | import struct | |
24 | import mimetypes |
|
13 | import warnings | |
25 |
|
14 | |||
26 | from IPython.core.formatters import _safe_get_formatter_method |
|
15 | from IPython.core.formatters import _safe_get_formatter_method | |
27 | from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode, |
|
16 | from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode, | |
@@ -514,7 +503,29 b' class SVG(DisplayObject):' | |||||
514 | return self.data |
|
503 | return self.data | |
515 |
|
504 | |||
516 |
|
505 | |||
517 |
class JSON( |
|
506 | class JSON(DisplayObject): | |
|
507 | """JSON expects a JSON-able dict or list | |||
|
508 | ||||
|
509 | not an already-serialized JSON string. | |||
|
510 | ||||
|
511 | Scalar types (None, number, string) are not allowed, only dict or list containers. | |||
|
512 | """ | |||
|
513 | # wrap data in a property, which warns about passing already-serialized JSON | |||
|
514 | _data = None | |||
|
515 | def _check_data(self): | |||
|
516 | if self.data is not None and not isinstance(self.data, (dict, list)): | |||
|
517 | raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data)) | |||
|
518 | ||||
|
519 | @property | |||
|
520 | def data(self): | |||
|
521 | return self._data | |||
|
522 | ||||
|
523 | @data.setter | |||
|
524 | def data(self, data): | |||
|
525 | if isinstance(data, string_types): | |||
|
526 | warnings.warn("JSON expects JSONable dict or list, not JSON strings") | |||
|
527 | data = json.loads(data) | |||
|
528 | self._data = data | |||
518 |
|
529 | |||
519 | def _repr_json_(self): |
|
530 | def _repr_json_(self): | |
520 | return self.data |
|
531 | return self.data |
@@ -12,6 +12,7 b' Inheritance diagram:' | |||||
12 |
|
12 | |||
13 | import abc |
|
13 | import abc | |
14 | import inspect |
|
14 | import inspect | |
|
15 | import json | |||
15 | import sys |
|
16 | import sys | |
16 | import traceback |
|
17 | import traceback | |
17 | import warnings |
|
18 | import warnings | |
@@ -232,15 +233,7 b' def warn_format_error(method, self, *args, **kwargs):' | |||||
232 | else: |
|
233 | else: | |
233 | traceback.print_exception(*exc_info) |
|
234 | traceback.print_exception(*exc_info) | |
234 | return None |
|
235 | return None | |
235 | if r is None or isinstance(r, self._return_type) or \ |
|
236 | return self._check_return(r, args[0]) | |
236 | (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)): |
|
|||
237 | return r |
|
|||
238 | else: |
|
|||
239 | warnings.warn( |
|
|||
240 | "%s formatter returned invalid type %s (expected %s) for object: %s" % \ |
|
|||
241 | (self.format_type, type(r), self._return_type, _safe_repr(args[0])), |
|
|||
242 | FormatterWarning |
|
|||
243 | ) |
|
|||
244 |
|
237 | |||
245 |
|
238 | |||
246 | class FormatterABC(with_metaclass(abc.ABCMeta, object)): |
|
239 | class FormatterABC(with_metaclass(abc.ABCMeta, object)): | |
@@ -259,7 +252,6 b' class FormatterABC(with_metaclass(abc.ABCMeta, object)):' | |||||
259 | enabled = True |
|
252 | enabled = True | |
260 |
|
253 | |||
261 | @abc.abstractmethod |
|
254 | @abc.abstractmethod | |
262 | @warn_format_error |
|
|||
263 | def __call__(self, obj): |
|
255 | def __call__(self, obj): | |
264 | """Return a JSON'able representation of the object. |
|
256 | """Return a JSON'able representation of the object. | |
265 |
|
257 | |||
@@ -358,6 +350,21 b' class BaseFormatter(Configurable):' | |||||
358 | else: |
|
350 | else: | |
359 | return True |
|
351 | return True | |
360 |
|
352 | |||
|
353 | def _check_return(self, r, obj): | |||
|
354 | """Check that a return value is appropriate | |||
|
355 | ||||
|
356 | Return the value if so, None otherwise, warning if invalid. | |||
|
357 | """ | |||
|
358 | if r is None or isinstance(r, self._return_type) or \ | |||
|
359 | (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)): | |||
|
360 | return r | |||
|
361 | else: | |||
|
362 | warnings.warn( | |||
|
363 | "%s formatter returned invalid type %s (expected %s) for object: %s" % \ | |||
|
364 | (self.format_type, type(r), self._return_type, _safe_repr(obj)), | |||
|
365 | FormatterWarning | |||
|
366 | ) | |||
|
367 | ||||
361 | def lookup(self, obj): |
|
368 | def lookup(self, obj): | |
362 | """Look up the formatter for a given instance. |
|
369 | """Look up the formatter for a given instance. | |
363 |
|
370 | |||
@@ -794,16 +801,41 b' class LatexFormatter(BaseFormatter):' | |||||
794 | class JSONFormatter(BaseFormatter): |
|
801 | class JSONFormatter(BaseFormatter): | |
795 | """A JSON string formatter. |
|
802 | """A JSON string formatter. | |
796 |
|
803 | |||
797 |
To define the callables that compute the JSON |
|
804 | To define the callables that compute the JSONable representation of | |
798 | your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type` |
|
805 | your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type` | |
799 | or :meth:`for_type_by_name` methods to register functions that handle |
|
806 | or :meth:`for_type_by_name` methods to register functions that handle | |
800 | this. |
|
807 | this. | |
801 |
|
808 | |||
802 |
The return value of this formatter should be a |
|
809 | The return value of this formatter should be a JSONable list or dict. | |
|
810 | JSON scalars (None, number, string) are not allowed, only dict or list containers. | |||
803 | """ |
|
811 | """ | |
804 | format_type = Unicode('application/json') |
|
812 | format_type = Unicode('application/json') | |
|
813 | _return_type = (list, dict) | |||
805 |
|
814 | |||
806 | print_method = ObjectName('_repr_json_') |
|
815 | print_method = ObjectName('_repr_json_') | |
|
816 | ||||
|
817 | def _check_return(self, r, obj): | |||
|
818 | """Check that a return value is appropriate | |||
|
819 | ||||
|
820 | Return the value if so, None otherwise, warning if invalid. | |||
|
821 | """ | |||
|
822 | if r is None: | |||
|
823 | return | |||
|
824 | md = None | |||
|
825 | if isinstance(r, tuple): | |||
|
826 | # unpack data, metadata tuple for type checking on first element | |||
|
827 | r, md = r | |||
|
828 | ||||
|
829 | # handle deprecated JSON-as-string form from IPython < 3 | |||
|
830 | if isinstance(r, string_types): | |||
|
831 | warnings.warn("JSON expects JSONable list/dict containers, not JSON strings", | |||
|
832 | FormatterWarning) | |||
|
833 | r = json.loads(r) | |||
|
834 | ||||
|
835 | if md is not None: | |||
|
836 | # put the tuple back together | |||
|
837 | r = (r, md) | |||
|
838 | return super(JSONFormatter, self)._check_return(r, obj) | |||
807 |
|
839 | |||
808 |
|
840 | |||
809 | class JavascriptFormatter(BaseFormatter): |
|
841 | class JavascriptFormatter(BaseFormatter): |
@@ -64,7 +64,7 b' class MagicsDisplay(object):' | |||||
64 | return magic_dict |
|
64 | return magic_dict | |
65 |
|
65 | |||
66 | def _repr_json_(self): |
|
66 | def _repr_json_(self): | |
67 |
return |
|
67 | return self._jsonable() | |
68 |
|
68 | |||
69 |
|
69 | |||
70 | @magics_class |
|
70 | @magics_class |
@@ -1,11 +1,9 b'' | |||||
1 | #----------------------------------------------------------------------------- |
|
1 | # Copyright (c) IPython Development Team. | |
2 | # Copyright (C) 2010-2011 The IPython Development Team. |
|
2 | # Distributed under the terms of the Modified BSD License. | |
3 | # |
|
3 | ||
4 | # Distributed under the terms of the BSD License. |
|
4 | import json | |
5 | # |
|
|||
6 | # The full license is in the file COPYING.txt, distributed with this software. |
|
|||
7 | #----------------------------------------------------------------------------- |
|
|||
8 | import os |
|
5 | import os | |
|
6 | import warnings | |||
9 |
|
7 | |||
10 | import nose.tools as nt |
|
8 | import nose.tools as nt | |
11 |
|
9 | |||
@@ -130,3 +128,21 b' def test_displayobject_repr():' | |||||
130 | nt.assert_equal(repr(j), object.__repr__(j)) |
|
128 | nt.assert_equal(repr(j), object.__repr__(j)) | |
131 | j._show_mem_addr = False |
|
129 | j._show_mem_addr = False | |
132 | nt.assert_equal(repr(j), '<IPython.core.display.Javascript object>') |
|
130 | nt.assert_equal(repr(j), '<IPython.core.display.Javascript object>') | |
|
131 | ||||
|
132 | def test_json(): | |||
|
133 | d = {'a': 5} | |||
|
134 | lis = [d] | |||
|
135 | j = display.JSON(d) | |||
|
136 | nt.assert_equal(j._repr_json_(), d) | |||
|
137 | with warnings.catch_warnings(record=True) as w: | |||
|
138 | j = display.JSON(json.dumps(d)) | |||
|
139 | assert len(w) == 1 | |||
|
140 | nt.assert_equal(j._repr_json_(), d) | |||
|
141 | j = display.JSON(lis) | |||
|
142 | nt.assert_equal(j._repr_json_(), lis) | |||
|
143 | with warnings.catch_warnings(record=True) as w: | |||
|
144 | j = display.JSON(json.dumps(lis)) | |||
|
145 | assert len(w) == 1 | |||
|
146 | nt.assert_equal(j._repr_json_(), lis) | |||
|
147 | ||||
|
148 | No newline at end of file |
@@ -1,5 +1,6 b'' | |||||
1 | """Tests for the Formatters.""" |
|
1 | """Tests for the Formatters.""" | |
2 |
|
2 | |||
|
3 | import warnings | |||
3 | from math import pi |
|
4 | from math import pi | |
4 |
|
5 | |||
5 | try: |
|
6 | try: | |
@@ -8,10 +9,11 b' except:' | |||||
8 | numpy = None |
|
9 | numpy = None | |
9 | import nose.tools as nt |
|
10 | import nose.tools as nt | |
10 |
|
11 | |||
|
12 | from IPython import get_ipython | |||
11 | from IPython.config import Config |
|
13 | from IPython.config import Config | |
12 | from IPython.core.formatters import ( |
|
14 | from IPython.core.formatters import ( | |
13 | PlainTextFormatter, HTMLFormatter, PDFFormatter, _mod_name_key, |
|
15 | PlainTextFormatter, HTMLFormatter, PDFFormatter, _mod_name_key, | |
14 | DisplayFormatter, |
|
16 | DisplayFormatter, JSONFormatter, | |
15 | ) |
|
17 | ) | |
16 | from IPython.utils.io import capture_output |
|
18 | from IPython.utils.io import capture_output | |
17 |
|
19 | |||
@@ -418,3 +420,14 b' def test_ipython_display_formatter():' | |||||
418 | nt.assert_equal(md, {}) |
|
420 | nt.assert_equal(md, {}) | |
419 | nt.assert_equal(catcher, [yes]) |
|
421 | nt.assert_equal(catcher, [yes]) | |
420 |
|
422 | |||
|
423 | def test_json_as_string_deprecated(): | |||
|
424 | class JSONString(object): | |||
|
425 | def _repr_json_(self): | |||
|
426 | return '{}' | |||
|
427 | ||||
|
428 | f = JSONFormatter() | |||
|
429 | with warnings.catch_warnings(record=True) as w: | |||
|
430 | d = f(JSONString()) | |||
|
431 | nt.assert_equal(d, {}) | |||
|
432 | nt.assert_equal(len(w), 1) | |||
|
433 | No newline at end of file |
@@ -8,6 +8,7 b' from __future__ import absolute_import' | |||||
8 | import io |
|
8 | import io | |
9 | import os |
|
9 | import os | |
10 | import sys |
|
10 | import sys | |
|
11 | import warnings | |||
11 | from unittest import TestCase, skipIf |
|
12 | from unittest import TestCase, skipIf | |
12 |
|
13 | |||
13 | try: |
|
14 | try: | |
@@ -18,6 +19,7 b' except ImportError:' | |||||
18 |
|
19 | |||
19 | import nose.tools as nt |
|
20 | import nose.tools as nt | |
20 |
|
21 | |||
|
22 | from IPython import get_ipython | |||
21 | from IPython.core import magic |
|
23 | from IPython.core import magic | |
22 | from IPython.core.error import UsageError |
|
24 | from IPython.core.error import UsageError | |
23 | from IPython.core.magic import (Magics, magics_class, line_magic, |
|
25 | from IPython.core.magic import (Magics, magics_class, line_magic, | |
@@ -980,3 +982,13 b' def test_bookmark():' | |||||
980 | with tt.AssertPrints('bmname'): |
|
982 | with tt.AssertPrints('bmname'): | |
981 | ip.run_line_magic('bookmark', '-l') |
|
983 | ip.run_line_magic('bookmark', '-l') | |
982 | ip.run_line_magic('bookmark', '-d bmname') |
|
984 | ip.run_line_magic('bookmark', '-d bmname') | |
|
985 | ||||
|
986 | def test_ls_magic(): | |||
|
987 | ip = get_ipython() | |||
|
988 | json_formatter = ip.display_formatter.formatters['application/json'] | |||
|
989 | json_formatter.enabled = True | |||
|
990 | lsmagic = ip.magic('lsmagic') | |||
|
991 | with warnings.catch_warnings(record=True) as w: | |||
|
992 | j = json_formatter(lsmagic) | |||
|
993 | nt.assert_equal(sorted(j), ['cell', 'line']) | |||
|
994 | nt.assert_equal(w, []) # no warnings |
General Comments 0
You need to be logged in to leave comments.
Login now