# The MIT License (MIT)
# Copyright © 2021 Yuma Rao
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
# documentation files (the “Software”), to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
# the Software.
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
import sys
import shtab
import argparse
import bittensor
from typing import List, Optional
from .commands import (
AutocompleteCommand,
DelegateStakeCommand,
DelegateUnstakeCommand,
GetIdentityCommand,
GetWalletHistoryCommand,
InspectCommand,
ListCommand,
ListDelegatesCommand,
MetagraphCommand,
MyDelegatesCommand,
NewColdkeyCommand,
NewHotkeyCommand,
NominateCommand,
OverviewCommand,
PowRegisterCommand,
ProposalsCommand,
RegenColdkeyCommand,
RegenColdkeypubCommand,
RegenHotkeyCommand,
RegisterCommand,
RegisterSubnetworkCommand,
RootGetWeightsCommand,
RootList,
RootRegisterCommand,
RootSetBoostCommand,
RootSetSlashCommand,
RootSetWeightsCommand,
RunFaucetCommand,
SenateCommand,
SetIdentityCommand,
SetTakeCommand,
StakeCommand,
StakeShow,
SubnetGetHyperparamsCommand,
SubnetHyperparamsCommand,
SubnetListCommand,
SubnetLockCostCommand,
SubnetSudoCommand,
SwapHotkeyCommand,
TransferCommand,
UnStakeCommand,
UpdateCommand,
UpdateWalletCommand,
VoteCommand,
WalletBalanceCommand,
WalletCreateCommand,
CommitWeightCommand,
RevealWeightCommand,
CheckColdKeySwapCommand,
SetChildrenCommand,
GetChildrenCommand,
RevokeChildrenCommand,
SetChildKeyTakeCommand,
GetChildKeyTakeCommand,
)
# Create a console instance for CLI display.
console = bittensor.__console__
ALIAS_TO_COMMAND = {
"subnets": "subnets",
"root": "root",
"wallet": "wallet",
"stake": "stake",
"sudo": "sudo",
"legacy": "legacy",
"s": "subnets",
"r": "root",
"w": "wallet",
"st": "stake",
"su": "sudo",
"l": "legacy",
"subnet": "subnets",
"roots": "root",
"wallets": "wallet",
"stakes": "stake",
"sudos": "sudo",
"i": "info",
"info": "info",
"weights": "weights",
"wt": "weights",
"weight": "weights",
}
COMMANDS = {
"subnets": {
"name": "subnets",
"aliases": ["s", "subnet"],
"help": "Commands for managing and viewing subnetworks.",
"commands": {
"list": SubnetListCommand,
"metagraph": MetagraphCommand,
"lock_cost": SubnetLockCostCommand,
"create": RegisterSubnetworkCommand,
"pow_register": PowRegisterCommand,
"register": RegisterCommand,
"hyperparameters": SubnetHyperparamsCommand,
},
},
"root": {
"name": "root",
"aliases": ["r", "roots"],
"help": "Commands for managing and viewing the root network.",
"commands": {
"list": RootList,
"weights": RootSetWeightsCommand,
"get_weights": RootGetWeightsCommand,
"boost": RootSetBoostCommand,
"slash": RootSetSlashCommand,
"senate_vote": VoteCommand,
"senate": SenateCommand,
"register": RootRegisterCommand,
"proposals": ProposalsCommand,
"set_take": SetTakeCommand,
"delegate": DelegateStakeCommand,
"undelegate": DelegateUnstakeCommand,
"my_delegates": MyDelegatesCommand,
"list_delegates": ListDelegatesCommand,
"nominate": NominateCommand,
},
},
"wallet": {
"name": "wallet",
"aliases": ["w", "wallets"],
"help": "Commands for managing and viewing wallets.",
"commands": {
"list": ListCommand,
"overview": OverviewCommand,
"transfer": TransferCommand,
"inspect": InspectCommand,
"balance": WalletBalanceCommand,
"create": WalletCreateCommand,
"new_hotkey": NewHotkeyCommand,
"new_coldkey": NewColdkeyCommand,
"regen_coldkey": RegenColdkeyCommand,
"regen_coldkeypub": RegenColdkeypubCommand,
"regen_hotkey": RegenHotkeyCommand,
"faucet": RunFaucetCommand,
"update": UpdateWalletCommand,
"swap_hotkey": SwapHotkeyCommand,
"set_identity": SetIdentityCommand,
"get_identity": GetIdentityCommand,
"history": GetWalletHistoryCommand,
"check_coldkey_swap": CheckColdKeySwapCommand,
},
},
"stake": {
"name": "stake",
"aliases": ["st", "stakes"],
"help": "Commands for staking and removing stake and setting child hotkey accounts.",
"commands": {
"show": StakeShow,
"add": StakeCommand,
"remove": UnStakeCommand,
"get_children": GetChildrenCommand,
"set_children": SetChildrenCommand,
"revoke_children": RevokeChildrenCommand,
"set_childkey_take": SetChildKeyTakeCommand,
"get_childkey_take": GetChildKeyTakeCommand,
},
},
"weights": {
"name": "weights",
"aliases": ["wt", "weight"],
"help": "Commands for managing weight for subnets.",
"commands": {
"commit": CommitWeightCommand,
"reveal": RevealWeightCommand,
},
},
"sudo": {
"name": "sudo",
"aliases": ["su", "sudos"],
"help": "Commands for subnet management",
"commands": {
# "dissolve": None,
"set": SubnetSudoCommand,
"get": SubnetGetHyperparamsCommand,
},
},
"legacy": {
"name": "legacy",
"aliases": ["l"],
"help": "Miscellaneous commands.",
"commands": {
"update": UpdateCommand,
"faucet": RunFaucetCommand,
},
},
"info": {
"name": "info",
"aliases": ["i"],
"help": "Instructions for enabling autocompletion for the CLI.",
"commands": {
"autocomplete": AutocompleteCommand,
},
},
}
[docs]
class CLIErrorParser(argparse.ArgumentParser):
"""
Custom ArgumentParser for better error messages.
"""
[docs]
def error(self, message):
"""
This method is called when an error occurs. It prints a custom error message.
"""
sys.stderr.write(f"Error: {message}\n")
self.print_help()
sys.exit(2)
[docs]
class cli:
"""
Implementation of the Command Line Interface (CLI) class for the Bittensor protocol.
This class handles operations like key management (hotkey and coldkey) and token transfer.
"""
def __init__(
self,
config: Optional["bittensor.config"] = None,
args: Optional[List[str]] = None,
):
"""
Initializes a bittensor.CLI object.
Args:
config (bittensor.config, optional): The configuration settings for the CLI.
args (List[str], optional): List of command line arguments.
"""
# Turns on console for cli.
bittensor.turn_console_on()
# If no config is provided, create a new one from args.
if config is None:
config = cli.create_config(args)
self.config = config
if self.config.command in ALIAS_TO_COMMAND:
self.config.command = ALIAS_TO_COMMAND[self.config.command]
else:
console.print(
f":cross_mark:[red]Unknown command: {self.config.command}[/red]"
)
sys.exit()
# Check if the config is valid.
cli.check_config(self.config)
# If no_version_checking is not set or set as False in the config, version checking is done.
if not self.config.get("no_version_checking", d=True):
try:
bittensor.utils.check_version()
except bittensor.utils.VersionCheckError:
# If version checking fails, inform user with an exception.
raise RuntimeError(
"To avoid internet-based version checking, pass --no_version_checking while running the CLI."
)
[docs]
@staticmethod
def __create_parser__() -> "argparse.ArgumentParser":
"""
Creates the argument parser for the Bittensor CLI.
Returns:
argparse.ArgumentParser: An argument parser object for Bittensor CLI.
"""
# Define the basic argument parser.
parser = CLIErrorParser(
description=f"bittensor cli v{bittensor.__version__}",
usage="btcli <command> <command args>",
add_help=True,
)
# Add shtab completion
parser.add_argument(
"--print-completion",
choices=shtab.SUPPORTED_SHELLS,
help="Print shell tab completion script",
)
# Add arguments for each sub-command.
cmd_parsers = parser.add_subparsers(dest="command")
# Add argument parsers for all available commands.
for command in COMMANDS.values():
if isinstance(command, dict):
subcmd_parser = cmd_parsers.add_parser(
name=command["name"],
aliases=command["aliases"],
help=command["help"],
)
subparser = subcmd_parser.add_subparsers(
help=command["help"], dest="subcommand", required=True
)
for subcommand in command["commands"].values():
subcommand.add_args(subparser)
else:
command.add_args(cmd_parsers)
return parser
[docs]
@staticmethod
def create_config(args: List[str]) -> "bittensor.config":
"""
From the argument parser, add config to bittensor.executor and local config
Args:
args (List[str]): List of command line arguments.
Returns:
bittensor.config: The configuration object for Bittensor CLI.
"""
parser = cli.__create_parser__()
# If no arguments are passed, print help text and exit the program.
if len(args) == 0:
parser.print_help()
sys.exit()
return bittensor.config(parser, args=args)
[docs]
@staticmethod
def check_config(config: "bittensor.config"):
"""
Checks if the essential configuration exists under different command
Args:
config (bittensor.config): The configuration settings for the CLI.
"""
# Check if command exists, if so, run the corresponding check_config.
# If command doesn't exist, inform user and exit the program.
if config.command in COMMANDS:
command = config.command
command_data = COMMANDS[command]
if isinstance(command_data, dict):
if config["subcommand"] is not None:
command_data["commands"][config["subcommand"]].check_config(config)
else:
console.print(
f":cross_mark:[red]Missing subcommand for: {config.command}[/red]"
)
sys.exit(1)
else:
command_data.check_config(config)
else:
console.print(f":cross_mark:[red]Unknown command: {config.command}[/red]")
sys.exit(1)
[docs]
def run(self):
"""
Executes the command from the configuration.
"""
# Check for print-completion argument
if self.config.print_completion:
parser = cli.__create_parser__()
shell = self.config.print_completion
print(shtab.complete(parser, shell))
return
# Check if command exists, if so, run the corresponding method.
# If command doesn't exist, inform user and exit the program.
command = self.config.command
if command in COMMANDS:
command_data = COMMANDS[command]
if isinstance(command_data, dict):
command_data["commands"][self.config["subcommand"]].run(self)
else:
command_data.run(self)
else:
console.print(
f":cross_mark:[red]Unknown command: {self.config.command}[/red]"
)
sys.exit()