Skip to content

Commit 912a99e

Browse files
authored
refactor: move to dataclassy (#14)
* chore: add dataclassy as dependency * refactor: upgrade to dataclassy and other small improvements * refactor: create manager object instead of working with globals * test: add integration tests * feat: add ability to print and search tokens. clean up output * fix: forgot click dependency
1 parent c97874c commit 912a99e

File tree

10 files changed

+247
-128
lines changed

10 files changed

+247
-128
lines changed

setup.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@
5151
long_description_content_type="text/markdown",
5252
url="https://github.com/ApeWorX/py-tokenlists",
5353
packages=find_packages(exclude=["tests", "tests.*"]),
54-
install_requires=["semantic-version>=2.8.5,<3", "pyyaml>=5.4.1,<6"],
54+
install_requires=[
55+
"click>=8.0.0",
56+
"dataclassy>=0.10.3,<1.0",
57+
"pyyaml>=5.4.1,<6",
58+
"semantic-version>=2.8.5,<3",
59+
],
5560
entry_points={"console_scripts": ["tokenlists=tokenlists._cli:cli"]},
5661
extras_require=extras_require,
5762
classifiers=[

tests/integration/conftest.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from pathlib import Path
2+
3+
import pytest # type: ignore
4+
from click.testing import CliRunner
5+
6+
from tokenlists import TokenListManager, _cli
7+
8+
9+
@pytest.fixture
10+
def runner(monkeypatch):
11+
runner = CliRunner()
12+
with runner.isolated_filesystem() as temp_dir:
13+
monkeypatch.setattr(_cli, "TokenListManager", lambda: TokenListManager(Path(temp_dir)))
14+
yield runner
15+
16+
17+
@pytest.fixture
18+
def cli(runner):
19+
# NOTE: Depends on `runner` fixture for config side effects
20+
yield _cli.cli

tests/integration/test_cli.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
TEST_URI = "tokens.1inch.eth"
2+
3+
4+
def test_empty_list(runner, cli):
5+
result = runner.invoke(cli, ["list"])
6+
assert result.exit_code == 0
7+
assert "No tokenlists exist" in result.output
8+
9+
10+
def test_install(runner, cli):
11+
result = runner.invoke(cli, ["list"])
12+
assert result.exit_code == 0
13+
assert "No tokenlists exist" in result.output
14+
15+
result = runner.invoke(cli, ["install", TEST_URI])
16+
assert result.exit_code == 0
17+
18+
result = runner.invoke(cli, ["list"])
19+
assert result.exit_code == 0
20+
assert "1inch" in result.output
21+
22+
23+
def test_remove(runner, cli):
24+
result = runner.invoke(cli, ["install", TEST_URI])
25+
assert result.exit_code == 0
26+
27+
result = runner.invoke(cli, ["list"])
28+
assert result.exit_code == 0
29+
assert "1inch" in result.output
30+
31+
result = runner.invoke(cli, ["remove", "1inch"])
32+
assert result.exit_code == 0
33+
assert result.exit_code == 0
34+
35+
result = runner.invoke(cli, ["list"])
36+
assert result.exit_code == 0
37+
assert "No tokenlists exist" in result.output

tokenlists/__init__.py

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,8 @@
1-
from .manager import (
2-
available_token_lists,
3-
default_token_list,
4-
get_token_info,
5-
get_token_list,
6-
install_token_list,
7-
set_default_token_list,
8-
)
1+
from .manager import TokenListManager
92
from .typing import TokenInfo, TokenList
103

114
__all__ = [
125
"TokenInfo",
136
"TokenList",
14-
"install_token_list",
15-
"set_default_token_list",
16-
"available_token_lists",
17-
"default_token_list",
18-
"get_token_info",
19-
"get_token_list",
7+
"TokenListManager",
208
]

tokenlists/_cli.py

Lines changed: 84 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
1+
import re
2+
13
import click
2-
import yaml
34

4-
import tokenlists
5+
from .manager import TokenListManager
6+
from .typing import TokenSymbol
7+
8+
9+
class TokenlistChoice(click.Choice):
10+
def __init__(self, case_sensitive=True):
11+
self.case_sensitive = case_sensitive
12+
13+
@property
14+
def choices(self):
15+
return list(TokenListManager().available_tokenlists())
516

617

718
@click.group()
@@ -11,38 +22,85 @@ def cli():
1122
"""
1223

1324

14-
@cli.command(short_help="Display the names and versions of all installed tokenlists")
15-
def show_lists():
16-
click.echo("Installed Token Lists:")
17-
for token_list in map(tokenlists.get_token_list, tokenlists.available_token_lists()):
18-
click.echo(f"- {token_list.name} (v{token_list.version})")
25+
@cli.command(name="list", short_help="Display the names and versions of all installed tokenlists")
26+
def _list():
27+
manager = TokenListManager()
1928

29+
available_tokenlists = manager.available_tokenlists()
30+
if available_tokenlists:
31+
click.echo("Installed Token Lists:")
32+
for tokenlist in map(manager.get_tokenlist, available_tokenlists):
33+
click.echo(f"- {tokenlist.name} (v{tokenlist.version})")
2034

21-
@cli.command(short_help="Display the info for a particular token")
22-
@click.argument("symbol", type=tokenlists.typing.TokenSymbol)
23-
@click.option(
24-
"--token-list",
25-
type=click.Choice(tokenlists.available_token_lists()),
26-
default=tokenlists.default_token_list(),
27-
)
28-
def token_info(symbol, token_list):
29-
token_info = tokenlists.get_token_info(symbol, token_list)
30-
click.echo(yaml.dump(token_info.to_dict()))
31-
32-
33-
@cli.command(short_help="Install a new token list")
35+
else:
36+
click.echo("WARNING: No tokenlists exist!")
37+
38+
39+
@cli.command(short_help="Install a new tokenlist")
3440
@click.argument("uri")
3541
def install(uri):
36-
tokenlists.install_token_list(uri)
42+
manager = TokenListManager()
43+
44+
manager.install_tokenlist(uri)
3745

3846

39-
@cli.command(short_help="Remove an existing token list")
40-
@click.argument("name", type=click.Choice(tokenlists.available_token_lists()))
47+
@cli.command(short_help="Remove an existing tokenlist")
48+
@click.argument("name", type=TokenlistChoice())
4149
def remove(name):
42-
tokenlists.uninstall_token_list(name)
50+
manager = TokenListManager()
51+
52+
manager.remove_tokenlist(name)
4353

4454

4555
@cli.command(short_help="Set the default tokenlist")
46-
@click.argument("name", type=click.Choice(tokenlists.available_token_lists()))
56+
@click.argument("name", type=TokenlistChoice())
4757
def set_default(name):
48-
tokenlists.set_default_token_list(name)
58+
manager = TokenListManager()
59+
60+
manager.set_default_tokenlist(name)
61+
62+
63+
@cli.command(short_help="Display the names and versions of all installed tokenlists")
64+
@click.option("--search", default="")
65+
@click.option("--tokenlist-name", type=TokenlistChoice(), default=None)
66+
@click.option("--chain-id", default=1, type=int)
67+
def list_tokens(search, tokenlist_name, chain_id):
68+
manager = TokenListManager()
69+
70+
if not manager.default_tokenlist:
71+
raise click.ClickException("No tokenlists available!")
72+
73+
pattern = re.compile(search or ".*")
74+
75+
for token_info in filter(
76+
lambda t: pattern.match(t.symbol),
77+
manager.get_tokens(tokenlist_name, chain_id),
78+
):
79+
click.echo("{address} ({symbol})".format(**token_info.to_dict()))
80+
81+
82+
@cli.command(short_help="Display the info for a particular token")
83+
@click.argument("symbol", type=TokenSymbol)
84+
@click.option("--tokenlist-name", type=TokenlistChoice(), default=None)
85+
@click.option("--case-insensitive", default=False, is_flag=True)
86+
@click.option("--chain-id", default=1, type=int)
87+
def token_info(symbol, tokenlist_name, chain_id, case_insensitive):
88+
manager = TokenListManager()
89+
90+
if not manager.default_tokenlist:
91+
raise click.ClickException("No tokenlists available!")
92+
93+
token_info = manager.get_token_info(symbol, tokenlist_name, chain_id, case_insensitive)
94+
95+
click.echo(
96+
"""
97+
Symbol: {symbol}
98+
Name: {name}
99+
Chain ID: {chainId}
100+
Address: {address}
101+
Decimals: {decimals}
102+
Tags: {tags}
103+
""".format(
104+
tags=[], **token_info.to_dict()
105+
)
106+
)

tokenlists/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from pathlib import Path
22
from typing import Optional
33

4-
DATA_FOLDER = Path.home().joinpath(".tokenlists")
5-
DEFAULT_TOKEN_LIST: Optional[str] = None
4+
DEFAULT_CACHE_PATH = Path.home().joinpath(".tokenlists")
5+
DEFAULT_TOKENLIST: Optional[str] = None
66

77
UNISWAP_ENS_TOKENLISTS_HOST = "https://wispy-bird-88a7.uniswap.workers.dev/?url=http://{}.link"

0 commit comments

Comments
 (0)