code: def clean_to_json_compatible(obj, **kwargs):
      # Try handling value as a scalar value that we have a conversion for.
      # Return immediately if we know we've hit a primitive value
  
      # Bail out fast for simple scalar types
      if isinstance(obj, (int, float, str)):
          return obj
  
      if isinstance(obj, dict):
          return {
              k: clean_to_json_compatible(v, **kwargs)
              for k, v in obj.items()
          }
      elif isinstance(obj, (list, tuple)):
          if obj:
              # Must process list recursively even though it may be slow
              return [
                  clean_to_json_compatible(v, **kwargs) for v in obj
              ]
  
      # unpack kwargs
      numpy_allowed = kwargs.get("numpy_allowed", False)
      datetime_allowed = kwargs.get("datetime_allowed", False)
  
      modules = kwargs.get("modules", {})
      sage_all = modules["sage_all"]
      np = modules["np"]
      pd = modules["pd"]
      image = modules["image"]
  
      # Sage
      if sage_all is not None:
          if obj in sage_all.RR:
              return float(obj)
          elif obj in sage_all.ZZ:
              return int(obj)
  
      # numpy
      if np is not None:
          if obj is np.ma.core.masked:
              return float("nan")
          elif isinstance(obj, np.ndarray):
              if numpy_allowed and obj.dtype.kind in (
                  "b",
                  "i",
                  "u",
                  "f",
              ):
                  return np.ascontiguousarray(obj)
              elif obj.dtype.kind == "M":
                  # datetime64 array
                  return np.datetime_as_string(obj).tolist()
              elif obj.dtype.kind == "U":
                  return obj.tolist()
              elif obj.dtype.kind == "O":
                  # Treat object array as a lists, continue processing
                  obj = obj.tolist()
          elif isinstance(obj, np.datetime64):
              return str(obj)
  
      # pandas
      if pd is not None:
          if obj is pd.NaT:
              return None
          elif isinstance(obj, (pd.Series, pd.DatetimeIndex)):
              if numpy_allowed and obj.dtype.kind in (
                  "b",
                  "i",
                  "u",
                  "f",
              ):
                  return np.ascontiguousarray(obj.values)
              elif obj.dtype.kind == "M":
                  if isinstance(obj, pd.Series):
                      with warnings.catch_warnings():
                          warnings.simplefilter("ignore", FutureWarning)
                          # Series.dt.to_pydatetime will return Index[object]
                          # https://github.com/pandas-dev/pandas/pull/52459
                          dt_values = np.array(
                              obj.dt.to_pydatetime()
                          ).tolist()
                  else:  # DatetimeIndex
                      dt_values = obj.to_pydatetime().tolist()
  
                  if not datetime_allowed:
                      # Note: We don't need to handle dropping timezones here because
                      # numpy's datetime64 doesn't support them and pandas's tz_localize
                      # above drops them.
                      for i in range(len(dt_values)):
                          dt_values[i] = dt_values[i].isoformat()
  
                  return dt_values
  
      # datetime and date
      try:
          # Need to drop timezone for scalar datetimes. Don't need to convert
          # to string since engine can do that
          obj = obj.to_pydatetime()
      except (TypeError, AttributeError):
          pass
  
      if not datetime_allowed:
          try:
              return obj.isoformat()
          except (TypeError, AttributeError):
              pass
      elif isinstance(obj, datetime.datetime):
          return obj
  
      # Try .tolist() convertible, do not recurse inside
      try:
          return obj.tolist()
      except AttributeError:
          pass
  
      # Do best we can with decimal
      if isinstance(obj, decimal.Decimal):
          return float(obj)
  
      # PIL
      if image is not None and isinstance(obj, image.Image):
          return ImageUriValidator.pil_image_to_uri(obj)
  
      # Plotly
      try:
          obj = obj.to_plotly_json()
      except AttributeError:
          pass
  
      # Recurse into lists and dictionaries
      if isinstance(obj, dict):
          return {
              k: clean_to_json_compatible(v, **kwargs)
              for k, v in obj.items()
          }
      elif isinstance(obj, (list, tuple)):
          if obj:
              # Must process list recursively even though it may be slow
              return [
                  clean_to_json_compatible(v, **kwargs) for v in obj
              ]
  
      return obj
  
imports: import json
  import decimal
  import datetime
  import warnings
  from pathlib import Path
  from plotly.io._utils import validate_coerce_fig_to_dict, validate_coerce_output_type
  from _plotly_utils.optional_imports import get_module
  from _plotly_utils.basevalidators import ImageUriValidator
label_desc: Use image node_id 9 for calling a global function with example usage: clean_to_json_compatible(obj) and returns: obj, obj, unknown, obj, float, ImageUriValidator, unknown, float, float, None, obj, obj, unknown, int, unknown, np, str, np, np, dt_values, obj