Skip to content

Commit c84ea65

Browse files
authored
Refactor: Use pricing from sdk side instead of hardcoded in cli (#394)
* Refactor: pricing class to simplify logic / use new sdk feature * refactor: use new pricing logic from sdk side * Refactor: instance commands to use logic from sdk / simplify and reduce number of number call * Fix: unit test * fix: linting issue * Unit: some more testing on pricing * fix: unit test * Feature: Credits handling (#396) * Fix: unit test * Feature: new credits commands * Feature: pricing now handle credits payment * Feature: aleph account balance also show credits ballance * Feature: handle credit payment using aleph instance create * feature: add credits to mocked pricing aggregate * Unit: credit payment case for instance creations * Unit: new unit test for credits commands * Fix: pyrpojecttoml to use aleph-message 1.0.4 and credit branch from sdk * fix: test_credits * Fix: mocked was trying to access .commands.credits instead of .commands.credit * fix: remove aleph credit list * Feature: aleph credits history * fix: import goes to the top of the file * fix: using main branch of aleph sdk * fix: removed unused fixture * fix: use get_balance instead of get_credit_balance who does not exist anymore * fix: unit test & use get_balances from alephHttpClient instead of hardcoded one * Feature: Handle `REMOVING` and `REMOVED` status (#398) * Refactor: pricing class to simplify logic / use new sdk feature * Fix: handle the two new state of messages * Fix: handle new message status in aleph message get * feat: new unit test on the 'rejected' and 'removing' state using aleph message get * fix: linting issue * (unit test): test case for aleph message get when 'removed' 'forgot' and when message not exist * fix: display reason why message will get removed * fix: remove reason (not handled yet on sdk side) * fix: remove unused model / variable
1 parent 9ba559d commit c84ea65

20 files changed

+1485
-808
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ dynamic = [ "version" ]
3030
dependencies = [
3131
"aiodns==3.2",
3232
"aiohttp==3.11.13",
33-
"aleph-message>=1.0.1",
33+
"aleph-message>=1.0.4",
3434
"aleph-sdk-python @ git+https://github.com/aleph-im/aleph-sdk-python@main",
3535
"base58==2.1.1", # Needed now as default with _load_account changement
3636
"click<8.2",

src/aleph_client/__main__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
about,
77
account,
88
aggregate,
9+
credit,
910
domain,
1011
files,
1112
instance,
@@ -30,9 +31,11 @@
3031
app.add_typer(files.app, name="file", help="Manage files (upload and pin on IPFS) on aleph.im & twentysix.cloud")
3132
app.add_typer(program.app, name="program", help="Manage programs (micro-VMs) on aleph.im & twentysix.cloud")
3233
app.add_typer(instance.app, name="instance", help="Manage instances (VMs) on aleph.im & twentysix.cloud")
34+
app.add_typer(credit.app, name="credits", help="Credits commmands on aleph.im")
3335
app.add_typer(domain.app, name="domain", help="Manage custom domain (DNS) on aleph.im & twentysix.cloud")
3436
app.add_typer(node.app, name="node", help="Get node info on aleph.im & twentysix.cloud")
3537
app.add_typer(about.app, name="about", help="Display the informations of Aleph CLI")
38+
3639
app.command("pricing")(pricing.prices_for_service)
3740

3841
if __name__ == "__main__":

src/aleph_client/commands/account.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import aiohttp
1111
import typer
12-
from aleph.sdk import AuthenticatedAlephHttpClient
12+
from aleph.sdk import AlephHttpClient, AuthenticatedAlephHttpClient
1313
from aleph.sdk.account import _load_account
1414
from aleph.sdk.chains.common import generate_key
1515
from aleph.sdk.chains.solana import parse_private_key as parse_solana_private_key
@@ -303,14 +303,16 @@ async def balance(
303303

304304
if address:
305305
try:
306-
balance_data = await get_balance(address)
306+
async with AlephHttpClient() as client:
307+
balance_data = await client.get_balances(address)
308+
available = balance_data.balance - balance_data.locked_amount
307309
infos = [
308-
Text.from_markup(f"Address: [bright_cyan]{balance_data['address']}[/bright_cyan]"),
310+
Text.from_markup(f"Address: [bright_cyan]{balance_data.address}[/bright_cyan]"),
309311
Text.from_markup(
310-
f"\nBalance: [bright_cyan]{displayable_amount(balance_data['balance'], decimals=2)}[/bright_cyan]"
312+
f"\nBalance: [bright_cyan]{displayable_amount(balance_data.balance, decimals=2)}[/bright_cyan]"
311313
),
312314
]
313-
details = balance_data.get("details")
315+
details = balance_data.details
314316
if details:
315317
infos += [Text("\n ↳ Details")]
316318
for chain_, chain_balance in details.items():
@@ -319,26 +321,33 @@ async def balance(
319321
f"\n {chain_}: [orange3]{displayable_amount(chain_balance, decimals=2)}[/orange3]"
320322
)
321323
]
322-
available_color = "bright_cyan" if balance_data["available_amount"] >= 0 else "red"
324+
available_color = "bright_cyan" if available >= 0 else "red"
323325
infos += [
324326
Text.from_markup(
325-
f"\n - Locked: [bright_cyan]{displayable_amount(balance_data['locked_amount'], decimals=2)}"
327+
f"\n - Locked: [bright_cyan]{displayable_amount(balance_data.locked_amount, decimals=2)}"
326328
"[/bright_cyan]"
327329
),
328330
Text.from_markup(
329331
f"\n - Available: [{available_color}]"
330-
f"{displayable_amount(balance_data['available_amount'], decimals=2)}"
332+
f"{displayable_amount(available, decimals=2)}"
331333
f"[/{available_color}]"
332334
),
333335
]
334336

337+
infos += [
338+
Text("\nCredits:"),
339+
Text.from_markup(
340+
f"[bright_cyan] {displayable_amount(balance_data.credit_balance, decimals=2)}[/bright_cyan]"
341+
),
342+
]
343+
335344
# Get vouchers and add them to Account Info panel
336345
async with AuthenticatedAlephHttpClient(account=account) as client:
337346
vouchers = await client.voucher.get_vouchers(address=address)
338347
if vouchers:
339348
voucher_names = [voucher.name for voucher in vouchers]
340349
infos += [
341-
Text("\n\nVouchers:"),
350+
Text("\nVouchers:"),
342351
Text.from_markup(f"\n [bright_cyan]{', '.join(voucher_names)}[/bright_cyan]"),
343352
]
344353

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import logging
2+
from pathlib import Path
3+
from typing import Annotated, Optional
4+
5+
import typer
6+
from aiohttp import ClientResponseError
7+
from aleph.sdk import AlephHttpClient
8+
from aleph.sdk.account import _load_account
9+
from aleph.sdk.conf import settings
10+
from aleph.sdk.types import AccountFromPrivateKey
11+
from aleph.sdk.utils import displayable_amount
12+
from rich import box
13+
from rich.console import Console
14+
from rich.panel import Panel
15+
from rich.table import Table
16+
from rich.text import Text
17+
18+
from aleph_client.commands import help_strings
19+
from aleph_client.commands.utils import setup_logging
20+
from aleph_client.utils import AsyncTyper
21+
22+
logger = logging.getLogger(__name__)
23+
app = AsyncTyper(no_args_is_help=True)
24+
console = Console()
25+
26+
27+
@app.command()
28+
async def show(
29+
address: Annotated[
30+
str,
31+
typer.Argument(help="Address of the wallet you want to check / None if you want check your current accounts"),
32+
] = "",
33+
private_key: Annotated[Optional[str], typer.Option(help=help_strings.PRIVATE_KEY)] = settings.PRIVATE_KEY_STRING,
34+
private_key_file: Annotated[
35+
Optional[Path], typer.Option(help=help_strings.PRIVATE_KEY_FILE)
36+
] = settings.PRIVATE_KEY_FILE,
37+
json: Annotated[bool, typer.Option(help="Display as json")] = False,
38+
debug: Annotated[bool, typer.Option()] = False,
39+
):
40+
"""Display the numbers of credits for a specific address."""
41+
42+
setup_logging(debug)
43+
44+
account: AccountFromPrivateKey = _load_account(private_key, private_key_file)
45+
46+
if account and not address:
47+
address = account.get_address()
48+
49+
if address:
50+
async with AlephHttpClient(api_server=settings.API_HOST) as client:
51+
credit = await client.get_balances(address=address)
52+
if json:
53+
typer.echo(credit.model_dump_json(indent=4))
54+
else:
55+
infos = [
56+
Text.from_markup(f"Address: {address}\n"),
57+
Text("Credits:"),
58+
Text.from_markup(f" {displayable_amount(credit.credit_balance, decimals=2)}"),
59+
]
60+
console.print(
61+
Panel(
62+
Text.assemble(*infos),
63+
title="Credits Infos",
64+
border_style="blue",
65+
expand=False,
66+
title_align="left",
67+
)
68+
)
69+
else:
70+
typer.echo("Error: Please provide either a private key, private key file, or an address.")
71+
72+
73+
@app.command(name="history")
74+
async def history(
75+
address: Annotated[
76+
str,
77+
typer.Argument(help="Address of the wallet you want to check / None if you want check your current accounts"),
78+
] = "",
79+
private_key: Annotated[Optional[str], typer.Option(help=help_strings.PRIVATE_KEY)] = settings.PRIVATE_KEY_STRING,
80+
private_key_file: Annotated[
81+
Optional[Path], typer.Option(help=help_strings.PRIVATE_KEY_FILE)
82+
] = settings.PRIVATE_KEY_FILE,
83+
page_size: Annotated[int, typer.Option(help="Numbers of element per page")] = 100,
84+
page: Annotated[int, typer.Option(help="Current Page")] = 1,
85+
json: Annotated[bool, typer.Option(help="Display as json")] = False,
86+
debug: Annotated[bool, typer.Option()] = False,
87+
):
88+
setup_logging(debug)
89+
90+
account: AccountFromPrivateKey = _load_account(private_key, private_key_file)
91+
92+
if account and not address:
93+
address = account.get_address()
94+
95+
try:
96+
# Comment the original API call for testing
97+
async with AlephHttpClient(api_server=settings.API_HOST) as client:
98+
filtered_credits = await client.get_credit_history(address=address, page_size=page_size, page=page)
99+
if json:
100+
typer.echo(filtered_credits.model_dump_json(indent=4))
101+
else:
102+
table = Table(title="Credits History", border_style="blue", box=box.ROUNDED)
103+
table.add_column("Timestamp")
104+
table.add_column("Amount", justify="right")
105+
table.add_column("Payment Method")
106+
table.add_column("Origin")
107+
table.add_column("Origin Ref")
108+
table.add_column("Expiration Date")
109+
110+
for credit in filtered_credits.credit_history:
111+
timestamp = Text(credit.message_timestamp.strftime("%Y-%m-%d %H:%M:%S"))
112+
amount = Text(displayable_amount(credit.amount, decimals=2), style="cyan")
113+
payment_method = Text(credit.payment_method if credit.payment_method else "-")
114+
origin = Text(credit.origin if credit.origin else "-")
115+
origin_ref = Text(credit.origin_ref if credit.origin_ref else "-")
116+
expiration = Text(
117+
credit.expiration_date.strftime("%Y-%m-%d") if credit.expiration_date else "Never",
118+
style="red" if credit.expiration_date else "green",
119+
)
120+
121+
table.add_row(timestamp, amount, payment_method, origin, origin_ref, expiration)
122+
123+
# Add pagination footer
124+
pagination_info = Text.assemble(
125+
"Page: ",
126+
Text(f"{filtered_credits.pagination_page}", style="cyan"),
127+
f" of {filtered_credits.pagination_total} | ",
128+
"Items per page: ",
129+
Text(f"{filtered_credits.pagination_per_page}"),
130+
" | ",
131+
"Total items: ",
132+
Text(f"{filtered_credits.pagination_total}"),
133+
)
134+
table.caption = pagination_info
135+
136+
console.print(table)
137+
138+
# Add summary panel
139+
infos = [
140+
Text.from_markup(f"[bold]Address:[/bold] {address}"),
141+
]
142+
console.print(
143+
Panel(
144+
Text.assemble(*infos),
145+
title="Credits Info",
146+
border_style="blue",
147+
expand=False,
148+
title_align="left",
149+
)
150+
)
151+
except ClientResponseError as e:
152+
typer.echo("Failed to retrieve credits history.")
153+
raise (e)

0 commit comments

Comments
 (0)