diff --git a/ethpm_cli/main.py b/ethpm_cli/main.py index ac0b57d..c0cb7d3 100644 --- a/ethpm_cli/main.py +++ b/ethpm_cli/main.py @@ -11,11 +11,15 @@ from ethpm_cli._utils.xdg import get_xdg_ethpmcli_root from ethpm_cli.config import Config from ethpm_cli.constants import INFURA_HTTP_URI -from ethpm_cli.install import install_package, list_installed_packages +from ethpm_cli.install import ( + install_package, + list_installed_packages, + uninstall_package, +) from ethpm_cli.package import Package from ethpm_cli.parser import ETHPM_PARSER from ethpm_cli.scraper import scrape -from ethpm_cli.validation import validate_install_cli_args +from ethpm_cli.validation import validate_install_cli_args, validate_uninstall_cli_args __version__ = pkg_resources.require("ethpm-cli")[0].version @@ -71,6 +75,11 @@ def main() -> None: args.uri, config.ethpm_dir, ) + elif args.command == "uninstall": + validate_uninstall_cli_args(args) + config = Config(args) + uninstall_package(args.package, config) + logger.info("%s uninstalled from %s", args.package, config.ethpm_dir) elif args.command == "scrape": scraper(args) elif args.command == "list": @@ -78,6 +87,6 @@ def main() -> None: list_installed_packages(config) else: parser.error( - "%s is an invalid command. Use `ethpmcli --help` to " + "%s is an invalid command. Use `ethpm --help` to " "see the list of available commands." % args.command ) diff --git a/ethpm_cli/parser.py b/ethpm_cli/parser.py index cf34be8..e27e83d 100644 --- a/ethpm_cli/parser.py +++ b/ethpm_cli/parser.py @@ -5,7 +5,14 @@ def get_ethpm_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="ethpm-cli") subparsers = parser.add_subparsers(help="commands", dest="command") + scrape_parser(subparsers) + install_parser(subparsers) + uninstall_parser(subparsers) + list_parser(subparsers) + return parser + +def scrape_parser(subparsers: argparse._SubParsersAction) -> None: scrape_parser = subparsers.add_parser( "scrape", help="Scrape for new VersionRelease events." ) @@ -24,6 +31,8 @@ def get_ethpm_parser() -> argparse.ArgumentParser: help="Block number to begin scraping from.", ) + +def install_parser(subparsers: argparse._SubParsersAction) -> None: install_parser = subparsers.add_parser("install", help="Install uri") install_parser.add_argument( "uri", @@ -48,15 +57,33 @@ def get_ethpm_parser() -> argparse.ArgumentParser: help="Flag to use locally running IPFS node.", ) + +def uninstall_parser(subparsers: argparse._SubParsersAction) -> None: + uninstall_parser = subparsers.add_parser("uninstall", help="Uninstall a package.") + uninstall_parser.add_argument( + "package", + action="store", + type=str, + help="Package name / alias of package you want to uninstall.", + ) + uninstall_parser.add_argument( + "--ethpm-dir", + dest="ethpm_dir", + action="store", + type=Path, + help="Path to specific ethpm_packages dir.", + ) + + +def list_parser(subparsers: argparse._SubParsersAction) -> None: list_parser = subparsers.add_parser("list", help="List installed packages") list_parser.add_argument( "--ethpm-dir", dest="ethpm_dir", action="store", type=Path, - help="Path to specific ethpm_packages dir.", + help="Path to specific _ethpm_packages dir.", ) - return parser ETHPM_PARSER = get_ethpm_parser() diff --git a/ethpm_cli/validation.py b/ethpm_cli/validation.py index ef7474b..53ecaf5 100644 --- a/ethpm_cli/validation.py +++ b/ethpm_cli/validation.py @@ -19,10 +19,16 @@ def validate_parent_directory(parent_dir: Path, child_dir: Path) -> None: def validate_install_cli_args(args: Namespace) -> None: validate_target_uri(args.uri) - if args.alias is not None: + if args.alias: validate_alias(args.alias) - if args.ethpm_dir is not None: + if args.ethpm_dir: + validate_ethpm_dir(args.ethpm_dir) + + +def validate_uninstall_cli_args(args: Namespace) -> None: + validate_package_name(args.package) + if args.ethpm_dir: validate_ethpm_dir(args.ethpm_dir) @@ -49,9 +55,9 @@ def validate_alias(alias: str) -> None: def validate_ethpm_dir(ethpm_dir: Path) -> None: - if ethpm_dir.name != "ethpm_packages" or not ethpm_dir.is_dir(): + if ethpm_dir.name != "_ethpm_packages" or not ethpm_dir.is_dir(): raise InstallError( - f"--packages-dir must point to an existing 'ethpm_packages' directory." + f"--ethpm-dir must point to an existing '_ethpm_packages' directory." ) diff --git a/setup.py b/setup.py index 8624755..1e41630 100644 --- a/setup.py +++ b/setup.py @@ -7,15 +7,16 @@ extras_require = { 'test': [ + "pexpect>=4.7.0,<5", "pytest>=4.4.0,<5", "pytest-xdist==1.*", "tox>=2.9.1,<3", ], 'lint': [ - 'black>=19.3b0,<20', - 'flake8>=3.7.0,<4', - 'isort>=4.3.17,<5', - 'mypy<0.800', + "black>=19.3b0,<20", + "flake8>=3.7.0,<4", + "isort>=4.3.17,<5", + "mypy<0.800", "pydocstyle>=3.0.0,<4", ], 'doc': [ diff --git a/tests/cli/test_ethpm.py b/tests/cli/test_ethpm.py new file mode 100644 index 0000000..6ad9b3e --- /dev/null +++ b/tests/cli/test_ethpm.py @@ -0,0 +1,55 @@ +import shutil + +import pexpect + +from ethpm_cli._utils.testing import check_dir_trees_equal + + +def test_ethpm_list(test_assets_dir): + ethpm_dir = test_assets_dir / "multiple" / "_ethpm_packages" + child = pexpect.spawn(f"ethpm list --ethpm-dir {ethpm_dir}") + child.expect("EthPM CLI v0.1.0a0\r\n") + child.expect("\r\n") + child.expect( + r"owned==1.0.0 --- \(ipfs://QmbeVyFLSuEUxiXKwSsEjef6icpdTdA4kGG9BcrJXKNKUW\)\r\n" + ) + child.expect( + r"wallet==1.0.0 --- \(ipfs://QmRMSm4k37mr2T3A2MGxAj2eAHGR5veibVt1t9Leh5waV1\)\r\n" + ) + child.expect( + r"- safe-math-lib==1.0.0 --- \(ipfs://QmWgvM8yXGyHoGWqLFXvareJsoCZVsdrpKNCLMun3RaSJm\)\r\n" + ) + child.expect( + r"- owned==1.0.0 --- \(ipfs://QmbeVyFLSuEUxiXKwSsEjef6icpdTdA4kGG9BcrJXKNKUW\)\r\n" + ) + + +def test_ethpm_install(tmp_path, test_assets_dir): + ethpm_dir = tmp_path / "_ethpm_packages" + ethpm_dir.mkdir() + child = pexpect.spawn( + "ethpm install ipfs://QmbeVyFLSuEUxiXKwSsEjef6icpdTdA4kGG9BcrJXKNKUW " + f"--ethpm-dir {ethpm_dir}" + ) + child.expect("EthPM CLI v0.1.0a0\r\n") + child.expect("\r\n") + child.expect( + "owned package sourced from ipfs://QmbeVyFLSuEUxiXKwSsEjef6icpdTdA4kGG9BcrJXKNKUW " + f"installed to {ethpm_dir}.\r\n" + ) + assert check_dir_trees_equal( + ethpm_dir, test_assets_dir / "owned" / "ipfs_uri" / "_ethpm_packages" + ) + + +def test_ethpm_uninstall(tmp_path, test_assets_dir): + ethpm_dir = tmp_path / "_ethpm_packages" + shutil.copytree(test_assets_dir / "multiple" / "_ethpm_packages", ethpm_dir) + assert check_dir_trees_equal( + ethpm_dir, test_assets_dir / "multiple" / "_ethpm_packages" + ) + child = pexpect.spawn(f"ethpm uninstall owned --ethpm-dir {ethpm_dir}") + child.expect(f"owned uninstalled from {ethpm_dir}\r\n") + assert check_dir_trees_equal( + ethpm_dir, test_assets_dir / "wallet" / "ipfs_uri" / "_ethpm_packages" + ) diff --git a/tests/cli/test_ipfs_scrape.py b/tests/cli/test_ipfs_scrape.py new file mode 100644 index 0000000..d87c179 --- /dev/null +++ b/tests/cli/test_ipfs_scrape.py @@ -0,0 +1,11 @@ +import pexpect + + +def test_ipfs_scrape(tmp_path): + ipfs_dir = tmp_path / "ipfs" + ipfs_dir.mkdir() + child = pexpect.spawn(f"ethpm scrape --ipfs-dir {ipfs_dir} --start-block 1") + child.expect("EthPM CLI v0.1.0a0\r\n") + child.expect("\r\n") + child.expect("Scraping from block 1.\r\n") + child.expect("Blocks 1-5001 scraped. 0 VersionRelease events found.\r\n") diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..64ca5c4 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,10 @@ +from pathlib import Path + +import pytest + +ASSETS_DIR = Path(__file__).parent / "core" / "assets" + + +@pytest.fixture +def test_assets_dir(): + return ASSETS_DIR diff --git a/tests/core/conftest.py b/tests/core/conftest.py index b7edfed..5d9ca98 100644 --- a/tests/core/conftest.py +++ b/tests/core/conftest.py @@ -1,17 +1,9 @@ import json -from pathlib import Path import pytest from ethpm_cli.constants import ETHPM_DIR_NAME -ASSETS_DIR = Path(__file__).parent / "assets" - - -@pytest.fixture -def test_assets_dir(): - return ASSETS_DIR - @pytest.fixture def owned_pkg_data(test_assets_dir): diff --git a/tests/core/test_validation.py b/tests/core/test_validation.py index b63f273..b55c78e 100644 --- a/tests/core/test_validation.py +++ b/tests/core/test_validation.py @@ -3,6 +3,7 @@ import pytest +from ethpm_cli.constants import ETHPM_DIR_NAME from ethpm_cli.exceptions import InstallError, UriNotSupportedError, ValidationError from ethpm_cli.validation import validate_install_cli_args @@ -55,7 +56,7 @@ def test_validate_install_cli_args_rejects_unsupported_uris(uri, args): def test_validate_install_cli_args_validates_absolute_ethpm_dir_paths(args, tmpdir): - ethpm_dir = Path(tmpdir) / "ethpm_packages" + ethpm_dir = Path(tmpdir) / ETHPM_DIR_NAME ethpm_dir.mkdir() args.ethpm_dir = ethpm_dir @@ -66,10 +67,10 @@ def test_validate_install_cli_args_validates_relative_paths_to_cwd( args, tmpdir, monkeypatch ): p = Path(tmpdir) - ethpm_dir = p / "ethpm_packages" + ethpm_dir = p / ETHPM_DIR_NAME ethpm_dir.mkdir() monkeypatch.chdir(p) - args.ethpm_dir = Path("./ethpm_packages") + args.ethpm_dir = Path("./_ethpm_packages") assert validate_install_cli_args(args) is None @@ -86,7 +87,7 @@ def test_validate_install_cli_args_rejects_invalid_relative_paths( args, tmpdir, monkeypatch ): p = Path(tmpdir) - ethpm_dir = p / "ethpm_packages" + ethpm_dir = p / ETHPM_DIR_NAME ethpm_dir.mkdir() monkeypatch.chdir(p) args.ethpm_dir = Path("./invalid") diff --git a/tox.ini b/tox.ini index cc59a2d..132dd69 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,7 @@ envlist= combine_as_imports=True force_sort_within_sections=True include_trailing_comma=True -known_third_party=hypothesis,pytest +known_third_party=hypothesis,pytest,pexpect known_first_party=ethpm_cli line_length=88 multi_line_output=3