def check_for_aliased_package(package_name: str, config: Config) -> None: aliases = get_package_aliases(package_name, config) if aliases: raise InstallError( f"Found {package_name} installed under the alias(es): {aliases}. " "To uninstall an aliased package, use the alias as the uninstall argument." ) else: raise InstallError( f"No package with the name {package_name} found installed under {config.ethpm_dir}." )
def lookup_registry_by_alias( alias: str, all_registries: Tuple[StoredRegistry, ...]) -> StoredRegistry: all_aliases = (registry.alias for registry in all_registries) if alias not in all_aliases: raise InstallError( f"Alias: {alias} not found in registry store. " f"Available registry aliases include: {list(all_aliases)}.") for registry in all_registries: if alias == registry.alias: return registry raise InstallError(f"Unable to lookup registry under the alias: {alias}.")
def remove_registry(uri_or_alias: str, config: Config) -> None: store_path = config.xdg_ethpmcli_root / REGISTRY_STORE if not store_path.is_file(): raise InstallError( f"Unable to remove registry: {uri_or_alias}. " f"No registry store found in {config.xdg_ethpmcli_root}.") registry = resolve_uri_or_alias(uri_or_alias, store_path) if registry.active: raise InstallError( "Unable to remove an active registry. Please activate a different " f"registry before removing registry: {registry.uri}.") old_store_data = json.loads(store_path.read_text()) updated_store_data = dissoc(old_store_data, registry.uri) write_store_data_to_disk(updated_store_data, store_path)
def validate_chain_data_store(chain_data_path: Path, w3: Web3) -> None: """ Validates that chain_data_path points to a file corresponding to the provided web3 instance. """ if not chain_data_path.is_file(): raise InstallError( f"{chain_data_path} does not appear to be a valid EthPM CLI datastore." ) chain_data = json.loads(chain_data_path.read_text()) if chain_data["chain_id"] != w3.eth.chainId: raise InstallError( f"Chain ID found in EthPM CLI datastore: {chain_data['chain_id']} " f"does not match chain ID of provided web3 instance: {w3.eth.chainId}" )
def resolve_uri_and_alias(registry_uri: Optional[URI], alias: Optional[str], store_path: Path) -> StoredRegistry: if (registry_uri and alias) or (not registry_uri and not alias): raise InstallError("Cannot resolve both an alias and registry uri.") all_registries = get_all_registries(store_path) if alias: return lookup_registry_by_alias(alias, all_registries) all_registry_uris = (reg.uri for reg in all_registries) if not registry_uri or registry_uri not in all_registry_uris: raise InstallError( f"No registry @ {registry_uri} is available in {store_path}.") for reg in all_registries: if reg.uri == registry_uri: return reg raise InstallError( "Cannot resolve registry uri: {registry_uri} or alias: {alias}.")
def update_registry_store(registry_uri: URI, alias: Optional[str], store_path: Path) -> None: all_registries = get_all_registries(store_path) all_registry_uris = (registry.uri for registry in all_registries) if registry_uri in all_registry_uris: raise InstallError(f"Registry @ {registry_uri} already stored.") old_store_data = json.loads(store_path.read_text()) new_registry_data = generate_registry_store_data(registry_uri, alias) updated_store_data = assoc(old_store_data, registry_uri, new_registry_data) write_store_data_to_disk(updated_store_data, store_path)
def uninstall_package(package_name: str, config: Config) -> None: if is_package_installed(package_name, config): tmp_pkg_dir = Path(tempfile.mkdtemp()) / ETHPM_PACKAGES_DIR shutil.copytree(config.ethpm_dir, tmp_pkg_dir) shutil.rmtree(tmp_pkg_dir / package_name) uninstall_from_ethpm_lock(package_name, (tmp_pkg_dir / LOCKFILE_NAME)) shutil.rmtree(config.ethpm_dir) tmp_pkg_dir.replace(config.ethpm_dir) return aliases = get_package_aliases(package_name, config) if aliases: raise InstallError( f"Found {package_name} installed under the alias(es): {aliases}. " "To uninstall an aliased package, use the alias as the uninstall argument." ) else: raise InstallError( f"No package with the name {package_name} found installed under {config.ethpm_dir}." )
def validate_install_cli_args(args: Namespace) -> None: validate_supported_uri(args.uri) if args.alias: validate_alias(args.alias) if args.ethpm_dir: validate_ethpm_dir(args.ethpm_dir) if is_etherscan_uri(args.uri): if not args.package_name or not args.package_version: raise InstallError( "To install an Etherscan verified contract, you must specify both the " "--package-name and --package-version.") else: if args.package_name: raise InstallError( "You cannot redefine the package_name of an existing package. " "Consider aliasing the package instead.") if args.package_version: raise InstallError( "You cannot redefine the version of an existing package.")
def resolve_sources( package: Package, ipfs_backend: BaseIPFSBackend) -> Iterable[Tuple[str, str]]: for path, source_object in package.manifest["sources"].items(): # for inlined sources if "content" in source_object: yield path, source_object["content"] else: ipfs_uri = next( (uri for uri in source_object["urls"] if is_ipfs_uri(uri)), None) if not ipfs_uri: raise InstallError( "Manifest is missing a content-addressed uri.") contents = to_text( ipfs_backend.fetch_uri_contents(ipfs_uri)).rstrip("\n") yield path, contents
def install_package(pkg: Package, config: Config) -> None: if is_package_installed(pkg.alias, config): raise InstallError( f"Installation conflict: Package: '{pkg.manifest['package_name']}' " f"aliased to '{pkg.alias}' already installed on the filesystem at " f"{config.ethpm_dir / pkg.alias}. Try installing this package with " "a different alias.") # Create temporary package directory tmp_pkg_dir = Path(tempfile.mkdtemp()) write_pkg_installation_files(pkg, tmp_pkg_dir, config.ipfs_backend) # Copy temp package directory to ethpm dir namespace dest_pkg_dir = config.ethpm_dir / pkg.alias validate_parent_directory(config.ethpm_dir, dest_pkg_dir) shutil.copytree(tmp_pkg_dir, dest_pkg_dir) install_to_ethpm_lock(pkg, (config.ethpm_dir / LOCKFILE_NAME))
def get_manifest(args: argparse.Namespace, config: Config) -> None: package = Package(args, config.ipfs_backend) manifest = json.loads(package.raw_manifest) if args.pretty: pretty_print_raw_manifest(manifest) elif args.output_file: if args.output_file.exists() or not args.output_file.parent.is_dir(): raise InstallError( f"Invalid output file: {args.output_file}. Output file must not exist " "and live inside a valid parent directory.") args.output_file.touch() args.output_file.write_bytes(package.raw_manifest) cli_logger.info( f"Manifest sourced from: {args.uri} written to {args.output_file}." ) else: cli_logger.info(manifest)
def activate_package(args: Namespace, config: Config) -> None: # support: etherscan / ipfs / github / erc1319 url = parse.urlparse(args.package_or_uri) if url.scheme: if url.scheme not in SUPPORTED_SCHEMES: raise UriNotSupportedError( f"URIs with a scheme of {url.scheme} are not supported. " f"Currently supported schemes include: {SUPPORTED_SCHEMES}") try: args.package_name = "etherscan" # for etherscan URIs args.package_version = "1.0.0" # for etherscan URIs args.uri = args.package_or_uri cli_pkg = Package(args, config.ipfs_backend) manifest = cli_pkg.manifest except UriNotSupportedError: raise UriNotSupportedError( f"{args.package_or_uri} is not a supported URI. The only URIs currently supported " "are Registry, Github Blob, Etherscan and IPFS") else: if not is_package_installed(args.package_or_uri, config): raise InstallError( f"Package: {args.package_or_uri} not installed in ethPM dir: {config.ethpm_dir}." ) manifest = json.loads((config.ethpm_dir / args.package_or_uri / "manifest.json").read_text()) pkg = ethpmPackage(manifest, config.w3) activation_banner = ( f"{(LIGHTNING_EMOJI + PACKAGE_EMOJI) * 4}{LIGHTNING_EMOJI}\n" f"{bold_white('Activating package')}: {bold_blue(pkg.name)}@{bold_green(pkg.version)}\n" f"{(LIGHTNING_EMOJI + PACKAGE_EMOJI) * 4}{LIGHTNING_EMOJI}\n") cli_logger.info(activation_banner) if "contractTypes" in pkg.manifest: num_contract_types = len(pkg.manifest["contractTypes"]) else: num_contract_types = 0 if "deployments" in pkg.manifest: num_deployments = sum( len(deps) for _, deps in pkg.manifest["deployments"].items()) else: num_deployments = 0 if num_contract_types > 0: available_factories = generate_contract_factories(pkg) if len(available_factories) > 0: formatted_factories = list_keys_for_display(available_factories) factories_banner = ( f"Successfully generated {len(available_factories)} contract " f"{pluralize(len(available_factories), 'factory')} on mainnet from " f"{num_contract_types} detected contract {pluralize(num_contract_types, 'type')}.\n" f"{''.join(formatted_factories)}\n" "To get a contract factory on a different chain, call " f"`{bold_white('get_factory(target_factory, target_w3)')}`\n" "using the available contract fatories and Web3 instances.\n\n" ) else: factories_banner = "\n" else: available_factories = {} factories_banner = "No detected contract types.\n" if num_deployments > 0: available_instances = generate_deployments(pkg, config) formatted_instances = list_keys_for_display(available_instances) deployments_banner = ( f"Successfully generated {len(available_instances)} contract " f"{pluralize(len(available_instances), 'instance')} from {num_deployments} detected " f"{pluralize(num_deployments, 'deployment')}.\n" f"{''.join(formatted_instances)}\n") else: available_instances = {} deployments_banner = "No detected deployments.\n" if config.private_key: auth_banner = ( f"Deployments configured to sign for: {config.w3.eth.defaultAccount}\n" ) else: auth_banner = ( "Contract instances and web3 instances have not been configured with an account.\n" "Use the --keyfile-password flag to enable automatic signing.\n") available_w3s = get_w3s(config) formatted_w3s = list_keys_for_display(available_w3s) web3_banner = "Available Web3 Instances\n" f"{''.join(formatted_w3s)}\n" banner = ( f"{factories_banner}{deployments_banner}{web3_banner}{auth_banner}\n" "The API for web3.py contract factories and instances can be found here:\n" f"{bold_white('https://web3py.readthedocs.io/en/stable/contracts.html')}\n\n" "Starting IPython console... ") helper_fns = {"get_factory": get_factory} embed( user_ns={ **available_factories, **available_instances, # ignore b/c conflicting types w/in dict values **available_w3s, # type: ignore **helper_fns, # type: ignore }, banner1=banner, colors="neutral", )
def validate_parent_directory(parent_dir: Path, child_dir: Path) -> None: if parent_dir not in child_dir.parents: raise InstallError( f"{parent_dir} was not found in {child_dir} directory tree.")
def validate_ethpm_dir(ethpm_dir: Path) -> None: if ethpm_dir.name != "_ethpm_packages" or not ethpm_dir.is_dir(): raise InstallError( "--ethpm-dir must point to an existing '_ethpm_packages' directory." )
def get_active_registry(store_path: Path) -> StoredRegistry: all_registries = get_all_registries(store_path) for registry in all_registries: if registry.active is True: return registry raise InstallError("Invalid registry store data found.")
def update_package(args: Namespace, config: Config) -> None: if not is_package_installed(args.package, config): check_for_aliased_package(args.package, config) return installed_package = resolve_installed_package_by_id(args.package, config) active_registry = get_active_registry(config.xdg_ethpmcli_root / REGISTRY_STORE) if is_valid_registry_uri(installed_package.install_uri): validate_same_registry(installed_package.install_uri, active_registry.uri) connected_chain_id = config.w3.eth.chainId active_registry_uri = parse_registry_uri(active_registry.uri) if not to_int(text=active_registry_uri.chain_id) == connected_chain_id: raise InstallError( f"Registry URI chain: {active_registry_uri.chain_id} doesn't match " f"connected web3: {connected_chain_id}.") config.w3.pm.set_registry(active_registry_uri.address) all_package_names = config.w3.pm.get_all_package_names() if installed_package.resolved_package_name not in all_package_names: raise InstallError( f"{installed_package.resolved_package_name} is not available on the active registry " f"{active_registry.uri}. Available packages include: {all_package_names}." ) all_release_data = config.w3.pm.get_all_package_releases( installed_package.resolved_package_name) all_versions = [version for version, _ in all_release_data] if installed_package.resolved_version not in all_versions: raise InstallError( f"{installed_package.resolved_package_name}@{installed_package.resolved_version} not " f"found on the active registry {active_registry.uri}.") on_chain_install_uri = pluck_release_data( all_release_data, installed_package.resolved_version) if on_chain_install_uri != installed_package.resolved_uri: raise InstallError( f"Install URI found on active registry for {installed_package.resolved_package_name}@" f"{installed_package.resolved_version}: {on_chain_install_uri} does not match the " f"install URI found in local lockfile: {installed_package.resolved_uri}." ) cli_logger.info( f"{len(all_versions)} versions of {installed_package.resolved_package_name} " f"found: {all_versions} \n" f"On the active registry: {active_registry.uri}") count = 0 while True: count += 1 target_version = input( "Please enter the version you want to install. ") if count > 5: raise InstallError("Max attempts (5) reached. ") elif target_version == installed_package.resolved_version: cli_logger.info(f"Version already installed: {target_version}. ") elif target_version not in all_versions: cli_logger.info(f"Version unavailable: {target_version}. ") else: break # Create an updated args/Package for new install updated_args = copy.deepcopy(args) if installed_package.resolved_package_name != args.package: updated_args.alias = args.package updated_args.uri = pluck_release_data(all_release_data, target_version) updated_args.package_version = target_version updated_package = Package(updated_args, config.ipfs_backend) # atomic replace with tempfile.TemporaryDirectory() as tmpdir: tmp_ethpm_dir = Path(tmpdir) / ETHPM_PACKAGES_DIR shutil.copytree(config.ethpm_dir, tmp_ethpm_dir) tmp_config = copy.copy(config) tmp_config.ethpm_dir = tmp_ethpm_dir uninstall_package(args.package, tmp_config) install_package(updated_package, tmp_config) shutil.rmtree(config.ethpm_dir) tmp_ethpm_dir.replace(config.ethpm_dir) cli_logger.info(f"{updated_args.package} successfully updated to version " f"{updated_args.package_version}.")