From 5696c03447eca430569fddd532f9e6bf195ce86d Mon Sep 17 00:00:00 2001 From: 1yam Date: Thu, 2 Oct 2025 13:03:13 +0200 Subject: [PATCH 1/4] fix: allow None type for ports when they are not set / removed by front --- src/aleph/sdk/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aleph/sdk/types.py b/src/aleph/sdk/types.py index e76aedbc..b839a14b 100644 --- a/src/aleph/sdk/types.py +++ b/src/aleph/sdk/types.py @@ -309,7 +309,7 @@ class Ports(BaseModel): ports: Dict[int, PortFlags] -AllForwarders = RootModel[Dict[ItemHash, Ports]] +AllForwarders = RootModel[Dict[ItemHash, Optional[Ports]]] class DictLikeModel(BaseModel): From 91a9851b55721e58b3b764a3e099fe52e44e68e4 Mon Sep 17 00:00:00 2001 From: 1yam Date: Thu, 2 Oct 2025 13:26:41 +0200 Subject: [PATCH 2/4] fix: allow all node to be found --- src/aleph/sdk/client/services/crn.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index e8d57c8c..82cec51b 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -230,9 +230,8 @@ async def get_crns_list(self, only_active: bool = True) -> CrnList: dict The parsed JSON response from /crns.json. """ - # We want filter_inactive = (not only_active) # Convert bool to string for the query parameter - filter_inactive_str = str(not only_active).lower() + filter_inactive_str = str(only_active).lower() params = {"filter_inactive": filter_inactive_str} # Create a new session for external domain requests From f2a50d0c34087a6ede2a351fee8039a42068e72d Mon Sep 17 00:00:00 2001 From: 1yam Date: Thu, 2 Oct 2025 14:28:04 +0200 Subject: [PATCH 3/4] fix: get_program_price should now take priority on cost field if it exist --- src/aleph/sdk/client/http.py | 11 +++++++- src/aleph/sdk/query/responses.py | 2 +- tests/unit/test_price.py | 47 ++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/aleph/sdk/client/http.py b/src/aleph/sdk/client/http.py index 0ce33a7a..ac18b6cd 100644 --- a/src/aleph/sdk/client/http.py +++ b/src/aleph/sdk/client/http.py @@ -3,6 +3,7 @@ import os.path import ssl import time +from decimal import Decimal from io import BytesIO from pathlib import Path from typing import ( @@ -543,8 +544,16 @@ async def get_program_price(self, item_hash: str) -> PriceResponse: try: resp.raise_for_status() response_json = await resp.json() + token_value = response_json.get( + "cost", response_json.get("required_tokens") + ) + if isinstance(token_value, str): + required_tokens = Decimal(token_value) + else: + required_tokens = Decimal(str(token_value)) + return PriceResponse( - required_tokens=response_json["required_tokens"], + required_tokens=required_tokens, payment_type=response_json["payment_type"], ) except aiohttp.ClientResponseError as e: diff --git a/src/aleph/sdk/query/responses.py b/src/aleph/sdk/query/responses.py index b5958d47..96e19072 100644 --- a/src/aleph/sdk/query/responses.py +++ b/src/aleph/sdk/query/responses.py @@ -79,7 +79,7 @@ class MessagesResponse(PaginationResponse): class PriceResponse(BaseModel): """Response from an aleph.im node API on the path /api/v0/price/{item_hash}""" - required_tokens: float + required_tokens: Decimal payment_type: str diff --git a/tests/unit/test_price.py b/tests/unit/test_price.py index e60680f8..133c7fee 100644 --- a/tests/unit/test_price.py +++ b/tests/unit/test_price.py @@ -1,3 +1,5 @@ +from decimal import Decimal + import pytest from aleph.sdk.exceptions import InvalidHashError @@ -21,6 +23,51 @@ async def test_get_program_price_valid(): assert response == expected +@pytest.mark.asyncio +async def test_get_program_price_cost_and_required_token(): + """ + Test that the get_program_price method returns the correct PriceResponse + when + 1 ) cost & required_token is here (priority to cost) who is a string that convert to decimal + 2 ) When only required_token is here who is a float that now would be to be convert to decimal + """ + # Case 1 + expected = { + "required_tokens": 0.001527777777777778, + "cost": "0.001527777777777777", + "payment_type": "credit", + } + + # Case 2 + expected_old = { + "required_tokens": 0.001527777777777778, + "payment_type": "credit", + } + + # Expected model using the cost field as the source of truth + expected_model = PriceResponse( + required_tokens=Decimal("0.001527777777777777"), + payment_type=expected["payment_type"], + ) + + # Expected model for the old format + expected_model_old = PriceResponse( + required_tokens=Decimal(str(expected_old["required_tokens"])), + payment_type=expected_old["payment_type"], + ) + + mock_session = make_mock_get_session(expected) + mock_session_old = make_mock_get_session(expected_old) + + async with mock_session: + response = await mock_session.get_program_price("cacacacacacaca") + assert response == expected_model + + async with mock_session_old: + response = await mock_session_old.get_program_price("cacacacacacaca") + assert response == expected_model_old + + @pytest.mark.asyncio async def test_get_program_price_invalid(): """ From 0a04862a477be96b6bba2473281104f1ee6f3bc5 Mon Sep 17 00:00:00 2001 From: 1yam Date: Tue, 7 Oct 2025 17:27:01 +0200 Subject: [PATCH 4/4] Feature: new field for PriceRespond `cost` instead of filling required_token with cost --- src/aleph/sdk/client/http.py | 14 ++++++-------- src/aleph/sdk/query/responses.py | 1 + tests/unit/test_price.py | 11 ++++++++--- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/aleph/sdk/client/http.py b/src/aleph/sdk/client/http.py index ac18b6cd..c1facba1 100644 --- a/src/aleph/sdk/client/http.py +++ b/src/aleph/sdk/client/http.py @@ -3,7 +3,6 @@ import os.path import ssl import time -from decimal import Decimal from io import BytesIO from pathlib import Path from typing import ( @@ -532,7 +531,10 @@ async def get_estimated_price( try: resp.raise_for_status() response_json = await resp.json() + cost = response_json.get("cost", None) + return PriceResponse( + cost=cost, required_tokens=response_json["required_tokens"], payment_type=response_json["payment_type"], ) @@ -544,16 +546,12 @@ async def get_program_price(self, item_hash: str) -> PriceResponse: try: resp.raise_for_status() response_json = await resp.json() - token_value = response_json.get( - "cost", response_json.get("required_tokens") - ) - if isinstance(token_value, str): - required_tokens = Decimal(token_value) - else: - required_tokens = Decimal(str(token_value)) + cost = response_json.get("cost", None) + required_tokens = response_json["required_tokens"] return PriceResponse( required_tokens=required_tokens, + cost=cost, payment_type=response_json["payment_type"], ) except aiohttp.ClientResponseError as e: diff --git a/src/aleph/sdk/query/responses.py b/src/aleph/sdk/query/responses.py index 96e19072..6efade14 100644 --- a/src/aleph/sdk/query/responses.py +++ b/src/aleph/sdk/query/responses.py @@ -80,6 +80,7 @@ class PriceResponse(BaseModel): """Response from an aleph.im node API on the path /api/v0/price/{item_hash}""" required_tokens: Decimal + cost: Optional[str] = None payment_type: str diff --git a/tests/unit/test_price.py b/tests/unit/test_price.py index 133c7fee..f2759193 100644 --- a/tests/unit/test_price.py +++ b/tests/unit/test_price.py @@ -46,7 +46,8 @@ async def test_get_program_price_cost_and_required_token(): # Expected model using the cost field as the source of truth expected_model = PriceResponse( - required_tokens=Decimal("0.001527777777777777"), + required_tokens=Decimal("0.001527777777777778"), + cost=expected["cost"], payment_type=expected["payment_type"], ) @@ -61,11 +62,15 @@ async def test_get_program_price_cost_and_required_token(): async with mock_session: response = await mock_session.get_program_price("cacacacacacaca") - assert response == expected_model + assert str(response.required_tokens) == str(expected_model.required_tokens) + assert response.cost == expected_model.cost + assert response.payment_type == expected_model.payment_type async with mock_session_old: response = await mock_session_old.get_program_price("cacacacacacaca") - assert response == expected_model_old + assert str(response.required_tokens) == str(expected_model_old.required_tokens) + assert response.cost == expected_model_old.cost + assert response.payment_type == expected_model_old.payment_type @pytest.mark.asyncio