Skip to content

Commit

Permalink
Merge branch 'main' of github.com:ApeWorX/ape into feat/0-9
Browse files Browse the repository at this point in the history
  • Loading branch information
bitwise-constructs committed Nov 21, 2024
2 parents a3b94e4 + 74c7746 commit 6a0df8c
Show file tree
Hide file tree
Showing 36 changed files with 543 additions and 262 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ repos:
]

- repo: https://github.com/executablebooks/mdformat
rev: 0.7.18
rev: 0.7.19
hooks:
- id: mdformat
additional_dependencies: [mdformat-gfm, mdformat-frontmatter, mdformat-pyproject]
Expand Down
21 changes: 18 additions & 3 deletions docs/userguides/console.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Follow [this guide](./networks.html) for more information on networks in Ape.

## Namespace Extras

You can also create scripts to be included in the console namespace by adding a file (`ape_console_extras.py`) to your root project directory. All non-internal symbols from this file will be included in the console namespace. Internal symbols are prefixed by an underscore (`_`).
You can also create scripts to be included in the console namespace by adding a file (`ape_console_extras.py`) to your root project directory. All non-internal symbols from this file will be included in the console namespace. Internal symbols are prefixed by an underscore (`_`).

An example file might look something like this:

Expand All @@ -75,7 +75,7 @@ Out[2]: '0x68f768988e9bd4be971d527f72483f321975fa52aff9692b6d0e0af71fb77aaf'

### Init Function

If you include a function named `ape_init_extras`, it will be executed with the symbols from the existing namespace being provided as keyword arguments. This allows you to alter the scripts namespace using locals already included in the Ape namespace. If you return a `dict`, these values will be added to the console namespace. For example, you could set up an initialized Web3.py object by using one from an existing Ape Provider.
If you include a function named `ape_init_extras`, it will be executed with the symbols from the existing namespace being provided as keyword arguments. This allows you to alter the scripts namespace using locals already included in the Ape namespace. If you return a `dict`, these values will be added to the console namespace. For example, you could set up an initialized Web3.py object by using one from an existing Ape Provider.

```python
def ape_init_extras(chain):
Expand All @@ -91,7 +91,7 @@ Out[1]: 1

### Global Extras

You can also add an `ape_console_extras.py` file to the global ape data directory (`$HOME/.ape/ape_console_extras.py`) and it will execute regardless of what project context you are in. This may be useful for variables and utility functions you use across all of your projects.
You can also add an `ape_console_extras.py` file to the global ape data directory (`$HOME/.ape/ape_console_extras.py`) and it will execute regardless of what project context you are in. This may be useful for variables and utility functions you use across all of your projects.

## Configure

Expand Down Expand Up @@ -164,3 +164,18 @@ Out[3]: '0.00040634 ETH'
In [4]: %bal 0xE3747e6341E0d3430e6Ea9e2346cdDCc2F8a4b5b
Out[4]: '0.00040634 ETH'
```

## Executing Code

You can also use the `ape console` to execute programs directly from strings.
This is similar to the `python -c|--code` option except it will display the output cell.
Anything available in `ape console` is also available in `ape console --code`.

```shell
ape console -c 'project.name'
Out[1]: 'my-project'
ape console -c 'x = 3\nx + 1'
Out[1]: 4
ape console -c 'networks.active_provider.name'
Out[1]: 'test'
```
8 changes: 8 additions & 0 deletions docs/userguides/developing_plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ from ape import plugins
# Here, we register our provider plugin so we can use it in 'ape'.
@plugins.register(plugins.ProviderPlugin)
def providers():
# NOTE: By keeping this import local, we avoid slower plugin load times.
from ape_my_plugin.provider import MyProvider

# NOTE: 'MyProvider' defined in a prior code-block.
yield "ethereum", "local", MyProvider
```
Expand All @@ -69,6 +72,11 @@ This decorator hooks into ape core and ties everything together by looking for a
Then, it will loop through these potential `ape` plugins and see which ones have created a plugin type registration.
If the plugin type registration is found, then `ape` knows this package is a plugin and attempts to process it according to its registration interface.

```{warning}
Ensure your plugin's `__init__.py` file imports quickly by keeping all expensive imports in the hook functions locally.
This helps Ape register plugins faster, which is required when checking for API implementations.
```

### CLI Plugins

The `ape` CLI is built using the python package [click](https://palletsprojects.com/p/click/).
Expand Down
21 changes: 18 additions & 3 deletions docs/userguides/networks.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Networks

When interacting with a blockchain, you will have to select an ecosystem (e.g. Ethereum, Arbitrum, or Fantom), a network (e.g. Mainnet or Sepolia) and a provider (e.g. Eth-Tester, Node (Geth), or Alchemy).
Networks are part of ecosystems and typically defined in plugins.
For example, the `ape-ethereum` plugin comes with Ape and can be used for handling EVM-like behavior.
The `ape-ethereum` ecosystem and network(s) plugin comes with Ape and can be used for handling EVM-like behavior.
Networks are part of ecosystems and typically defined in plugins or custom-network configurations.
However, Ape works out-of-the-box (in a limited way) with any network defined in the [evmchains](https://github.com/ApeWorX/evmchains) library.

## Selecting a Network

Expand All @@ -25,7 +26,7 @@ ape test --network ethereum:local:foundry
ape console --network arbitrum:testnet:alchemy # NOTICE: All networks, even from other ecosystems, use this.
```

To see all possible values for `--network`, run the command:
To see all networks that work with the `--network` flag (besides those _only_ defined in `evmchains`), run the command:

```shell
ape networks list
Expand Down Expand Up @@ -100,6 +101,20 @@ ape networks list

In the remainder of this guide, any example below using Ethereum, you can replace with an L2 ecosystem's name and network combination.

## evmchains Networks

If a network is in the [evmchains](https://github.com/ApeWorX/evmchains) library, it will work in Ape automatically, even without a plugin or any custom configuration for that network.

```shell
ape console --network moonbeam
```

This works because the `moonbeam` network data is available in the `evmchains` library, and Ape is able to look it up.

```{warning}
Support for networks from evm-chains alone may be limited and require additional configuration to work in production use-cases.
```

## Custom Network Connection

You can add custom networks to Ape without creating a plugin.
Expand Down
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"pytest-cov>=4.0.0,<5", # Coverage analyzer plugin
"pytest-mock", # For creating mocks
"pytest-benchmark", # For performance tests
"pytest-rerunfailures", # For flakey tests
"pytest-timeout>=2.2.0,<3", # For avoiding timing out during tests
"hypothesis>=6.2.0,<7.0", # Strategy-based fuzzer
"hypothesis-jsonschema==0.19.0", # JSON Schema fuzzer extension
Expand All @@ -36,10 +37,10 @@
"flake8-pydantic", # For detecting issues with Pydantic models
"flake8-type-checking", # Detect imports to move in/out of type-checking blocks
"isort>=5.13.2,<6", # Import sorting linter
"mdformat>=0.7.18", # Auto-formatter for markdown
"mdformat>=0.7.19", # 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
"mdformat-pyproject>=0.0.1", # Allows configuring in pyproject.toml
"mdformat-pyproject>=0.0.2", # Allows configuring in pyproject.toml
],
"doc": ["sphinx-ape"],
"release": [ # `release` GitHub Action job uses this
Expand Down
2 changes: 1 addition & 1 deletion src/ape/api/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ def prepare_transaction(self, txn: TransactionAPI) -> TransactionAPI:
:class:`~ape.api.transactions.TransactionAPI`
"""

# NOTE: Allow overriding nonce, assume user understand what this does
# NOTE: Allow overriding nonce, assume user understands what this does
if txn.nonce is None:
txn.nonce = self.nonce
elif txn.nonce < self.nonce:
Expand Down
51 changes: 43 additions & 8 deletions src/ape/api/networks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
)
from eth_pydantic_types import HexBytes
from eth_utils import keccak, to_int
from evmchains import PUBLIC_CHAIN_META
from pydantic import model_validator

from ape.exceptions import (
Expand Down Expand Up @@ -109,7 +110,7 @@ def data_folder(self) -> Path:
"""
return self.config_manager.DATA_FOLDER / self.name

@cached_property
@property
def custom_network(self) -> "NetworkAPI":
"""
A :class:`~ape.api.networks.NetworkAPI` for custom networks where the
Expand All @@ -125,13 +126,11 @@ def custom_network(self) -> "NetworkAPI":
if ethereum_class is None:
raise NetworkError("Core Ethereum plugin missing.")

request_header = self.config_manager.REQUEST_HEADER
init_kwargs = {"name": "ethereum", "request_header": request_header}
ethereum = ethereum_class(**init_kwargs) # type: ignore
init_kwargs = {"name": "ethereum"}
evm_ecosystem = ethereum_class(**init_kwargs) # type: ignore
return NetworkAPI(
name="custom",
ecosystem=ethereum,
request_header=request_header,
ecosystem=evm_ecosystem,
_default_provider="node",
_is_custom=True,
)
Expand Down Expand Up @@ -301,6 +300,11 @@ def networks(self) -> dict[str, "NetworkAPI"]:
network_api._is_custom = True
networks[net_name] = network_api

# Add any remaining networks from EVM chains here (but don't override).
# NOTE: Only applicable to EVM-based ecosystems, of course.
# Otherwise, this is a no-op.
networks = {**self._networks_from_evmchains, **networks}

return networks

@cached_property
Expand All @@ -311,6 +315,33 @@ def _networks_from_plugins(self) -> dict[str, "NetworkAPI"]:
if ecosystem_name == self.name
}

@cached_property
def _networks_from_evmchains(self) -> dict[str, "NetworkAPI"]:
# NOTE: Purposely exclude plugins here so we also prefer plugins.
networks = {
network_name: create_network_type(data["chainId"], data["chainId"])(
name=network_name, ecosystem=self
)
for network_name, data in PUBLIC_CHAIN_META.get(self.name, {}).items()
if network_name not in self._networks_from_plugins
}
forked_networks: dict[str, ForkedNetworkAPI] = {}
for network_name, network in networks.items():
if network_name.endswith("-fork"):
# Already a fork.
continue

fork_network_name = f"{network_name}-fork"
if any(x == fork_network_name for x in networks):
# The forked version of this network is already known.
continue

forked_networks[fork_network_name] = ForkedNetworkAPI(
name=fork_network_name, ecosystem=self
)

return {**networks, **forked_networks}

def __post_init__(self):
if len(self.networks) == 0:
raise NetworkError("Must define at least one network in ecosystem")
Expand Down Expand Up @@ -520,7 +551,6 @@ def get_network(self, network_name: str) -> "NetworkAPI":
Returns:
:class:`~ape.api.networks.NetworkAPI`
"""

names = {network_name, network_name.replace("-", "_"), network_name.replace("_", "-")}
networks = self.networks
for name in names:
Expand Down Expand Up @@ -1057,7 +1087,6 @@ def providers(self): # -> dict[str, Partial[ProviderAPI]]
Returns:
dict[str, partial[:class:`~ape.api.providers.ProviderAPI`]]
"""

from ape.plugins._utils import clean_plugin_name

providers = {}
Expand Down Expand Up @@ -1089,6 +1118,12 @@ def providers(self): # -> dict[str, Partial[ProviderAPI]]
network=self,
)

# Any EVM-chain works with node provider.
if "node" not in providers and self.name in self.ecosystem._networks_from_evmchains:
# NOTE: Arbitrarily using sepolia to access the Node class.
node_provider_cls = self.network_manager.ethereum.sepolia.get_provider("node").__class__
providers["node"] = partial(node_provider_cls, name="node", network=self)

return providers

def _get_plugin_providers(self):
Expand Down
8 changes: 4 additions & 4 deletions src/ape/api/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class BlockAPI(BaseInterfaceModel):
An abstract class representing a block and its attributes.
"""

# NOTE: All fields in this class (and it's subclasses) should not be `Optional`
# NOTE: All fields in this class (and its subclasses) should not be `Optional`
# except the edge cases noted below

num_transactions: HexInt = 0
Expand Down Expand Up @@ -231,7 +231,7 @@ def connection_str(self) -> str:
@abstractmethod
def connect(self):
"""
Connect a to a provider, such as start-up a process or create an HTTP connection.
Connect to a provider, such as start-up a process or create an HTTP connection.
"""

@abstractmethod
Expand Down Expand Up @@ -352,7 +352,7 @@ def network_choice(self) -> str:
def make_request(self, rpc: str, parameters: Optional[Iterable] = None) -> Any:
"""
Make a raw RPC request to the provider.
Advanced featues such as tracing may utilize this to by-pass unnecessary
Advanced features such as tracing may utilize this to by-pass unnecessary
class-serializations.
"""

Expand Down Expand Up @@ -933,7 +933,7 @@ def auto_mine(self) -> bool:
@abstractmethod
def auto_mine(self) -> bool:
"""
Enable or disbale automine.
Enable or disable automine.
"""

def _increment_call_func_coverage_hit_count(self, txn: TransactionAPI):
Expand Down
6 changes: 3 additions & 3 deletions src/ape/cli/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,12 +373,12 @@ def __init__(

@property
def base_type(self) -> type["ProviderAPI"]:
# perf: property exists to delay import ProviderAPI at init time.
from ape.api.providers import ProviderAPI

if self._base_type is not None:
return self._base_type

# perf: property exists to delay import ProviderAPI at init time.
from ape.api.providers import ProviderAPI

self._base_type = ProviderAPI
return ProviderAPI

Expand Down
11 changes: 8 additions & 3 deletions src/ape/cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,8 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def parse_args(self, ctx: "Context", args: list[str]) -> list[str]:
from ape.api.providers import ProviderAPI

arguments = args # Renamed for better pdb support.
base_type = ProviderAPI if self._use_cls_types else str
base_type: Optional[type] = None if self._use_cls_types else str
if existing_option := next(
iter(
x
Expand All @@ -85,13 +83,20 @@ def parse_args(self, ctx: "Context", args: list[str]) -> list[str]:
),
None,
):
if base_type is None:
from ape.api.providers import ProviderAPI

base_type = ProviderAPI

# Checking instance above, not sure why mypy still mad.
existing_option.type.base_type = base_type # type: ignore

else:
# Add the option automatically.
# NOTE: Local import here only avoids circular import issues.
from ape.cli.options import NetworkOption

# NOTE: None base-type will default to `ProviderAPI`.
option = NetworkOption(base_type=base_type, callback=self._network_callback)
self.params.append(option)

Expand Down
6 changes: 5 additions & 1 deletion src/ape/contracts/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from .base import ContractContainer, ContractEvent, ContractInstance, ContractLog, ContractNamespace
def __getattr__(name: str):
import ape.contracts.base as module

return getattr(module, name)


__all__ = [
"ContractContainer",
Expand Down
Loading

0 comments on commit 6a0df8c

Please sign in to comment.