Skip to content

Commit 10b292d

Browse files
feat: Expand JSON utils with TransactionResult classes and extrinsic_identifier
- Add TransactionResult and MultiTransactionResult classes for consistent transaction responses across all commands - Add print_transaction_response() helper function - Update schema to use {success, message, extrinsic_identifier} format - Migrate wallets.py: transfer, swap_hotkey, set_id - Migrate sudo.py: trim command - Migrate stake/add.py and stake/remove.py - Migrate liquidity.py: add_liquidity, remove_liquidity, modify_liquidity - Update tests for new transaction response utilities (25 tests passing) This addresses feedback from @thewhaleking on PR #781 to apply standardized JSON output to all json_console usages with extrinsic_identifier support. Closes #635
1 parent 6ea32ba commit 10b292d

7 files changed

Lines changed: 251 additions & 134 deletions

File tree

bittensor_cli/src/bittensor/json_utils.py

Lines changed: 125 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,138 @@
44
This module provides consistent JSON response formatting across all btcli commands.
55
All JSON outputs should use these utilities to ensure schema compliance.
66
7-
Standard Response Format:
7+
Standard Transaction Response Format:
88
{
9-
"success": bool, # Required: Whether the operation succeeded
10-
"data": {...}, # Optional: Command-specific response data
11-
"error": str # Optional: Error message if success=False
9+
"success": bool, # Required: Whether the operation succeeded
10+
"message": str | None, # Optional: Human-readable message
11+
"extrinsic_identifier": str | None # Optional: Block-extrinsic ID (e.g., "12345-2")
1212
}
1313
14-
For transaction responses, data should include:
14+
Standard Data Response Format:
1515
{
16-
"extrinsic_hash": str, # The transaction hash
17-
"block_hash": str # The block containing the transaction
16+
"success": bool, # Required: Whether the operation succeeded
17+
"data": {...}, # Optional: Command-specific response data
18+
"error": str # Optional: Error message if success=False
1819
}
1920
"""
2021

2122
import json
2223
from typing import Any, Optional, Union
2324
from rich.console import Console
2425

25-
# JSON console for outputting JSON responses
2626
json_console = Console()
2727

2828

29+
def transaction_response(
30+
success: bool,
31+
message: Optional[str] = None,
32+
extrinsic_identifier: Optional[str] = None,
33+
) -> dict[str, Any]:
34+
"""
35+
Create a standardized transaction response dictionary.
36+
37+
Args:
38+
success: Whether the transaction succeeded
39+
message: Human-readable status message
40+
extrinsic_identifier: The extrinsic ID (e.g., "12345678-2")
41+
42+
Returns:
43+
Dictionary with standardized transaction format
44+
"""
45+
return {
46+
"success": success,
47+
"message": message,
48+
"extrinsic_identifier": extrinsic_identifier,
49+
}
50+
51+
52+
def print_transaction_response(
53+
success: bool,
54+
message: Optional[str] = None,
55+
extrinsic_identifier: Optional[str] = None,
56+
) -> None:
57+
"""
58+
Print a standardized transaction response as JSON.
59+
60+
Args:
61+
success: Whether the transaction succeeded
62+
message: Human-readable status message
63+
extrinsic_identifier: The extrinsic ID (e.g., "12345678-2")
64+
"""
65+
json_console.print_json(data=transaction_response(success, message, extrinsic_identifier))
66+
67+
68+
class TransactionResult:
69+
"""
70+
Helper class for building transaction responses.
71+
72+
Provides a clean interface for transaction commands that need to
73+
build up response data before printing.
74+
"""
75+
76+
def __init__(
77+
self,
78+
success: bool,
79+
message: Optional[str] = None,
80+
extrinsic_identifier: Optional[str] = None,
81+
):
82+
self.success = success
83+
self.message = message
84+
self.extrinsic_identifier = extrinsic_identifier
85+
86+
def as_dict(self) -> dict[str, Any]:
87+
"""Return the response as a dictionary."""
88+
return transaction_response(
89+
self.success,
90+
self.message,
91+
self.extrinsic_identifier,
92+
)
93+
94+
def print(self) -> None:
95+
"""Print the response as JSON."""
96+
json_console.print_json(data=self.as_dict())
97+
98+
99+
class MultiTransactionResult:
100+
"""
101+
Helper class for commands that process multiple transactions.
102+
103+
Builds a keyed dictionary of transaction results.
104+
"""
105+
106+
def __init__(self):
107+
self._results: dict[str, TransactionResult] = {}
108+
109+
def add(
110+
self,
111+
key: str,
112+
success: bool,
113+
message: Optional[str] = None,
114+
extrinsic_identifier: Optional[str] = None,
115+
) -> None:
116+
"""Add a transaction result with the given key."""
117+
self._results[key] = TransactionResult(success, message, extrinsic_identifier)
118+
119+
def add_result(self, key: str, result: TransactionResult) -> None:
120+
"""Add an existing TransactionResult with the given key."""
121+
self._results[key] = result
122+
123+
def as_dict(self) -> dict[str, dict[str, Any]]:
124+
"""Return all results as a dictionary."""
125+
return {k: v.as_dict() for k, v in self._results.items()}
126+
127+
def print(self) -> None:
128+
"""Print all results as JSON."""
129+
json_console.print_json(data=self.as_dict())
130+
131+
29132
def json_response(
30133
success: bool,
31134
data: Optional[Any] = None,
32135
error: Optional[str] = None,
33136
) -> str:
34137
"""
35-
Create a standardized JSON response string.
138+
Create a standardized JSON response string for data queries.
36139
37140
Args:
38141
success: Whether the operation succeeded
@@ -62,7 +165,7 @@ def json_response(
62165

63166
def json_success(data: Any) -> str:
64167
"""
65-
Create a successful JSON response.
168+
Create a successful JSON response string.
66169
67170
Args:
68171
data: Response data to include
@@ -75,7 +178,7 @@ def json_success(data: Any) -> str:
75178

76179
def json_error(error: str, data: Optional[Any] = None) -> str:
77180
"""
78-
Create an error JSON response.
181+
Create an error JSON response string.
79182
80183
Args:
81184
error: Error message describing what went wrong
@@ -87,43 +190,9 @@ def json_error(error: str, data: Optional[Any] = None) -> str:
87190
return json_response(success=False, data=data, error=error)
88191

89192

90-
def json_transaction(
91-
success: bool,
92-
extrinsic_hash: Optional[str] = None,
93-
block_hash: Optional[str] = None,
94-
error: Optional[str] = None,
95-
**extra_data: Any,
96-
) -> str:
97-
"""
98-
Create a standardized transaction response.
99-
100-
Args:
101-
success: Whether the transaction succeeded
102-
extrinsic_hash: The transaction/extrinsic hash
103-
block_hash: The block hash containing the transaction
104-
error: Error message if transaction failed
105-
**extra_data: Additional transaction-specific data
106-
107-
Returns:
108-
JSON string with transaction details
109-
"""
110-
data: dict[str, Any] = {}
111-
112-
if extrinsic_hash is not None:
113-
data["extrinsic_hash"] = extrinsic_hash
114-
115-
if block_hash is not None:
116-
data["block_hash"] = block_hash
117-
118-
# Add any extra data
119-
data.update(extra_data)
120-
121-
return json_response(success=success, data=data if data else None, error=error)
122-
123-
124193
def print_json(response: str) -> None:
125194
"""
126-
Print a JSON response to the console.
195+
Print a JSON string response to the console.
127196
128197
Args:
129198
response: JSON string to print
@@ -152,6 +221,16 @@ def print_json_error(error: str, data: Optional[Any] = None) -> None:
152221
print_json(json_error(error, data))
153222

154223

224+
def print_json_data(data: Any) -> None:
225+
"""
226+
Print data directly as JSON (for simple data responses).
227+
228+
Args:
229+
data: Data to print as JSON
230+
"""
231+
json_console.print_json(data=data)
232+
233+
155234
def serialize_balance(balance: Any) -> dict[str, Union[int, float]]:
156235
"""
157236
Serialize a Balance object to a consistent dictionary format.

bittensor_cli/src/commands/liquidity/liquidity.py

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
json_console,
1515
print_extrinsic_id,
1616
)
17+
from bittensor_cli.src.bittensor.json_utils import (
18+
print_transaction_response,
19+
print_json_data,
20+
TransactionResult,
21+
MultiTransactionResult,
22+
)
1723
from bittensor_cli.src.bittensor.balances import Balance, fixed_to_float
1824
from bittensor_cli.src.commands.liquidity.utils import (
1925
LiquidityPosition,
@@ -295,13 +301,7 @@ async def add_liquidity(
295301
else:
296302
ext_id = None
297303
if json_output:
298-
json_console.print_json(
299-
data={
300-
"success": success,
301-
"message": message,
302-
"extrinsic_identifier": ext_id,
303-
}
304-
)
304+
print_transaction_response(success, message, ext_id)
305305
else:
306306
if success:
307307
console.print(
@@ -558,9 +558,7 @@ async def show_liquidity_list(
558558
if not json_output:
559559
console.print(liquidity_table)
560560
else:
561-
json_console.print(
562-
json.dumps({"success": True, "err_msg": "", "positions": json_table})
563-
)
561+
print_json_data({"success": True, "err_msg": "", "positions": json_table})
564562

565563

566564
async def remove_liquidity(
@@ -584,9 +582,7 @@ async def remove_liquidity(
584582
success, msg, positions = await get_liquidity_list(subtensor, wallet, netuid)
585583
if not success:
586584
if json_output:
587-
json_console.print_json(
588-
data={"success": False, "err_msg": msg, "positions": positions}
589-
)
585+
print_json_data({"success": False, "err_msg": msg, "positions": positions})
590586
else:
591587
return print_error(f"Error: {msg}")
592588
return None
@@ -629,14 +625,11 @@ async def remove_liquidity(
629625
else:
630626
print_error(f"Error removing {posid}: {msg}")
631627
else:
632-
json_table = {}
628+
json_results = MultiTransactionResult()
633629
for (success, msg, ext_receipt), posid in zip(results, position_ids):
634-
json_table[posid] = {
635-
"success": success,
636-
"err_msg": msg,
637-
"extrinsic_identifier": await ext_receipt.get_extrinsic_identifier(),
638-
}
639-
json_console.print_json(data=json_table)
630+
ext_id = await ext_receipt.get_extrinsic_identifier() if ext_receipt else None
631+
json_results.add(str(posid), success, msg, ext_id)
632+
json_results.print()
640633
return None
641634

642635

@@ -657,7 +650,7 @@ async def modify_liquidity(
657650
if not await subtensor.subnet_exists(netuid=netuid):
658651
err_msg = f"Subnet with netuid: {netuid} does not exist in {subtensor}."
659652
if json_output:
660-
json_console.print(json.dumps({"success": False, "err_msg": err_msg}))
653+
print_transaction_response(False, err_msg, None)
661654
else:
662655
print_error(err_msg)
663656
return False
@@ -687,9 +680,7 @@ async def modify_liquidity(
687680
)
688681
if json_output:
689682
ext_id = await ext_receipt.get_extrinsic_identifier() if success else None
690-
json_console.print_json(
691-
data={"success": success, "err_msg": msg, "extrinsic_identifier": ext_id}
692-
)
683+
print_transaction_response(success, msg, ext_id)
693684
else:
694685
if success:
695686
await print_extrinsic_id(ext_receipt)

bittensor_cli/src/commands/stake/add.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
get_hotkey_pub_ss58,
2727
print_extrinsic_id,
2828
)
29+
from bittensor_cli.src.bittensor.json_utils import print_json_data
2930
from bittensor_wallet import Wallet
3031

3132
if TYPE_CHECKING:
@@ -528,13 +529,11 @@ async def stake_extrinsic(
528529
staking_address
529530
] = await ext_receipt.get_extrinsic_identifier()
530531
if json_output:
531-
json_console.print_json(
532-
data={
533-
"staking_success": successes,
534-
"error_messages": error_messages,
535-
"extrinsic_ids": extrinsic_ids,
536-
}
537-
)
532+
print_json_data({
533+
"staking_success": successes,
534+
"error_messages": error_messages,
535+
"extrinsic_ids": extrinsic_ids,
536+
})
538537

539538

540539
# Helper functions

bittensor_cli/src/commands/stake/remove.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
get_hotkey_pub_ss58,
3030
print_extrinsic_id,
3131
)
32+
from bittensor_cli.src.bittensor.json_utils import print_json_data
3233

3334
if TYPE_CHECKING:
3435
from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
@@ -370,7 +371,7 @@ async def unstake(
370371
f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]Unstaking operations completed."
371372
)
372373
if json_output:
373-
json_console.print_json(data=successes)
374+
print_json_data(successes)
374375
return True
375376

376377

@@ -580,7 +581,7 @@ async def unstake_all(
580581
"extrinsic_identifier": ext_id,
581582
}
582583
if json_output:
583-
json_console.print(json.dumps({"success": successes}))
584+
print_json_data({"success": successes})
584585

585586

586587
# Extrinsics

0 commit comments

Comments
 (0)