Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: remove global config and flatten [APE-1455] #14

Merged
merged 7 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.2.0
rev: v4.5.0
hooks:
- id: check-yaml

Expand All @@ -10,24 +10,24 @@ repos:
- id: isort

- repo: https://github.com/psf/black
rev: 23.3.0
rev: 23.12.0
hooks:
- id: black
name: black

- repo: https://github.com/pycqa/flake8
rev: 6.0.0
rev: 6.1.0
hooks:
- id: flake8

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.991
rev: v1.7.1
hooks:
- id: mypy
additional_dependencies: [types-setuptools, pydantic]

- repo: https://github.com/executablebooks/mdformat
rev: 0.7.14
rev: 0.7.17
hooks:
- id: mdformat
additional_dependencies: [mdformat-gfm, mdformat-frontmatter]
Expand Down
9 changes: 0 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ To use the addressbook in a project, add common addresses to your `ape-config.ya

```yaml
addressbook:
entries:
shared_account: "0x2192f6112a026bce4047CeD2A16553Fd31E798B6"
```

Expand All @@ -43,11 +42,3 @@ from ape_addressbook import addressbook

address = addressbook["shared_account"]
```

You can also add global addresses:

```python
from ape_addressbook import addressbook

addressbook.set_global_entry("global_address", "0x2192f6112a026bce4047CeD2A16553Fd31E798B6")
```
82 changes: 13 additions & 69 deletions ape_addressbook/addressbook.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import json
from pathlib import Path
from typing import Dict, Iterator, Optional, cast
from typing import Dict, Iterator, cast

from ape._pydantic_compat import root_validator
from ape.api import PluginConfig
from ape.exceptions import AccountsError
from ape.logging import logger
from ape.types import AddressType, BaseModel
from ape.types import AddressType
from ape.utils import ManagerAccessMixin
from eth_utils import is_checksum_address, to_checksum_address
from pydantic import validator


def _validate_entries(entries: Dict) -> Dict:
Expand All @@ -29,19 +26,15 @@ def _validate_entries(entries: Dict) -> Dict:


class AddressBookConfig(PluginConfig):
entries: Dict[str, AddressType] = {}

@validator("entries", pre=True)
@root_validator(pre=True)
def validate_entries(cls, entries):
return _validate_entries(entries)

def __len__(self) -> int:
return len(self.dict())

class GlobalAddressBook(BaseModel):
entries: Dict[str, AddressType] = {}

@validator("entries", pre=True)
def validate_entries(cls, entries):
return _validate_entries(entries)
class Config:
extra = "allow"


class AddressBook(ManagerAccessMixin):
Expand All @@ -61,25 +54,6 @@ class AddressBook(ManagerAccessMixin):

"""

@property
def global_config_file(self) -> Path:
"""
The file path to the global addressbook entries JSON file,
located in Ape's data folder.
"""

return self.config_manager.DATA_FOLDER / "addressbook.json"

@property
def global_config(self) -> GlobalAddressBook:
"""
All of the entries stored in the global addressbook JSON file.
"""

file = self.global_config_file
cls = GlobalAddressBook
return cls.parse_file(file) if file.is_file() else cls()

@property
def config(self) -> AddressBookConfig:
"""
Expand All @@ -97,16 +71,18 @@ def registry(self) -> Dict[str, AddressType]:
and project addresses.
"""

registry = self.global_config.dict().get("entries", [])
registry.update(self.config.dict().get("entries", []))
return registry
data = self.config.dict()

# Sorted for consistency's sake.
return {k: data[k] for k in sorted(data)}

@property
def aliases(self) -> Iterator[str]:
"""
An iterator over all aliases in the registry.
"""

# NOTE: self.registry is sorted.
for alias in self.registry:
yield alias

Expand All @@ -119,37 +95,5 @@ def __getitem__(self, alias: str) -> AddressType:

return self.registry[alias]

def set_global_entry(
self, alias: str, address: AddressType, ecosystem_name: Optional[str] = None
):
"""
Add an address in the global registry of the addressbook.

Args:
alias (str): An alias for the address.
address (``AddressType``): The address.
ecosystem_name (Optional[str]): The ecosystem the address belongs to.
This is only used to help decode the address. The parameter defaults
to ``None`` but will use the connected provider's ecosystem if is connected.
Else, will attempt to use Ethereum, which should work for any EVM ecosystem.
"""

if alias in self.aliases:
raise AccountsError(f"Alias '{alias}' already in addressbook.")

if ecosystem_name is None and self.network_manager.active_provider:
ecosystem = self.provider.network.ecosystem
elif ecosystem_name is not None:
ecosystem = (
self.network_manager.ecosystems.get(ecosystem_name) or self.network_manager.ethereum
)
else:
# Default to ethereum
ecosystem = self.network_manager.ethereum

global_config = self.global_config.copy()
global_config.entries[alias] = ecosystem.decode_address(address)
self.global_config_file.write_text(json.dumps(global_config.dict()))


addressbook = AddressBook()
9 changes: 5 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
"hypothesis>=6.2.0,<7.0", # Strategy-based fuzzer
],
"lint": [
"black>=23.3.0,<24", # auto-formatter and linter
"mypy>=0.991,<1", # Static type analyzer
"black>=23.12.0,<24", # auto-formatter and linter
"mypy>=1.7.1,<2", # Static type analyzer
"types-setuptools", # Needed due to mypy typeshed
"flake8>=6.0.0,<7", # Style linter
"flake8>=6.1.0,<7", # Style linter
"isort>=5.10.1,<6", # Import sorting linter
"mdformat>=0.7.16", # Auto-formatter for markdown
"mdformat>=0.7.17", # Auto-formatter for markdown
"mdformat-gfm>=0.3.5", # Needed for formatting GitHub-flavored markdown
"mdformat-frontmatter>=0.4.1", # Needed for frontmatters-style headers in issue templates
],
Expand Down Expand Up @@ -58,6 +58,7 @@
include_package_data=True,
install_requires=[
"eth-ape>=0.6.0,<0.7",
"pydantic", # Use same version as eth-ape.
],
python_requires=">=3.8,<4",
extras_require=extras_require,
Expand Down
39 changes: 2 additions & 37 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import shutil
import tempfile
from pathlib import Path

Expand All @@ -7,16 +6,13 @@
from ape.managers.config import CONFIG_FILE_NAME
from eth_utils import to_checksum_address

from ape_addressbook import addressbook

PROJECT_ALIAS_UNCHECKSUMMED = "project_entry_quotes"
PROJECT_ALIAS_NO_QUOTES = "project_entry_no_quotes"
PROJECT_ADDRESS_NON_CHECKSUMMED = "0xbc8563cb0eedbd1b95ccafd0c156e2daf5e18c29"
PROJECT_ADDRESS = to_checksum_address(PROJECT_ADDRESS_NON_CHECKSUMMED)

APE_CONFIG = rf"""
addressbook:
entries:
# This address purposely is not checksummed.
{PROJECT_ALIAS_UNCHECKSUMMED}: '{PROJECT_ADDRESS_NON_CHECKSUMMED}'

Expand All @@ -26,7 +22,7 @@


@pytest.fixture(autouse=True, scope="session")
def madeup_project_and_data_folder():
def madeup_project():
"""
Prevents actually affecting global addressbook
and sets a temporary project.
Expand All @@ -39,32 +35,12 @@ def madeup_project_and_data_folder():
config_file.touch()
config_file.write_text(APE_CONFIG)

# Set the data folder to a temp dir as well so we can
# create global entries.
original = ape.config.DATA_FOLDER
data_folder = path / "data"
data_folder.mkdir()
ape.config.DATA_FOLDER = data_folder

with ape.config.using_project(Path(temp_dir)):
with ape.config.using_project(path):
yield

# Clean up.
ape.config.DATA_FOLDER = original
if config_file.is_file():
config_file.unlink()
if data_folder.is_dir():
shutil.rmtree(data_folder)


@pytest.fixture
def global_alias():
return "global_address"


@pytest.fixture
def global_address():
return "0x2192f6112a026bce4047CeD2A16553Fd31E798B6"


@pytest.fixture
Expand All @@ -80,14 +56,3 @@ def project_alias_no_quotes():
@pytest.fixture
def project_address():
return PROJECT_ADDRESS


@pytest.fixture
def book(global_alias, global_address):
address = global_address.lower() # Use lower to show checksum works.
addressbook.set_global_entry(global_alias, address)

yield addressbook

if addressbook.global_config_file.is_file():
addressbook.global_config_file.unlink()
39 changes: 12 additions & 27 deletions tests/test_addressbook.py
Original file line number Diff line number Diff line change
@@ -1,66 +1,51 @@
from ape_addressbook import addressbook


def test_config(book, project_alias_unchecksummed, project_alias_no_quotes, project_address):
def test_config(project_alias_unchecksummed, project_alias_no_quotes, project_address):
"""
This test shows that we are able to read values from a
project's config as well as handle checksumming them if needbe.
"""

actual = book.config.entries
actual = addressbook.config
assert len(actual) == 2
assert actual[project_alias_unchecksummed] == project_address
assert actual[project_alias_no_quotes] == project_address


def test_registry(
book,
global_alias,
project_alias_unchecksummed,
project_alias_no_quotes,
project_address,
global_address,
):
actual = book.registry
assert len(actual) == 3
assert actual[global_alias] == global_address
actual = addressbook.registry
assert len(actual) == 2
assert actual[project_alias_unchecksummed] == project_address
assert actual[project_alias_no_quotes] == project_address


def test_aliases(book, global_alias, project_alias_unchecksummed, project_alias_no_quotes):
def test_aliases(project_alias_unchecksummed, project_alias_no_quotes):
"""
The aliases includes both project and global addresses.
"""

assert list(addressbook.aliases) == [
global_alias,
project_alias_unchecksummed,
project_alias_no_quotes,
]
actual = list(addressbook.aliases)
expected = [project_alias_no_quotes, project_alias_unchecksummed]
assert actual == expected


def test_contains(
book,
global_alias,
global_address,
project_alias_unchecksummed,
project_address,
project_alias_no_quotes,
):
assert global_alias in book
assert project_alias_unchecksummed in book
assert project_alias_no_quotes in book
assert project_alias_unchecksummed in addressbook
assert project_alias_no_quotes in addressbook


def test_get_item(
book,
global_alias,
global_address,
project_alias_unchecksummed,
project_address,
project_alias_no_quotes,
):
assert book[global_alias] == global_address
assert book[project_alias_unchecksummed] == project_address
assert book[project_alias_no_quotes] == project_address
assert addressbook[project_alias_unchecksummed] == project_address
assert addressbook[project_alias_no_quotes] == project_address