|
@@ -23,6 +23,7 @@ |
|
|
# SUCH DAMAGE. |
|
|
# SUCH DAMAGE. |
|
|
|
|
|
|
|
|
import contextlib |
|
|
import contextlib |
|
|
|
|
|
import functools |
|
|
import glob |
|
|
import glob |
|
|
import hashlib |
|
|
import hashlib |
|
|
import importlib.resources |
|
|
import importlib.resources |
|
@@ -32,11 +33,27 @@ import pathlib |
|
|
import shutil |
|
|
import shutil |
|
|
import sys |
|
|
import sys |
|
|
import tempfile |
|
|
import tempfile |
|
|
import urllib |
|
|
|
|
|
|
|
|
import urllib.request |
|
|
|
|
|
|
|
|
from importlib.abc import MetaPathFinder, Loader |
|
|
from importlib.abc import MetaPathFinder, Loader |
|
|
from importlib.machinery import ModuleSpec |
|
|
from importlib.machinery import ModuleSpec |
|
|
|
|
|
|
|
|
|
|
|
def _printanyexc(f): |
|
|
|
|
|
'''Prints any exception that gets raised by the wrapped function.''' |
|
|
|
|
|
|
|
|
|
|
|
@functools.wraps(f) |
|
|
|
|
|
def wrapper(*args, **kwargs): |
|
|
|
|
|
try: |
|
|
|
|
|
return f(*args, **kwargs) |
|
|
|
|
|
except Exception: |
|
|
|
|
|
import traceback |
|
|
|
|
|
|
|
|
|
|
|
traceback.print_exc() |
|
|
|
|
|
|
|
|
|
|
|
raise |
|
|
|
|
|
|
|
|
|
|
|
return wrapper |
|
|
|
|
|
|
|
|
@contextlib.contextmanager |
|
|
@contextlib.contextmanager |
|
|
def tempset(obj, key, value): |
|
|
def tempset(obj, key, value): |
|
|
'''A context (with) manager for changing the value of an item in a |
|
|
'''A context (with) manager for changing the value of an item in a |
|
@@ -88,6 +105,26 @@ def tempattrset(obj, key, value): |
|
|
else: |
|
|
else: |
|
|
delattr(obj, key) |
|
|
delattr(obj, key) |
|
|
|
|
|
|
|
|
|
|
|
class IPFSCAS(object): |
|
|
|
|
|
gwhost = 'gateway.ipfs.io' |
|
|
|
|
|
gwhost = 'cloudflare-ipfs.com' |
|
|
|
|
|
|
|
|
|
|
|
def make_url(self, url): |
|
|
|
|
|
return urllib.parse.urlunparse(('https', self.gwhost, |
|
|
|
|
|
'/ipfs/' + url.netloc) + ('', ) * 3) |
|
|
|
|
|
|
|
|
|
|
|
def fetch_data(self, url): |
|
|
|
|
|
if url.scheme != 'ipfs': |
|
|
|
|
|
raise ValueError('cannot handle scheme %s' % |
|
|
|
|
|
repr(url.scheme)) |
|
|
|
|
|
gwurl = self.make_url(url) |
|
|
|
|
|
|
|
|
|
|
|
with urllib.request.urlopen(gwurl) as req: |
|
|
|
|
|
if req.status // 100 != 2: |
|
|
|
|
|
raise RuntimeError('bad fetch') |
|
|
|
|
|
|
|
|
|
|
|
return req.read() |
|
|
|
|
|
|
|
|
class FileDirCAS(object): |
|
|
class FileDirCAS(object): |
|
|
'''A file loader for CAS that operates on a directory. It looks |
|
|
'''A file loader for CAS that operates on a directory. It looks |
|
|
at files, caches their hash, and loads them upon request.''' |
|
|
at files, caches their hash, and loads them upon request.''' |
|
@@ -296,6 +333,7 @@ def defaultinit(casf): |
|
|
cachedir.mkdir(exist_ok=True) |
|
|
cachedir.mkdir(exist_ok=True) |
|
|
|
|
|
|
|
|
casf.register(FileDirCAS(cachedir)) |
|
|
casf.register(FileDirCAS(cachedir)) |
|
|
|
|
|
casf.register(IPFSCAS()) |
|
|
|
|
|
|
|
|
# The global version |
|
|
# The global version |
|
|
_casfinder = CASFinder() |
|
|
_casfinder = CASFinder() |
|
@@ -423,6 +461,9 @@ class Test(unittest.TestCase): |
|
|
# it can be imported |
|
|
# it can be imported |
|
|
from cas.v1_f_330884aa2febb5e19fb7194ec6a69ed11dd3d77122f1a5175ee93e73cf0161c3 import hello |
|
|
from cas.v1_f_330884aa2febb5e19fb7194ec6a69ed11dd3d77122f1a5175ee93e73cf0161c3 import hello |
|
|
|
|
|
|
|
|
|
|
|
# and that the last loader is the IPFSCAS |
|
|
|
|
|
self.assertIsInstance(f._loaders[-1], IPFSCAS) |
|
|
|
|
|
|
|
|
with CASFinder() as f: |
|
|
with CASFinder() as f: |
|
|
defaultinit(f) |
|
|
defaultinit(f) |
|
|
|
|
|
|
|
@@ -514,6 +555,35 @@ class Test(unittest.TestCase): |
|
|
finally: |
|
|
finally: |
|
|
sys.path.remove(fixdir) |
|
|
sys.path.remove(fixdir) |
|
|
|
|
|
|
|
|
|
|
|
@mock.patch('urllib.request.urlopen') |
|
|
|
|
|
def test_ipfscasloader(self, uomock): |
|
|
|
|
|
# prep return test data |
|
|
|
|
|
with open(self.fixtures / 'hello.py') as fp: |
|
|
|
|
|
# that returns the correct data |
|
|
|
|
|
ipfsdata = fp.read() |
|
|
|
|
|
|
|
|
|
|
|
# that the ipfs CAS loader |
|
|
|
|
|
ipfs = IPFSCAS() |
|
|
|
|
|
|
|
|
|
|
|
# that the request is successfull |
|
|
|
|
|
uomock.return_value.__enter__.return_value.status = 200 |
|
|
|
|
|
|
|
|
|
|
|
# and returns the correct data |
|
|
|
|
|
uomock.return_value.__enter__.return_value.read.return_value = ipfsdata |
|
|
|
|
|
|
|
|
|
|
|
# that when called |
|
|
|
|
|
hashurl = urllib.parse.urlparse('ipfs://bafkreibtbcckul7lwxqz7nyzj3dknhwrdxj5o4jc6gsroxxjhzz46albym') |
|
|
|
|
|
data = ipfs.fetch_data(hashurl) |
|
|
|
|
|
|
|
|
|
|
|
# it opens the correct url |
|
|
|
|
|
uomock.assert_called_with('https://cloudflare-ipfs.com/ipfs/bafkreibtbcckul7lwxqz7nyzj3dknhwrdxj5o4jc6gsroxxjhzz46albym') |
|
|
|
|
|
|
|
|
|
|
|
# and returns the correct data |
|
|
|
|
|
self.assertEqual(data, ipfsdata) |
|
|
|
|
|
|
|
|
|
|
|
with self.assertRaises(ValueError): |
|
|
|
|
|
ipfs.fetch_data(urllib.parse.urlparse('hash://sha256/asldfkj')) |
|
|
|
|
|
|
|
|
def test_overlappingaliases(self): |
|
|
def test_overlappingaliases(self): |
|
|
# make sure that an aliases file is consistent and does not |
|
|
# make sure that an aliases file is consistent and does not |
|
|
# override other urls. That is that any hashes are |
|
|
# override other urls. That is that any hashes are |
|
|