Skip to content

Commit 2173274

Browse files
1yamhoh
andauthored
Feature: get_program_price (#143)
* Feature: get_program_price functions * Fix: add also to abstract AlehpClient * Fix: black issue * Fix: add superfuild to pyproject.toml * Revert "Fix: add superfuild to pyproject.toml" This reverts commit d206c0c. * Fix: isort issue * Fix: type * Fix: unit test * Fix: style issue * Update src/aleph/sdk/client/http.py Co-authored-by: Hugo Herter <[email protected]> --------- Co-authored-by: Hugo Herter <[email protected]>
1 parent 0de453d commit 2173274

File tree

6 files changed

+101
-3
lines changed

6 files changed

+101
-3
lines changed

src/aleph/sdk/client/abstract.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing import (
88
Any,
99
AsyncIterable,
10+
Coroutine,
1011
Dict,
1112
Iterable,
1213
List,
@@ -40,7 +41,7 @@
4041
from aleph.sdk.utils import extended_json_encoder
4142

4243
from ..query.filters import MessageFilter, PostFilter
43-
from ..query.responses import PostsResponse
44+
from ..query.responses import PostsResponse, PriceResponse
4445
from ..types import GenericMessage, StorageEnum
4546
from ..utils import Writable, compute_sha256
4647

@@ -241,6 +242,18 @@ def watch_messages(
241242
"""
242243
raise NotImplementedError("Did you mean to import `AlephHttpClient`?")
243244

245+
@abstractmethod
246+
def get_program_price(
247+
self,
248+
item_hash: str,
249+
) -> Coroutine[Any, Any, PriceResponse]:
250+
"""
251+
Get Program message Price
252+
253+
:param item_hash: item_hash of executable message
254+
"""
255+
raise NotImplementedError("Did you mean to import `AlephHttpClient`?")
256+
244257

245258
class AuthenticatedAlephClient(AlephClient):
246259
account: Account

src/aleph/sdk/client/http.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,14 @@
1212
from pydantic import ValidationError
1313

1414
from ..conf import settings
15-
from ..exceptions import FileTooLarge, ForgottenMessageError, MessageNotFoundError
15+
from ..exceptions import (
16+
FileTooLarge,
17+
ForgottenMessageError,
18+
InvalidHashError,
19+
MessageNotFoundError,
20+
)
1621
from ..query.filters import MessageFilter, PostFilter
17-
from ..query.responses import MessagesResponse, Post, PostsResponse
22+
from ..query.responses import MessagesResponse, Post, PostsResponse, PriceResponse
1823
from ..types import GenericMessage
1924
from ..utils import (
2025
Writable,
@@ -409,3 +414,17 @@ async def watch_messages(
409414
yield parse_message(data)
410415
elif msg.type == aiohttp.WSMsgType.ERROR:
411416
break
417+
418+
async def get_program_price(self, item_hash: str) -> PriceResponse:
419+
async with self.http_session.get(f"/api/v0/price/{item_hash}") as resp:
420+
try:
421+
resp.raise_for_status()
422+
response_json = await resp.json()
423+
return PriceResponse(
424+
required_tokens=response_json["required_tokens"],
425+
payment_type=response_json["payment_type"],
426+
)
427+
except aiohttp.ClientResponseError as e:
428+
if e.status == 400:
429+
raise InvalidHashError(f"Bad request or no such hash {item_hash}")
430+
raise e

src/aleph/sdk/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,9 @@ def __init__(self, required_funds: float, available_funds: float):
7878
super().__init__(
7979
f"Insufficient funds: required {required_funds}, available {available_funds}"
8080
)
81+
82+
83+
class InvalidHashError(QueryError):
84+
"""The Hash is not valid"""
85+
86+
pass

src/aleph/sdk/query/responses.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,10 @@ class MessagesResponse(PaginationResponse):
7272

7373
messages: List[AlephMessage]
7474
pagination_item = "messages"
75+
76+
77+
class PriceResponse(BaseModel):
78+
"""Response from an aleph.im node API on the path /api/v0/price/{item_hash}"""
79+
80+
required_tokens: float
81+
payment_type: str

tests/unit/conftest.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from unittest.mock import AsyncMock, MagicMock
99

1010
import pytest as pytest
11+
from aiohttp import ClientResponseError
1112
from aleph_message.models import AggregateMessage, AlephMessage, PostMessage
1213

1314
import aleph.sdk.chains.ethereum as ethereum
@@ -230,6 +231,10 @@ class CustomMockResponse(MockResponse):
230231
async def json(self):
231232
return resp
232233

234+
def raise_for_status(self):
235+
if status >= 400:
236+
raise ClientResponseError(None, None, status=status)
237+
233238
@property
234239
def status(self):
235240
return status
@@ -259,6 +264,21 @@ def get(self, *_args, **_kwargs):
259264
return client
260265

261266

267+
def make_mock_get_session_400(
268+
get_return_value: Union[Dict[str, Any], bytes]
269+
) -> AlephHttpClient:
270+
class MockHttpSession(AsyncMock):
271+
def get(self, *_args, **_kwargs):
272+
return make_custom_mock_response(get_return_value, 400)
273+
274+
http_session = MockHttpSession()
275+
276+
client = AlephHttpClient(api_server="http://localhost")
277+
client.http_session = http_session
278+
279+
return client
280+
281+
262282
@pytest.fixture
263283
def mock_session_with_rejected_message(
264284
ethereum_account, rejected_message

tests/unit/test_price.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import pytest
2+
3+
from aleph.sdk.exceptions import InvalidHashError
4+
from aleph.sdk.query.responses import PriceResponse
5+
from tests.unit.conftest import make_mock_get_session, make_mock_get_session_400
6+
7+
8+
@pytest.mark.asyncio
9+
async def test_get_program_price_valid():
10+
"""
11+
Test that the get_program_price method returns the correct PriceResponse
12+
when given a valid item hash.
13+
"""
14+
expected_response = {
15+
"required_tokens": 3.0555555555555556e-06,
16+
"payment_type": "superfluid",
17+
}
18+
mock_session = make_mock_get_session(expected_response)
19+
async with mock_session:
20+
response = await mock_session.get_program_price("cacacacacacaca")
21+
assert response == PriceResponse(**expected_response)
22+
23+
24+
@pytest.mark.asyncio
25+
async def test_get_program_price_invalid():
26+
"""
27+
Test that the get_program_price method raises an InvalidHashError
28+
when given an invalid item hash.
29+
"""
30+
mock_session = make_mock_get_session_400({"error": "Invalid hash"})
31+
async with mock_session:
32+
with pytest.raises(InvalidHashError):
33+
await mock_session.get_program_price("invalid_item_hash")

0 commit comments

Comments
 (0)