| @@ -37,6 +37,18 @@ from importlib.machinery import ModuleSpec | |||
| @contextlib.contextmanager | |||
| def tempset(obj, key, value): | |||
| '''A context (with) manager for changing the value of an item in a | |||
| dictionary, and restoring it after the with block. | |||
| Example usage: | |||
| ``` | |||
| d = dict(a=5, b=10) | |||
| with tempset(d, 'a', 15): | |||
| print(repr(d['a']) | |||
| print(repr(d['a']) | |||
| ``` | |||
| ''' | |||
| try: | |||
| oldvalue = obj[key] | |||
| obj[key] = value | |||
| @@ -45,16 +57,26 @@ def tempset(obj, key, value): | |||
| obj[key] = oldvalue | |||
| class FileDirCAS(object): | |||
| '''A file loader for CAS that operates on a directory. It looks | |||
| at files, caches their hash, and loads them upon request.''' | |||
| def __init__(self, path): | |||
| self._path = pathlib.Path(path) | |||
| self._hashes = {} | |||
| def refresh_dir(self): | |||
| '''Internal method to refresh the internal cache of | |||
| hashes.''' | |||
| for i in glob.glob(os.path.join(self._path, '*.py')): | |||
| _, hash = self.read_hash_file(i) | |||
| self._hashes[hash] = i | |||
| def read_hash_file(self, fname): | |||
| @staticmethod | |||
| def read_hash_file(fname): | |||
| '''Helper function that will read the file at fname, and | |||
| return the tuple of it's contents and it's hash.''' | |||
| with open(fname, 'rb') as fp: | |||
| data = fp.read() | |||
| hash = hashlib.sha256(data).hexdigest() | |||
| @@ -62,9 +84,15 @@ class FileDirCAS(object): | |||
| return data, hash | |||
| def is_package(self, hash): | |||
| '''Decode the provided hash, and decide if it's a package | |||
| or not.''' | |||
| return False | |||
| def exec_module(self, hash, module): | |||
| '''Give the hash and module, load the code associated | |||
| with the hash, and exec it in the module's context.''' | |||
| self.refresh_dir() | |||
| parts = hash.split('_', 2) | |||
| @@ -78,6 +106,10 @@ class FileDirCAS(object): | |||
| exec(data, module.__dict__) | |||
| class CASFinder(MetaPathFinder, Loader): | |||
| '''Overall class for using Content Addressable Storage to load | |||
| Python modules into your code. It contains code to dispatch to | |||
| the various loaders to attempt to load the hash.''' | |||
| def __init__(self): | |||
| self._loaders = [] | |||
| @@ -93,12 +125,26 @@ class CASFinder(MetaPathFinder, Loader): | |||
| self.disconnect() | |||
| def disconnect(self): | |||
| '''Disconnect this Finder from being used to load modules. | |||
| As this claims an entire namespace, only the first loaded | |||
| one will work, and any others will be hidden until the | |||
| first one is disconnected. | |||
| This can be used w/ a with block to automatically | |||
| disconnect when no longer needed. This is mostly useful | |||
| for testing.''' | |||
| try: | |||
| sys.meta_path.remove(self) | |||
| except ValueError: | |||
| pass | |||
| def register(self, loader): | |||
| '''Register a loader w/ this finder. This will attempt | |||
| to load the hash passed to it. It is also (currently) | |||
| responsible for executing the code in the module.''' | |||
| self._loaders.append(loader) | |||
| # MetaPathFinder methods | |||