From 250eb96aeb55ce6e536a51528be823b43691856f Mon Sep 17 00:00:00 2001 From: bittoby <218712309+bittoby@users.noreply.github.com> Date: Wed, 15 Apr 2026 18:07:41 +0200 Subject: [PATCH] refactor: extract duplicate network/contract resolution to _resolve_contract_and_network helper --- gittensor/cli/issue_commands/admin.py | 39 ++------- gittensor/cli/issue_commands/helpers.py | 21 +++++ gittensor/cli/issue_commands/mutations.py | 29 +++---- gittensor/cli/issue_commands/view.py | 32 +++---- gittensor/cli/issue_commands/vote.py | 21 +---- tests/cli/test_cli_helpers.py | 100 ++++++++++++++++------ tests/cli/test_issues_list_json.py | 6 +- 7 files changed, 135 insertions(+), 113 deletions(-) diff --git a/gittensor/cli/issue_commands/admin.py b/gittensor/cli/issue_commands/admin.py index 905295fe..00018d72 100644 --- a/gittensor/cli/issue_commands/admin.py +++ b/gittensor/cli/issue_commands/admin.py @@ -19,13 +19,12 @@ from .help import StyledGroup from .helpers import ( _make_contract_client, + _resolve_contract_and_network, console, format_alpha, - get_contract_address, print_error, print_network_header, print_success, - resolve_network, validate_issue_id, validate_ss58_address, with_network_contract_options, @@ -60,11 +59,7 @@ def admin_cancel(issue_id: int, network: str, rpc_url: str, contract: str, walle $ gitt a cancel-issue 5 --network test [/dim] """ - contract_addr = get_contract_address(contract) - ws_endpoint, network_name = resolve_network(network, rpc_url) - - if not contract_addr: - raise click.ClickException('Contract address not configured.') + contract_addr, ws_endpoint, network_name = _resolve_contract_and_network(contract, network, rpc_url) try: validate_issue_id(issue_id) @@ -124,11 +119,7 @@ def admin_payout(issue_id: int, network: str, rpc_url: str, contract: str, walle $ gitt a payout-issue 3 --network test [/dim] """ - contract_addr = get_contract_address(contract) - ws_endpoint, network_name = resolve_network(network, rpc_url) - - if not contract_addr: - raise click.ClickException('Contract address not configured.') + contract_addr, ws_endpoint, network_name = _resolve_contract_and_network(contract, network, rpc_url) try: validate_issue_id(issue_id) @@ -184,11 +175,7 @@ def admin_set_owner(new_owner: str, network: str, rpc_url: str, contract: str, w $ gitt admin set-owner 5Hxxx... [/dim] """ - contract_addr = get_contract_address(contract) - ws_endpoint, network_name = resolve_network(network, rpc_url) - - if not contract_addr: - raise click.ClickException('Contract address not configured.') + contract_addr, ws_endpoint, network_name = _resolve_contract_and_network(contract, network, rpc_url) try: validate_ss58_address(new_owner, 'new_owner') @@ -240,11 +227,7 @@ def admin_set_treasury( $ gitt admin set-treasury 5Hxxx... [/dim] """ - contract_addr = get_contract_address(contract) - ws_endpoint, network_name = resolve_network(network, rpc_url) - - if not contract_addr: - raise click.ClickException('Contract address not configured.') + contract_addr, ws_endpoint, network_name = _resolve_contract_and_network(contract, network, rpc_url) try: validate_ss58_address(new_treasury, 'new_treasury') @@ -297,11 +280,7 @@ def admin_add_validator(hotkey: str, network: str, rpc_url: str, contract: str, $ gitt admin add-vali 5Hxxx... [/dim] """ - contract_addr = get_contract_address(contract) - ws_endpoint, network_name = resolve_network(network, rpc_url) - - if not contract_addr: - raise click.ClickException('Contract address not configured.') + contract_addr, ws_endpoint, network_name = _resolve_contract_and_network(contract, network, rpc_url) try: validate_ss58_address(hotkey, 'hotkey') @@ -355,11 +334,7 @@ def admin_remove_validator( $ gitt admin remove-vali 5Hxxx... [/dim] """ - contract_addr = get_contract_address(contract) - ws_endpoint, network_name = resolve_network(network, rpc_url) - - if not contract_addr: - raise click.ClickException('Contract address not configured.') + contract_addr, ws_endpoint, network_name = _resolve_contract_and_network(contract, network, rpc_url) try: validate_ss58_address(hotkey, 'hotkey') diff --git a/gittensor/cli/issue_commands/helpers.py b/gittensor/cli/issue_commands/helpers.py index aebdd938..95d10005 100644 --- a/gittensor/cli/issue_commands/helpers.py +++ b/gittensor/cli/issue_commands/helpers.py @@ -589,6 +589,27 @@ def resolve_network(network: Optional[str] = None, rpc_url: Optional[str] = None return NETWORK_MAP['finney'], 'finney' +def _resolve_contract_and_network( + contract: str, + network: Optional[str] = None, + rpc_url: Optional[str] = None, + *, + missing_contract_message: str = 'Contract address not configured.', +) -> Tuple[str, str, str]: + """Resolve contract address, WS endpoint, and network name from CLI options. + + Combines get_contract_address and resolve_network into one call, raising + click.ClickException when the contract address is empty. + + Returns (contract_addr, ws_endpoint, network_name). + """ + contract_addr = get_contract_address(contract) + ws_endpoint, network_name = resolve_network(network, rpc_url) + if not contract_addr: + raise click.ClickException(missing_contract_message) + return contract_addr, ws_endpoint, network_name + + # ============================================================================ # Contract storage reading helpers (shared by view and admin commands) # ============================================================================ diff --git a/gittensor/cli/issue_commands/mutations.py b/gittensor/cli/issue_commands/mutations.py index f1e16977..ec88d7fc 100644 --- a/gittensor/cli/issue_commands/mutations.py +++ b/gittensor/cli/issue_commands/mutations.py @@ -18,14 +18,13 @@ from .helpers import ( MAX_ISSUE_NUMBER, _is_interactive, + _resolve_contract_and_network, console, format_alpha, - get_contract_address, load_config, print_error, print_network_header, print_success, - resolve_network, validate_bounty_amount, validate_github_issue, validate_repository, @@ -119,15 +118,14 @@ def issue_register( """ console.print('\n[bold cyan]Register Issue for Bounty[/bold cyan]\n') - contract_addr = get_contract_address(contract) - ws_endpoint, network_name = resolve_network(network, rpc_url) + contract_addr, ws_endpoint, network_name = _resolve_contract_and_network( + contract, + network, + rpc_url, + missing_contract_message='Contract address not configured. Run ./up.sh --issues to deploy the contract first.', + ) config = load_config() - if not contract_addr: - raise click.ClickException( - 'Contract address not configured. Run ./up.sh --issues to deploy the contract first.' - ) - # Validate inputs before showing summary try: owner, repo_name = validate_repository(repo) @@ -304,13 +302,12 @@ def issue_harvest(wallet_name: str, wallet_hotkey: str, network: str, rpc_url: s """ console.print('\n[bold cyan]Manual Emission Harvest[/bold cyan]\n') - contract_addr = get_contract_address(contract) - ws_endpoint, network_name = resolve_network(network, rpc_url) - - if not contract_addr: - raise click.ClickException( - 'Contract address not configured. Set CONTRACT_ADDRESS env var or run ./up.sh --issues.' - ) + contract_addr, ws_endpoint, network_name = _resolve_contract_and_network( + contract, + network, + rpc_url, + missing_contract_message='Contract address not configured. Set CONTRACT_ADDRESS env var or run ./up.sh --issues.', + ) print_network_header(network_name, contract_addr) console.print(f'[dim]Wallet: {wallet_name}/{wallet_hotkey}[/dim]\n') diff --git a/gittensor/cli/issue_commands/view.py b/gittensor/cli/issue_commands/view.py index d09a2e6f..5de937ee 100644 --- a/gittensor/cli/issue_commands/view.py +++ b/gittensor/cli/issue_commands/view.py @@ -22,15 +22,14 @@ from .helpers import ( _read_contract_packed_storage, _read_issues_from_child_storage, + _resolve_contract_and_network, colorize_status, console, emit_error_json, format_alpha, - get_contract_address, print_error, print_network_header, read_issues_from_contract, - resolve_network, with_cli_behavior_options, with_network_contract_options, ) @@ -60,11 +59,12 @@ def issues_list(issue_id: int, network: str, rpc_url: str, contract: str, verbos $ gitt i list --json [/dim] """ - contract_addr = get_contract_address(contract) - ws_endpoint, network_name = resolve_network(network, rpc_url) - - if not contract_addr: - raise click.ClickException('Contract address not configured. Set via: gitt config set contract_address .') + contract_addr, ws_endpoint, network_name = _resolve_contract_and_network( + contract, + network, + rpc_url, + missing_contract_message='Contract address not configured. Set via: gitt config set contract_address .', + ) if not as_json: print_network_header(network_name, contract_addr) @@ -188,11 +188,7 @@ def issues_bounty_pool(network: str, rpc_url: str, contract: str, verbose: bool, $ gitt i bounty-pool --json [/dim] """ - contract_addr = get_contract_address(contract) - ws_endpoint, network_name = resolve_network(network, rpc_url) - - if not contract_addr: - raise click.ClickException('Contract address not configured.') + contract_addr, ws_endpoint, network_name = _resolve_contract_and_network(contract, network, rpc_url) if not as_json: print_network_header(network_name, contract_addr) @@ -238,11 +234,7 @@ def issues_pending_harvest(network: str, rpc_url: str, contract: str, verbose: b $ gitt i pending-harvest --json [/dim] """ - contract_addr = get_contract_address(contract) - ws_endpoint, network_name = resolve_network(network, rpc_url) - - if not contract_addr: - raise click.ClickException('Contract address not configured.') + contract_addr, ws_endpoint, network_name = _resolve_contract_and_network(contract, network, rpc_url) if not as_json: print_network_header(network_name, contract_addr) @@ -305,11 +297,7 @@ def admin_info(network: str, rpc_url: str, contract: str, verbose: bool, as_json $ gitt a info --json [/dim] """ - contract_addr = get_contract_address(contract) - ws_endpoint, network_name = resolve_network(network, rpc_url) - - if not contract_addr: - raise click.ClickException('Contract address not configured.') + contract_addr, ws_endpoint, network_name = _resolve_contract_and_network(contract, network, rpc_url) if not as_json: print_network_header(network_name, contract_addr) diff --git a/gittensor/cli/issue_commands/vote.py b/gittensor/cli/issue_commands/vote.py index 0e1c719d..547c2f4c 100644 --- a/gittensor/cli/issue_commands/vote.py +++ b/gittensor/cli/issue_commands/vote.py @@ -20,12 +20,11 @@ from .help import StyledGroup from .helpers import ( _make_contract_client, + _resolve_contract_and_network, console, - get_contract_address, print_error, print_network_header, print_success, - resolve_network, validate_issue_id, validate_ss58_address, with_cli_behavior_options, @@ -102,11 +101,7 @@ def val_vote_solution( $ gitt vote solution 1 5Hxxx... 5Hyyy... https://github.com/.../pull/123 [/dim] """ - contract_addr = get_contract_address(contract) - ws_endpoint, network_name = resolve_network(network, rpc_url) - - if not contract_addr: - raise click.ClickException('Contract address not configured.') + contract_addr, ws_endpoint, network_name = _resolve_contract_and_network(contract, network, rpc_url) try: validate_issue_id(issue_id) @@ -177,11 +172,7 @@ def val_vote_cancel_issue( $ gitt vote cancel 42 "Issue invalid" [/dim] """ - contract_addr = get_contract_address(contract) - ws_endpoint, network_name = resolve_network(network, rpc_url) - - if not contract_addr: - raise click.ClickException('Contract address not configured.') + contract_addr, ws_endpoint, network_name = _resolve_contract_and_network(contract, network, rpc_url) try: validate_issue_id(issue_id) @@ -225,11 +216,7 @@ def vote_list_validators(network: str, rpc_url: str, contract: str, as_json: boo $ gitt vote list --json [/dim] """ - contract_addr = get_contract_address(contract) - ws_endpoint, network_name = resolve_network(network, rpc_url) - - if not contract_addr: - raise click.ClickException('Contract address not configured.') + contract_addr, ws_endpoint, network_name = _resolve_contract_and_network(contract, network, rpc_url) if not as_json: print_network_header(network_name, contract_addr) diff --git a/tests/cli/test_cli_helpers.py b/tests/cli/test_cli_helpers.py index c5293f2c..dfabd6b8 100644 --- a/tests/cli/test_cli_helpers.py +++ b/tests/cli/test_cli_helpers.py @@ -373,8 +373,12 @@ class TestCliRegisterValidation: def test_register_rejects_low_bounty(self, cli_root, runner): with ( patch( - 'gittensor.cli.issue_commands.mutations.get_contract_address', - return_value='0x1234567890123456789012345678901234567890', + 'gittensor.cli.issue_commands.mutations._resolve_contract_and_network', + return_value=( + '0x1234567890123456789012345678901234567890', + 'wss://entrypoint-finney.opentensor.ai:443', + 'finney', + ), ), patch('gittensor.cli.issue_commands.mutations.validate_repository', return_value=('owner', 'repo')), patch('gittensor.cli.issue_commands.mutations.validate_github_issue', return_value={}), @@ -389,8 +393,12 @@ def test_register_rejects_low_bounty(self, cli_root, runner): def test_register_rejects_bad_repo_format(self, cli_root, runner): with patch( - 'gittensor.cli.issue_commands.mutations.get_contract_address', - return_value='0x1234567890123456789012345678901234567890', + 'gittensor.cli.issue_commands.mutations._resolve_contract_and_network', + return_value=( + '0x1234567890123456789012345678901234567890', + 'wss://entrypoint-finney.opentensor.ai:443', + 'finney', + ), ): result = runner.invoke( cli_root, @@ -402,8 +410,12 @@ def test_register_rejects_bad_repo_format(self, cli_root, runner): def test_register_rejects_issue_zero(self, cli_root, runner): with patch( - 'gittensor.cli.issue_commands.mutations.get_contract_address', - return_value='0x1234567890123456789012345678901234567890', + 'gittensor.cli.issue_commands.mutations._resolve_contract_and_network', + return_value=( + '0x1234567890123456789012345678901234567890', + 'wss://entrypoint-finney.opentensor.ai:443', + 'finney', + ), ): result = runner.invoke( cli_root, @@ -417,8 +429,12 @@ def test_register_rejects_issue_number_over_max(self, cli_root, runner): over_max = str(MAX_ISSUE_NUMBER + 1) with ( patch( - 'gittensor.cli.issue_commands.mutations.get_contract_address', - return_value='0x1234567890123456789012345678901234567890', + 'gittensor.cli.issue_commands.mutations._resolve_contract_and_network', + return_value=( + '0x1234567890123456789012345678901234567890', + 'wss://entrypoint-finney.opentensor.ai:443', + 'finney', + ), ), patch('gittensor.cli.issue_commands.mutations.validate_repository', return_value=('a', 'b')), patch('gittensor.cli.issue_commands.mutations.validate_github_issue', return_value={}), @@ -437,8 +453,12 @@ class TestCliVoteValidation: def test_vote_solution_rejects_issue_id_zero(self, cli_root, runner): with patch( - 'gittensor.cli.issue_commands.vote.get_contract_address', - return_value='0x1234567890123456789012345678901234567890', + 'gittensor.cli.issue_commands.vote._resolve_contract_and_network', + return_value=( + '0x1234567890123456789012345678901234567890', + 'wss://entrypoint-finney.opentensor.ai:443', + 'finney', + ), ): result = runner.invoke( cli_root, @@ -457,8 +477,12 @@ def test_vote_solution_rejects_issue_id_zero(self, cli_root, runner): def test_vote_solution_rejects_pr_zero(self, cli_root, runner): with patch( - 'gittensor.cli.issue_commands.vote.get_contract_address', - return_value='0x1234567890123456789012345678901234567890', + 'gittensor.cli.issue_commands.vote._resolve_contract_and_network', + return_value=( + '0x1234567890123456789012345678901234567890', + 'wss://entrypoint-finney.opentensor.ai:443', + 'finney', + ), ): result = runner.invoke( cli_root, @@ -477,8 +501,12 @@ def test_vote_solution_rejects_pr_zero(self, cli_root, runner): def test_vote_solution_rejects_invalid_pr(self, cli_root, runner): with patch( - 'gittensor.cli.issue_commands.vote.get_contract_address', - return_value='0x1234567890123456789012345678901234567890', + 'gittensor.cli.issue_commands.vote._resolve_contract_and_network', + return_value=( + '0x1234567890123456789012345678901234567890', + 'wss://entrypoint-finney.opentensor.ai:443', + 'finney', + ), ): result = runner.invoke( cli_root, @@ -501,8 +529,12 @@ class TestCliAdminValidation: def test_admin_cancel_rejects_issue_id_zero(self, cli_root, runner): with patch( - 'gittensor.cli.issue_commands.admin.get_contract_address', - return_value='0x1234567890123456789012345678901234567890', + 'gittensor.cli.issue_commands.admin._resolve_contract_and_network', + return_value=( + '0x1234567890123456789012345678901234567890', + 'wss://entrypoint-finney.opentensor.ai:443', + 'finney', + ), ): result = runner.invoke( cli_root, @@ -514,8 +546,12 @@ def test_admin_cancel_rejects_issue_id_zero(self, cli_root, runner): def test_admin_payout_rejects_issue_id_zero(self, cli_root, runner): with patch( - 'gittensor.cli.issue_commands.admin.get_contract_address', - return_value='0x1234567890123456789012345678901234567890', + 'gittensor.cli.issue_commands.admin._resolve_contract_and_network', + return_value=( + '0x1234567890123456789012345678901234567890', + 'wss://entrypoint-finney.opentensor.ai:443', + 'finney', + ), ): result = runner.invoke( cli_root, @@ -531,8 +567,12 @@ class TestCliVoteCancelValidation: def test_vote_cancel_rejects_issue_id_zero(self, cli_root, runner): with patch( - 'gittensor.cli.issue_commands.vote.get_contract_address', - return_value='0x1234567890123456789012345678901234567890', + 'gittensor.cli.issue_commands.vote._resolve_contract_and_network', + return_value=( + '0x1234567890123456789012345678901234567890', + 'wss://entrypoint-finney.opentensor.ai:443', + 'finney', + ), ): result = runner.invoke( cli_root, @@ -547,7 +587,10 @@ class TestCliMissingContractConfig: """Ensure missing contract config exits non-zero.""" def test_register_missing_contract_fails(self, cli_root, runner): - with patch('gittensor.cli.issue_commands.mutations.get_contract_address', return_value=''): + with patch( + 'gittensor.cli.issue_commands.mutations._resolve_contract_and_network', + side_effect=click.ClickException('Contract address not configured.'), + ): result = runner.invoke( cli_root, ['issues', 'register', '--repo', 'a/b', '--issue', '1', '--bounty', '10', '-y'], @@ -557,7 +600,10 @@ def test_register_missing_contract_fails(self, cli_root, runner): assert 'Contract address not configured' in result.output def test_vote_missing_contract_fails(self, cli_root, runner): - with patch('gittensor.cli.issue_commands.vote.get_contract_address', return_value=''): + with patch( + 'gittensor.cli.issue_commands.vote._resolve_contract_and_network', + side_effect=click.ClickException('Contract address not configured.'), + ): result = runner.invoke( cli_root, [ @@ -574,7 +620,10 @@ def test_vote_missing_contract_fails(self, cli_root, runner): assert 'Contract address not configured' in result.output def test_admin_missing_contract_fails(self, cli_root, runner): - with patch('gittensor.cli.issue_commands.admin.get_contract_address', return_value=''): + with patch( + 'gittensor.cli.issue_commands.admin._resolve_contract_and_network', + side_effect=click.ClickException('Contract address not configured.'), + ): result = runner.invoke( cli_root, ['admin', 'cancel-issue', '1'], @@ -584,7 +633,10 @@ def test_admin_missing_contract_fails(self, cli_root, runner): assert 'Contract address not configured' in result.output def test_harvest_missing_contract_fails(self, cli_root, runner): - with patch('gittensor.cli.issue_commands.mutations.get_contract_address', return_value=''): + with patch( + 'gittensor.cli.issue_commands.mutations._resolve_contract_and_network', + side_effect=click.ClickException('Contract address not configured.'), + ): result = runner.invoke( cli_root, ['harvest'], diff --git a/tests/cli/test_issues_list_json.py b/tests/cli/test_issues_list_json.py index 9c07db1d..41fe3131 100644 --- a/tests/cli/test_issues_list_json.py +++ b/tests/cli/test_issues_list_json.py @@ -21,8 +21,10 @@ def test_issues_list_json_missing_issue_returns_structured_error(cli_root, runner): """Requesting a nonexistent issue ID must return a structured JSON error with non-zero exit.""" with ( - patch('gittensor.cli.issue_commands.view.get_contract_address', return_value='5Fakeaddr'), - patch('gittensor.cli.issue_commands.view.resolve_network', return_value=('ws://x', 'test')), + patch( + 'gittensor.cli.issue_commands.view._resolve_contract_and_network', + return_value=('5Fakeaddr', 'ws://x', 'test'), + ), patch('gittensor.cli.issue_commands.view.read_issues_from_contract', return_value=FAKE_ISSUES), ): result = runner.invoke(cli_root, ['issues', 'list', '--json', '--id', '999'], catch_exceptions=False)