""" utils ===== Low-level functionality NOT intended for users to EVER use. """ import json import os.path ### general file setup tools ### def load_json_dict(filename, *args): """Checks if file exists. Returns {} if something fails.""" data = {} if os.path.exists(filename): with open(filename, "r") as f: try: data = json.load(f) if not isinstance(data, dict): data = {} except: pass # TODO: issue a warning and bubble it up if args: return {key: data[key] for key in args if key in data} return data def save_json_dict(filename, json_dict): """Will error if filename is not appropriate, but it's checked elsewhere. """ if isinstance(json_dict, dict): with open(filename, "w") as f: f.write(json.dumps(json_dict, indent=4)) else: raise TypeError("json_dict was not a dictionay. couldn't save.") ### Custom JSON encoders ### class _plotlyJSONEncoder(json.JSONEncoder): def numpyJSONEncoder(self, obj): try: import numpy if type(obj).__module__.split('.')[0] == numpy.__name__: l = obj.tolist() d = self.datetimeJSONEncoder(l) return d if d is not None else l except: pass return None def datetimeJSONEncoder(self, obj): # if datetime or iterable of datetimes, convert to a string that plotly understands # format as %Y-%m-%d %H:%M:%S.%f, %Y-%m-%d %H:%M:%S, or %Y-%m-%d depending on what non-zero resolution was provided import datetime try: if isinstance(obj, (datetime.datetime, datetime.date)): if obj.microsecond != 0: return obj.strftime('%Y-%m-%d %H:%M:%S.%f') elif obj.second != 0 or obj.minute != 0 or obj.hour != 0: return obj.strftime('%Y-%m-%d %H:%M:%S') else: return obj.strftime('%Y-%m-%d') elif isinstance(obj[0], (datetime.datetime, datetime.date)): return [o.strftime( '%Y-%m-%d %H:%M:%S.%f') if o.microsecond != 0 else o.strftime('%Y-%m-%d %H:%M:%S') if o.second != 0 or o.minute != 0 or o.hour != 0 else o.strftime('%Y-%m-%d') for o in obj] except: pass return None def pandasJSONEncoder(self, obj): try: import pandas if isinstance(obj, pandas.Series): return obj.tolist() except: pass return None def sageJSONEncoder(self, obj): try: from sage.all import RR, ZZ if obj in RR: return float(obj) elif obj in ZZ: return int(obj) except: pass return None def default(self, obj): try: return json.dumps(obj) except TypeError as e: encoders = (self.datetimeJSONEncoder, self.numpyJSONEncoder, self.pandasJSONEncoder, self.sageJSONEncoder) for encoder in encoders: s = encoder(obj) if s is not None: return s raise e return json.JSONEncoder.default(self, obj) ### unicode stuff ### def decode_unicode(coll): if isinstance(coll, list): for no, entry in enumerate(coll): if isinstance(entry, (dict, list)): coll[no] = decode_unicode(entry) else: if isinstance(entry, unicode): try: coll[no] = str(entry) except UnicodeEncodeError: pass elif isinstance(coll, dict): keys, vals = coll.keys(), coll.values() for key, val in zip(keys, vals): if isinstance(val, (dict, list)): coll[key] = decode_unicode(val) elif isinstance(val, unicode): try: coll[key] = str(val) except UnicodeEncodeError: pass coll[str(key)] = coll.pop(key) return coll ### docstring templating ### def template_doc(**names): def _decorator(func): if func.__doc__ is not None: func.__doc__ = func.__doc__.format(**names) return func return _decorator