Skip to content

Commit 2c60bac

Browse files
Merge branch 'main' into chore/format-conftest-with-black
Signed-off-by: Subhra Sameer Dash <[email protected]>
2 parents 2add15a + 56e6ed3 commit 2c60bac

File tree

5 files changed

+101
-89
lines changed

5 files changed

+101
-89
lines changed

.github/scripts/bot-next-issue-recommendation.js

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -69,23 +69,41 @@ module.exports = async ({ github, context, core }) => {
6969
}
7070

7171
let recommendedIssues = [];
72+
let recommendedLabel = null;
73+
let isFallback = false;
74+
let recommendationScope = 'repo';
7275

73-
if (difficultyLevels.goodFirstIssue) {
74-
// Recommend beginner issues first, then Good First Issues
75-
recommendedIssues = await searchIssues(github, core, repoOwner, repoName, 'beginner');
76-
if (recommendedIssues.length === 0) {
77-
recommendedIssues = await searchIssues(github, core, repoOwner, repoName, 'good first issue');
78-
}
79-
} else if (difficultyLevels.beginner) {
80-
// Recommend beginner or Good First Issues
81-
recommendedIssues = await searchIssues(github, core, repoOwner, repoName, 'beginner');
82-
if (recommendedIssues.length === 0) {
83-
recommendedIssues = await searchIssues(github, core, repoOwner, repoName, 'good first issue');
84-
}
76+
recommendedIssues = await searchIssues(github, core, repoOwner, repoName, 'beginner');
77+
recommendedLabel = 'Beginner';
78+
79+
if (recommendedIssues.length === 0) {
80+
isFallback = true;
81+
recommendedIssues = await searchIssues(github, core, repoOwner, repoName, 'good first issue');
82+
recommendedLabel = 'Good First Issue';
83+
}
84+
85+
if (recommendedIssues.length === 0) {
86+
recommendationScope = 'org';
87+
recommendedLabel = 'Good First Issue';
88+
recommendedIssues = await github.rest.search.issuesAndPullRequests({
89+
q: `org:hiero-ledger type:issue state:open label:"good first issue" no:assignee`,
90+
per_page: 6,
91+
}).then(res => res.data.items);
8592
}
93+
94+
// Remove the issue they just solved
95+
recommendedIssues = recommendedIssues.filter(i => i.number !== issueNumber);
8696

8797
// Generate and post comment
88-
await generateAndPostComment(github, context, core, prNumber, recommendedIssues, difficultyLevels.goodFirstIssue);
98+
const completedLabel = difficultyLevels.goodFirstIssue ? 'Good First Issue' : 'Beginner';
99+
const completedLabelText = completedLabel === 'Beginner' ? 'Beginner issue' : completedLabel;
100+
const recommendationMeta = {
101+
completedLabelText,
102+
recommendedLabel,
103+
isFallback,
104+
recommendationScope,
105+
};
106+
await generateAndPostComment(github, context, core, prNumber, recommendedIssues, recommendationMeta);
89107

90108
} catch (error) {
91109
core.setFailed(`Error processing issue #${issueNumber}: ${error.message}`);
@@ -94,12 +112,12 @@ module.exports = async ({ github, context, core }) => {
94112

95113
async function searchIssues(github, core, owner, repo, label) {
96114
try {
97-
const query = `repo:${owner}/${repo} is:issue is:open label:"${label}" no:assignee`;
115+
const query = `repo:${owner}/${repo} type:issue state:open label:"${label}" no:assignee`;
98116
core.info(`Searching for issues with query: ${query}`);
99117

100118
const { data: searchResult } = await github.rest.search.issuesAndPullRequests({
101119
q: query,
102-
per_page: 5,
120+
per_page: 6,
103121
});
104122

105123
core.info(`Found ${searchResult.items.length} issues with label "${label}"`);
@@ -110,15 +128,21 @@ async function searchIssues(github, core, owner, repo, label) {
110128
}
111129
}
112130

113-
async function generateAndPostComment(github, context, core, prNumber, recommendedIssues, wasGoodFirstIssue) {
131+
async function generateAndPostComment(github, context, core, prNumber, recommendedIssues, { completedLabelText, recommendedLabel, isFallback, recommendationScope }) {
114132
const marker = '<!-- next-issue-bot-marker -->';
115133

116134
// Build comment content
117-
let comment = `${marker}\n\n🎉 **Congratulations on completing a beginner/Good First Issue!**\n\n`;
135+
let comment = `${marker}\n\n🎉 **Nice work completing a ${completedLabelText}!**\n\n`;
118136
comment += `Thank you for your contribution to the Hiero Python SDK! We're excited to have you as part of our community.\n\n`;
119137

120138
if (recommendedIssues.length > 0) {
121-
comment += `Here are some ${wasGoodFirstIssue ? 'beginner-level' : 'similar'} issues you might be interested in working on next:\n\n`;
139+
if (recommendationScope === 'org') {
140+
comment += `Here are some **Good First Issues across the Hiero organization** you might be interested in working on next:\n\n`;
141+
} else if (isFallback) {
142+
comment += `Here are some **${recommendedLabel}** issues at a similar level you might be interested in working on next:\n\n`;
143+
} else {
144+
comment += `Here are some issues labeled **${recommendedLabel}** you might be interested in working on next:\n\n`;
145+
}
122146

123147
// Sanitize title: escape markdown link syntax and special characters
124148
const sanitizeTitle = (title) => title
@@ -143,8 +167,11 @@ async function generateAndPostComment(github, context, core, prNumber, recommend
143167
}
144168
});
145169
} else {
146-
comment += `There are currently no open ${wasGoodFirstIssue ? 'beginner' : 'similar'} issues in this repository. \n\n`;
147-
comment += `You can check out good first issues across the entire Hiero organization: [Hiero Good First Issues](https://github.com/issues?q=is%3Aopen+is%3Aissue+org%3Ahiero-ledger+archived%3Afalse+label%3A%22good+first+issue%22+)\n\n`;
170+
comment += `There are currently no open issues available at or near the ${completedLabelText} level in this repository.\n\n`;
171+
const orgLabel = recommendedLabel === 'Beginner' ? 'beginner' : 'good first issue';
172+
const orgLabelQuery = encodeURIComponent(`label:"${orgLabel}"`);
173+
comment += `You can check out ${recommendedLabel.toLowerCase()} issues across the entire Hiero organization: ` +
174+
`[Hiero ${recommendedLabel} Issues](https://github.com/issues?q=org%3Ahiero-ledger+type%3Aissue+state%3Aopen+${orgLabelQuery})\n\n`;
148175
}
149176

150177
comment += `🌟 **Stay connected with the project:**\n`;

CHANGELOG.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
1111
### Tests
1212

1313
- Formatted `tests/unit/conftest.py` with black for code style consistency. (#1522)
14+
- format `black tests/unit/nft_id_test.py` with Black.(#1544)
1415
- Format `tests/unit/executable_test.py` with Black.(#1530)
1516
- Format `tests/unit/hedera_trust_manager_test.py` with Black for consistent code style (#1539)
1617
- Format tests/unit/logger_test.py with black for code style consistency (#1541)
1718
- Format `tests/unit/batch_transaction_test.py` with Black.(`#1520`)
1819
- Style: formatted `tests/unit/prng_transaction_test.py` with black (#1546)
1920
- Formatted contract unit tests with black for consistent style. (#1523)
2021
- Format account test files with Black (#1519)
22+
- Format `tests/unit/node*.py` with Black for consistent code style (#1545)
2123
- Improve unit test coverage for Hbar, including edge cases, validation, comparisons, and hashing. (#1483)
2224
- Standardize formatting of evm_address_test.py using Black for improved consistency and readability (#1529)
2325
- Formatted unit test files using Black.
@@ -133,7 +135,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
133135
- Added dry-run support and refactored `.github/workflows/bot-workflows.yml` to use dedicated script `.github/scripts/bot-workflows.js` for improved maintainability and testability. (`#1288`)
134136

135137
### Changed
136-
138+
- Refactored `examples/consensus/topic_create_transaction.py` to use `Client.from_env()` (#1611)
137139
- Updated GitHub Actions setup-node action to v6.2.0.
138140
- chore: format tests/unit/mock_server.py with black (#1542)
139141
- Updated actions/checkout to v6.0.1 and actions/github-script v8.0.0 in bot-next-issue-recommendation workflow (#1586)
@@ -218,10 +220,9 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
218220
- Enhance TopicInfo `__str__` method and tests with additional coverage, and update the format_key function in `key_format.py` to handle objects with a \_to_proto method.
219221
- Update changelog workflow to trigger automatically on pull requests instead of manual dispatch (#1567)
220222
- Formatted key-related unit test files (`key_utils_test.py`, `test_key_format.py`, `test_key_list.py`) using the black formatter
221-
- chore: update maintainer guidelines link in MAINTAINERS.md (#1605)
222223

223224
### Fixed
224-
225+
- Fix the next-issue recommendation bot to post the correct issue recommendation comment. (#1593)
225226
- Ensured that the GFI assignment bot skips posting `/assign` reminders for repository collaborators to avoid unnecessary notifications.(#1568).
226227
- Reduced notification spam by skipping the entire advanced qualification job for non-advanced issues and irrelevant events (#1517)
227228
- Aligned token freeze example filename references and improved error handling by catching broader exceptions with clearer messages. (#1412)

examples/consensus/topic_create_transaction.py

Lines changed: 15 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,56 +2,17 @@
22
uv run examples/consensus/topic_create_transaction.py
33
python examples/consensus/topic_create_transaction.py
44
"""
5+
from hiero_sdk_python import Client, TopicCreateTransaction, ResponseCode, PrivateKey
56

6-
import os
7-
import sys
8-
from typing import Tuple
9-
from dotenv import load_dotenv
10-
11-
from hiero_sdk_python import (
12-
Client,
13-
AccountId,
14-
PrivateKey,
15-
TopicCreateTransaction,
16-
Network,
17-
)
18-
19-
# Load environment variables from .env file
20-
load_dotenv()
21-
network_name = os.getenv("NETWORK", "testnet").lower()
22-
23-
24-
def setup_client() -> Tuple[Client, PrivateKey]:
7+
def setup_client():
258
"""
26-
Sets up and configures the Hiero client for the testnet.
27-
Reads OPERATOR_ID and OPERATOR_KEY from environment variables.
9+
Sets up and configures the Hiero client.
10+
Reads OPERATOR_ID and OPERATOR_KEY from environment variables via Client.from_env().
2811
"""
29-
network = Network(network_name)
30-
print(f"Connecting to Hedera {network_name} network!")
31-
client = Client(network)
32-
33-
operator_id_str = os.getenv("OPERATOR_ID")
34-
operator_key_str = os.getenv("OPERATOR_KEY")
35-
36-
# Check if the environment variables are loaded correctly
37-
if not operator_id_str or not operator_key_str:
38-
print("Error: OPERATOR_ID or OPERATOR_KEY not found in environment.")
39-
print("Please create a .env file in the project's root directory with:")
40-
print("\nOPERATOR_ID=your_id_here")
41-
print("OPERATOR_KEY=your_key_here\n")
42-
sys.exit(1)
43-
44-
try:
45-
operator_id = AccountId.from_string(operator_id_str)
46-
operator_key = PrivateKey.from_string(operator_key_str)
47-
except (TypeError, ValueError) as e:
48-
print(f"Error: Invalid OPERATOR_ID or OPERATOR_KEY format: {e}")
49-
sys.exit(1)
50-
51-
client.set_operator(operator_id, operator_key)
12+
client = Client.from_env()
13+
print(f"Network: {client.network.network}")
5214
print(f"Client set up with operator id {client.operator_account_id}")
53-
return client, operator_key
54-
15+
return client, client.operator_private_key
5516

5617
def create_topic(client: Client, operator_key: PrivateKey):
5718
"""
@@ -64,18 +25,18 @@ def create_topic(client: Client, operator_key: PrivateKey):
6425
.freeze_with(client)
6526
.sign(operator_key)
6627
)
67-
6828
try:
6929
receipt = transaction.execute(client)
70-
if receipt and receipt.topic_id:
71-
print(f"Success! Topic created with ID: {receipt.topic_id}")
72-
else:
30+
if receipt.status != ResponseCode.SUCCESS:
31+
print(f"Topic creation failed: {ResponseCode(receipt.status).name}")
32+
raise SystemExit(1)
33+
if not receipt.topic_id:
7334
print("Topic creation failed: Topic ID not returned in receipt.")
74-
sys.exit(1)
35+
raise SystemExit(1)
36+
print(f"Success! Topic created with ID: {receipt.topic_id}")
7537
except Exception as e:
7638
print(f"Topic creation failed: {str(e)}")
77-
sys.exit(1)
78-
39+
raise SystemExit(1)
7940

8041
def main():
8142
"""
@@ -84,6 +45,5 @@ def main():
8445
client, operator_key = setup_client()
8546
create_topic(client, operator_key)
8647

87-
8848
if __name__ == "__main__":
89-
main()
49+
main()

tests/unit/nft_id_test.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,54 @@
55
from hiero_sdk_python.hapi.services import basic_types_pb2
66

77
pytestmark = pytest.mark.unit
8+
9+
810
def test_nft_id():
9-
#return true
11+
# return true
1012
nftid_constructor_tokenid = TokenId(shard=0, realm=1, num=2)
11-
nftid_constructor_test = NftId(token_id=nftid_constructor_tokenid, serial_number=1234)
13+
nftid_constructor_test = NftId(
14+
token_id=nftid_constructor_tokenid, serial_number=1234
15+
)
1216

1317
assert str(nftid_constructor_test) == "0.1.2/1234"
14-
assert repr(nftid_constructor_test) == "NftId(token_id=TokenId(shard=0, realm=1, num=2, checksum=None), serial_number=1234)"
18+
assert (
19+
repr(nftid_constructor_test)
20+
== "NftId(token_id=TokenId(shard=0, realm=1, num=2, checksum=None), serial_number=1234)"
21+
)
1522
assert nftid_constructor_test._to_proto().__eq__(
1623
basic_types_pb2.NftID(
1724
token_ID=basic_types_pb2.TokenID(shardNum=0, realmNum=1, tokenNum=2),
18-
serial_number=1234)
25+
serial_number=1234,
26+
)
1927
)
2028
assert NftId._from_proto(
2129
nft_id_proto=basic_types_pb2.NftID(
2230
token_ID=basic_types_pb2.TokenID(shardNum=0, realmNum=1, tokenNum=2),
23-
serial_number=1234
31+
serial_number=1234,
2432
)
2533
).__eq__(nftid_constructor_test)
2634

27-
#return false
35+
# return false
2836
with pytest.raises(TypeError):
2937
nftid_failed_constructor_tokenid1 = TokenId(shard=0, realm=1, num="A")
3038
with pytest.raises(TypeError):
3139
nftid_failed_constructor_tokenid = TokenId(shard=0, realm="b", num=1)
3240
with pytest.raises(TypeError):
33-
nftid_failed_constructor_tokenid = TokenId(shard='c', realm=1, num=1)
41+
nftid_failed_constructor_tokenid = TokenId(shard="c", realm=1, num=1)
3442
with pytest.raises(TypeError):
3543
nftid_failed_constructor = NftId(token_id=None, serial_number=1234)
3644
with pytest.raises(TypeError):
3745
nftid_failed_constructor = NftId(token_id=1234, serial_number=1234)
3846
with pytest.raises(TypeError):
39-
nftid_failed_constructor = NftId(token_id=TokenId(shard=0, realm=1, num=0), serial_number="asdfasdfasdf")
47+
nftid_failed_constructor = NftId(
48+
token_id=TokenId(shard=0, realm=1, num=0), serial_number="asdfasdfasdf"
49+
)
4050
with pytest.raises(ValueError):
41-
nftid_failed_constructor = NftId(token_id=TokenId(shard=0, realm=1, num=0), serial_number=-1234)
51+
nftid_failed_constructor = NftId(
52+
token_id=TokenId(shard=0, realm=1, num=0), serial_number=-1234
53+
)
4254

43-
#don't need to test protobuf cause its final and type checked
55+
# don't need to test protobuf cause its final and type checked
4456
with pytest.raises(ValueError):
4557
NftId.from_string("")
4658

@@ -66,6 +78,7 @@ def test_nft_id():
6678
with pytest.raises(ValueError):
6779
NftId.from_string(fail_str)
6880

81+
6982
def test_get_nft_id_with_checksum(mock_client):
7083
"""Should return string with checksum when ledger id is provided."""
7184
client = mock_client
@@ -74,4 +87,4 @@ def test_get_nft_id_with_checksum(mock_client):
7487
token_id = TokenId.from_string("0.0.1")
7588
nft_id = NftId(token_id, 1)
7689

77-
assert nft_id.to_string_with_checksum(client) == "0.0.1-dfkxr/1"
90+
assert nft_id.to_string_with_checksum(client) == "0.0.1-dfkxr/1"

tests/unit/node_tls_test.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ def test_node_validate_tls_certificate_no_address_book():
255255
@patch("grpc.insecure_channel")
256256
def test_node_get_channel_secure(
257257
_mock_insecure, mock_secure, mock_node_with_address_book
258+
mock_insecure, mock_secure, mock_node_with_address_book
258259
):
259260
"""Test channel creation for secure connection."""
260261
node = mock_node_with_address_book
@@ -280,6 +281,7 @@ def test_node_get_channel_secure(
280281
@patch("grpc.insecure_channel")
281282
def test_node_get_channel_insecure(
282283
_mock_insecure, mock_secure, mock_node_without_address_book
284+
mock_insecure, mock_secure, mock_node_without_address_book
283285
):
284286
"""Test channel creation for insecure connection."""
285287
node = mock_node_without_address_book
@@ -290,6 +292,11 @@ def test_node_get_channel_insecure(
290292
channel = node._get_channel()
291293

292294
_mock_insecure.assert_called_once()
295+
mock_insecure.return_value = mock_channel
296+
297+
channel = node._get_channel()
298+
299+
mock_insecure.assert_called_once()
293300
mock_secure.assert_not_called()
294301
assert channel is not None
295302

@@ -298,6 +305,7 @@ def test_node_get_channel_insecure(
298305
@patch("grpc.insecure_channel")
299306
def test_node_get_channel_reuses_existing(
300307
__mock_insecure, mock_secure, mock_node_with_address_book
308+
mock_insecure, mock_secure, mock_node_with_address_book
301309
):
302310
"""Test that channel is reused when already created."""
303311
node = mock_node_with_address_book
@@ -353,6 +361,9 @@ def test_secure_connect_raise_error_if_no_certificate_is_available(
353361
node = mock_node_without_address_book
354362
node._apply_transport_security(enabled=True)
355363
with pytest.raises(ValueError, match=r"No certificate available\."):
364+
node._apply_transport_security(True)
365+
366+
with pytest.raises(ValueError, match="No certificate available."):
356367
node._get_channel()
357368

358369

0 commit comments

Comments
 (0)