Skip to content

Commit

Permalink
Merge branch 'main' into feat/auto-adhoc
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey authored Nov 20, 2024
2 parents 51b1752 + 623f95a commit 7e88a49
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 42 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ repos:
- id: flake8

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.2
rev: v1.13.0
hooks:
- id: mypy
additional_dependencies: [types-PyYAML, types-requests, types-setuptools, pydantic]
Expand Down
45 changes: 39 additions & 6 deletions ape_etherscan/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
from ape import plugins

from .config import EtherscanConfig
from .dependency import EtherscanDependency
from .explorer import Etherscan
from .query import EtherscanQueryEngine
from .utils import NETWORKS


@plugins.register(plugins.ExplorerPlugin)
def explorers():
from ape_etherscan.explorer import Etherscan
from ape_etherscan.utils import NETWORKS

for ecosystem_name in NETWORKS:
for network_name in NETWORKS[ecosystem_name]:
yield ecosystem_name, network_name, Etherscan
Expand All @@ -17,14 +14,50 @@ def explorers():

@plugins.register(plugins.QueryPlugin)
def query_engines():
from ape_etherscan.query import EtherscanQueryEngine

yield EtherscanQueryEngine


@plugins.register(plugins.Config)
def config_class():
from ape_etherscan.config import EtherscanConfig

return EtherscanConfig


@plugins.register(plugins.DependencyPlugin)
def dependencies():
from ape_etherscan.dependency import EtherscanDependency

yield "etherscan", EtherscanDependency


def __getattr__(name: str):
if name == "Etherscan":
from ape_etherscan.explorer import Etherscan

return Etherscan

elif name == "EtherscanConfig":
from ape_etherscan.config import EtherscanConfig

return EtherscanConfig

elif name == "EtherscanDependency":
from ape_etherscan.dependency import EtherscanDependency

return EtherscanDependency

elif name == "EtherscanQueryEngine":
from ape_etherscan.query import EtherscanQueryEngine

return EtherscanQueryEngine

elif name == "NETWORKS":
from ape_etherscan.utils import NETWORKS

return NETWORKS

else:
raise AttributeError(name)
17 changes: 10 additions & 7 deletions ape_etherscan/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@
import time
from collections.abc import Iterator
from io import StringIO
from typing import Optional
from typing import TYPE_CHECKING, Optional

from ape.api import PluginConfig
from ape.logging import logger
from ape.utils import USER_AGENT, ManagerAccessMixin
from requests import Session
from yarl import URL

from ape_etherscan.config import EtherscanConfig
from ape_etherscan.exceptions import (
ContractNotVerifiedError,
IncompatibleCompilerSettingsError,
Expand All @@ -27,17 +25,22 @@
)
from ape_etherscan.utils import API_KEY_ENV_KEY_MAP

if TYPE_CHECKING:
from ape.api import PluginConfig

from ape_etherscan.config import EtherscanConfig


def get_network_config(
etherscan_config: EtherscanConfig, ecosystem_name: str, network_name: str
) -> Optional[PluginConfig]:
etherscan_config: "EtherscanConfig", ecosystem_name: str, network_name: str
) -> Optional["PluginConfig"]:
if ecosystem_name in etherscan_config:
return etherscan_config[ecosystem_name].get(network_name)
return None


def get_etherscan_uri(
etherscan_config: EtherscanConfig, ecosystem_name: str, network_name: str
etherscan_config: "EtherscanConfig", ecosystem_name: str, network_name: str
) -> str:
# Look for explicitly configured Etherscan config
network_conf = get_network_config(etherscan_config, ecosystem_name, network_name)
Expand Down Expand Up @@ -176,7 +179,7 @@ def get_etherscan_uri(


def get_etherscan_api_uri(
etherscan_config: EtherscanConfig, ecosystem_name: str, network_name: str
etherscan_config: "EtherscanConfig", ecosystem_name: str, network_name: str
) -> str:
# Look for explicitly configured Etherscan config
network_conf = get_network_config(etherscan_config, ecosystem_name, network_name)
Expand Down
13 changes: 8 additions & 5 deletions ape_etherscan/dependency.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
from pathlib import Path
from typing import TYPE_CHECKING

from ape.api.projects import DependencyAPI
from ape.exceptions import ProjectError
from ape.types import AddressType
from ethpm_types import PackageManifest
from hexbytes import HexBytes
from pydantic import field_validator

from .explorer import Etherscan
from ape_etherscan.explorer import Etherscan

if TYPE_CHECKING:
from ape.types import AddressType
from ethpm_types import PackageManifest


class EtherscanDependency(DependencyAPI):
Expand All @@ -25,7 +28,7 @@ def version_id(self) -> str:
return f"{self.ecosystem}_{self.network}"

@property
def address(self) -> AddressType:
def address(self) -> "AddressType":
return self.network_manager.ethereum.decode_address(self.etherscan)

@property
Expand Down Expand Up @@ -53,7 +56,7 @@ def fetch(self, destination: Path):
manifest = self._get_manifest()
manifest.unpack_sources(destination)

def _get_manifest(self) -> PackageManifest:
def _get_manifest(self) -> "PackageManifest":
ecosystem = self.network_manager.get_ecosystem(self.ecosystem)
network = ecosystem.get_network(self.network)

Expand Down
6 changes: 4 additions & 2 deletions ape_etherscan/explorer.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import json
from typing import Optional
from typing import TYPE_CHECKING, Optional

import requests
from ape.api import ExplorerAPI, PluginConfig
from ape.contracts import ContractInstance
from ape.exceptions import ProviderNotConnectedError
from ape.managers.project import ProjectManager
from ape.types import AddressType, ContractType
from ape.utils import ZERO_ADDRESS

Expand All @@ -24,6 +23,9 @@
from ape_etherscan.verify import SourceVerifier
from ape_etherscan.utils import NETWORKS

if TYPE_CHECKING:
from ape.managers.project import ProjectManager


class Etherscan(ExplorerAPI):
"""
Expand Down
29 changes: 16 additions & 13 deletions ape_etherscan/verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,26 @@
import time
from enum import Enum
from pathlib import Path
from typing import Optional
from typing import TYPE_CHECKING, Optional

from ape.api import CompilerAPI
from ape.contracts import ContractInstance
from ape.logging import LogLevel, logger
from ape.managers.project import ProjectManager
from ape.types import AddressType
from ape.utils import ManagerAccessMixin, cached_property
from ethpm_types import Compiler, ContractType

from ape_etherscan.client import AccountClient, ClientFactory, ContractClient
from ape_etherscan.exceptions import (
ContractVerificationError,
EtherscanResponseError,
IncompatibleCompilerSettingsError,
)

if TYPE_CHECKING:
from ape.api import CompilerAPI
from ape.contracts import ContractInstance
from ape.managers.project import ProjectManager
from ape.types import AddressType

from ape_etherscan.client import AccountClient, ClientFactory, ContractClient

DEFAULT_OPTIMIZATION_RUNS = 200
_SPDX_ID_TO_API_CODE = {
"none": 1,
Expand Down Expand Up @@ -211,24 +214,24 @@ class SourceVerifier(ManagerAccessMixin):

def __init__(
self,
address: AddressType,
client_factory: ClientFactory,
project: Optional[ProjectManager] = None,
address: "AddressType",
client_factory: "ClientFactory",
project: Optional["ProjectManager"] = None,
):
self.address = address
self.client_factory = client_factory
self.project = project or self.local_project

@cached_property
def account_client(self) -> AccountClient:
def account_client(self) -> "AccountClient":
return self.client_factory.get_account_client(str(self.address))

@cached_property
def contract_client(self) -> ContractClient:
def contract_client(self) -> "ContractClient":
return self.client_factory.get_contract_client(str(self.address))

@cached_property
def contract(self) -> ContractInstance:
def contract(self) -> "ContractInstance":
"""
The ape contract instance to verify.
"""
Expand Down Expand Up @@ -303,7 +306,7 @@ def license_code(self) -> LicenseType:
return LicenseType.from_spdx_id(spdx_id)

@property
def compiler_api(self) -> CompilerAPI:
def compiler_api(self) -> "CompilerAPI":
if compiler := self.compiler_manager.registered_compilers.get(self.ext):
return compiler

Expand Down
3 changes: 2 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
[flake8]
max-line-length = 100
ignore = E704,W503,PYD002
ignore = E704,W503,PYD002,TC003,TC006
exclude =
venv*
docs
build
type-checking-pydantic-enabled = True
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
],
"lint": [
"black>=24.10.0,<25", # auto-formatter and linter
"mypy>=1.12.0,<2", # Static type analyzer
"mypy>=1.13.0,<2", # Static type analyzer
"types-requests>=2.28.7", # Needed due to mypy typeshed
"types-setuptools", # Needed due to mypy typeshed
"types-PyYAML", # Needed due to mypy typeshed
Expand Down
15 changes: 9 additions & 6 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,14 @@
from json import JSONDecodeError
from pathlib import Path
from tempfile import mkdtemp
from typing import IO, Any, Optional, Union
from typing import IO, TYPE_CHECKING, Any, Optional, Union
from unittest.mock import MagicMock

import _io # type: ignore
import ape
import pytest
from ape.api import ExplorerAPI
from ape.exceptions import NetworkError
from ape.logging import logger
from ape.types import AddressType
from ape.utils import cached_property
from ape_solidity._utils import OUTPUT_SELECTION
from requests import Response
Expand All @@ -26,6 +24,11 @@
from ape_etherscan.types import EtherscanResponse
from ape_etherscan.verify import LicenseType

if TYPE_CHECKING:
from ape.api import ExplorerAPI
from ape.types import AddressType


DATA_FOLDER = Path(mkdtemp()).resolve()
ape.config.DATA_FOLDER = DATA_FOLDER

Expand Down Expand Up @@ -168,7 +171,7 @@ def explorer(get_explorer):
def get_explorer(mocker):
def fn(
ecosystem_name: str = "ethereum", network_name: str = "development", no_mock=False
) -> ExplorerAPI:
) -> "ExplorerAPI":
try:
ecosystem = ape.networks.get_ecosystem(ecosystem_name)
except NetworkError:
Expand Down Expand Up @@ -446,7 +449,7 @@ def _get_contract_type_response(self, file_name: str) -> Any:
def _expected_get_ct_params(self, address: str) -> dict:
return {"module": "contract", "action": "getsourcecode", "address": address}

def setup_mock_account_transactions_response(self, address: AddressType, **overrides):
def setup_mock_account_transactions_response(self, address: "AddressType", **overrides):
file_name = "get_account_transactions.json"
test_data_path = MOCK_RESPONSES_PATH / file_name
params = self.get_expected_account_txns_params(address)
Expand All @@ -460,7 +463,7 @@ def setup_mock_account_transactions_response(self, address: AddressType, **overr
return self._setup_account_response(params, response)

def setup_mock_account_transactions_with_ctor_args_response(
self, address: AddressType, **overrides
self, address: "AddressType", **overrides
):
file_name = "get_account_transactions_with_ctor_args.json"
test_data_path = MOCK_RESPONSES_PATH / file_name
Expand Down

0 comments on commit 7e88a49

Please sign in to comment.