Skip to content

Commit 8980910

Browse files
authored
feat: Add Client factory methods (from_env) and simplify examples (#1274)
Signed-off-by: Adityarya11 <[email protected]>
1 parent 08207a1 commit 8980910

File tree

9 files changed

+433
-181
lines changed

9 files changed

+433
-181
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
7676
- Added convenient factory methods to `Hbar` class for easier instantiation: `from_microbars()`, `from_millibars()`, `from_hbars()`, `from_kilobars()`, `from_megabars()`, and `from_gigabars()`. [#1272](https://github.com/hiero-ledger/hiero-sdk-python/issues/1272)
7777
- Added merge conflict bot workflow (`.github/workflows/bot-merge-conflict.yml`) and helper script (`.github/scripts/bot-merge-conflict.js`) to detect and notify about PR merge conflicts, with retry logic for unknown mergeable states, idempotent commenting, and push-to-main recheck logic (#1247)
7878
- Added workflow to prevent assigning intermediate issues to contributors without prior Good First Issue completion (#1143).
79+
- Added `Client.from_env()` and network-specific factory methods (e.g., `Client.for_testnet()`) to simplify client initialization and reduce boilerplate. [[#1251](https://github.com/hiero-ledger/hiero-sdk-python/issues/1251)]
7980

8081
### Changed
8182

examples/account/account_create_transaction.py

Lines changed: 32 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -21,102 +21,66 @@
2121
- Environment variables OPERATOR_ID and OPERATOR_KEY must be set
2222
- A .env file with the operator credentials (recommended)
2323
- Sufficient HBAR balance in the operator account to pay for account creation
24+
25+
Environment Variables:
26+
OPERATOR_ID (str): The account ID of the operator (format: "0.0.xxxxx")
27+
OPERATOR_KEY (str): The private key of the operator account
28+
NETWORK (str, optional): Network to use (default: "testnet")
2429
"""
25-
import os
2630
import sys
27-
from typing import Tuple
28-
from dotenv import load_dotenv
29-
3031
from hiero_sdk_python import (
3132
Client,
32-
Network,
33-
AccountId,
3433
PrivateKey,
3534
AccountCreateTransaction,
3635
ResponseCode,
3736
)
3837

39-
load_dotenv()
40-
network_name = os.getenv('NETWORK', 'testnet').lower()
41-
42-
def setup_client() -> Tuple[Client, PrivateKey]:
43-
"""
44-
Set up and configure a Hedera client for testnet operations.
45-
46-
Creates a client instance connected to the Hedera testnet and configures it
47-
with operator credentials from environment variables. The operator account
48-
is used to pay for transactions and sign them.
49-
50-
Returns:
51-
tuple: A tuple containing:
52-
- Client: Configured Hedera client instance
53-
- PrivateKey: The operator's private key for signing transactions
54-
55-
Raises:
56-
ValueError: If OPERATOR_ID or OPERATOR_KEY environment variables are not set
57-
Exception: If there's an error parsing the operator credentials
58-
59-
Environment Variables:
60-
OPERATOR_ID (str): The account ID of the operator (format: "0.0.xxxxx")
61-
OPERATOR_KEY (str): The private key of the operator account
62-
"""
63-
64-
network = Network(network_name)
65-
print(f"Connecting to Hedera {network_name} network!")
66-
client = Client(network)
67-
68-
operator_id = AccountId.from_string(os.getenv('OPERATOR_ID',''))
69-
operator_key = PrivateKey.from_string(os.getenv('OPERATOR_KEY',''))
70-
client.set_operator(operator_id, operator_key)
71-
72-
return client, operator_key
73-
74-
def create_new_account(client: Client, operator_key: PrivateKey) -> None:
38+
def create_new_account(client: Client) -> None:
7539
"""
7640
Create a new Hedera account with generated keys and initial balance.
77-
41+
7842
This function generates a new Ed25519 key pair, creates an account creation
79-
transaction, signs it with the operator key, and executes it on the network.
43+
transaction, signs it with the operator key, and executes it on the network.
8044
The new account is created with an initial balance and a custom memo.
81-
45+
8246
Args:
83-
client (Client): Configured Hedera client instance for network communication
84-
operator_key (PrivateKey): The operator's private key for signing the transaction
85-
47+
client (Client): Configured Hedera client instance with operator set
48+
8649
Returns:
87-
None: This function doesn't return a value but prints the results
88-
50+
None: This function doesn't return a value but prints the results
51+
8952
Raises:
9053
Exception: If the transaction fails or the account ID is not found in the receipt
91-
SystemExit: Calls sys.exit(1) if account creation fails
92-
54+
SystemExit: Calls sys.exit(1) if account creation fails
55+
9356
Side Effects:
9457
- Prints transaction status and account details to stdout
9558
- Creates a new account on the Hedera network
9659
- Deducts transaction fees from the operator account
9760
- Exits the program with code 1 if creation fails
98-
61+
9962
Example Output:
10063
Transaction status: ResponseCode.SUCCESS
10164
Account creation successful. New Account ID: 0.0.123456
102-
New Account Private Key: 302e020100300506032b657004220420...
65+
New Account Private Key: 302e020100300506032b657004220420...
10366
New Account Public Key: 302a300506032b6570032100...
10467
"""
10568
new_account_private_key = PrivateKey.generate("ed25519")
10669
new_account_public_key = new_account_private_key.public_key()
10770

71+
# Get the operator key from the client for signing
72+
operator_key = client.operator_private_key
73+
10874
transaction = (
10975
AccountCreateTransaction()
11076
.set_key(new_account_public_key)
11177
.set_initial_balance(100000000) # 1 HBAR in tinybars
11278
.set_account_memo("My new account")
113-
.freeze_with(client)
11479
)
11580

116-
transaction.sign(operator_key)
117-
11881
try:
119-
receipt = transaction.execute(client)
82+
# Explicit signing with key retrieved from client
83+
receipt = transaction.freeze_with(client).sign(operator_key).execute(client)
12084
print(f"Transaction status: {receipt.status}")
12185

12286
if receipt.status != ResponseCode.SUCCESS:
@@ -125,16 +89,18 @@ def create_new_account(client: Client, operator_key: PrivateKey) -> None:
12589

12690
new_account_id = receipt.account_id
12791
if new_account_id is not None:
128-
print(f"Account creation successful. New Account ID: {new_account_id}")
129-
print(f"New Account Private Key: {new_account_private_key.to_string()}")
130-
print(f"New Account Public Key: {new_account_public_key.to_string()}")
92+
print(f"Account creation successful. New Account ID: {new_account_id}")
93+
print(f" New Account Private Key: {new_account_private_key.to_string()}")
94+
print(f" New Account Public Key: {new_account_public_key.to_string()}")
13195
else:
132-
raise Exception("AccountID not found in receipt. Account may not have been created.")
96+
raise Exception("AccountID not found in receipt. Account may not have been created.")
13397

13498
except Exception as e:
135-
print(f"Account creation failed: {str(e)}")
99+
print(f"Account creation failed: {str(e)}")
136100
sys.exit(1)
137101

138-
if __name__ == "__main__":
139-
client, operator_key = setup_client()
140-
create_new_account(client, operator_key)
102+
if __name__ == "__main__":
103+
client = Client.from_env()
104+
print(f"Operator: {client.operator_account_id}")
105+
create_new_account(client)
106+
client.close()

examples/account/account_delete_transaction.py

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,15 @@
55
python examples/account/account_delete_transaction.py
66
77
"""
8-
import os
98
import sys
10-
11-
from dotenv import load_dotenv
12-
13-
from hiero_sdk_python import AccountId, Client, Hbar, Network, PrivateKey
14-
from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction
15-
from hiero_sdk_python.account.account_delete_transaction import AccountDeleteTransaction
16-
from hiero_sdk_python.response_code import ResponseCode
17-
18-
load_dotenv()
19-
20-
network_name = os.getenv('NETWORK', 'testnet').lower()
21-
22-
def setup_client():
23-
"""Initialize and set up the client with operator account"""
24-
network = Network(network_name)
25-
print(f"Connecting to Hedera {network_name} network!")
26-
client = Client(network)
27-
28-
operator_id = AccountId.from_string(os.getenv("OPERATOR_ID", ""))
29-
operator_key = PrivateKey.from_string(os.getenv("OPERATOR_KEY", ""))
30-
client.set_operator(operator_id, operator_key)
31-
print(f"Client set up with operator id {client.operator_account_id}")
32-
33-
return client
9+
from hiero_sdk_python import (
10+
Client,
11+
Hbar,
12+
PrivateKey,
13+
AccountCreateTransaction,
14+
AccountDeleteTransaction,
15+
ResponseCode
16+
)
3417

3518
def create_account(client):
3619
"""Create a test account"""
@@ -66,7 +49,7 @@ def account_delete():
6649
2. Creating an account
6750
3. Deleting the account
6851
"""
69-
client = setup_client()
52+
client = Client.from_env()
7053

7154
# Create an account first
7255
account_id, account_private_key = create_account(client)

examples/client/client.py

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,113 @@
11
"""
22
Complete network and client setup example with detailed logging.
33
4-
uv run examples/client/client.py
5-
python examples/client/client.py
4+
This example demonstrates the internal steps of setting up a client
5+
(Network -> Client -> Operator) which is useful for understanding
6+
the architecture. It also demonstrates the quick `from_env()` method.
7+
8+
Usage:
9+
uv run examples/client/client.py
10+
python examples/client/client.py
611
"""
712
import os
813
from dotenv import load_dotenv
914

10-
from hiero_sdk_python.client.network import Network
11-
from hiero_sdk_python.client.client import Client
12-
from hiero_sdk_python.account.account_id import AccountId
13-
from hiero_sdk_python.crypto.private_key import PrivateKey
15+
from hiero_sdk_python import Client, Network, AccountId, PrivateKey
1416

1517
load_dotenv()
1618

17-
1819
def setup_network():
1920
"""Create and configure the network with logging."""
2021
network_name = os.getenv('NETWORK', 'testnet').lower()
22+
2123
print("Step 1: Create the network configuration")
2224
network = Network(network_name)
23-
print(f"Connected to: {network.network}")
24-
print(f"Nodes available: {len(network.nodes)}")
25+
26+
print(f" - Connected to: {network.network}")
27+
print(f" - Nodes available: {len(network.nodes)}")
28+
2529
return network
2630

2731

2832
def setup_client(network):
2933
"""Create and initialize the client with the network."""
3034
print("\nStep 2: Create the client with the network")
3135
client = Client(network)
32-
print(f"Client initialized with network: {client.network.network}")
36+
37+
print(f" - Client initialized with network: {client.network.network}")
3338
return client
3439

35-
3640
def setup_operator(client):
3741
"""Configure operator credentials for the client."""
3842
print("\nStep 3: Configure operator credentials")
39-
operator_id = AccountId.from_string(os.getenv("OPERATOR_ID", "0.0.0"))
40-
operator_key = PrivateKey.from_string(os.getenv("OPERATOR_KEY", ""))
41-
client.set_operator(operator_id, operator_key)
42-
print(f"Operator set: {client.operator_account_id}")
43+
44+
operator_id_str = os.getenv("OPERATOR_ID")
45+
operator_key_str = os.getenv("OPERATOR_KEY")
46+
47+
if not operator_id_str or not operator_key_str:
48+
print(" - ⚠️ OPERATOR_ID or OPERATOR_KEY missing in environment.")
49+
return
4350

51+
operator_id = AccountId.from_string(operator_id_str)
52+
operator_key = PrivateKey.from_string(operator_key_str)
53+
54+
client.set_operator(operator_id, operator_key)
55+
print(f" - Operator set: {client.operator_account_id}")
4456

4557
def display_client_configuration(client):
4658
"""Display client configuration details."""
4759
print("\n=== Client Configuration ===")
4860
print(f"Client is ready to use!")
49-
print(f"Current node: {client.network.current_node._account_id}")
50-
print(f"Mirror address: {client.network.get_mirror_address()}")
5161
print(f"Max retry attempts: {client.max_attempts}")
62+
63+
64+
nodes = client.get_node_account_ids()
65+
print(f"Total Nodes: {len(nodes)}")
5266

5367

5468
def display_available_nodes(client):
55-
"""Display all available nodes in the network."""
56-
print("\n=== Available Nodes ===")
57-
for node_id in client.get_node_account_ids():
69+
"""Display a sample of available nodes in the network."""
70+
print("\n=== Available Nodes (Sample) ===")
71+
nodes = client.get_node_account_ids()
72+
73+
# showing first 5 Nodes
74+
for i, node_id in enumerate(nodes[:5]):
5875
print(f" - Node: {node_id}")
76+
77+
if len(nodes) > 5:
78+
print(f" ... and {len(nodes) - 5} more.")
5979

6080

61-
def main():
62-
"""Complete setup of network and client with full configuration details."""
63-
print("=== Network and Client Setup ===\n")
64-
81+
def demonstrate_manual_setup():
82+
"""Run the detailed, step-by-step setup."""
83+
print("\n--- [ Method 1: Manual Setup] ---")
6584
network = setup_network()
6685
client = setup_client(network)
6786
setup_operator(client)
6887
display_client_configuration(client)
6988
display_available_nodes(client)
70-
71-
print("\nClient is ready for transactions!")
7289
client.close()
7390

7491

92+
def demonstrate_fast_setup():
93+
"""Run the quick setup used in production."""
94+
print("\n--- [ Method 2: Fast Setup (from_env) ] ---")
95+
print("Initializing client from environment variables...")
96+
try:
97+
client = Client.from_env()
98+
print(f"✅ Success! Connected as operator: {client.operator_account_id}")
99+
client.close()
100+
except Exception as e:
101+
print(f"❌ Failed: {e}")
102+
103+
104+
def main():
105+
# 1. Run the verbose example
106+
demonstrate_manual_setup()
107+
108+
# 2. Run the concise example (Best Practice)
109+
demonstrate_fast_setup()
110+
111+
75112
if __name__ == "__main__":
76-
main()
113+
main()

0 commit comments

Comments
 (0)