Source code for fplore.files.base

# -*- coding: utf-8 -*-

import os
from functools import wraps
import re
from collections import OrderedDict
from contextlib import contextmanager
from struct import pack

import numpy as np

from ..logging import log

RegexType = type(re.compile(''))


[docs]class FPLOFileException(Exception): pass
[docs]def loads(*attrs, **kwargs): def wrapper(load_orig): load_orig._loaded_attrs = attrs load_orig._disk_cache = kwargs.get('disk_cache', False) load_orig._mem_map = kwargs.get('mem_map', set()) return load_orig return wrapper
[docs]def get_cachepath(classname, attrname, filepath): path, filename = os.path.split(filepath) mtime = os.path.getmtime(filepath) fsize = os.path.getsize(filepath) try: cksum = pack('f', mtime).hex() + pack('I', fsize).hex() except AttributeError: # Py2 cksum = pack('f', mtime).encode('hex') + pack('I', fsize).encode('hex') cachedir = "{}/.cache".format(path) cachefile = "{}.{}-{}-{}.npy".format(classname, attrname, filename, cksum) return os.path.join(cachedir, cachefile)
[docs]def load_wrapper(load_orig): """Turns return value into dict with loaded attribute names as keys""" loaded_attrs = getattr(load_orig, '_loaded_attrs', None) @wraps(load_orig) def load(self): rv = load_orig(self) if loaded_attrs is not None and len(loaded_attrs) > 0: if len(loaded_attrs) == 1: rv = {loaded_attrs[0]: rv} else: rv = dict(zip(loaded_attrs, rv)) return rv return load
[docs]def load_cache_wrapper(classname, load_orig): @wraps(load_orig) def load(self): try: rv = self._load_cache except AttributeError: loaded_attrs = getattr(load_orig, '_loaded_attrs', None) mem_map = getattr(load_orig, '_mem_map', set()) disk_cache = bool(mem_map) or getattr(load_orig, '_disk_cache', False) if not disk_cache: rv = load_orig(self) else: rv = {} for attrname in loaded_attrs: cachepath = get_cachepath(classname, attrname, self.filepath) if not os.path.isfile(cachepath): log.debug('Creating cache for {}', classname) if not os.path.isdir(os.path.dirname(cachepath)): os.mkdir(os.path.dirname(cachepath)) rv = load_orig(self) for a, v in rv.items(): cp = get_cachepath(classname, a, self.filepath) # todo: possibly allow pickle for non-mem-mapped np.save(cp, v, allow_pickle=False) log.debug('Created cache {}.', cp) if attrname in mem_map: attr_rv = np.load(cachepath, mmap_mode='r') log.info('Mem-mapped {} from cache ({}).', attrname, cachepath) else: attr_rv = np.load(cachepath) attr_rv.flags.writeable = False log.info('Loaded {} from cache ({}).', attrname, cachepath) rv[attrname] = attr_rv self._load_cache = rv self.is_loaded = True return rv return load
[docs]def loaded_attr(name): def attr(self): return self.load()[name] attr.__name__ = name return attr
[docs]class FPLOFileType(type): def __init__(cls, name, bases, attrs): def register_loader(filename): cls.registry['loaders'][filename] = cls fplo_file = getattr(cls, '__fplo_file__', None) if fplo_file: if isinstance(fplo_file, str): register_loader(fplo_file) elif isinstance(fplo_file, RegexType): cls.registry['loaders_re'][fplo_file] = cls else: for f in fplo_file: register_loader(f) load = attrs.get('load', None) if load is not None and not isinstance(load, classmethod): load = load_wrapper(load) setattr(cls, 'load', load_cache_wrapper(name, load)) for attr in load._loaded_attrs: setattr(cls, attr, property(loaded_attr(attr))) else: log.debug("{} has no explicit loader.", name)
[docs]class FPLOFile(object, metaclass=FPLOFileType): registry = {'loaders': {}, 'loaders_re': OrderedDict()} is_loaded = False load_default = False
[docs] @classmethod def get_file_class(cls, path): fname = os.path.basename(path) try: return cls.registry['loaders'][fname] except KeyError: for rgx, loader in cls.registry['loaders_re'].items(): if rgx.match(fname): return loader raise
[docs] @classmethod def open(cls, path, load=False, run=None): if os.path.isdir(path): raise Exception("Not a file.") FileClass = cls.get_file_class(path) file_obj = FileClass(path, run=run) if load or (load is None and cls.load_default): file_obj.load() return file_obj
[docs] @classmethod def load(cls, path): return cls.open(path, load=True)
def __init__(self, filepath, run=None): self.filepath = filepath self.run = run # todo: load run if None def __repr__(self): if self.run: args = "'{}', run={}".format( os.path.basename(self.filepath), repr(self.run)) else: args = "'{}'".format(self.filepath) return "{}({})".format(type(self).__name__, args)
[docs]@contextmanager def writeable(var): _writeable = var.flags.writeable var.flags.writeable = True try: yield finally: var.flags.writeable = _writeable