def test_missing_configuration_file(_default_filepath_mock, click_runner): cmd_args = ('ursula', 'run', '--network', TEMPORARY_DOMAIN) result = click_runner.invoke(nucypher_cli, cmd_args, catch_exceptions=False) assert result.exit_code != 0 configuration_type = UrsulaConfiguration.NAME assert NO_CONFIGURATIONS_ON_DISK.format(name=configuration_type.capitalize(), command=configuration_type) in result.output
def test_select_config_file_with_no_config_files(test_emitter, capsys, alice_blockchain_test_config, tmpdir): # Setup config_class = alice_blockchain_test_config # Prove there are no config files on the disk. assert not os.listdir(tmpdir) with pytest.raises(click.Abort): select_config_file(emitter=test_emitter, config_class=config_class, config_root=tmpdir) # Ensure we notified the user accurately. captured = capsys.readouterr() message = NO_CONFIGURATIONS_ON_DISK.format( name=config_class.NAME.capitalize(), command=config_class.NAME) assert message in captured.out
def select_config_file( emitter: StdoutEmitter, config_class: Type[CharacterConfiguration], config_root: str = None, checksum_address: str = None, ) -> str: """ Selects a nucypher character configuration file from the disk automatically or interactively. Behaviour ~~~~~~~~~ - If checksum address is supplied by parameter or worker address env var - confirm there is a corresponding file on the disk or raise ValueError. - If there is only one configuration file for the character, automatically return its filepath. - If there are multiple character configurations on the disk in the same configuration root, use interactive selection. - Aborts if there are no configurations associated with the supplied character configuration class. """ # # Scrape Disk Configurations # config_root = config_root or DEFAULT_CONFIG_ROOT default_config_file = glob.glob( config_class.default_filepath(config_root=config_root)) glob_pattern = f'{config_root}/{config_class.NAME}-0x*.{config_class._CONFIG_FILE_EXTENSION}' secondary_config_files = glob.glob(glob_pattern) config_files = [*default_config_file, *secondary_config_files] if not config_files: emitter.message(NO_CONFIGURATIONS_ON_DISK.format( name=config_class.NAME.capitalize(), command=config_class.NAME), color='red') raise click.Abort() checksum_address = checksum_address or os.environ.get( NUCYPHER_ENVVAR_WORKER_ADDRESS, None) # TODO: Deprecate worker_address in favor of checksum_address if checksum_address: # # Manual # parsed_addresses = { config_class.checksum_address_from_filepath(fp): fp for fp in config_files } try: config_file = parsed_addresses[checksum_address] except KeyError: raise ValueError( f"'{checksum_address}' is not a known {config_class.NAME} configuration account." ) elif len(config_files) > 1: # # Interactive # parsed_addresses = tuple( [config_class.checksum_address_from_filepath(fp)] for fp in config_files) # Display account info headers = ['Account'] emitter.echo( tabulate(parsed_addresses, headers=headers, showindex='always')) # Prompt the user for selection, and return prompt = f"Select {config_class.NAME} configuration" account_range = click.IntRange(min=0, max=len(config_files) - 1) choice = click.prompt(prompt, type=account_range, default=0) config_file = config_files[choice] emitter.echo(f"Selected {choice}: {config_file}", color='blue') else: # Default: Only one config file, use it. config_file = config_files[0] return config_file
def select_config_file( emitter: StdoutEmitter, config_class: Type[CharacterConfiguration], config_root: Optional[Path] = None, checksum_address: str = None, ) -> Path: """ Selects a nucypher character configuration file from the disk automatically or interactively. Behaviour ~~~~~~~~~ - If checksum address is supplied by parameter or operator address env var - confirm there is a corresponding file on the disk or raise ValueError. - If there is only one configuration file for the character, automatically return its filepath. - If there are multiple character configurations on the disk in the same configuration root, use interactive selection. - Aborts if there are no configurations associated with the supplied character configuration class. """ config_root = config_root or DEFAULT_CONFIG_ROOT config_files = get_config_filepaths(config_class=config_class, config_root=config_root) if not config_files: emitter.message(NO_CONFIGURATIONS_ON_DISK.format( name=config_class.NAME.capitalize(), command=config_class.NAME), color='red') raise click.Abort() checksum_address = checksum_address or os.environ.get( NUCYPHER_ENVVAR_OPERATOR_ADDRESS, None) # TODO: Deprecate operator_address in favor of checksum_address parsed_config_files = list() parsed_addresses_and_filenames = list() # parse configuration files for checksum address values for fp in config_files: try: config_checksum_address = config_class.checksum_address_from_filepath( fp) if checksum_address and config_checksum_address == checksum_address: # matching configuration file found, no need to continue - return filepath return fp parsed_config_files.append(fp) parsed_addresses_and_filenames.append( [config_checksum_address, Path(fp).name]) # store checksum & filename except config_class.OldVersion: # no use causing entire usage to crash if file can't be used anyway - inform the user; they can # decide for themself emitter.echo(IGNORE_OLD_CONFIGURATION.format(config_file=fp), color='yellow') if checksum_address: # shouldn't get here if checksum address was specified and corresponding file found raise ValueError( f"'{checksum_address}' is not a known {config_class.NAME} configuration account." ) if not parsed_config_files: # No available configuration files emitter.message(NO_CONFIGURATIONS_ON_DISK.format( name=config_class.NAME.capitalize(), command=config_class.NAME), color='red') raise click.Abort() elif len(parsed_config_files) > 1: # # Interactive # emitter.echo(f"\nConfiguration Directory: {config_root}\n") parsed_addresses_and_filenames = tuple( parsed_addresses_and_filenames ) # must be tuple-of-iterables for tabulation # Display account info headers = ['Account', 'Configuration File'] emitter.echo( tabulate(parsed_addresses_and_filenames, headers=headers, showindex='always')) # Prompt the user for selection, and return prompt = f"Select {config_class.NAME} configuration" account_range = click.IntRange(min=0, max=len(parsed_config_files) - 1) choice = click.prompt(prompt, type=account_range, default=0) config_file = parsed_config_files[choice] emitter.echo(f"Selected {choice}: {config_file}", color='blue') else: # Default: Only one config file, use it. config_file = parsed_config_files[0] emitter.echo( DEFAULT_TO_LONE_CONFIG_FILE.format( config_class=config_class.NAME.capitalize(), config_file=config_file)) return config_file