Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement write_to_disk for uri backends #164

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions ethpm/backends/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Union

from eth_typing import URI
Expand Down Expand Up @@ -34,3 +35,11 @@ def fetch_uri_contents(self, uri: URI) -> Union[bytes, URI]:
Fetch the contents stored at a URI.
"""
pass

@abstractmethod
def write_to_disk(self, uri: URI, target_path: Path) -> None:
"""
Writes the contents of target URI to target path.
Raises exception if target path exists.
"""
pass
21 changes: 18 additions & 3 deletions ethpm/backends/http.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import base64
import json
from pathlib import Path
import shutil
import tempfile

from eth_typing import URI
import requests
Expand All @@ -18,6 +21,10 @@ class GithubOverHTTPSBackend(BaseURIBackend):
Base class for all URIs pointing to a content-addressed Github URI.
"""

@property
def base_uri(self) -> str:
return GITHUB_API_AUTHORITY

def can_resolve_uri(self, uri: URI) -> bool:
return is_valid_content_addressed_github_uri(uri)

Expand All @@ -44,6 +51,14 @@ def fetch_uri_contents(self, uri: URI) -> bytes:
validate_blob_uri_contents(decoded_contents, uri)
return decoded_contents

@property
def base_uri(self) -> str:
return GITHUB_API_AUTHORITY
def write_to_disk(self, uri: URI, target_path: Path) -> None:
contents = self.fetch_uri_contents(uri)
if target_path.exists():
raise CannotHandleURI(
f"Github blob: {uri} cannot be written to disk since target path ({target_path}) "
"already exists. Please provide a target_path that does not exist."
)
with tempfile.NamedTemporaryFile() as temp:
temp.write(contents)
temp.seek(0)
shutil.copyfile(temp.name, target_path)
22 changes: 21 additions & 1 deletion ethpm/backends/ipfs.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from abc import abstractmethod
import os
from pathlib import Path
import shutil
import tempfile
from typing import Dict, List, Type

from eth_typing import URI
from eth_utils import import_string, to_bytes
import ipfshttpclient

Expand Down Expand Up @@ -49,6 +52,18 @@ def pin_assets(self, file_or_dir_path: Path) -> List[Dict[str, str]]:
"""
pass

def write_to_disk(self, uri: URI, target_path: Path) -> None:
contents = self.fetch_uri_contents(uri)
if target_path.exists():
raise CannotHandleURI(
f"IPFS uri: {uri} cannot be written to disk since target path ({target_path}) "
"already exists. Please provide a target_path that does not exist."
)
with tempfile.NamedTemporaryFile() as temp:
temp.write(contents)
temp.seek(0)
shutil.copyfile(temp.name, target_path)


class IPFSOverHTTPBackend(BaseIPFSBackend):
"""
Expand All @@ -59,7 +74,7 @@ class IPFSOverHTTPBackend(BaseIPFSBackend):
def __init__(self) -> None:
self.client = ipfshttpclient.connect(self.base_uri)

def fetch_uri_contents(self, uri: str) -> bytes:
def fetch_uri_contents(self, uri: URI) -> bytes:
ipfs_hash = extract_ipfs_path_from_uri(uri)
contents = self.client.cat(ipfs_hash)
validation_hash = generate_file_hash(contents)
Expand Down Expand Up @@ -108,6 +123,11 @@ def fetch_uri_contents(self, uri: str) -> bytes:
"IPFS gateway is currently disabled, please use a different IPFS backend."
)

def write_to_disk(self, uri: URI, target_path: Path) -> None:
raise CannotHandleURI(
"IPFS gateway is currently disabled, please use a different IPFS backend."
)


class InfuraIPFSBackend(IPFSOverHTTPBackend):
"""
Expand Down
8 changes: 8 additions & 0 deletions ethpm/backends/registry.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import os
from pathlib import Path

from eth_typing import URI
from web3 import Web3
from web3.providers.auto import load_provider_from_uri

from ethpm.backends.base import BaseURIBackend
from ethpm.constants import INFURA_API_KEY
from ethpm.exceptions import CannotHandleURI
from ethpm.utils.registry import fetch_standard_registry_abi
from ethpm.utils.uri import parse_registry_uri
from ethpm.validation import is_valid_registry_uri
Expand Down Expand Up @@ -41,3 +43,9 @@ def fetch_uri_contents(self, uri: str) -> URI:
self.w3.pm.set_registry(address)
_, _, manifest_uri = self.w3.pm.get_release_data(pkg_name, pkg_version)
return manifest_uri

def write_to_disk(self, uri: str, target_path: Path) -> None:
raise CannotHandleURI(
"Registry backends are not allowed to write resolved uris to disk. "
"Please use a different backend."
)
27 changes: 19 additions & 8 deletions tests/ethpm/backends/test_http_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,34 @@
from ethpm.constants import GITHUB_API_AUTHORITY
from ethpm.exceptions import CannotHandleURI, ValidationError

BLOB_URI = "https://api.github.com/repos/ethpm/py-ethpm/git/blobs/a7232a93f1e9e75d606f6c1da18aa16037e03480"

@pytest.mark.parametrize(
"uri",
(
"https://api.github.com/repos/ethpm/py-ethpm/git/blobs/a7232a93f1e9e75d606f6c1da18aa16037e03480",
),
)
def test_github_over_https_backend_fetch_uri_contents(uri, owned_contract, w3):

def test_github_over_https_backend_fetch_uri_contents(owned_contract, w3):
# these tests may occassionally fail CI as a result of their network requests
backend = GithubOverHTTPSBackend()
assert backend.base_uri == GITHUB_API_AUTHORITY
# integration with Package.from_uri
owned_package = Package.from_uri(uri, w3)
owned_package = Package.from_uri(BLOB_URI, w3)
assert owned_package.name == "owned"


def test_github_over_https_backend_raises_error_with_invalid_content_hash(w3):
invalid_uri = "https://api.github.com/repos/ethpm/py-ethpm/git/blobs/a7232a93f1e9e75d606f6c1da18aa16037e03123"
with pytest.raises(HTTPError):
Package.from_uri(invalid_uri, w3)


def test_github_backend_writes_to_disk(tmp_path):
backend = GithubOverHTTPSBackend()
contents = backend.fetch_uri_contents(BLOB_URI)
target_path = tmp_path / "github_uri.txt"
backend.write_to_disk(BLOB_URI, target_path)
assert target_path.read_bytes() == contents


def test_github_backend_write_to_disk_raises_exception_if_target_exists(tmp_path):
target_path = tmp_path / "test.txt"
target_path.touch()
with pytest.raises(CannotHandleURI, match="cannot be written to disk."):
GithubOverHTTPSBackend().write_to_disk(BLOB_URI, target_path)
18 changes: 18 additions & 0 deletions tests/ethpm/backends/test_ipfs_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
get_ipfs_backend_class,
)
from ethpm.constants import INFURA_GATEWAY_MULTIADDR
from ethpm.exceptions import CannotHandleURI

OWNED_MANIFEST_PATH = V2_PACKAGES_DIR / "owned" / "1.0.0.json"

Expand Down Expand Up @@ -116,3 +117,20 @@ def test_pin_assets_to_dummy_backend(dummy_ipfs_backend):
assert "StandardToken.sol" in dir_names
assert "QmRJHLmPVct2rbBpdGjP3xkXbF7romQigtmcs8TRfV1yC7" in dir_hashes
assert "2865" in dir_sizes


def test_ipfs_backend_write_to_disk(tmp_path):
ipfs_uri = "ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGV"
backend = LocalIPFSBackend()
contents = backend.fetch_uri_contents(ipfs_uri)
target_path = tmp_path / "ipfs_uri.txt"
backend.write_to_disk(ipfs_uri, target_path)
assert target_path.read_bytes() == contents


def test_ipfs_backend_write_to_disk_raises_exception_if_target_exists(tmp_path):
ipfs_uri = "ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGV"
target_path = tmp_path / "test.txt"
target_path.touch()
with pytest.raises(CannotHandleURI, match="cannot be written to disk."):
LocalIPFSBackend().write_to_disk(ipfs_uri, target_path)