Skip to content

Commit fe500d6

Browse files
Merge branch 'ApeWorX:main' into main
2 parents 5bc68a5 + 6d1c75e commit fe500d6

File tree

15 files changed

+174
-73
lines changed

15 files changed

+174
-73
lines changed

.github/workflows/docs.yaml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Docs
2+
3+
on:
4+
push:
5+
branches: [main]
6+
release:
7+
types: [released]
8+
pull_request:
9+
types: [opened, synchronize]
10+
11+
jobs:
12+
docs:
13+
runs-on: ubuntu-latest
14+
15+
permissions:
16+
contents: write
17+
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Setup Python
22+
uses: actions/setup-python@v5
23+
with:
24+
python-version: "3.10"
25+
26+
- name: Install Dependencies
27+
run: |
28+
python -m pip install --upgrade pip
29+
pip install .[doc]
30+
31+
- name: Ape Docs
32+
uses: apeworx/sphinx-ape@main
33+
with:
34+
github-token: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ jobs:
6363
strategy:
6464
matrix:
6565
os: [ubuntu-latest, macos-latest] # eventually add `windows-latest`
66-
python-version: [3.9, "3.10", "3.11", "3.12"]
66+
python-version: [3.9, "3.10", "3.11", "3.12", "3.13"]
6767

6868
steps:
6969
- uses: actions/checkout@v4

.pre-commit-config.yaml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: v4.6.0
3+
rev: v5.0.0
44
hooks:
55
- id: check-yaml
66

@@ -10,24 +10,25 @@ repos:
1010
- id: isort
1111

1212
- repo: https://github.com/psf/black
13-
rev: 24.4.2
13+
rev: 24.10.0
1414
hooks:
1515
- id: black
1616
name: black
1717

1818
- repo: https://github.com/pycqa/flake8
19-
rev: 7.0.0
19+
rev: 7.1.1
2020
hooks:
2121
- id: flake8
22+
additional_dependencies: [flake8-breakpoint, flake8-print, flake8-pydantic, flake8-type-checking]
2223

2324
- repo: https://github.com/pre-commit/mirrors-mypy
24-
rev: v1.10.0
25+
rev: v1.13.0
2526
hooks:
2627
- id: mypy
2728
additional_dependencies: [types-setuptools, pydantic]
2829

2930
- repo: https://github.com/executablebooks/mdformat
30-
rev: 0.7.17
31+
rev: 0.7.19
3132
hooks:
3233
- id: mdformat
3334
additional_dependencies: [mdformat-gfm, mdformat-frontmatter, mdformat-pyproject]

ape_ledger/__init__.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
1-
from ape import plugins
1+
from importlib import import_module
2+
from typing import Any
23

3-
from ape_ledger.accounts import AccountContainer, LedgerAccount
4+
from ape import plugins
45

56

67
@plugins.register(plugins.AccountPlugin)
78
def account_types():
9+
from ape_ledger.accounts import AccountContainer, LedgerAccount
10+
811
return AccountContainer, LedgerAccount
12+
13+
14+
def __getattr__(name: str) -> Any:
15+
if name in ("AccountContainer", "LedgerAccount"):
16+
return getattr(import_module("ape_ledger.accounts"), name)
17+
18+
else:
19+
raise AttributeError(name)
20+
21+
22+
__all__ = [
23+
"AccountContainer",
24+
"LedgerAccount",
25+
]

ape_ledger/_cli.py

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
1-
from typing import Union
1+
from typing import TYPE_CHECKING, Union
22

33
import click
4-
from ape import accounts
5-
from ape.cli import (
6-
ape_cli_context,
7-
existing_alias_argument,
8-
network_option,
9-
non_existing_alias_argument,
10-
skip_confirmation_option,
11-
)
12-
from eth_account import Account
13-
from eth_account.messages import encode_defunct
4+
from ape.cli.arguments import existing_alias_argument, non_existing_alias_argument
5+
from ape.cli.options import ape_cli_context, network_option, skip_confirmation_option
6+
7+
if TYPE_CHECKING:
8+
# NOTE: Type-checking only imports so CLI help loads faster.
9+
from ape.api import AccountAPI
10+
from ape_ledger.accounts import LedgerAccount
11+
from ape_ledger.hdpath import HDAccountPath, HDBasePath
1412

15-
from ape_ledger.accounts import LedgerAccount
16-
from ape_ledger.choices import AddressPromptChoice
1713
from ape_ledger.exceptions import LedgerSigningError
18-
from ape_ledger.hdpath import HDAccountPath, HDBasePath
1914

2015

21-
def _select_account(hd_path: Union[HDBasePath, str]) -> tuple[str, HDAccountPath]:
16+
def _select_account(hd_path: Union["HDBasePath", str]) -> tuple[str, "HDAccountPath"]:
17+
# NOTE: Lazy import so CLI help loads faster.
18+
from ape_ledger.choices import AddressPromptChoice
19+
2220
choices = AddressPromptChoice(hd_path)
2321
return choices.get_user_selected_account()
2422

@@ -52,8 +50,18 @@ def _list(cli_ctx):
5250
click.echo(f" {account.address}{alias_display}{hd_path_display}")
5351

5452

55-
def _get_ledger_accounts() -> list[LedgerAccount]:
56-
return [a for a in accounts if isinstance(a, LedgerAccount)]
53+
def _get_ledger_accounts() -> list["LedgerAccount"]:
54+
from ape.utils.basemodel import ManagerAccessMixin
55+
56+
from ape_ledger.accounts import LedgerAccount
57+
58+
return [a for a in ManagerAccessMixin.account_manager if isinstance(a, LedgerAccount)]
59+
60+
61+
def _hdpath_callback(ctx, param, val) -> "HDBasePath":
62+
from ape_ledger.hdpath import HDBasePath
63+
64+
return HDBasePath(val)
5765

5866

5967
@cli.command()
@@ -66,24 +74,30 @@ def _get_ledger_accounts() -> list[LedgerAccount]:
6674
"Defaults to m/44'/60'/{x}'/0/0 where {{x}} is the account ID. "
6775
"Exclude {x} to append the account ID to the end of the base path."
6876
),
69-
callback=lambda ctx, param, arg: HDBasePath(arg),
77+
callback=_hdpath_callback,
7078
)
7179
def add(cli_ctx, alias, hd_path):
7280
"""Add an account from your Ledger hardware wallet"""
7381

7482
address, account_hd_path = _select_account(hd_path)
75-
container = accounts.containers["ledger"]
83+
container = cli_ctx.account_manager.containers["ledger"]
7684
container.save_account(alias, address, str(account_hd_path))
7785
cli_ctx.logger.success(f"Account '{address}' successfully added with alias '{alias}'.")
7886

7987

88+
def _filter_accounts(acct: "AccountAPI") -> bool:
89+
from ape_ledger.accounts import LedgerAccount
90+
91+
return isinstance(acct, LedgerAccount)
92+
93+
8094
@cli.command()
8195
@ape_cli_context()
82-
@existing_alias_argument(account_type=LedgerAccount)
96+
@existing_alias_argument(account_type=_filter_accounts)
8397
def delete(cli_ctx, alias):
8498
"""Remove a Ledger account from ape"""
8599

86-
container = accounts.containers["ledger"]
100+
container = cli_ctx.account_manager.containers["ledger"]
87101
container.delete_account(alias)
88102
cli_ctx.logger.success(f"Account '{alias}' has been removed.")
89103

@@ -94,7 +108,7 @@ def delete(cli_ctx, alias):
94108
def delete_all(cli_ctx, skip_confirmation):
95109
"""Remove all Ledger accounts from ape"""
96110

97-
container = accounts.containers["ledger"]
111+
container = cli_ctx.account_manager.containers["ledger"]
98112
ledger_accounts = _get_ledger_accounts()
99113
if len(ledger_accounts) == 0:
100114
cli_ctx.logger.warning("No accounts found.")
@@ -131,11 +145,14 @@ def sign_message(cli_ctx, alias, message, network):
131145

132146

133147
def _sign_message(cli_ctx, alias, message):
134-
if alias not in accounts.aliases:
148+
from eth_account.account import Account
149+
from eth_account.messages import encode_defunct
150+
151+
if alias not in cli_ctx.account_manager.aliases:
135152
cli_ctx.abort(f"Account with alias '{alias}' does not exist.")
136153

137154
eip191message = encode_defunct(text=message)
138-
account = accounts.load(alias)
155+
account = cli_ctx.account_manager.load(alias)
139156
signature = account.sign_message(eip191message)
140157

141158
if not signature:
@@ -153,9 +170,13 @@ def _sign_message(cli_ctx, alias, message):
153170

154171

155172
@cli.command(short_help="Verify a message with your Trezor device")
173+
@ape_cli_context()
156174
@click.argument("message")
157175
@click.argument("signature")
158-
def verify_message(message, signature):
176+
def verify_message(cli_ctx, message, signature):
177+
from eth_account.account import Account
178+
from eth_account.messages import encode_defunct
179+
159180
eip191message = encode_defunct(text=message)
160181

161182
try:
@@ -164,5 +185,9 @@ def verify_message(message, signature):
164185
message = "Message cannot be verified. Check the signature and try again."
165186
raise LedgerSigningError(message) from exc
166187

167-
alias = accounts[signer_address].alias if signer_address in accounts else ""
188+
alias = (
189+
cli_ctx.account_manager[signer_address].alias
190+
if signer_address in cli_ctx.account_manager
191+
else ""
192+
)
168193
click.echo(f"Signer: {signer_address} {alias}")

ape_ledger/choices.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
from typing import Any, Optional, Union
1+
from typing import TYPE_CHECKING, Any, Optional, Union
22

33
import click
44
from ape.cli import PromptChoice
5-
from click import Context, Parameter
65

7-
from ape_ledger.client import get_device
8-
from ape_ledger.hdpath import HDAccountPath, HDBasePath
6+
if TYPE_CHECKING:
7+
from click import Context, Parameter
8+
9+
from ape_ledger.client import LedgerDeviceClient
10+
from ape_ledger.hdpath import HDAccountPath, HDBasePath
911

1012

1113
class AddressPromptChoice(PromptChoice):
@@ -17,10 +19,12 @@ class AddressPromptChoice(PromptChoice):
1719

1820
def __init__(
1921
self,
20-
hd_path: Union[HDBasePath, str],
22+
hd_path: Union["HDBasePath", str],
2123
index_offset: int = 0,
2224
page_size: int = DEFAULT_PAGE_SIZE,
2325
):
26+
from ape_ledger.hdpath import HDBasePath
27+
2428
if isinstance(hd_path, str):
2529
hd_path = HDBasePath(base_path=hd_path)
2630

@@ -58,7 +62,7 @@ def convert(
5862
self._choice_index = self._choice_index if address_index is None else address_index
5963
return address
6064

61-
def get_user_selected_account(self) -> tuple[str, HDAccountPath]:
65+
def get_user_selected_account(self) -> tuple[str, "HDAccountPath"]:
6266
"""Returns the selected address from the user along with the HD path.
6367
The user is able to page using special characters ``n`` and ``p``.
6468
"""
@@ -69,7 +73,7 @@ def get_user_selected_account(self) -> tuple[str, HDAccountPath]:
6973

7074
address = self._get_user_selection()
7175

72-
account_id = self._choice_index + self._index_offset
76+
account_id = (self._choice_index or 0) + self._index_offset
7377
return address, self._hd_root_path.get_account_path(account_id)
7478

7579
def _get_user_selection(self) -> str:
@@ -103,4 +107,11 @@ def _get_address(self, account_id: int) -> str:
103107
return device.get_address()
104108

105109

110+
def get_device(path: "HDAccountPath") -> "LedgerDeviceClient":
111+
# Perf: lazy load so CLI-usage is faster and abstracted for testing purposes.
112+
from ape_ledger.client import get_device as _get_device
113+
114+
return _get_device(path)
115+
116+
106117
__all__ = ["AddressPromptChoice"]

ape_ledger/client.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import atexit
22
from functools import cached_property
3+
from typing import TYPE_CHECKING
34

45
import hid # type: ignore
56
from ape.logging import LogLevel, logger
@@ -8,13 +9,14 @@
89
from ledgereth.messages import sign_message, sign_typed_data_draft
910
from ledgereth.transactions import SignedType2Transaction, create_transaction
1011

11-
from ape_ledger.hdpath import HDAccountPath
12+
if TYPE_CHECKING:
13+
from ape_ledger.hdpath import HDAccountPath
1214

1315

1416
class DeviceFactory:
1517
device_map: dict[str, "LedgerDeviceClient"] = {}
1618

17-
def create_device(self, account: HDAccountPath):
19+
def create_device(self, account: "HDAccountPath"):
1820
if account.path in self.device_map:
1921
return self.device_map[account.path]
2022

@@ -36,7 +38,7 @@ def get_dongle(debug: bool = False, reopen_on_fail: bool = True) -> HIDDongleHID
3638
raise # the OSError
3739

3840
class LedgerDeviceClient:
39-
def __init__(self, account: HDAccountPath):
41+
def __init__(self, account: "HDAccountPath"):
4042
self._account = account.path.lstrip("m/")
4143

4244
@cached_property
@@ -80,5 +82,5 @@ def sign_transaction(self, txn: dict) -> tuple[int, int, int]:
8082
_device_factory = DeviceFactory()
8183

8284

83-
def get_device(account: HDAccountPath):
85+
def get_device(account: "HDAccountPath") -> LedgerDeviceClient:
8486
return _device_factory.create_device(account)

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
extensions = ["sphinx_ape"]

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.. dynamic-toc-tree::

docs/userguides/quickstart.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
```{include} ../../README.md
2+
```

0 commit comments

Comments
 (0)