Skip to content

Commit c06924c

Browse files
committed
WIP: porting
1 parent 68979f6 commit c06924c

File tree

9 files changed

+363
-479
lines changed

9 files changed

+363
-479
lines changed

src/torus/_common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def format_balance(balance: int, unit: BalanceUnit = BalanceUnit.nano) -> str:
8484
case BalanceUnit.joule | BalanceUnit.j:
8585
in_joules = from_nano(balance)
8686
round_joules = round(in_joules, 4)
87-
return f"{round_joules:,} COMAI"
87+
return f"{round_joules:,} TOR"
8888

8989

9090
K = TypeVar("K")

src/torus/balance.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def from_horus(amount: int, subnet_tempo: int = 100) -> float:
3030

3131
def repr_j(amount: int):
3232
"""
33-
Given an amount in nano, returns a representation of it in tokens/COMAI.
33+
Given an amount in nano, returns a representation of it in tokens/TOR.
3434
3535
E.g. "103.2J".
3636
"""

src/torus/cli/_common.py

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import rich
66
import rich.prompt
77
import typer
8+
from pydantic import BaseModel
89
from rich import box
910
from rich.console import Console
1011
from rich.table import Table
@@ -18,7 +19,6 @@
1819
from torus.errors import InvalidPasswordError, PasswordNotProvidedError
1920
from torus.types import (
2021
AgentInfoWithOptionalBalance,
21-
NetworkParams,
2222
Ss58Address,
2323
SubnetParamsWithEmission,
2424
)
@@ -242,6 +242,53 @@ def print_table_from_plain_dict(
242242
console.print(table)
243243

244244

245+
T = TypeVar("T", bound=BaseModel)
246+
247+
248+
def render_pydantic_table(
249+
objects: list[T], console: Console, title: str = ""
250+
) -> None:
251+
"""
252+
Renders a rich table from a list of Pydantic objects.
253+
254+
Args:
255+
objects: A list of Pydantic objects.
256+
console: The rich Console object.
257+
title: Optional title for the table.
258+
"""
259+
if not objects:
260+
return
261+
262+
# Create a rich table
263+
table = Table(title=title, show_header=True, header_style="bold magenta")
264+
265+
# Add columns to the table based on the Pydantic model fields
266+
for field_name, field in objects[0].model_fields.items():
267+
table.add_column(field_name, style="white", vertical="middle")
268+
269+
# Add rows to the table from Pydantic objects
270+
for obj in objects:
271+
row_data: list[str | Table] = []
272+
for field_name, field in obj.model_fields.items():
273+
value = getattr(obj, field_name)
274+
if isinstance(value, BaseModel):
275+
subtable = Table(
276+
show_header=False,
277+
padding=(0, 0, 0, 0),
278+
border_style="bright_black",
279+
)
280+
for subfield_name, subfield in value.model_fields.items():
281+
subfield_value = getattr(value, subfield_name)
282+
subtable.add_row(f"{subfield_name}: {subfield_value}")
283+
row_data.append(subtable)
284+
else:
285+
row_data.append(str(value))
286+
table.add_row(*row_data)
287+
288+
# Render the table
289+
console.print(table)
290+
291+
245292
def print_table_standardize(
246293
result: dict[str, list[Any]], console: Console
247294
) -> None:
@@ -355,14 +402,9 @@ def get_universal_password(ctx: CustomCtx) -> str:
355402
return universal_password
356403

357404

358-
def tranform_network_params(params: NetworkParams):
405+
def tranform_network_params(params: dict[str, Any]):
359406
"""Transform network params to be human readable."""
360-
governance_config = params["governance_config"]
361-
allocation = governance_config["proposal_reward_treasury_allocation"]
362-
governance_config = cast(dict[str, Any], governance_config)
363-
governance_config["proposal_reward_treasury_allocation"] = f"{allocation}%"
364-
params_ = cast(dict[str, Any], params)
365-
params_["governance_config"] = governance_config
407+
params_ = params
366408
general_params = dict_from_nano(
367409
params_,
368410
[

src/torus/cli/agent.py

Lines changed: 70 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,19 @@
1-
import importlib.util
21
from typing import Any, Optional, cast
32

43
import typer
5-
import uvicorn
64
from typer import Context
75

86
from torus._common import intersection_update
7+
from torus.balance import from_nano
98
from torus.cli._common import (
109
make_custom_context,
1110
print_module_info,
1211
print_table_from_plain_dict,
12+
render_pydantic_table,
1313
)
1414
from torus.errors import ChainTransactionError
1515
from torus.key import check_ss58_address
16-
from torus.misc import get_map_modules
17-
from torus.module._rate_limiters.limiters import (
18-
IpLimiterParams,
19-
StakeLimiterParams,
20-
)
21-
from torus.module.server import ModuleServer
16+
from torus.misc import get_governance_config, get_map_modules
2217
from torus.types import Ss58Address
2318

2419
module_app = typer.Typer(no_args_is_help=True)
@@ -89,6 +84,73 @@ def add_to_whitelist(ctx: Context, curator_key: str, agent_key: str):
8984
context.info(f"Module {agent_key} added to whitelist")
9085

9186

87+
@module_app.command()
88+
def list_applications(ctx: Context):
89+
"""
90+
Lists all registered applications.
91+
"""
92+
context = make_custom_context(ctx)
93+
client = context.com_client()
94+
95+
with context.progress_status("Getting applications..."):
96+
applications = client.query_map_applications()
97+
render_pydantic_table(
98+
[*applications.values()], context.console, title="Applications"
99+
)
100+
101+
102+
@module_app.command()
103+
def add_application(
104+
ctx: Context,
105+
payer_key: str,
106+
application_key: str,
107+
data: str,
108+
removing: bool = False,
109+
):
110+
"""
111+
Adds an application to a module.
112+
"""
113+
context = make_custom_context(ctx)
114+
client = context.com_client()
115+
116+
resolved_key = context.load_key(payer_key, None)
117+
application_addr = context.resolve_key_ss58(application_key, None)
118+
application_burn = get_governance_config(client).agent_application_cost
119+
confirm = context.confirm(
120+
f"{from_nano(application_burn)} tokens will be burned. Do you want to continue?"
121+
)
122+
if not confirm:
123+
context.info("Application addition cancelled")
124+
return
125+
with context.progress_status(f"Adding application {application_key}..."):
126+
client.add_application(
127+
key=resolved_key,
128+
application_key=application_addr,
129+
data=data,
130+
removing=removing,
131+
)
132+
context.info("Application added.")
133+
134+
135+
@module_app.command()
136+
def accept_application(
137+
ctx: Context,
138+
curator_key: str,
139+
application_id: int,
140+
):
141+
"""
142+
Accepts an application.
143+
"""
144+
context = make_custom_context(ctx)
145+
client = context.com_client()
146+
147+
resolved_curator_key = context.load_key(curator_key, None)
148+
149+
with context.progress_status("Accepting application..."):
150+
client.accept_application(resolved_curator_key, application_id)
151+
context.info("Application accepted.")
152+
153+
92154
@module_app.command()
93155
def deregister(ctx: Context, key: str):
94156
"""
@@ -172,104 +234,6 @@ def update(
172234
raise ChainTransactionError(response.error_message) # type: ignore
173235

174236

175-
@module_app.command()
176-
def serve(
177-
ctx: typer.Context,
178-
class_path: str,
179-
key: str,
180-
port: int = 8000,
181-
ip: Optional[str] = None,
182-
whitelist: Optional[list[str]] = None,
183-
blacklist: Optional[list[str]] = None,
184-
ip_blacklist: Optional[list[str]] = None,
185-
test_mode: Optional[bool] = False,
186-
request_staleness: int = typer.Option(120),
187-
use_ip_limiter: Optional[bool] = typer.Option(
188-
False, help=("If this value is passed, the ip limiter will be used")
189-
),
190-
token_refill_rate_base_multiplier: Optional[int] = typer.Option(
191-
None,
192-
help=(
193-
"Multiply the base limit per stake. Only used in stake limiter mode."
194-
),
195-
),
196-
):
197-
"""
198-
Serves a module on `127.0.0.1` on port `port`. `class_path` should specify
199-
the dotted path to the module class e.g. `module.submodule.ClassName`.
200-
"""
201-
context = make_custom_context(ctx)
202-
use_testnet = context.get_use_testnet()
203-
path_parts = class_path.split(".")
204-
match path_parts:
205-
case [*module_parts, class_name]:
206-
module_path = ".".join(module_parts)
207-
if not module_path:
208-
# This could do some kind of relative import somehow?
209-
raise ValueError(
210-
f"Invalid class path: `{class_path}`, module name is missing"
211-
)
212-
if not class_name:
213-
raise ValueError(
214-
f"Invalid class path: `{class_path}`, class name is missing"
215-
)
216-
case _:
217-
# This is impossible
218-
raise Exception(f"Invalid class path: `{class_path}`")
219-
220-
try:
221-
module = importlib.import_module(module_path)
222-
except ModuleNotFoundError:
223-
context.error(f"Module `{module_path}` not found")
224-
raise typer.Exit(code=1)
225-
226-
try:
227-
class_obj = getattr(module, class_name)
228-
except AttributeError:
229-
context.error(f"Class `{class_name}` not found in module `{module}`")
230-
raise typer.Exit(code=1)
231-
232-
keypair = context.load_key(key, None)
233-
234-
token_refill_rate = token_refill_rate_base_multiplier or 1
235-
limiter_params = (
236-
IpLimiterParams()
237-
if use_ip_limiter
238-
else StakeLimiterParams(token_ratio=token_refill_rate)
239-
)
240-
241-
if whitelist is None:
242-
context.info(
243-
"WARNING: No whitelist provided, will accept calls from any key"
244-
)
245-
246-
try:
247-
whitelist_ss58 = list_to_ss58(whitelist)
248-
except AssertionError:
249-
context.error("Invalid SS58 address passed to whitelist")
250-
exit(1)
251-
try:
252-
blacklist_ss58 = list_to_ss58(blacklist)
253-
except AssertionError:
254-
context.error("Invalid SS58 address passed on blacklist")
255-
exit(1)
256-
cast(list[Ss58Address] | None, whitelist)
257-
258-
server = ModuleServer(
259-
class_obj(),
260-
keypair,
261-
whitelist=whitelist_ss58,
262-
blacklist=blacklist_ss58,
263-
max_request_staleness=request_staleness,
264-
limiter=limiter_params,
265-
ip_blacklist=ip_blacklist,
266-
use_testnet=use_testnet,
267-
)
268-
app = server.get_fastapi_app()
269-
host = ip or "127.0.0.1"
270-
uvicorn.run(app, host=host, port=port) # type: ignore
271-
272-
273237
@module_app.command()
274238
def info(ctx: Context, name: str, balance: bool = False):
275239
"""

src/torus/cli/key.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,6 @@ def weight_delegation(
374374
ctx: Context,
375375
key: str,
376376
target: str,
377-
netuid: int,
378377
):
379378
context = make_custom_context(ctx)
380379
client = context.com_client()
@@ -388,4 +387,22 @@ def weight_delegation(
388387
):
389388
raise typer.Abort()
390389

391-
client.delegate_weight_control(resolved_key, resolved_target, netuid)
390+
client.delegate_weight_control(resolved_key, resolved_target)
391+
392+
393+
@key_app.command()
394+
def regain_weight_delegation(
395+
ctx: Context,
396+
key: str,
397+
):
398+
context = make_custom_context(ctx)
399+
client = context.com_client()
400+
resolved_key = context.load_key(key, None)
401+
402+
if not context.confirm(
403+
"Are you sure you want to regain vote power "
404+
f"from {typer.style(key, fg=typer.colors.CYAN)}?"
405+
):
406+
raise typer.Abort()
407+
408+
client.regain_weight_control(resolved_key)

0 commit comments

Comments
 (0)