|
|
@@ -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 |
|
|
|