Format: text/x-python
⬇ Download File (4.9 KB)
   1  """Utilities to manipulate JSON objects."""
   2  
   3  # Copyright (c) IPython Development Team.
   4  # Distributed under the terms of the Modified BSD License.
   5  
   6  import math
   7  import numbers
   8  import re
   9  import types
  10  from binascii import b2a_base64
  11  from datetime import date, datetime
  12  
  13  from jupyter_client._version import version_info as jupyter_client_version
  14  
  15  next_attr_name = "__next__"
  16  
  17  # -----------------------------------------------------------------------------
  18  # Globals and constants
  19  # -----------------------------------------------------------------------------
  20  
  21  # timestamp formats
  22  ISO8601 = "%Y-%m-%dT%H:%M:%S.%f"
  23  ISO8601_PAT = re.compile(
  24      r"^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(\.\d{1,6})?Z?([\+\-]\d{2}:?\d{2})?$"
  25  )
  26  
  27  # holy crap, strptime is not threadsafe.
  28  # Calling it once at import seems to help.
  29  datetime.strptime("1", "%d")
  30  
  31  # -----------------------------------------------------------------------------
  32  # Classes and functions
  33  # -----------------------------------------------------------------------------
  34  
  35  
  36  # constants for identifying png/jpeg data
  37  PNG = b"\x89PNG\r\n\x1a\n"
  38  # front of PNG base64-encoded
  39  PNG64 = b"iVBORw0KG"
  40  JPEG = b"\xff\xd8"
  41  # front of JPEG base64-encoded
  42  JPEG64 = b"/9"
  43  # constants for identifying gif data
  44  GIF_64 = b"R0lGODdh"
  45  GIF89_64 = b"R0lGODlh"
  46  # front of PDF base64-encoded
  47  PDF64 = b"JVBER"
  48  
  49  JUPYTER_CLIENT_MAJOR_VERSION = jupyter_client_version[0]
  50  
  51  
  52  def encode_images(format_dict):
  53      """b64-encodes images in a displaypub format dict
  54  
  55      Perhaps this should be handled in json_clean itself?
  56  
  57      Parameters
  58      ----------
  59      format_dict : dict
  60          A dictionary of display data keyed by mime-type
  61  
  62      Returns
  63      -------
  64      format_dict : dict
  65          A copy of the same dictionary,
  66          but binary image data ('image/png', 'image/jpeg' or 'application/pdf')
  67          is base64-encoded.
  68  
  69      """
  70  
  71      # no need for handling of ambiguous bytestrings on Python 3,
  72      # where bytes objects always represent binary data and thus
  73      # base64-encoded.
  74      return format_dict
  75  
  76  
  77  def json_clean(obj):  # pragma: no cover
  78      """Deprecated, this is a no-op for jupyter-client>=7.
  79  
  80      Clean an object to ensure it's safe to encode in JSON.
  81  
  82      Atomic, immutable objects are returned unmodified.  Sets and tuples are
  83      converted to lists, lists are copied and dicts are also copied.
  84  
  85      Note: dicts whose keys could cause collisions upon encoding (such as a dict
  86      with both the number 1 and the string '1' as keys) will cause a ValueError
  87      to be raised.
  88  
  89      Parameters
  90      ----------
  91      obj : any python object
  92  
  93      Returns
  94      -------
  95      out : object
  96          A version of the input which will not cause an encoding error when
  97          encoded as JSON.  Note that this function does not *encode* its inputs,
  98          it simply sanitizes it so that there will be no encoding errors later.
  99  
 100      """
 101      if int(JUPYTER_CLIENT_MAJOR_VERSION) >= 7:
 102          return obj
 103  
 104      # types that are 'atomic' and ok in json as-is.
 105      atomic_ok = (str, type(None))
 106  
 107      # containers that we need to convert into lists
 108      container_to_list = (tuple, set, types.GeneratorType)
 109  
 110      # Since bools are a subtype of Integrals, which are a subtype of Reals,
 111      # we have to check them in that order.
 112  
 113      if isinstance(obj, bool):
 114          return obj
 115  
 116      if isinstance(obj, numbers.Integral):
 117          # cast int to int, in case subclasses override __str__ (e.g. boost enum, #4598)
 118          return int(obj)
 119  
 120      if isinstance(obj, numbers.Real):
 121          # cast out-of-range floats to their reprs
 122          if math.isnan(obj) or math.isinf(obj):
 123              return repr(obj)
 124          return float(obj)
 125  
 126      if isinstance(obj, atomic_ok):
 127          return obj
 128  
 129      if isinstance(obj, bytes):
 130          # unanmbiguous binary data is base64-encoded
 131          # (this probably should have happened upstream)
 132          return b2a_base64(obj).decode("ascii")
 133  
 134      if isinstance(obj, container_to_list) or (
 135          hasattr(obj, "__iter__") and hasattr(obj, next_attr_name)
 136      ):
 137          obj = list(obj)
 138  
 139      if isinstance(obj, list):
 140          return [json_clean(x) for x in obj]
 141  
 142      if isinstance(obj, dict):
 143          # First, validate that the dict won't lose data in conversion due to
 144          # key collisions after stringification.  This can happen with keys like
 145          # True and 'true' or 1 and '1', which collide in JSON.
 146          nkeys = len(obj)
 147          nkeys_collapsed = len(set(map(str, obj)))
 148          if nkeys != nkeys_collapsed:
 149              msg = (
 150                  "dict cannot be safely converted to JSON: "
 151                  "key collision would lead to dropped values"
 152              )
 153              raise ValueError(msg)
 154          # If all OK, proceed by making the new dict that will be json-safe
 155          out = {}
 156          for k, v in obj.items():
 157              out[str(k)] = json_clean(v)
 158          return out
 159      if isinstance(obj, (datetime, date)):
 160          return obj.strftime(ISO8601)
 161  
 162      # we don't understand it, it's probably an unserializable object
 163      raise ValueError("Can't clean for JSON: %r" % obj)
 164