Skip to content

Commit aadd695

Browse files
committed
wip: implement address config
1 parent 4e029f2 commit aadd695

4 files changed

Lines changed: 309 additions & 0 deletions

File tree

multiversx_sdk_cli/address.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
from functools import cache
2+
from pathlib import Path
3+
from typing import Any
4+
5+
from multiversx_sdk_cli.errors import (
6+
AliasAlreadyExistsError,
7+
AliasProtectedError,
8+
InvalidAddressConfigValue,
9+
UnknownAddressAliasError,
10+
)
11+
from multiversx_sdk_cli.utils import read_json_file, write_json_file
12+
13+
SDK_PATH = Path("~/multiversx-sdk").expanduser().resolve()
14+
LOCAL_ADDRESS_CONFIG_PATH = Path("addresses.mxpy.json").resolve()
15+
GLOBAL_ADDRESS_CONFIG_PATH = SDK_PATH / "addresses.mxpy.json"
16+
17+
18+
def get_defaults() -> dict[str, str]:
19+
return {
20+
"kind": "",
21+
"path": "",
22+
"index": "",
23+
"password": "",
24+
"passwordPath": "",
25+
}
26+
27+
28+
@cache
29+
def get_value(name: str) -> str:
30+
_guard_valid_name(name)
31+
data = get_active_address()
32+
default_value = get_defaults()[name]
33+
value = data.get(name, default_value)
34+
assert isinstance(value, str)
35+
return value
36+
37+
38+
def _guard_valid_name(name: str):
39+
if name not in get_defaults().keys():
40+
raise InvalidAddressConfigValue(f"Key is not present in address config: [{name}]")
41+
42+
43+
def get_active_address() -> dict[str, str]:
44+
data = read_address_config_file()
45+
addresses: dict[str, Any] = data.get("addresses", {})
46+
active_address: str = data.get("active", "default")
47+
result: dict[str, str] = addresses.get(active_address, {})
48+
49+
return result
50+
51+
52+
@cache
53+
def read_address_config_file() -> dict[str, Any]:
54+
config_path = resolve_address_config_path()
55+
if config_path.exists():
56+
data: dict[str, Any] = read_json_file(config_path)
57+
return data
58+
return dict()
59+
60+
61+
def resolve_address_config_path() -> Path:
62+
if LOCAL_ADDRESS_CONFIG_PATH.is_file():
63+
return LOCAL_ADDRESS_CONFIG_PATH
64+
return GLOBAL_ADDRESS_CONFIG_PATH
65+
66+
67+
def set_value(name: str, value: Any):
68+
_guard_valid_name(name)
69+
data = read_address_config_file()
70+
active_env = data.get("active", "default")
71+
data.setdefault("addresses", {})
72+
data["addresses"].setdefault(active_env, {})
73+
data["addresses"][active_env][name] = value
74+
write_file(data)
75+
76+
77+
def write_file(data: dict[str, Any]):
78+
env_path = resolve_address_config_path()
79+
write_json_file(str(env_path), data)
80+
81+
82+
def set_active(name: str):
83+
data = read_address_config_file()
84+
_guard_valid_address_name(data, name)
85+
data["active"] = name
86+
write_file(data)
87+
88+
89+
def _guard_valid_address_name(env: Any, name: str):
90+
envs = env.get("addresses", {})
91+
if name not in envs:
92+
raise UnknownAddressAliasError(name)
93+
94+
95+
def create_new_address_config(name: str, template: str):
96+
data = read_address_config_file()
97+
_guard_alias_unique(data, name)
98+
new_address = {}
99+
if template:
100+
_guard_valid_address_name(data, template)
101+
new_address = data["addresses"][template]
102+
103+
data["active"] = name
104+
data.setdefault("addresses", {})
105+
data["addresses"][name] = new_address
106+
write_file(data)
107+
108+
109+
def _guard_alias_unique(env: Any, name: str):
110+
envs = env.get("addresses", {})
111+
if name in envs:
112+
raise AliasAlreadyExistsError(name)
113+
114+
115+
def delete_config_value(name: str):
116+
"""Deletes a key-value pair of the active address config."""
117+
_guard_valid_alias_deletion(name)
118+
data = read_address_config_file()
119+
active_env = data.get("active", "default")
120+
data.setdefault("addresses", {})
121+
data["addresses"].setdefault(active_env, {})
122+
del data["addresses"][active_env][name]
123+
write_file(data)
124+
125+
126+
def delete_alias(name: str):
127+
_guard_valid_alias_deletion(name)
128+
data = read_address_config_file()
129+
data["addresses"].pop(name, None)
130+
if data["active"] == name:
131+
data["active"] = "default"
132+
write_file(data)
133+
134+
135+
def _guard_valid_alias_deletion(name: str):
136+
if name == "default":
137+
raise AliasProtectedError(name)

multiversx_sdk_cli/cli.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from multiversx_sdk import LibraryConfig
1010
from rich.logging import RichHandler
1111

12+
import multiversx_sdk_cli.cli_address
1213
import multiversx_sdk_cli.cli_config
1314
import multiversx_sdk_cli.cli_contracts
1415
import multiversx_sdk_cli.cli_data
@@ -112,6 +113,7 @@ def setup_parser(args: list[str]):
112113
subparsers = parser.add_subparsers()
113114
commands: list[Any] = []
114115

116+
commands.append(multiversx_sdk_cli.cli_address.setup_parser(subparsers))
115117
commands.append(multiversx_sdk_cli.cli_contracts.setup_parser(args, subparsers))
116118
commands.append(multiversx_sdk_cli.cli_transactions.setup_parser(args, subparsers))
117119
commands.append(multiversx_sdk_cli.cli_validators.setup_parser(args, subparsers))

multiversx_sdk_cli/cli_address.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import logging
2+
import os
3+
from typing import Any
4+
5+
from multiversx_sdk_cli import cli_shared
6+
from multiversx_sdk_cli.address import (
7+
create_new_address_config,
8+
delete_alias,
9+
delete_config_value,
10+
get_active_address,
11+
get_value,
12+
read_address_config_file,
13+
resolve_address_config_path,
14+
set_active,
15+
set_value,
16+
)
17+
from multiversx_sdk_cli.utils import dump_out_json
18+
from multiversx_sdk_cli.ux import confirm_continuation
19+
20+
logger = logging.getLogger("cli.address")
21+
22+
23+
def setup_parser(subparsers: Any) -> Any:
24+
parser = cli_shared.add_group_subparser(subparsers, "address", "Configure MultiversX CLI to use a default wallet.")
25+
subparsers = parser.add_subparsers()
26+
27+
sub = cli_shared.add_command_subparser(
28+
subparsers, "address", "new", "Creates a new address config and sets it as the active address."
29+
)
30+
_add_alias_arg(sub)
31+
sub.add_argument(
32+
"--template",
33+
required=False,
34+
help="an address config from which to create the new address",
35+
)
36+
sub.set_defaults(func=new_address_config)
37+
38+
sub = cli_shared.add_command_subparser(subparsers, "address", "list", "List available addresses")
39+
sub.set_defaults(func=list_addresses)
40+
41+
sub = cli_shared.add_command_subparser(subparsers, "address", "dump", "Dumps the active address.")
42+
sub.set_defaults(func=dump)
43+
44+
sub = cli_shared.add_command_subparser(subparsers, "address", "get", "Gets a config value from the active address.")
45+
sub.add_argument("value", type=str, help="the value to get from the active address (e.g. path)")
46+
sub.set_defaults(func=get_address_config_value)
47+
48+
sub = cli_shared.add_command_subparser(subparsers, "address", "set", "Sets a config value for the active address.")
49+
sub.add_argument("key", type=str, help="the key to set for the active address (e.g. index)")
50+
sub.add_argument("value", type=str, help="the value to set for the specified key")
51+
sub.set_defaults(func=set_address_config_value)
52+
53+
sub = cli_shared.add_command_subparser(
54+
subparsers, "address", "delete", "Deletes a config value from the active address."
55+
)
56+
sub.add_argument("value", type=str, help="the value to delete for the active address")
57+
sub.set_defaults(func=delete_address_config_value)
58+
59+
sub = cli_shared.add_command_subparser(subparsers, "address", "switch", "Switch to a different address.")
60+
_add_alias_arg(sub)
61+
sub.set_defaults(func=switch_address)
62+
63+
sub = cli_shared.add_command_subparser(
64+
subparsers,
65+
"address",
66+
"remove",
67+
"Deletes an address using the alias. No default address will be set. Use `address switch` to set a new address.",
68+
)
69+
_add_alias_arg(sub)
70+
sub.set_defaults(func=remove_address)
71+
72+
sub = cli_shared.add_command_subparser(
73+
subparsers,
74+
"address",
75+
"reset",
76+
"Deletes the config file. No default address will be set.",
77+
)
78+
sub.set_defaults(func=delete_address_config_file)
79+
80+
parser.epilog = cli_shared.build_group_epilog(subparsers)
81+
return subparsers
82+
83+
84+
def _add_alias_arg(sub: Any):
85+
sub.add_argument("alias", type=str, help="the alias of the wallet")
86+
87+
88+
def new_address_config(args: Any):
89+
create_new_address_config(name=args.alias, template=args.template)
90+
dump_out_json(get_active_address())
91+
92+
93+
def list_addresses(args: Any):
94+
_ensure_address_config_file_exists()
95+
96+
data = read_address_config_file()
97+
dump_out_json(data)
98+
99+
100+
def dump(args: Any):
101+
_ensure_address_config_file_exists()
102+
dump_out_json(get_active_address())
103+
104+
105+
def get_address_config_value(args: Any):
106+
_ensure_address_config_file_exists()
107+
value = get_value(args.value)
108+
print(value)
109+
110+
111+
def set_address_config_value(args: Any):
112+
_ensure_address_config_file_exists()
113+
set_value(args.key, args.value)
114+
115+
116+
def delete_address_config_value(args: Any):
117+
_ensure_address_config_file_exists()
118+
119+
delete_config_value(args.value)
120+
dump_out_json(get_active_address())
121+
122+
123+
def switch_address(args: Any):
124+
_ensure_address_config_file_exists()
125+
126+
set_active(args.alias)
127+
dump_out_json(get_active_address())
128+
129+
130+
def remove_address(args: Any):
131+
_ensure_address_config_file_exists()
132+
delete_alias(args.alias)
133+
134+
135+
def delete_address_config_file(args: Any):
136+
address_file = resolve_address_config_path()
137+
if not address_file.is_file():
138+
logger.info("Address config file not found. Aborting...")
139+
return
140+
141+
confirm_continuation(f"The file `{str(address_file)}` will be deleted. Do you want to continue? (y/n)")
142+
os.remove(address_file)
143+
logger.info("Successfully deleted the address config file.")
144+
145+
146+
def _ensure_address_config_file_exists():
147+
address_file = resolve_address_config_path()
148+
if not address_file.is_file():
149+
logger.info("Address config file not found. Aborting...")
150+
exit(1)

multiversx_sdk_cli/errors.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,23 @@ def __init__(self, name: str):
176176
class EnvironmentAlreadyExistsError(KnownError):
177177
def __init__(self, name: str):
178178
super().__init__(f"Environment entry already exists: {name}.")
179+
180+
181+
class UnknownAddressAliasError(KnownError):
182+
def __init__(self, name: str):
183+
super().__init__(f"Alias is not known: {name}.")
184+
185+
186+
class AliasAlreadyExistsError(KnownError):
187+
def __init__(self, name: str):
188+
super().__init__(f"Alias already exists: {name}.")
189+
190+
191+
class AliasProtectedError(KnownError):
192+
def __init__(self, name: str):
193+
super().__init__(f"This environment name is protected: {name}.")
194+
195+
196+
class InvalidAddressConfigValue(KnownError):
197+
def __init__(self, message: str):
198+
super().__init__(message)

0 commit comments

Comments
 (0)