Skip to content

Commit

Permalink
Merge branch 'main' into remove_setuptools_dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
wakamex authored Apr 17, 2024
2 parents f0a4d81 + 4776fb1 commit 2a87914
Show file tree
Hide file tree
Showing 13 changed files with 197 additions and 78 deletions.
7 changes: 5 additions & 2 deletions src/ape/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,14 @@ def format_commands(self, ctx, formatter) -> None:
"Plugin": [],
"3rd-Party Plugin": [],
}
metadata = PluginMetadataList.load(ManagerAccessMixin.plugin_manager)

pl_metadata = PluginMetadataList.load(
ManagerAccessMixin.plugin_manager, include_available=False
)

for cli_name, cmd in commands:
help = cmd.get_short_help_str(limit)
plugin = metadata.get_plugin(cli_name)
plugin = pl_metadata.get_plugin(cli_name)
if not plugin:
continue

Expand Down
2 changes: 1 addition & 1 deletion src/ape/managers/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from decimal import Decimal
from typing import Any, Dict, List, Sequence, Tuple, Type, Union

from dateutil.parser import parse # type: ignore
from dateutil.parser import parse
from eth_pydantic_types import HexBytes
from eth_typing.evm import ChecksumAddress
from eth_utils import (
Expand Down
32 changes: 10 additions & 22 deletions src/ape/plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import functools
import importlib
import pkgutil
import subprocess
from importlib.metadata import distributions
from typing import Any, Callable, Generator, Iterator, List, Optional, Set, Tuple, Type

from ape.__modules__ import __modules__
Expand Down Expand Up @@ -165,30 +164,19 @@ def registered_plugins(self) -> Set[str]:
self._register_plugins()
return {x[0] for x in pluggy_manager.list_name_plugin()}

@functools.cached_property
def _plugin_modules(self) -> Tuple[str, ...]:
# NOTE: Unable to use pkgutil.iter_modules() for installed plugins
# because it does not work with editable installs.
# See https://github.com/python/cpython/issues/99805.
result = subprocess.check_output(
["pip", "list", "--format", "freeze", "--disable-pip-version-check"]
)
packages = result.decode("utf8").splitlines()
installed_plugin_module_names = {
p.split("==")[0].replace("-", "_") for p in packages if p.startswith("ape-")
}
core_plugin_module_names = {
n for _, n, ispkg in pkgutil.iter_modules() if n.startswith("ape_")
}

# NOTE: Returns tuple because this shouldn't change.
return tuple(installed_plugin_module_names.union(core_plugin_module_names))

def _register_plugins(self):
if self.__registered:
return

for module_name in self._plugin_modules:
plugins = [
x.name.replace("-", "_")
for x in distributions()
if getattr(x, "name", "").startswith("ape-")
]
locals = [p for p in __modules__ if p != "ape"]
plugin_modules = tuple([*plugins, *locals])

for module_name in plugin_modules:
try:
module = importlib.import_module(module_name)
pluggy_manager.register(module)
Expand Down
26 changes: 18 additions & 8 deletions src/ape/plugins/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,17 @@ class PluginMetadataList(BaseModel):
third_party: "PluginGroup"

@classmethod
def load(cls, plugin_manager):
registered_plugins = plugin_manager.registered_plugins
available_plugins = github_client.available_plugins
return cls.from_package_names(registered_plugins.union(available_plugins))
def load(cls, plugin_manager, include_available: bool = True):
plugins = plugin_manager.registered_plugins
if include_available:
plugins = plugins.union(github_client.available_plugins)

return cls.from_package_names(plugins, include_available=include_available)

@classmethod
def from_package_names(cls, packages: Iterable[str]) -> "PluginMetadataList":
def from_package_names(
cls, packages: Iterable[str], include_available: bool = True
) -> "PluginMetadataList":
PluginMetadataList.model_rebuild()
core = PluginGroup(plugin_type=PluginType.CORE)
available = PluginGroup(plugin_type=PluginType.AVAILABLE)
Expand All @@ -140,11 +144,17 @@ def from_package_names(cls, packages: Iterable[str]) -> "PluginMetadataList":
plugin = PluginMetadata(name=name.strip(), version=version)
if plugin.in_core:
core.plugins[name] = plugin
elif plugin.is_available and not plugin.is_installed:
continue

# perf: only check these once.
is_installed = plugin.is_installed
is_available = include_available and plugin.is_available

if include_available and is_available and not is_installed:
available.plugins[name] = plugin
elif plugin.is_installed and not plugin.in_core and not plugin.is_available:
elif is_installed and not plugin.in_core and not is_available:
third_party.plugins[name] = plugin
elif plugin.is_installed:
elif is_installed:
installed.plugins[name] = plugin
else:
logger.error(f"'{plugin.name}' is not a plugin.")
Expand Down
18 changes: 17 additions & 1 deletion src/ape/utils/abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,14 @@ def _encode(self, _type: ABIType, value: Any):
and not isinstance(value, tuple)
):
if isinstance(value, dict):
return tuple([value[m.name] for m in _type.components])
return tuple(
(
self._encode(m, value[m.name])
if isinstance(value[m.name], dict)
else value[m.name]
)
for m in _type.components
)

elif isinstance(value, (list, tuple)):
# NOTE: Args must be passed in correct order.
Expand Down Expand Up @@ -371,6 +378,15 @@ def reduce(struct) -> tuple:
"values": values,
}

if conflicts := [p for p in properties if p in methods]:
conflicts_str = ", ".join(conflicts)
logger.debug(
"The following methods are unavailable on the struct "
f"due to having the same name as a field: {conflicts_str}"
)
for conflict in conflicts:
del methods[conflict]

struct_def = make_dataclass(
name,
properties,
Expand Down
14 changes: 9 additions & 5 deletions src/ape_ethereum/ecosystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -935,14 +935,18 @@ def get_abi(_topic: HexStr) -> Optional[LogInputABICollection]:
converted_arguments[key] = value

yield ContractLog(
block_hash=log["blockHash"],
block_number=log["blockNumber"],
block_hash=log.get("blockHash") or log.get("block_hash") or "",
block_number=log.get("blockNumber") or log.get("block_number") or 0,
contract_address=self.decode_address(log["address"]),
event_arguments=converted_arguments,
event_name=abi.event_name,
log_index=log["logIndex"],
transaction_hash=log["transactionHash"],
transaction_index=log["transactionIndex"],
log_index=log.get("logIndex") or log.get("log_index") or 0,
transaction_hash=log.get("transactionHash") or log.get("transaction_hash") or "",
transaction_index=(
log.get("transactionIndex")
if "transactionIndex" in log
else log.get("transaction_index")
),
)

def enrich_calltree(self, call: CallTreeNode, **kwargs) -> CallTreeNode:
Expand Down
11 changes: 2 additions & 9 deletions src/ape_ethereum/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,21 +863,14 @@ def fetch_log_page(block_range):
start, stop = block_range
update = {"start_block": start, "stop_block": stop}
page_filter = log_filter.model_copy(update=update)
# eth-tester expects a different format, let web3 handle the conversions for it.
raw = "EthereumTester" not in self.client_version
logs = self._get_logs(page_filter.model_dump(mode="json"), raw)
filter_params = page_filter.model_dump(mode="json")
logs = self._make_request("eth_getLogs", [filter_params])
return self.network.ecosystem.decode_logs(logs, *log_filter.events)

with ThreadPoolExecutor(self.concurrency) as pool:
for page in pool.map(fetch_log_page, block_ranges):
yield from page

def _get_logs(self, filter_params, raw=True) -> List[Dict]:
if not raw:
return [vars(d) for d in self.web3.eth.get_logs(filter_params)]

return self._make_request("eth_getLogs", [filter_params])

def prepare_transaction(self, txn: TransactionAPI) -> TransactionAPI:
# NOTE: Use "expected value" for Chain ID, so if it doesn't match actual, we raise
txn.chain_id = self.network.chain_id
Expand Down
25 changes: 23 additions & 2 deletions src/ape_test/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from ast import literal_eval
from functools import cached_property
from re import Pattern
from typing import Any, Dict, Optional, cast
from typing import Any, Dict, Iterator, Optional, cast

from eth.exceptions import HeaderNotFound
from eth_pydantic_types import HexBytes
Expand All @@ -25,7 +25,7 @@
UnknownSnapshotError,
VirtualMachineError,
)
from ape.types import BlockID, SnapshotID
from ape.types import BlockID, ContractLog, LogFilter, SnapshotID
from ape.utils import DEFAULT_TEST_CHAIN_ID, DEFAULT_TEST_HD_PATH, gas_estimation_error_message
from ape_ethereum.provider import Web3Provider

Expand Down Expand Up @@ -268,6 +268,27 @@ def set_timestamp(self, new_timestamp: int):
def mine(self, num_blocks: int = 1):
self.evm_backend.mine_blocks(num_blocks)

def get_contract_logs(self, log_filter: LogFilter) -> Iterator[ContractLog]:
from_block = max(0, log_filter.start_block)

if log_filter.stop_block is None:
to_block = None
else:
latest_block = self.get_block("latest").number
to_block = (
min(latest_block, log_filter.stop_block)
if latest_block is not None
else log_filter.stop_block
)

log_gen = self.tester.ethereum_tester.get_logs(
address=log_filter.addresses,
from_block=from_block,
to_block=to_block,
topics=log_filter.topic_filter,
)
yield from self.network.ecosystem.decode_logs(log_gen, *log_filter.events)

def get_virtual_machine_error(self, exception: Exception, **kwargs) -> VirtualMachineError:
if isinstance(exception, ValidationError):
match = self._CANNOT_AFFORD_GAS_PATTERN.match(str(exception))
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/test_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def test_block_dict(block):
"num_transactions": 0,
"number": 0,
"parentHash": block.parent_hash.hex(),
"size": 548,
"size": block.size,
"timestamp": block.timestamp,
"totalDifficulty": 0,
"transactions": [],
Expand All @@ -32,7 +32,7 @@ def test_block_json(block):
f'"hash":"{block.hash.hex()}",'
'"num_transactions":0,"number":0,'
f'"parentHash":"{block.parent_hash.hex()}",'
f'"size":548,"timestamp":{block.timestamp},'
f'"size":{block.size},"timestamp":{block.timestamp},'
f'"totalDifficulty":0,"transactions":[]}}'
)
assert actual == expected
27 changes: 13 additions & 14 deletions tests/functional/test_contract_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import pytest
from eth_pydantic_types import HexBytes
from eth_utils import to_hex
from ethpm_types import ContractType

from ape.api import ReceiptAPI
Expand Down Expand Up @@ -124,7 +123,7 @@ def test_contract_logs_range(chain, contract_instance, owner, assert_log_values)
def test_contract_logs_range_by_address(
mocker, chain, eth_tester_provider, test_accounts, contract_instance, owner, assert_log_values
):
get_logs_spy = mocker.spy(eth_tester_provider.web3.eth, "get_logs")
get_logs_spy = mocker.spy(eth_tester_provider.tester.ethereum_tester, "get_logs")
contract_instance.setAddress(test_accounts[1], sender=owner)
height = chain.blocks.height
logs = [
Expand All @@ -137,18 +136,18 @@ def test_contract_logs_range_by_address(
# NOTE: This spy assertion tests against a bug where address queries were not
# 0x-prefixed. However, this was still valid in EthTester and thus was not causing
# test failures.
height_arg = to_hex(chain.blocks.height)
get_logs_spy.assert_called_once_with(
{
"address": [contract_instance.address],
"fromBlock": height_arg,
"toBlock": height_arg,
"topics": [
"0x7ff7bacc6cd661809ed1ddce28d4ad2c5b37779b61b9e3235f8262be529101a9",
"0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8",
],
}
)
height_arg = chain.blocks.height
actual = get_logs_spy.call_args[-1]
expected = {
"address": [contract_instance.address],
"from_block": height_arg,
"to_block": height_arg,
"topics": [
"0x7ff7bacc6cd661809ed1ddce28d4ad2c5b37779b61b9e3235f8262be529101a9",
"0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8",
],
}
assert actual == expected
assert logs == [contract_instance.AddressChange(newAddress=test_accounts[1])]


Expand Down
Loading

0 comments on commit 2a87914

Please sign in to comment.