diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index d6375927..26bd49e2 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -25,6 +25,7 @@ from numpy.typing import NDArray from rich.console import Console from rich.prompt import Confirm, Prompt +from rich.table import Table from scalecodec import GenericCall from scalecodec.utils.ss58 import ss58_encode, ss58_decode import typer @@ -89,6 +90,63 @@ def confirm_action( return Confirm.ask(message, default=default) +def create_table(*columns, title: str = "", **overrides) -> Table: + """ + Creates a Rich Table with consistent CLI styling. + + Default styling: no edge borders, bold white headers, bright black borders, + footer enabled, center-aligned title, and no lines between rows. + + Args: + *columns: Optional Column objects to add to the table upfront. + title: Table title with rich markup support. + **overrides: Any Table() parameter to override defaults (e.g., show_footer, + border_style, box, expand). + + Returns: + Configured Rich Table ready for adding columns/rows. + + Examples: + Basic usage (add columns later): + >>> table = create_table(title="My Subnets") + >>> table.add_column("Netuid", justify="center") + >>> table.add_row("1") + + With Column objects upfront: + >>> from rich.table import Column + >>> table = create_table( + ... Column("Name", justify="left"), + ... Column("Value", justify="right"), + ... title="Settings" + ... ) + >>> table.add_row("Timeout", "30s") + + Custom styling: + >>> from rich import box + >>> table = create_table( + ... title="Custom", + ... border_style="blue", + ... box=box.ROUNDED + ... ) + """ + defaults = { + "title": title, + "show_footer": True, + "show_edge": False, + "header_style": "bold white", + "border_style": "bright_black", + "style": "bold", + "title_justify": "center", + "show_lines": False, + "pad_edge": True, + } + + # Merge overrides into defaults + config = {**defaults, **overrides} + + return Table(*columns, **config) + + jinja_env = Environment( loader=PackageLoader("bittensor_cli", "src/bittensor/templates"), autoescape=select_autoescape(), diff --git a/bittensor_cli/src/commands/stake/add.py b/bittensor_cli/src/commands/stake/add.py index 275049be..b9bff970 100644 --- a/bittensor_cli/src/commands/stake/add.py +++ b/bittensor_cli/src/commands/stake/add.py @@ -17,6 +17,7 @@ from bittensor_cli.src.bittensor.utils import ( confirm_action, console, + create_table, get_hotkey_wallets_for_wallet, is_valid_ss58_address, print_error, @@ -651,19 +652,11 @@ def _define_stake_table( Returns: Table: An initialized rich Table object with appropriate columns """ - table = Table( + table = create_table( title=f"\n[{COLOR_PALETTE.G.HEADER}]Staking to:\n" f"Wallet: [{COLOR_PALETTE.G.CK}]{wallet.name}[/{COLOR_PALETTE.G.CK}], " f"Coldkey ss58: [{COLOR_PALETTE.G.CK}]{wallet.coldkeypub.ss58_address}[/{COLOR_PALETTE.G.CK}]\n" f"Network: {subtensor.network}[/{COLOR_PALETTE.G.HEADER}]\n", - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, ) table.add_column("Netuid", justify="center", style="grey89") diff --git a/bittensor_cli/src/commands/stake/auto_staking.py b/bittensor_cli/src/commands/stake/auto_staking.py index 3d788832..0afee7bc 100644 --- a/bittensor_cli/src/commands/stake/auto_staking.py +++ b/bittensor_cli/src/commands/stake/auto_staking.py @@ -4,12 +4,12 @@ from bittensor_wallet import Wallet from rich import box -from rich.table import Table from bittensor_cli.src import COLOR_PALETTE from bittensor_cli.src.bittensor.utils import ( confirm_action, console, + create_table, json_console, print_success, get_subnet_name, @@ -127,7 +127,7 @@ def resolve_identity(hotkey: str) -> Optional[str]: json_console.print(json.dumps(data_output)) return data_output - table = Table( + table = create_table( title=( f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Auto Stake Destinations" f" for [bold]{coldkey_display}[/bold]\n" @@ -135,13 +135,6 @@ def resolve_identity(hotkey: str) -> Optional[str]: f"Coldkey: {coldkey_ss58}\n" f"[/{COLOR_PALETTE['GENERAL']['HEADER']}]" ), - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, box=box.SIMPLE_HEAD, ) @@ -214,18 +207,11 @@ async def set_auto_stake_destination( hotkey_identity = delegate_info.display if prompt_user and not json_output: - table = Table( + table = create_table( title=( f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Confirm Auto-Stake Destination" f"[/{COLOR_PALETTE['GENERAL']['HEADER']}]" ), - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, box=box.SIMPLE_HEAD, ) table.add_column( diff --git a/bittensor_cli/src/commands/stake/move.py b/bittensor_cli/src/commands/stake/move.py index f28d689f..69934f53 100644 --- a/bittensor_cli/src/commands/stake/move.py +++ b/bittensor_cli/src/commands/stake/move.py @@ -4,7 +4,6 @@ from typing import TYPE_CHECKING, Optional from bittensor_wallet import Wallet -from rich.table import Table from rich.prompt import Prompt from bittensor_cli.src import COLOR_PALETTE @@ -16,6 +15,7 @@ from bittensor_cli.src.bittensor.utils import ( confirm_action, console, + create_table, print_error, group_subnets, get_subnet_name, @@ -167,7 +167,7 @@ async def display_stake_movement_cross_subnets( ) # Create and display table - table = Table( + table = create_table( title=( f"\n[{COLOR_PALETTE.G.HEADER}]" f"Moving stake from: " @@ -178,14 +178,6 @@ async def display_stake_movement_cross_subnets( f"[/{COLOR_PALETTE.G.SUBHEAD}]\nNetwork: {subtensor.network}\n" f"[/{COLOR_PALETTE.G.HEADER}]" ), - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, ) table.add_column( @@ -352,16 +344,8 @@ async def stake_move_transfer_selection( raise ValueError # Display hotkeys with stakes - table = Table( + table = create_table( title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Hotkeys with Stakes\n", - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, ) table.add_column("Index", justify="right") table.add_column("Identity", style=COLOR_PALETTE["GENERAL"]["SUBHEADING"]) @@ -405,13 +389,12 @@ async def stake_move_transfer_selection( origin_hotkey_ss58 = origin_hotkey_info["hotkey_ss58"] # Display available netuids for selected hotkey - table = Table( + table = create_table( title=f"\n[{COLOR_PALETTE.G.HEADER}]Available Stakes for Hotkey\n[/{COLOR_PALETTE.G.HEADER}]" f"[{COLOR_PALETTE.G.HK}]{origin_hotkey_ss58}[/{COLOR_PALETTE.G.HK}]\n", - show_edge=False, - header_style="bold white", - border_style="bright_black", - title_justify="center", + show_footer=False, + show_lines=True, + pad_edge=False, width=len(origin_hotkey_ss58) + 20, ) table.add_column("Netuid", style="cyan") @@ -483,13 +466,12 @@ async def stake_swap_selection( raise ValueError # Display available stakes - table = Table( + table = create_table( title=f"\n[{COLOR_PALETTE.G.HEADER}]Available Stakes for Hotkey\n[/{COLOR_PALETTE.G.HEADER}]" f"[{COLOR_PALETTE.G.HK}]{wallet.hotkey_str}: {hotkey_ss58}[/{COLOR_PALETTE.G.HK}]\n", - show_edge=False, - header_style="bold white", - border_style="bright_black", - title_justify="center", + show_footer=False, + show_lines=True, + pad_edge=False, width=len(hotkey_ss58) + 20, ) diff --git a/bittensor_cli/src/commands/stake/remove.py b/bittensor_cli/src/commands/stake/remove.py index eb930a60..d1490e9f 100644 --- a/bittensor_cli/src/commands/stake/remove.py +++ b/bittensor_cli/src/commands/stake/remove.py @@ -18,6 +18,7 @@ from bittensor_cli.src.bittensor.utils import ( confirm_action, console, + create_table, print_success, print_verbose, print_error, @@ -450,21 +451,13 @@ async def unstake_all( if not unstake_all_alpha else "Unstaking Summary - All Alpha Stakes" ) - table = Table( + table = create_table( title=( f"\n[{COLOR_PALETTE.G.HEADER}]{table_title}[/{COLOR_PALETTE.G.HEADER}]\n" f"Wallet: [{COLOR_PALETTE.G.COLDKEY}]{wallet.name}[/{COLOR_PALETTE.G.COLDKEY}], " f"Coldkey ss58: [{COLOR_PALETTE.G.CK}]{coldkey_ss58}[/{COLOR_PALETTE.G.CK}]\n" f"Network: [{COLOR_PALETTE.G.HEADER}]{subtensor.network}[/{COLOR_PALETTE.G.HEADER}]\n" ), - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, ) table.add_column("Netuid", justify="center", style="grey89") table.add_column( @@ -1055,16 +1048,8 @@ async def _unstake_selection( # Display existing hotkeys, id, and staked netuids. subnet_filter = f" for Subnet {netuid}" if netuid is not None else "" - table = Table( + table = create_table( title=f"\n[{COLOR_PALETTE.G.HEADER}]Hotkeys with Stakes{subnet_filter}\n", - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, ) table.add_column("Index", justify="right") table.add_column("Identity", style=COLOR_PALETTE.G.SUBHEAD) @@ -1092,18 +1077,10 @@ async def _unstake_selection( netuid_stakes = hotkey_stakes[selected_hotkey_ss58] # Display hotkey's staked netuids with amount. - table = Table( + table = create_table( title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Stakes for hotkey \n" f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{selected_hotkey_name}\n" f"{selected_hotkey_ss58}\n", - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, ) table.add_column("Subnet", justify="right") table.add_column("Symbol", style=COLOR_PALETTE["GENERAL"]["SYMBOL"]) @@ -1341,16 +1318,8 @@ def _create_unstake_table( f"Coldkey ss58: [{COLOR_PALETTE.G.CK}]{wallet_coldkey_ss58}[/{COLOR_PALETTE.G.CK}]\n" f"Network: {network}[/{COLOR_PALETTE.G.HEADER}]\n" ) - table = Table( + table = create_table( title=title, - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, ) table.add_column("Netuid", justify="center", style="grey89") diff --git a/bittensor_cli/src/commands/stake/wizard.py b/bittensor_cli/src/commands/stake/wizard.py index f1886f65..1b11f93c 100644 --- a/bittensor_cli/src/commands/stake/wizard.py +++ b/bittensor_cli/src/commands/stake/wizard.py @@ -10,12 +10,12 @@ from bittensor_wallet import Wallet from rich.prompt import Prompt -from rich.table import Table from rich.panel import Panel from bittensor_cli.src import COLOR_PALETTE from bittensor_cli.src.bittensor.utils import ( console, + create_table, print_error, is_valid_ss58_address, get_hotkey_pub_ss58, @@ -159,12 +159,11 @@ def get_identity(hotkey_ss58_: str) -> str: return old_identity.display return "~" - table = Table( + table = create_table( title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Your Available Stakes[/{COLOR_PALETTE['GENERAL']['HEADER']}]\n", - show_edge=False, - header_style="bold white", - border_style="bright_black", - title_justify="center", + show_footer=False, + show_lines=True, + pad_edge=False, ) table.add_column("Hotkey Identity", style=COLOR_PALETTE["GENERAL"]["SUBHEADING"]) diff --git a/bittensor_cli/src/commands/subnets/subnets.py b/bittensor_cli/src/commands/subnets/subnets.py index 610606ba..ece49f57 100644 --- a/bittensor_cli/src/commands/subnets/subnets.py +++ b/bittensor_cli/src/commands/subnets/subnets.py @@ -30,6 +30,7 @@ confirm_action, console, create_and_populate_table, + create_table, print_success, print_verbose, print_error, @@ -364,17 +365,9 @@ def define_table( tao_emission_percentage: str, total_tao_flow_ema: float, ): - defined_table = Table( + defined_table = create_table( title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Subnets" f"\nNetwork: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{subtensor.network}\n\n", - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, ) defined_table.add_column( @@ -1095,17 +1088,9 @@ async def show_root(): tao_sum = sum(root_state.tao_stake).tao - table = Table( + table = create_table( title=f"[{COLOR_PALETTE.G.HEADER}]Root Network\n[{COLOR_PALETTE.G.SUBHEAD}]" f"Network: {subtensor.network}[/{COLOR_PALETTE.G.SUBHEAD}]\n", - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, ) table.add_column("[bold white]Position", style="white", justify="center") @@ -1340,18 +1325,10 @@ async def show_subnet( # Define table properties mechanism_label = f"Mechanism {selected_mechanism_id}" - table = Table( + table = create_table( title=f"[{COLOR_PALETTE['GENERAL']['HEADER']}]Subnet [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid_}" f"{': ' + get_subnet_name(subnet_info)}" f"\n[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]Network: {subtensor.network} • {mechanism_label}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]\n", - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, ) # For table footers @@ -1892,22 +1869,13 @@ async def _storage_key(storage_fn: str) -> StorageKey: return if prompt and not json_output: - # TODO make this a reusable function, also used in subnets list # Show creation table. - table = Table( + table = create_table( title=( f"\n[{COLOR_PALETTE.G.HEADER}]" f"Register to [{COLOR_PALETTE.G.SUBHEAD}]netuid: {netuid}[/{COLOR_PALETTE.G.SUBHEAD}]" f"\nNetwork: [{COLOR_PALETTE.G.SUBHEAD}]{subtensor.network}[/{COLOR_PALETTE.G.SUBHEAD}]\n" ), - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, ) table.add_column( "Netuid", style="rgb(253,246,227)", no_wrap=True, justify="center" @@ -2476,16 +2444,8 @@ async def metagraph_cmd( table_cols_indices.append(idx) table_cols.append(v) - table = Table( + table = create_table( *table_cols, - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_style="bold white", - title_justify="center", - show_lines=False, expand=True, title=( f"[underline dark_orange]Metagraph[/underline dark_orange]\n\n" @@ -2496,7 +2456,6 @@ async def metagraph_cmd( f"Issuance: [bright_blue]{metadata_info['issuance']}[/bright_blue], " f"Difficulty: [bright_cyan]{metadata_info['difficulty']}[/bright_cyan]\n" ), - pad_edge=True, ) if all(x is False for x in display_cols.values()): @@ -2521,7 +2480,7 @@ def create_identity_table(title: str = None): if not title: title = "Subnet Identity" - table = Table( + table = create_table( Column( "Item", justify="right", @@ -2530,14 +2489,6 @@ def create_identity_table(title: str = None): ), Column("Value", style=COLOR_PALETTE["GENERAL"]["SUBHEADING"]), title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]{title}\n", - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, ) return table