From 23590c608984f91c7841ed73950de0d569c0cceb Mon Sep 17 00:00:00 2001 From: Daniel Rojas Date: Fri, 26 Sep 2025 13:07:14 -0500 Subject: [PATCH 1/2] fix: add payload to genlayer exception and raise with response --- genlayer_py/exceptions.py | 27 +++++++++++++++++++++++++-- genlayer_py/provider/provider.py | 9 +++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/genlayer_py/exceptions.py b/genlayer_py/exceptions.py index f01d2f1..ce25f74 100644 --- a/genlayer_py/exceptions.py +++ b/genlayer_py/exceptions.py @@ -1,4 +1,27 @@ +from typing import Any, Optional + + class GenLayerError(Exception): + """Base exception class for GenLayer SDK errors. + + This exception can carry additional context information through an optional + payload dictionary, useful for debugging and error handling. + + Args: + message: Human-readable error description. + payload: Optional dictionary containing additional error context, + such as transaction details, error codes, or debug information. + + Attributes: + payload: Dictionary of additional error context (None if not provided). + + Example: + >>> raise GenLayerError( + ... "Transaction failed", + ... {"tx_hash": "0x123...", "reason": "insufficient funds"} + ... ) """ - An error raised by GenLayer. - """ + + def __init__(self, message: str, payload: Optional[dict[str, Any]] = None): + super().__init__(message) + self.payload = payload diff --git a/genlayer_py/provider/provider.py b/genlayer_py/provider/provider.py index c80094b..946a540 100644 --- a/genlayer_py/provider/provider.py +++ b/genlayer_py/provider/provider.py @@ -38,9 +38,14 @@ def make_request( try: resp = response.json() except ValueError as err: - response_preview = response.text[:500] if len(response.text) <= 500 else f"{response.text[:500]}..." + response_preview = ( + response.text[:500] + if len(response.text) <= 500 + else f"{response.text[:500]}..." + ) raise GenLayerError( - f"{method} returned invalid JSON: {err}. Response content: {response_preview}" + f"{method} returned invalid JSON: {err}. Response content: {response_preview}", + payload={"response": response}, ) from err self._raise_on_error(resp, method) return resp From c1d287a7957dd1d760f11d015f3d60a6857245d5 Mon Sep 17 00:00:00 2001 From: Daniel Rojas Date: Fri, 26 Sep 2025 13:08:06 -0500 Subject: [PATCH 2/2] style: apply black --- genlayer_py/consensus/abi/__init__.py | 26 +++++--- genlayer_py/types/transactions.py | 7 +- .../test_wait_for_transaction_receipt.py | 65 ++++++++++++++----- 3 files changed, 68 insertions(+), 30 deletions(-) diff --git a/genlayer_py/consensus/abi/__init__.py b/genlayer_py/consensus/abi/__init__.py index 1204c07..ca92402 100644 --- a/genlayer_py/consensus/abi/__init__.py +++ b/genlayer_py/consensus/abi/__init__.py @@ -1,18 +1,24 @@ import json import importlib.resources -with importlib.resources.as_file( - importlib.resources.files("genlayer_py.consensus.abi").joinpath( - "consensus_data_abi.json" - ) -) as path, open(path, "r", encoding="utf-8") as f: +with ( + importlib.resources.as_file( + importlib.resources.files("genlayer_py.consensus.abi").joinpath( + "consensus_data_abi.json" + ) + ) as path, + open(path, "r", encoding="utf-8") as f, +): CONSENSUS_DATA_ABI = json.load(f) -with importlib.resources.as_file( - importlib.resources.files("genlayer_py.consensus.abi").joinpath( - "consensus_main_abi.json" - ) -) as path, open(path, "r", encoding="utf-8") as f: +with ( + importlib.resources.as_file( + importlib.resources.files("genlayer_py.consensus.abi").joinpath( + "consensus_main_abi.json" + ) + ) as path, + open(path, "r", encoding="utf-8") as f, +): CONSENSUS_MAIN_ABI = json.load(f) __all__ = ["CONSENSUS_DATA_ABI", "CONSENSUS_MAIN_ABI"] diff --git a/genlayer_py/types/transactions.py b/genlayer_py/types/transactions.py index bef5595..b718991 100644 --- a/genlayer_py/types/transactions.py +++ b/genlayer_py/types/transactions.py @@ -68,11 +68,14 @@ class TransactionStatus(str, Enum): TransactionStatus.LEADER_TIMEOUT, TransactionStatus.VALIDATORS_TIMEOUT, TransactionStatus.CANCELED, - TransactionStatus.FINALIZED + TransactionStatus.FINALIZED, ] + def is_decided_state(status: str) -> bool: - return status in [TRANSACTION_STATUS_NAME_TO_NUMBER[state] for state in DECIDED_STATES] + return status in [ + TRANSACTION_STATUS_NAME_TO_NUMBER[state] for state in DECIDED_STATES + ] class TransactionResult(str, Enum): diff --git a/tests/unit/transactions/test_wait_for_transaction_receipt.py b/tests/unit/transactions/test_wait_for_transaction_receipt.py index 2a06893..ab55cd0 100644 --- a/tests/unit/transactions/test_wait_for_transaction_receipt.py +++ b/tests/unit/transactions/test_wait_for_transaction_receipt.py @@ -136,8 +136,15 @@ def test_wait_for_transaction_with_custom_parameters( def test_wait_for_accepted_with_all_decided_states(self, mock_client): """Test that ACCEPTED status accepts all decided states""" - decided_statuses = ["5", "6", "8", "7", "12", "13"] # ACCEPTED, UNDETERMINED, CANCELED, FINALIZED, VALIDATORS_TIMEOUT, LEADER_TIMEOUT - + decided_statuses = [ + "5", + "6", + "8", + "7", + "12", + "13", + ] # ACCEPTED, UNDETERMINED, CANCELED, FINALIZED, VALIDATORS_TIMEOUT, LEADER_TIMEOUT + for status_num in decided_statuses: mock_transaction = { "hash": "0x4b8037744adab7ea8335b4f839979d20031d83a8ccdf706e0ae61312930335f6", @@ -150,16 +157,16 @@ def test_wait_for_accepted_with_all_decided_states(self, mock_client): "nonce": "1", "created_at": "2023-01-01T00:00:00Z", } - + mock_client.get_transaction.return_value = mock_transaction - + result = wait_for_transaction_receipt( self=mock_client, transaction_hash="0x4b8037744adab7ea8335b4f839979d20031d83a8ccdf706e0ae61312930335f6", status=TransactionStatus.ACCEPTED, full_transaction=True, ) - + assert result == mock_transaction def test_wait_for_specific_status_not_affected(self, mock_client): @@ -175,16 +182,16 @@ def test_wait_for_specific_status_not_affected(self, mock_client): "nonce": "1", "created_at": "2023-01-01T00:00:00Z", } - + mock_client.get_transaction.return_value = mock_transaction - + result = wait_for_transaction_receipt( self=mock_client, transaction_hash="0x4b8037744adab7ea8335b4f839979d20031d83a8ccdf706e0ae61312930335f6", status=TransactionStatus.FINALIZED, full_transaction=True, ) - + assert result == mock_transaction @@ -199,32 +206,54 @@ def test_decided_states_constant(self): TransactionStatus.LEADER_TIMEOUT, TransactionStatus.VALIDATORS_TIMEOUT, TransactionStatus.CANCELED, - TransactionStatus.FINALIZED + TransactionStatus.FINALIZED, ] - + assert DECIDED_STATES == expected_states def test_is_decided_state_with_decided_statuses(self): """Test is_decided_state returns True for all decided statuses""" - decided_status_numbers = ["5", "6", "8", "7", "12", "13"] # ACCEPTED, UNDETERMINED, CANCELED, FINALIZED, VALIDATORS_TIMEOUT, LEADER_TIMEOUT - + decided_status_numbers = [ + "5", + "6", + "8", + "7", + "12", + "13", + ] # ACCEPTED, UNDETERMINED, CANCELED, FINALIZED, VALIDATORS_TIMEOUT, LEADER_TIMEOUT + for status_num in decided_status_numbers: - assert is_decided_state(status_num) == True, f"Status {status_num} should be decided" + assert ( + is_decided_state(status_num) == True + ), f"Status {status_num} should be decided" def test_is_decided_state_with_non_decided_statuses(self): """Test is_decided_state returns False for non-decided statuses""" - non_decided_status_numbers = ["0", "1", "2", "3", "4", "9", "10", "11"] # UNINITIALIZED, PENDING, PROPOSING, COMMITTING, REVEALING, APPEAL_REVEALING, APPEAL_COMMITTING, READY_TO_FINALIZE - + non_decided_status_numbers = [ + "0", + "1", + "2", + "3", + "4", + "9", + "10", + "11", + ] # UNINITIALIZED, PENDING, PROPOSING, COMMITTING, REVEALING, APPEAL_REVEALING, APPEAL_COMMITTING, READY_TO_FINALIZE + for status_num in non_decided_status_numbers: - assert is_decided_state(status_num) == False, f"Status {status_num} should not be decided" + assert ( + is_decided_state(status_num) == False + ), f"Status {status_num} should not be decided" def test_is_decided_state_with_invalid_status(self): """Test is_decided_state returns False for invalid statuses""" invalid_statuses = ["999", "invalid", "", None] - + for status in invalid_statuses: if status is not None: - assert is_decided_state(status) == False, f"Invalid status {status} should not be decided" + assert ( + is_decided_state(status) == False + ), f"Invalid status {status} should not be decided" class TestSimplifyTransactionReceipt: