import ast import json import importlib from hashlib import md5 from itertools import tee, chain def dict_hash(dct): """Return a hash of the contents of a dictionary""" dct_s = json.dumps(dct, sort_keys=True) try: m = md5(dct_s) except TypeError: m = md5(dct_s.encode()) return m.hexdigest() def exec_then_eval(code, namespace=None): """Exec a code block & return evaluation of the last line""" # TODO: make this less brittle. namespace = namespace or {} block = ast.parse(code, mode='exec') last = ast.Expression(block.body.pop().value) exec(compile(block, '<string>', mode='exec'), namespace) return eval(compile(last, '<string>', mode='eval'), namespace) def import_obj(clsname, default_module=None): """ Import the object given by clsname. If default_module is specified, import from this module. """ if default_module is not None: if not clsname.startswith(default_module + '.'): clsname = '{0}.{1}'.format(default_module, clsname) mod, clsname = clsname.rsplit('.', 1) mod = importlib.import_module(mod) try: obj = getattr(mod, clsname) except AttributeError: raise ImportError('Cannot import {0} from {1}'.format(clsname, mod)) return obj def strip_vl_extension(filename): """Strip the vega-lite extension (either vl.json or json) from filename""" for ext in ['.vl.json', '.json']: if filename.endswith(ext): return filename[:-len(ext)] else: return filename def prev_this_next(it, sentinel=None): """Utility to return (prev, this, next) tuples from an iterator""" i1, i2, i3 = tee(it, 3) next(i3, None) return zip(chain([sentinel], i1), i2, chain(i3, [sentinel]))