Skip to content

Commit

Permalink
Merge branch 'main' into docs/concurrency
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey authored Oct 21, 2024
2 parents 48fe9bc + d43ea77 commit a605116
Show file tree
Hide file tree
Showing 33 changed files with 571 additions and 261 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,13 @@ jobs:
pip install .[test]
- name: Run Functional Tests
run: pytest tests/functional -m "not fuzzing" -s --cov=src --cov-append -n auto --dist loadgroup
run: ape test tests/functional -m "not fuzzing" -s --cov=src --cov-append -n auto --dist loadgroup

- name: Run Integration Tests
run: pytest tests/integration -m "not fuzzing" -s --cov=src --cov-append -n auto --dist loadgroup
run: ape test tests/integration -m "not fuzzing" -s --cov=src --cov-append -n auto --dist loadgroup

- name: Run Performance Tests
run: pytest tests/performance -s
run: ape test tests/performance -s

fuzzing:
runs-on: ubuntu-latest
Expand All @@ -123,4 +123,4 @@ jobs:
pip install .[test]
- name: Run Tests
run: pytest -m "fuzzing" --no-cov -s
run: ape test -m "fuzzing" --no-cov -s
83 changes: 80 additions & 3 deletions docs/userguides/config.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Configure Ape

You can configure Ape using configuration files with the name `ape-config.yaml`.
There are two locations you can place an `ape-config.yaml` file.
You can configure Ape using a `pyproject.toml` file and the prefix `tool.ape` or any configuration file named `ape-config.[yaml|yml|json]`.
There are two locations you can place config files.

1. In the root of your project
2. In your `$HOME/.ape` directory (global)
Expand All @@ -23,6 +23,13 @@ However, here is a list of common-use cases requiring the `ape-config.yaml` file
**Environment Variables**: `ape-config.yaml` files support environment-variable expansion.
Simply include environment variables (with the `$` prefix) in your config file and Ape will automatically expand them.

```toml
[tool.ape.plugin]
secret_rpc = "$MY_SECRET_RPC"
```

Or the equivalent YAML:

```yaml
plugin:
secret_rpc: $MY_SECRET_RPC
Expand All @@ -44,6 +51,13 @@ project

In this case, you want to configure Ape like:

```toml
[tool.ape]
base_path = "src"
```

Or the equivalent YAML:

```yaml
base_path: src
```
Expand All @@ -56,6 +70,13 @@ Some dependencies, such as python-based ones like `snekmate`, use this structure
Specify a different path to your `contracts/` directory.
This is useful when using a different naming convention, such as `src/` rather than `contracts/`.

```toml
[tool.ape]
contracts_folder = "src"
```

Or the equivalent YAML:

```yaml
contracts_folder: src
```
Expand All @@ -71,6 +92,13 @@ contracts_folder: "~/GlobalContracts"

You can change the default ecosystem by including the following:

```toml
[tool.ape]
default_ecosystem = "fantom"
```

Or the equivalent YAML:

```yaml
default_ecosystem: fantom
```
Expand All @@ -84,9 +112,18 @@ To learn more about dependencies, see [this guide](./dependencies.html).

A simple example of configuring dependencies looks like this:

```toml
[[tool.ape.dependencies]]
name = "openzeppelin"
github = "OpenZeppelin/openzeppelin-contracts"
version = "4.4.2"
```

Or the equivalent YAML:

```yaml
dependencies:
- name: OpenZeppelin
- name: openzeppelin
github: OpenZeppelin/openzeppelin-contracts
version: 4.4.2
```
Expand All @@ -98,6 +135,18 @@ Set deployments that were made outside of Ape in your `ape-config.yaml` to creat

Config example:

```toml
[[tool.ape.deployments.ethereum.mainnet]]
contract_type = "MyContract"
address = "0x5FbDB2315678afecb367f032d93F642f64180aa3"
[[tool.ape.deployments.ethereum.sepolia]]
contract_type = "MyContract"
address = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"
```

Or the equivalent YAML:

```yaml
deployments:
ethereum:
Expand Down Expand Up @@ -126,6 +175,13 @@ Ape does not add or edit deployments in your `ape-config.yaml` file.
When using the `node` provider, you can customize its settings.
For example, to change the URI for an Ethereum network, do:

```toml
[tool.ape.node.ethereum.mainnet]
uri = "http://localhost:5030"
```

Or the equivalent YAML:

```yaml
node:
ethereum:
Expand All @@ -145,6 +201,16 @@ For more information on networking as a whole, see [this guide](./networks.html)

Set default network and network providers:

```toml
[tool.ape.ethereum]
default_network = "mainnet-fork"

[tool.ape.ethereum.mainnet_fork]
default_provider = "hardhat"
```

Or the equivalent YAML:

```yaml
ethereum:
default_network: mainnet-fork
Expand Down Expand Up @@ -188,6 +254,17 @@ Set which `ape` plugins you want to always use.
The `ape-` prefix is not needed and shouldn't be included here.
```

```toml
[[tool.ape.plugins]]
name = "solidity"
version = "0.1.0b2"

[[tool.ape.plugins]]
name = "ens"
```

Or the equivalent YAML:

```yaml
plugins:
- name: solidity # ape-solidity plugin
Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ norecursedirs = "projects"
# NOTE: 'no:ape_test' Prevents the ape plugin from activating on our tests
# And 'pytest_ethereum' is not used and causes issues in some environments.
addopts = """
-p no:ape_test
-p no:pytest_ethereum
"""

Expand Down
15 changes: 15 additions & 0 deletions src/ape/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,21 @@ def __ape_extra_attributes__(self) -> Iterator[ExtraModelAttributes]:

@classmethod
def validate_file(cls, path: Path, **overrides) -> "ApeConfig":
"""
Create an ApeConfig class using the given path.
Supports both pyproject.toml and ape-config.[.yml|.yaml|.json] files.
Raises:
:class:`~ape.exceptions.ConfigError`: When given an unknown file type
or the data is invalid.
Args:
path (Path): The path to the file.
**overrides: Config overrides.
Returns:
:class:`~ape.api.config.ApeConfig`
"""
data = {**load_config(path), **overrides}

# NOTE: We are including the project path here to assist
Expand Down
16 changes: 16 additions & 0 deletions src/ape/api/explorers.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,19 @@ def publish_contract(self, address: AddressType):
Args:
address (:class:`~ape.types.address.AddressType`): The address of the deployed contract.
"""

@classmethod
def supports_chain(cls, chain_id: int) -> bool:
"""
Returns ``True`` when the given chain ID is claimed to be
supported by this explorer. Adhoc / custom networks rely on
this feature to have automatic-explorer support. Explorer
plugins should override this.
Args:
chain_id (int): The chain ID to check.
Returns:
bool
"""
return False
20 changes: 13 additions & 7 deletions src/ape/api/networks.py
Original file line number Diff line number Diff line change
Expand Up @@ -975,8 +975,8 @@ def explorer(self) -> Optional["ExplorerAPI"]:
Returns:
:class:`ape.api.explorers.ExplorerAPI`, optional
"""

for plugin_name, plugin_tuple in self.plugin_manager.explorers:
chain_id = None if self.network_manager.active_provider is None else self.provider.chain_id
for plugin_name, plugin_tuple in self._plugin_explorers:
ecosystem_name, network_name, explorer_class = plugin_tuple

# Check for explicitly configured custom networks
Expand All @@ -987,17 +987,23 @@ def explorer(self) -> Optional["ExplorerAPI"]:
and self.name in plugin_config[self.ecosystem.name]
)

# Return the first registered explorer (skipping any others)
if self.ecosystem.name == ecosystem_name and (
self.name == network_name or has_explorer_config
):
# Return the first registered explorer (skipping any others)
return explorer_class(
name=plugin_name,
network=self,
)
return explorer_class(name=plugin_name, network=self)

elif chain_id is not None and explorer_class.supports_chain(chain_id):
# NOTE: Adhoc networks will likely reach here.
return explorer_class(name=plugin_name, network=self)

return None # May not have an block explorer

@property
def _plugin_explorers(self) -> list[tuple]:
# Abstracted for testing purposes.
return self.plugin_manager.explorers

@property
def is_mainnet(self) -> bool:
"""
Expand Down
14 changes: 13 additions & 1 deletion src/ape/api/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,25 @@ def is_valid(self) -> bool:

@cached_property
def config_file(self) -> Path:
if self._using_pyproject_toml:
return self._pyproject_toml

# else: check for an ape-config file.
for ext in self.EXTENSIONS:
path = self.path / f"{self.CONFIG_FILE_NAME}{ext}"
if path.is_file():
return path

# Default
# Default: non-existing ape-config.yaml file.
return self.path / f"{self.CONFIG_FILE_NAME}.yaml"

@property
def _pyproject_toml(self) -> Path:
return self.path / "pyproject.toml"

@property
def _using_pyproject_toml(self) -> bool:
return self._pyproject_toml.is_file() and "[tool.ape" in self._pyproject_toml.read_text()

def extract_config(self, **overrides) -> ApeConfig:
return ApeConfig.validate_file(self.config_file, **overrides)
19 changes: 11 additions & 8 deletions src/ape/managers/chain.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
from collections import defaultdict
from collections.abc import Collection, Iterator
from concurrent.futures import ThreadPoolExecutor
from contextlib import contextmanager
Expand Down Expand Up @@ -1497,7 +1498,7 @@ class ChainManager(BaseManager):
from ape import chain
"""

_snapshots: list[SnapshotID] = []
_snapshots: defaultdict = defaultdict(list) # chain_id -> snapshots
_chain_id_map: dict[str, int] = {}
_block_container_map: dict[int, BlockContainer] = {}
_transaction_history_map: dict[int, TransactionHistory] = {}
Expand Down Expand Up @@ -1602,9 +1603,10 @@ def snapshot(self) -> SnapshotID:
Returns:
:class:`~ape.types.SnapshotID`: The snapshot ID.
"""
chain_id = self.provider.chain_id
snapshot_id = self.provider.snapshot()
if snapshot_id not in self._snapshots:
self._snapshots.append(snapshot_id)
if snapshot_id not in self._snapshots[chain_id]:
self._snapshots[chain_id].append(snapshot_id)

return snapshot_id

Expand All @@ -1623,15 +1625,16 @@ def restore(self, snapshot_id: Optional[SnapshotID] = None):
snapshot_id (Optional[:class:`~ape.types.SnapshotID`]): The snapshot ID. Defaults
to the most recent snapshot ID.
"""
if snapshot_id is None and not self._snapshots:
chain_id = self.provider.chain_id
if snapshot_id is None and not self._snapshots[chain_id]:
raise ChainError("There are no snapshots to revert to.")
elif snapshot_id is None:
snapshot_id = self._snapshots.pop()
elif snapshot_id not in self._snapshots:
snapshot_id = self._snapshots[chain_id].pop()
elif snapshot_id not in self._snapshots[chain_id]:
raise UnknownSnapshotError(snapshot_id)
else:
snapshot_index = self._snapshots.index(snapshot_id)
self._snapshots = self._snapshots[:snapshot_index]
snapshot_index = self._snapshots[chain_id].index(snapshot_id)
self._snapshots[chain_id] = self._snapshots[chain_id][:snapshot_index]

self.provider.restore(snapshot_id)
self.history.revert_to_block(self.blocks.height)
Expand Down
2 changes: 2 additions & 0 deletions src/ape/managers/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -1763,6 +1763,8 @@ def unpack(self, destination: Path, config_override: Optional[dict] = None) -> "
path.write_text(str(src.content), encoding="utf8")

# Unpack config file.
# NOTE: Always unpacks into a regular .yaml config file for simplicity
# and maximum portibility.
self.config.write_to_disk(destination / "ape-config.yaml")

return LocalProject(destination, config_override=config_override)
Expand Down
2 changes: 1 addition & 1 deletion src/ape/pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def _snapshot(self) -> Optional[SnapshotID]:

@allow_disconnected
def _restore(self, snapshot_id: SnapshotID):
if snapshot_id not in self.chain_manager._snapshots:
if snapshot_id not in self.chain_manager._snapshots[self.provider.chain_id]:
return
try:
self.chain_manager.restore(snapshot_id)
Expand Down
12 changes: 11 additions & 1 deletion src/ape/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
import inspect
import json
import sys

if sys.version_info.minor >= 11:
# 3.11 or greater
# NOTE: type-ignore is for when running mypy on python versions < 3.11
import tomllib # type: ignore[import-not-found]
else:
import toml as tomllib # type: ignore[no-redef]

from asyncio import gather
from collections.abc import Coroutine, Mapping
from datetime import datetime, timezone
Expand Down Expand Up @@ -208,7 +216,9 @@ def load_config(path: Path, expand_envars=True, must_exist=False) -> dict:
if expand_envars:
contents = expand_environment_variables(contents)

if path.suffix in (".json",):
if path.name == "pyproject.toml":
config = tomllib.loads(contents).get("tool", {}).get("ape", {})
elif path.suffix in (".json",):
config = json.loads(contents)
elif path.suffix in (".yml", ".yaml"):
config = yaml.safe_load(contents)
Expand Down
Loading

0 comments on commit a605116

Please sign in to comment.