Repository: opentensor/bittensor

Issue:
new_hotkey is listed twice under 'btcli --help' menu
new_hotkey is listed twice under 'btcli --help' menu


Discussion:

Resolving patch:
diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py
--- a/bittensor/_cli/__init__.py
+++ b/bittensor/_cli/__init__.py
@@ -59,11 +59,10 @@ def __new__(
             return naka_CLI(config=config)
         else:
             return cli_impl.CLI( config = config)
-
-    @staticmethod   
-    def config(args: List[str]) -> 'bittensor.config':
-        """ From the argument parser, add config to bittensor.executor and local config 
-            Return: bittensor.config object
+        
+    @staticmethod
+    def __create_parser__() -> 'argparse.ArgumentParser':
+        """ Creates the argument parser for the bittensor cli.
         """
         parser = argparse.ArgumentParser(
             description=f"bittensor cli v{bittensor.__version__}",
@@ -88,7 +87,6 @@ def config(args: List[str]) -> 'bittensor.config':
         MetagraphCommand.add_args( cmd_parsers )
         SetWeightsCommand.add_args( cmd_parsers )
         NewColdkeyCommand.add_args( cmd_parsers )
-        NewHotkeyCommand.add_args( cmd_parsers )
         MyDelegatesCommand.add_args( cmd_parsers )
         ListSubnetsCommand.add_args( cmd_parsers )
         RegenHotkeyCommand.add_args( cmd_parsers )
@@ -99,6 +97,15 @@ def config(args: List[str]) -> 'bittensor.config':
         RegenColdkeypubCommand.add_args( cmd_parsers )
         RecycleRegisterCommand.add_args( cmd_parsers )
 
+        return parser
+
+    @staticmethod   
+    def config(args: List[str]) -> 'bittensor.config':
+        """ From the argument parser, add config to bittensor.executor and local config 
+            Return: bittensor.config object
+        """
+        parser = cli.__create_parser__()
+
         # If no arguments are passed, print help text.
         if len(args) == 0:
             parser.print_help()


Test patch:
diff --git a/tests/integration_tests/test_cli_no_network.py b/tests/integration_tests/test_cli_no_network.py
--- a/tests/integration_tests/test_cli_no_network.py
+++ b/tests/integration_tests/test_cli_no_network.py
@@ -19,8 +19,10 @@
 
 import unittest
 from unittest.mock import MagicMock, patch
+from typing import Any
 import pytest
 from copy import deepcopy
+import re
 
 import bittensor
 
@@ -30,9 +32,24 @@ class TestCLINoNetwork(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls) -> None:
+        mock_delegate_info = {
+            "hotkey_ss58": "",
+            "total_stake": bittensor.Balance.from_rao(0),
+            "nominators": [],
+            "owner_ss58": "",
+            "take": 0.18, 
+            "validator_permits": [],
+            "registrations": [], 
+            "return_per_1000": bittensor.Balance.from_rao(0), 
+            "total_daily_return": bittensor.Balance.from_rao(0)
+        }
         cls._patched_subtensor = patch('bittensor._subtensor.subtensor_mock.mock_subtensor.mock', new=MagicMock(
             return_value=MagicMock(
                 get_subnets=MagicMock(return_value=[1]), # Mock subnet 1 ONLY.
+                block=10_000,
+                get_delegates=MagicMock(return_value=[
+                    bittensor.DelegateInfo( **mock_delegate_info )
+                ]),
             )
         ))
         cls._patched_subtensor.start()
@@ -64,10 +81,6 @@ def construct_config():
         return defaults
 
     def test_check_configs(self):
-        commands = ["run", "transfer", "register", "unstake", 
-        "stake", "overview", "new_coldkey", "new_hotkey", 
-        "regen_coldkey", "regen_hotkey", "metagraph", "weights", 
-        "set_weights", "inspect"]
         config = self.config
         config.no_prompt = True
         config.model = "core_server"
@@ -78,12 +91,30 @@ def test_check_configs(self):
         config.uids = [1,2,3]
         config.weights = [0.25, 0.25, 0.25, 0.25]
         config.no_version_checking = True
+        config.ss58_address = bittensor.Keypair.create_from_seed( b'0' * 32 ).ss58_address
+        config.public_key_hex = None
 
         cli = bittensor.cli
+
+        # Get argparser
+        parser = cli.__create_parser__() 
+        # Get all commands from argparser
+        commands = [ 
+            command for command in parser._actions[1].choices 
+        ]
+
+        def ask_response(prompt: str) -> Any:
+            if "delegate index" in prompt:
+                return 0
+            elif "wallet name" in prompt:
+                return "mock"
+            elif "hotkey" in prompt:
+                return "mock"
         
-        for cmd in commands:
-            config.command = cmd
-            cli.check_config(config)
+        with patch('rich.prompt.Prompt.ask', ask_response):
+            for cmd in commands:
+                config.command = cmd
+                cli.check_config(config)
 
     def test_new_coldkey( self ):
         config = self.config
@@ -256,8 +287,23 @@ def test_btcli_help(self):
         # Expected help output if all commands are listed
         assert 'positional arguments' in help_out
         # Verify that cli is printing the help message for 
-        assert 'overview' in help_out
-        assert 'run' in help_out
+        # Get argparser
+        parser = bittensor.cli.__create_parser__() 
+        # Get all commands from argparser
+        commands = [ 
+            command for command in parser._actions[1].choices 
+        ]
+        # Verify that all commands are listed in the help message
+        for command in commands:
+            assert command in help_out
+        
+        # Verify there are no duplicate commands
+        # Listed twice. Once in the positional arguments and once in the optional arguments
+        for command in commands:
+            pat = re.compile(rf'\n\s+({command})\s+\w')
+            matches = pat.findall(help_out)
+        
+            self.assertEqual( len(matches), 1, f"Duplicate command {command} in help output")
 
     def test_register_cuda_use_cuda_flag(self):
             class ExitEarlyException(Exception):