Nearly 100% of emitter messages from CLI actions are in literature.py

pull/1983/head
Kieran Prasch 2020-05-12 13:02:06 -07:00
parent 755a02443b
commit 2b5a39e97e
No known key found for this signature in database
GPG Key ID: 199AB839D4125A62
11 changed files with 191 additions and 75 deletions

View File

@ -15,18 +15,25 @@ You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import click
import os
from constant_sorrow.constants import NO_PASSWORD
from nacl.exceptions import CryptoError
from nucypher.blockchain.eth.decorators import validate_checksum_address
from nucypher.cli.literature import COLLECT_ETH_PASSWORD, COLLECT_NUCYPHER_PASSWORD
from nucypher.cli.literature import (
COLLECT_ETH_PASSWORD,
COLLECT_NUCYPHER_PASSWORD,
GENERIC_PASSWORD_PROMPT,
DECRYPTING_CHARACTER_KEYRING
)
from nucypher.config.constants import NUCYPHER_ENVVAR_KEYRING_PASSWORD
from nucypher.config.node import CharacterConfiguration
def get_password_from_prompt(prompt: str = "Enter password", envvar: str = '', confirm: bool = False) -> str:
def get_password_from_prompt(prompt: str = GENERIC_PASSWORD_PROMPT, envvar: str = '', confirm: bool = False) -> str:
password = os.environ.get(envvar, NO_PASSWORD)
if password is NO_PASSWORD: # Collect password, prefer env var
password = click.prompt(prompt, confirmation_prompt=confirm, hide_input=True)
@ -50,14 +57,18 @@ def get_nucypher_password(confirm: bool = False, envvar=NUCYPHER_ENVVAR_KEYRING_
return keyring_password
def unlock_nucypher_keyring(emitter, password: str, character_configuration: CharacterConfiguration):
emitter.message(f'Decrypting {character_configuration._NAME} keyring...', color='yellow')
def unlock_nucypher_keyring(emitter, password: str, character_configuration: CharacterConfiguration) -> bool:
emitter.message(DECRYPTING_CHARACTER_KEYRING.format(name=character_configuration.NAME), color='yellow')
# precondition
if character_configuration.dev_mode:
return True # Dev accounts are always unlocked
# NuCypher
# unlock
try:
character_configuration.attach_keyring()
character_configuration.keyring.unlock(password=password) # Takes ~3 seconds, ~1GB Ram
except CryptoError:
raise character_configuration.keyring.AuthenticationFailed
raise character_configuration.keyring.AuthenticationFailed
else:
return True

View File

@ -17,8 +17,9 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import click
import json
import click
from json.decoder import JSONDecodeError
from nucypher.blockchain.eth.clients import NuCypherGethGoerliProcess
@ -26,7 +27,8 @@ from nucypher.cli.literature import (
CHARACTER_DESTRUCTION,
SUCCESSFUL_DESTRUCTION,
CONFIRM_FORGET_NODES,
SUCCESSFUL_FORGET_NODES
SUCCESSFUL_FORGET_NODES, INVALID_CONFIGURATION_FILE_WARNING, INVALID_JSON_IN_CONFIGURATION_WARNING,
SUCCESSFUL_UPDATE_CONFIGURATION_VALUES
)
@ -36,18 +38,18 @@ def get_or_update_configuration(emitter, config_class, filepath: str, config_opt
config = config_class.from_configuration_file(filepath=filepath)
except config_class.ConfigurationError:
# Issue warning for invalid configuration...
emitter.message(f"Invalid Configuration at {filepath}.")
emitter.message(INVALID_CONFIGURATION_FILE_WARNING.format(filepath=filepath))
try:
# ... but try to display it anyways
response = config_class._read_configuration_file(filepath=filepath)
return emitter.echo(json.dumps(response, indent=4))
except JSONDecodeError:
# ... sorry
return emitter.message(f"Invalid JSON in Configuration File at {filepath}.")
return emitter.message(INVALID_JSON_IN_CONFIGURATION_WARNING.format(filepath=filepath))
else:
updates = config_options.get_updates()
if updates:
emitter.message(f"Updated configuration values: {', '.join(updates)}", color='yellow')
emitter.message(SUCCESSFUL_UPDATE_CONFIGURATION_VALUES.format(fields=', '.join(updates)), color='yellow')
config.update(**updates)
return emitter.echo(config.serialize())
@ -57,7 +59,7 @@ def destroy_configuration(emitter, character_config, force: bool = False) -> Non
try:
database = character_config.db_filepath
except AttributeError:
database = "No database found"
database = "No database found" # FIXME: This cannot be right.....
click.confirm(CHARACTER_DESTRUCTION.format(name=character_config._NAME,
root=character_config.config_root,

View File

@ -17,8 +17,8 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import click
from constant_sorrow.constants import UNKNOWN_DEVELOPMENT_CHAIN_ID
from nucypher.blockchain.eth.token import NU
from nucypher.cli.literature import (
RESTAKING_LOCK_AGREEMENT,

View File

@ -26,8 +26,15 @@ from json.decoder import JSONDecodeError
from typing import Set, Optional, Dict, List
from nucypher.blockchain.eth.registry import BaseContractRegistry
from nucypher.cli.literature import CONFIRM_URSULA_IPV4_ADDRESS, COLLECT_URSULA_IPV4_ADDRESS, \
FORCE_DETECT_URSULA_IP_WARNING, NO_DOMAIN_PEERS, SEEDNODE_NOT_STAKING_WARNING
from nucypher.cli.literature import (
CONFIRM_URSULA_IPV4_ADDRESS,
COLLECT_URSULA_IPV4_ADDRESS,
FORCE_DETECT_URSULA_IP_WARNING,
NO_DOMAIN_PEERS,
SEEDNODE_NOT_STAKING_WARNING,
START_LOADING_SEEDNODES,
UNREADABLE_SEEDNODE_ADVISORY
)
from nucypher.cli.types import IPV4_ADDRESS
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
from nucypher.network.exceptions import NodeSeemsToBeDown
@ -100,7 +107,7 @@ def load_seednodes(emitter,
"""
# Heads up
emitter.message("Connecting to preferred teacher nodes...", color='yellow')
emitter.message(START_LOADING_SEEDNODES, color='yellow')
from nucypher.characters.lawful import Ursula
# Aggregate URIs (Ordered by Priority)
@ -119,7 +126,7 @@ def load_seednodes(emitter,
network_middleware=network_middleware,
registry=registry)
except NodeSeemsToBeDown:
emitter.message(f"Failed to connect to teacher: {uri}")
emitter.message(UNREADABLE_SEEDNODE_ADVISORY.format(uri=uri))
continue
except Teacher.NotStaking:
emitter.message(SEEDNODE_NOT_STAKING_WARNING.format(uri=uri))

View File

@ -32,6 +32,19 @@ from nucypher.blockchain.eth.registry import InMemoryContractRegistry, Individua
from nucypher.blockchain.eth.signers import Signer
from nucypher.blockchain.eth.token import Stake, NU
from nucypher.cli.config import extract_checksum_address_from_filepath
from nucypher.cli.literature import (
NO_CONFIGURATIONS_ON_DISK,
SELECT_NETWORK,
IS_THIS_CORRECT,
PREALLOCATION_STAKE_ADVISORY,
SELECT_STAKING_ACCOUNT_INDEX,
GENERIC_SELECT_ACCOUNT,
NO_ETH_ACCOUNTS,
SELECT_STAKE,
NO_DIVISIBLE_STAKES,
ONLY_DISPLAYING_DIVISIBLE_STAKES_NOTE,
NO_STAKES_FOUND
)
from nucypher.cli.painting import paint_stakes
from nucypher.config.constants import DEFAULT_CONFIG_ROOT, NUCYPHER_ENVVAR_WORKER_ADDRESS
@ -43,19 +56,19 @@ def select_stake(stakeholder, emitter, divisible: bool = False, staker_address:
else:
stakes = stakeholder.all_stakes
if not stakes:
emitter.echo(f"No stakes found.", color='red')
emitter.echo(NO_STAKES_FOUND, color='red')
raise click.Abort
stakes = sorted((stake for stake in stakes if stake.is_active), key=lambda s: s.address_index_ordering_key)
if divisible:
emitter.echo("NOTE: Showing divisible stakes only", color='yellow')
emitter.echo(ONLY_DISPLAYING_DIVISIBLE_STAKES_NOTE, color='yellow')
stakes = list(filter(lambda s: bool(s.value >= stakeholder.economics.minimum_allowed_locked*2), stakes)) # TODO: Move to method on Stake
if not stakes:
emitter.echo(f"No divisible stakes found.", color='red')
emitter.echo(NO_DIVISIBLE_STAKES, color='red')
raise click.Abort
enumerated_stakes = dict(enumerate(stakes))
paint_stakes(stakeholder=stakeholder, emitter=emitter, staker_address=staker_address)
choice = click.prompt("Select Stake", type=click.IntRange(min=0, max=len(enumerated_stakes)-1))
choice = click.prompt(SELECT_STAKE, type=click.IntRange(min=0, max=len(enumerated_stakes)-1))
chosen_stake = enumerated_stakes[choice]
return chosen_stake
@ -97,7 +110,7 @@ def select_client_account(emitter,
wallet_accounts = wallet.accounts
enumerated_accounts = dict(enumerate(wallet_accounts))
if len(enumerated_accounts) < 1:
emitter.echo("No ETH accounts were found.", color='red', bold=True)
emitter.echo(NO_ETH_ACCOUNTS, color='red', bold=True)
raise click.Abort()
# Display account info
@ -127,7 +140,7 @@ def select_client_account(emitter,
emitter.echo(tabulate(rows, headers=headers, showindex='always'))
# Prompt the user for selection, and return
prompt = prompt or "Select index of account"
prompt = prompt or GENERIC_SELECT_ACCOUNT
account_range = click.IntRange(min=0, max=len(enumerated_accounts)-1)
choice = click.prompt(prompt, type=account_range, default=default)
chosen_account = enumerated_accounts[choice]
@ -154,16 +167,15 @@ def handle_client_account_for_staking(emitter,
if individual_allocation:
client_account = individual_allocation.beneficiary_address
staking_address = individual_allocation.contract_address
message = f"Beneficiary {client_account} will use preallocation contract {staking_address} to stake."
message = PREALLOCATION_STAKE_ADVISORY.format(client_account=client_account, staking_address=staking_address)
emitter.echo(message, color='yellow', verbosity=1)
if not force:
click.confirm("Is this correct?", abort=True)
click.confirm(IS_THIS_CORRECT, abort=True)
else:
if staking_address:
client_account = staking_address
else:
client_account = select_client_account(prompt="Select index of staking account",
client_account = select_client_account(prompt=SELECT_STAKING_ACCOUNT_INDEX,
emitter=emitter,
registry=stakeholder.registry,
network=stakeholder.network,
@ -177,7 +189,7 @@ def select_network(emitter) -> str:
headers = ["Network"]
rows = [[n] for n in NetworksInventory.NETWORKS]
emitter.echo(tabulate(rows, headers=headers, showindex='always'))
choice = click.prompt("Select Network", default=0, type=click.IntRange(0, len(NetworksInventory.NETWORKS)-1))
choice = click.prompt(SELECT_NETWORK, default=0, type=click.IntRange(0, len(NetworksInventory.NETWORKS)-1))
network = NetworksInventory.NETWORKS[choice]
return network
@ -194,12 +206,12 @@ def select_config_file(emitter,
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}'
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(f"No {config_class._NAME.capitalize()} configurations found. "
f"run 'nucypher {config_class._NAME} init' then try again.", color='red')
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
@ -213,7 +225,7 @@ def select_config_file(emitter,
try:
config_file = parsed_addresses[checksum_address]
except KeyError:
raise ValueError(f"'{checksum_address}' is not a known {config_class._NAME} configuration account.")
raise ValueError(f"'{checksum_address}' is not a known {config_class.NAME} configuration account.")
elif len(config_files) > 1:
@ -228,7 +240,7 @@ def select_config_file(emitter,
emitter.echo(tabulate(parsed_addresses, headers=headers, showindex='always'))
# Prompt the user for selection, and return
prompt = f"Select {config_class._NAME} configuration"
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]

View File

@ -20,13 +20,19 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import click
import os
import shutil
from constant_sorrow.constants import NO_CONTROL_PROTOCOL
from nacl.exceptions import CryptoError
from constant_sorrow.constants import NO_CONTROL_PROTOCOL
from nucypher.blockchain.eth.interfaces import BlockchainInterface, BlockchainInterfaceFactory
from nucypher.blockchain.eth.registry import BaseContractRegistry, InMemoryContractRegistry, LocalContractRegistry
from nucypher.cli.actions.auth import unlock_nucypher_keyring, get_nucypher_password
from nucypher.cli.actions.network import load_seednodes
from nucypher.cli.literature import (
FEDERATED_WARNING,
PRODUCTION_REGISTRY_ADVISORY,
LOCAL_REGISTRY_ADVISORY,
CONNECTING_TO_BLOCKCHAIN
)
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
@ -82,7 +88,7 @@ def make_cli_character(character_config,
# Federated
if character_config.federated_only:
emitter.message("WARNING: Running in Federated mode", color='yellow')
emitter.message(FEDERATED_WARNING, color='yellow')
return CHARACTER
@ -97,7 +103,7 @@ def establish_deployer_registry(emitter,
if download_registry:
registry = InMemoryContractRegistry.from_latest_publication()
emitter.message(f"Using latest published registry from {registry.source}")
emitter.message(PRODUCTION_REGISTRY_ADVISORY.format(source=registry.source))
return registry
# Establish a contract registry from disk if specified
@ -120,7 +126,7 @@ def establish_deployer_registry(emitter,
# All Done.
registry = LocalContractRegistry(filepath=registry_filepath)
emitter.message(f"Configured to registry filepath {registry_filepath}")
emitter.message(LOCAL_REGISTRY_ADVISORY.format(registry_filepath=registry_filepath))
return registry
@ -142,7 +148,7 @@ def connect_to_blockchain(provider_uri, emitter, debug: bool = False, light: boo
sync=False,
emitter=emitter)
blockchain = BlockchainInterfaceFactory.get_interface(provider_uri=provider_uri)
emitter.echo(message="Reading Latest Chaindata...")
emitter.echo(message=CONNECTING_TO_BLOCKCHAIN)
blockchain.connect()
return blockchain
except Exception as e:

View File

@ -1,30 +1,48 @@
"""
This file is part of nucypher.
nucypher is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
nucypher is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
"""Text blobs that are implemented as part of nucypher CLI emitter messages."""
#
# Common
#
IS_THIS_CORRECT = "Is this correct?"
#
# Blockchain
#
CONNECTING_TO_BLOCKCHAIN = "Reading Latest Chaindata..."
PRODUCTION_REGISTRY_ADVISORY = "Using latest published registry from {source}"
LOCAL_REGISTRY_ADVISORY = "Configured to registry filepath {registry_filepath}"
FEDERATED_WARNING = "WARNING: Running in Federated mode"
#
# Staking
#
RESTAKING_LOCK_AGREEMENT = """
By enabling the re-staking lock for {staking_address}, you are committing to automatically
re-stake all rewards until a future period. You will not be able to disable re-staking until {release_period}.
"""
RESTAKING_AGREEMENT = "By enabling the re-staking for {staking_address}, all staking rewards will be automatically added to your existing stake."
WINDING_DOWN_AGREEMENT = """
Over time, as the locked stake duration decreases
i.e. `winds down`, you will receive decreasing inflationary rewards.
Instead, by disabling `wind down` (default) the locked stake duration
can remain constant until you specify that `wind down` should begin. By
keeping the locked stake duration constant, it ensures that you will
receive maximum inflation compensation.
If `wind down` was previously disabled, you can enable it at any point
and the locked duration will decrease after each period.
For more information see https://docs.nucypher.com/en/latest/architecture/sub_stakes.html#winding-down.
"""
CONFIRM_STAGED_STAKE = """
* Ursula Node Operator Notice *
-------------------------------
@ -49,6 +67,29 @@ paid out in ethers retro-actively and on-demand.
Accept ursula node operator obligation?"""
WINDING_DOWN_AGREEMENT = """
Over time, as the locked stake duration decreases
i.e. `winds down`, you will receive decreasing inflationary rewards.
Instead, by disabling `wind down` (default) the locked stake duration
can remain constant until you specify that `wind down` should begin. By
keeping the locked stake duration constant, it ensures that you will
receive maximum inflation compensation.
If `wind down` was previously disabled, you can enable it at any point
and the locked duration will decrease after each period.
For more information see https://docs.nucypher.com/en/latest/architecture/sub_stakes.html#winding-down.
"""
RESTAKING_LOCK_AGREEMENT = """
By enabling the re-staking lock for {staking_address}, you are committing to automatically
re-stake all rewards until a future period. You will not be able to disable re-staking until {release_period}.
"""
RESTAKING_AGREEMENT = "By enabling the re-staking for {staking_address}," \
" all staking rewards will be automatically added to your existing stake."
CONFIRM_RESTAKING_LOCK = "Confirm enable re-staking lock for staker {staking_address} until {release_period}?"
CONFIRM_ENABLE_RESTAKING = "Confirm enable automatic re-staking for staker {staking_address}?"
@ -59,10 +100,37 @@ CONFIRM_LARGE_STAKE_VALUE = "Wow, {value} - That's a lot of NU - Are you sure th
CONFIRM_LARGE_STAKE_DURATION = "Woah, {lock_periods} is a long time - Are you sure this is correct?"
PREALLOCATION_STAKE_ADVISORY = "Beneficiary {client_account} will use preallocation contract {staking_address} to stake."
SELECT_STAKING_ACCOUNT_INDEX = "Select index of staking account"
SELECT_STAKE = "Select Stake"
ONLY_DISPLAYING_DIVISIBLE_STAKES_NOTE = "NOTE: Showing divisible stakes only"
NO_DIVISIBLE_STAKES = "No divisible stakes found."
NO_STAKES_FOUND = "No stakes found."
#
# Configuration
#
SELECT_NETWORK = "Select Network"
NO_CONFIGURATIONS_ON_DISK = "No {name} configurations found. run 'nucypher {command} init' then try again."
SUCCESSFUL_UPDATE_CONFIGURATION_VALUES = "Updated configuration values: {fields}"
INVALID_JSON_IN_CONFIGURATION_WARNING = "Invalid JSON in Configuration File at {filepath}."
INVALID_CONFIGURATION_FILE_WARNING = "Invalid Configuration at {filepath}."
NO_ETH_ACCOUNTS = "No ETH accounts were found."
GENERIC_SELECT_ACCOUNT = "Select index of account"
CHARACTER_DESTRUCTION = '''
Delete all {name} character files including:
- Private and Public Keys ({keystore})
@ -74,7 +142,6 @@ Are you sure?'''
SUCCESSFUL_DESTRUCTION = "Successfully destroyed NuCypher configuration"
CONFIRM_FORGET_NODES = "Permanently delete all known node data?"
SUCCESSFUL_FORGET_NODES = "Removed all stored known nodes metadata and certificates"
@ -88,18 +155,29 @@ COLLECT_ETH_PASSWORD = "Enter password to unlock account {checksum_address}"
COLLECT_NUCYPHER_PASSWORD = "Enter NuCypher keyring password"
GENERIC_PASSWORD_PROMPT = "Enter password"
DECRYPTING_CHARACTER_KEYRING = 'Decrypting {name} keyring...'
#
# Ursula Networking
# Networking
#
CONFIRM_URSULA_IPV4_ADDRESS = "Is this the public-facing IPv4 address ({rest_host}) you want to use for Ursula?"
COLLECT_URSULA_IPV4_ADDRESS = "Please enter Ursula's public-facing IPv4 address here:"
#
# Seednodes
#
START_LOADING_SEEDNODES = "Connecting to preferred teacher nodes..."
UNREADABLE_SEEDNODE_ADVISORY = "Failed to connect to teacher: {uri}"
FORCE_DETECT_URSULA_IP_WARNING = "WARNING: --force is set, using auto-detected IP '{rest_host}'"
NO_DOMAIN_PEERS = "WARNING - No Peers Available for domains: {domains}"

View File

@ -55,7 +55,7 @@ def echo_version(ctx, param, value):
def paint_new_installation_help(emitter, new_configuration):
character_config_class = new_configuration.__class__
character_name = character_config_class._NAME.lower()
character_name = character_config_class.NAME.lower()
emitter.message("Generated keyring {}".format(new_configuration.keyring_root), color='green')
emitter.message("Saved configuration file {}".format(new_configuration.config_file_location), color='green')

View File

@ -65,7 +65,7 @@ class BaseConfiguration(ABC):
return filepath
"""
_NAME = NotImplemented
NAME = NotImplemented
_CONFIG_FILE_EXTENSION = 'json'
INDENTATION = 2
@ -90,8 +90,8 @@ class BaseConfiguration(ABC):
filepath: str = None,
*args, **kwargs):
if self._NAME is NotImplemented:
error = f'_NAME must be implemented on BaseConfiguration subclass {self.__class__.__name__}'
if self.NAME is NotImplemented:
error = f'NAME must be implemented on BaseConfiguration subclass {self.__class__.__name__}'
raise TypeError(error)
self.config_root = config_root or self.DEFAULT_CONFIG_ROOT
@ -137,7 +137,7 @@ class BaseConfiguration(ABC):
:param modifier: String to modify default filename with.
:return: The generated filepath string.
"""
name = cls._NAME.lower()
name = cls.NAME.lower()
if modifier:
name += f'-{modifier}'
filename = f'{name}.{cls._CONFIG_FILE_EXTENSION.lower()}'

View File

@ -37,7 +37,7 @@ class UrsulaConfiguration(CharacterConfiguration):
from nucypher.characters.lawful import Ursula
CHARACTER_CLASS = Ursula
_NAME = CHARACTER_CLASS.__name__.lower()
NAME = CHARACTER_CLASS.__name__.lower()
DEFAULT_REST_HOST = '127.0.0.1'
DEFAULT_REST_PORT = 9151
@ -144,7 +144,7 @@ class AliceConfiguration(CharacterConfiguration):
from nucypher.characters.lawful import Alice
CHARACTER_CLASS = Alice
_NAME = CHARACTER_CLASS.__name__.lower()
NAME = CHARACTER_CLASS.__name__.lower()
DEFAULT_CONTROLLER_PORT = 8151
@ -187,7 +187,7 @@ class BobConfiguration(CharacterConfiguration):
from nucypher.characters.lawful import Bob
CHARACTER_CLASS = Bob
_NAME = CHARACTER_CLASS.__name__.lower()
NAME = CHARACTER_CLASS.__name__.lower()
DEFAULT_CONTROLLER_PORT = 7151
@ -203,7 +203,7 @@ class FelixConfiguration(CharacterConfiguration):
# Character
CHARACTER_CLASS = Felix
_NAME = CHARACTER_CLASS.__name__.lower()
NAME = CHARACTER_CLASS.__name__.lower()
DEFAULT_DB_NAME = '{}.db'.format(_NAME)
DEFAULT_DB_FILEPATH = os.path.join(DEFAULT_CONFIG_ROOT, DEFAULT_DB_NAME)
@ -248,7 +248,7 @@ class FelixConfiguration(CharacterConfiguration):
class StakeHolderConfiguration(CharacterConfiguration):
_NAME = 'stakeholder'
NAME = 'stakeholder'
CHARACTER_CLASS = StakeHolder
def __init__(self, checksum_addresses: set = None, *args, **kwargs):

View File

@ -39,7 +39,7 @@ def expected_configuration_filepaths():
class RestorableTestItem(BaseConfiguration):
_NAME = 'something'
NAME = 'something'
DEFAULT_CONFIG_ROOT = '/tmp'
VERSION = 1
@ -55,7 +55,7 @@ class RestorableTestItem(BaseConfiguration):
def test_base_configuration_defaults():
assert BaseConfiguration.DEFAULT_CONFIG_ROOT == DEFAULT_CONFIG_ROOT
assert BaseConfiguration._NAME == NotImplemented
assert BaseConfiguration.NAME == NotImplemented
assert BaseConfiguration._CONFIG_FILE_EXTENSION == expected_extension
@ -87,7 +87,7 @@ def test_configuration_implementation():
# Correct minimum viable implementation
class BareMinimumConfigurableItem(BaseConfiguration):
_NAME = 'bare-minimum'
NAME = 'bare-minimum'
VERSION = 2
def static_payload(self) -> dict: