Skip to content

Commit efab8cb

Browse files
committed
Merge branch 'upstream/main' into update-file-handler
Signed-off-by: anchit-goel <anchitgoel5@gmail.com>
2 parents e10a43a + e8ed509 commit efab8cb

8 files changed

Lines changed: 81 additions & 40 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ dependencies = [
1919
"cryptography>=50.0.0,<51",
2020
"requests>=2.31.0,<3",
2121
"pycryptodome>=3.18.0,<4",
22-
"eth-abi>=5.1.0,<6",
22+
"eth-abi>=5.1.0,<7",
2323
"python-dotenv>=1.2.1,<3",
2424
]
2525
classifiers = [

src/hiero_sdk_python/file/file_info_query.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,16 @@ def _make_request(self) -> query_pb2.Query:
6060
Query: The protobuf query message.
6161
6262
Raises:
63-
ValueError: If the file ID is not set.
64-
Exception: If any other error occurs during request construction.
63+
Exception: If any error occurs during request construction.
6564
"""
6665
try:
67-
if not self.file_id:
68-
raise ValueError("File ID must be set before making the request.")
69-
7066
query_header = self._make_request_header()
7167

7268
file_info_query = file_get_info_pb2.FileGetInfoQuery()
7369
file_info_query.header.CopyFrom(query_header)
74-
file_info_query.fileID.CopyFrom(self.file_id._to_proto())
70+
71+
if self.file_id is not None:
72+
file_info_query.fileID.CopyFrom(self.file_id._to_proto())
7573

7674
query = query_pb2.Query()
7775
query.fileGetInfo.CopyFrom(file_info_query)

src/hiero_sdk_python/file/file_update_transaction.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,9 @@ def _build_proto_body(self):
161161
162162
Returns:
163163
FileUpdateTransactionBody: The protobuf body for this transaction.
164-
165-
Raises:
166-
ValueError: If file_id is not set.
167164
"""
168-
if self.file_id is None:
169-
raise ValueError("Missing required FileID")
170-
171165
return FileUpdateTransactionBody(
172-
fileID=self.file_id._to_proto(),
166+
fileID=self.file_id._to_proto() if self.file_id is not None else None,
173167
keys=(KeyListProto(keys=[key._to_proto() for key in self.keys]) if self.keys else None),
174168
contents=self.contents if self.contents is not None else b"",
175169
expirationTime=(self.expiration_time._to_protobuf() if self.expiration_time else None),

tck/handlers/file.py

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,30 @@
33
from hiero_sdk_python.file.file_contents_query import FileContentsQuery
44
from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction
55
from hiero_sdk_python.file.file_id import FileId
6+
from hiero_sdk_python.file.file_info import FileInfo
7+
from hiero_sdk_python.file.file_info_query import FileInfoQuery
68
from hiero_sdk_python.file.file_update_transaction import FileUpdateTransaction
79
from hiero_sdk_python.hbar import Hbar
810
from hiero_sdk_python.response_code import ResponseCode
911
from hiero_sdk_python.timestamp import Timestamp
1012
from hiero_sdk_python.transaction.transaction_receipt import TransactionReceipt
1113
from tck.errors import JsonRpcError
1214
from tck.handlers.registry import rpc_method
13-
from tck.param.file import CreateFileParams, GetFileContentsParams, UpdateFileParams
15+
from tck.param.file import (
16+
CreateFileParams,
17+
GetFileContentsParams,
18+
GetFileInfoParams,
19+
UpdateFileParams,
20+
)
1421
from tck.response.file import (
1522
CreateFileResponse,
1623
GetFileContentsResponse,
24+
GetFileInfoResponse,
1725
UpdateFileResponse,
1826
)
1927
from tck.util.client_utils import get_client
2028
from tck.util.constants import DEFAULT_GRPC_TIMEOUT
21-
from tck.util.key_utils import get_key_from_string
29+
from tck.util.key_utils import get_key_from_string, key_to_string
2230
from tck.util.param_utils import to_int
2331

2432

@@ -84,21 +92,13 @@ def get_file_contents(params: GetFileContentsParams) -> GetFileContentsResponse:
8492

8593

8694
def _build_update_file_transaction(params: UpdateFileParams) -> FileUpdateTransaction:
87-
"""Build a FileUpdateTransaction from parsed params.
88-
89-
Each setter is called only when the corresponding field is not None so that
90-
omitted fields are left unchanged on-network. contents is pre-normalized so
91-
that the exact empty string ("") from JSON-RPC params maps to None, therefore
92-
set_contents is never invoked when the caller intends "leave unchanged".
93-
"""
95+
"""Build a FileUpdateTransaction from parsed params."""
9496
transaction = FileUpdateTransaction().set_grpc_deadline(DEFAULT_GRPC_TIMEOUT)
9597

9698
if params.fileId is not None:
97-
# ValueError from FileId.from_string propagates as an SDK/internal error.
9899
transaction.set_file_id(FileId.from_string(params.fileId))
99100

100101
if params.keys is not None:
101-
# Threshold-key rejection is enforced by the network, not client-side.
102102
transaction.set_keys([get_key_from_string(k) for k in params.keys])
103103

104104
if params.contents is not None:
@@ -130,3 +130,31 @@ def update_file(params: UpdateFileParams) -> UpdateFileResponse:
130130
receipt: TransactionReceipt = response.get_receipt(client, validate_status=True)
131131

132132
return UpdateFileResponse(status=ResponseCode(receipt.status).name)
133+
134+
135+
def _build_file_info_response(info: FileInfo) -> GetFileInfoResponse:
136+
"""Build a GetFileResponse from a FileInfo object."""
137+
138+
keys = [key_to_string(k) for k in info.keys] if info.keys else []
139+
140+
return GetFileInfoResponse(
141+
fileId=str(info.file_id) if info.file_id is not None else None,
142+
size=str(info.size) if info.size is not None else None,
143+
expirationTime=str(info.expiration_time.seconds) if info.expiration_time is not None else None,
144+
isDeleted=info.is_deleted,
145+
keys=keys,
146+
memo=info.file_memo,
147+
ledgerId=info.ledger_id.hex() if info.ledger_id is not None else None,
148+
)
149+
150+
151+
@rpc_method("getFileInfo")
152+
def get_file_info(params: GetFileInfoParams) -> GetFileInfoResponse:
153+
client = get_client(params.sessionId)
154+
query = FileInfoQuery().set_grpc_deadline(DEFAULT_GRPC_TIMEOUT)
155+
156+
if params.fileId is not None:
157+
query.set_file_id(FileId.from_string(params.fileId))
158+
159+
info = query.execute(client)
160+
return _build_file_info_response(info)

tck/param/file.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,18 +87,26 @@ def parse_json_params(cls, params: dict) -> UpdateFileParams:
8787
if not isinstance(key, str) or not key.strip():
8888
raise ValueError("keys must be a list of non-empty strings")
8989

90-
contents_raw = params.get("contents")
91-
9290
return cls(
9391
fileId=params.get("fileId"),
9492
keys=keys,
95-
# Per the spec, only the exact empty string ("") means "leave unchanged" (mapped to None).
96-
# Other values (including whitespace) are preserved verbatim.
97-
contents=None if contents_raw == "" else contents_raw,
93+
contents=params.get("contents"),
9894
# expirationTime is kept as a raw string; int/Timestamp conversion
9995
# happens in the handler layer.
10096
expirationTime=params.get("expirationTime"),
10197
memo=params.get("memo"),
10298
sessionId=parse_session_id(params),
10399
commonTransactionParams=parse_common_transaction_params(params),
104100
)
101+
102+
103+
@dataclass
104+
class GetFileInfoParams(BaseParams):
105+
"""Parameters for getting file information."""
106+
107+
fileId: str | None = None
108+
109+
@classmethod
110+
def parse_json_params(cls, params: dict) -> GetFileInfoParams:
111+
"""Parse JSON-RPC params into a GetFileInfoParams instance."""
112+
return cls(fileId=params.get("fileId"), sessionId=parse_session_id(params))

tck/response/file.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from dataclasses import dataclass
3+
from dataclasses import dataclass, field
44

55
from tck.response.base import StatusOnlyResponse
66

@@ -23,3 +23,14 @@ class GetFileContentsResponse:
2323
"""Response payload for getFileContents."""
2424

2525
contents: str | None = None
26+
27+
28+
@dataclass
29+
class GetFileInfoResponse:
30+
fileId: str | None = None
31+
size: str | None = None
32+
expirationTime: str | None = None
33+
isDeleted: bool | None = None
34+
keys: list[str] = field(default_factory=list)
35+
memo: str | None = None
36+
ledgerId: str | None = None

tests/unit/file_info_query_test.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,11 @@ def test_constructor():
3333
assert query.file_id == file_id
3434

3535

36-
def test_execute_fails_with_missing_file_id(mock_client):
37-
"""Test request creation with missing File ID."""
36+
def test_make_request_with_missing_file_id():
37+
"""Test File ID is omitted from proto when not set."""
3838
query = FileInfoQuery()
39-
40-
with pytest.raises(ValueError, match="File ID must be set before making the request."):
41-
query.execute(mock_client)
39+
proto = query._make_request()
40+
assert not proto.fileGetInfo.HasField("fileID")
4241

4342

4443
def test_get_method():

tests/unit/file_update_transaction_test.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,12 +243,15 @@ def test_build_scheduled_body(mock_account_ids, file_id):
243243
assert schedulable_body.fileUpdate.memo == StringValue(value=file_memo)
244244

245245

246-
def test_missing_file_id():
247-
"""Test that building a transaction without setting file_id raises a ValueError."""
246+
def test_missing_file_id(mock_account_ids):
247+
"""Test that building a transaction without setting file_id omits fileID in proto body."""
248+
operator_id, _, node_account_id, _, _ = mock_account_ids
248249
file_tx = FileUpdateTransaction()
250+
file_tx.operator_account_id = operator_id
251+
file_tx.set_node_account_ids([node_account_id])
249252

250-
with pytest.raises(ValueError, match="Missing required FileID"):
251-
file_tx.build_transaction_body()
253+
transaction_body = file_tx.build_transaction_body()
254+
assert not transaction_body.fileUpdate.HasField("fileID")
252255

253256

254257
def test_sign_transaction(mock_client, file_id):

0 commit comments

Comments
 (0)