diff --git a/docs/userguides/networks.md b/docs/userguides/networks.md index 4ea863dbf2..6e5b949834 100644 --- a/docs/userguides/networks.md +++ b/docs/userguides/networks.md @@ -135,3 +135,94 @@ from ape import chain block = chain.provider.get_block("latest") ``` + +## Provider Context Manager + +Use the [ProviderContextManager](../methoddocs/api.html#ape.api.networks.ProviderContextManager) to change the network-context in Python. +When entering a network for the first time, it will connect to that network. +**You do not need to call `.connect()` or `.disconnect()` manually**. + +For example, if you are using a script with a default network connection, you can change connection in the middle of the script by using the provider context manager: + +```python +from ape import chain, networks + +def main(): + start_provider = chain.provider.name + with networks.ethereum.mainnet.use_provider("geth") as provider: + # We are using a different provider than the one we started with. + assert start_provider != provider.name +``` + +Jump between networks to simulate multi-chain behavior. + +```python +import click +from ape import networks + +@click.command() +def cli(): + with networks.polygon.mainnet.use_provider("geth"): + ... + with networks.ethereum.mainnet.use_provider("geth"): + ... +``` + +The argument to [use_provider()](../methoddocs/api.html#ape.api.networks.NetworkAPI.use_provider) is the name of the provider you want to use. +You can also tell Ape to use the default provider by calling method [use_default_provider()](../methoddocs/api.html#ape.api.networks.NetworkAPI.use_default_provider) instead. +This will use whatever provider is set as default for your ecosystem / network combination (via one of your `ape-config.yaml` files). + +For example, let's say I have a default provider set like this: + +```yaml +arbitrum: + mainnet: + default_provider: alchemy +``` + +```python +import ape + +# Use the provider configured as the default for the arbitrum::mainnet network. +# In this case, it will use the "alchemy" provider. +with ape.networks.arbitrum.mainnet.use_default_provider(): + ... +``` + +You can also use the [parse_network_choice()](../methoddocs/managers.html#ape.managers.networks.NetworkManager.parse_network_choice) method when working with network choice strings: + +```python +from ape import networks + +# Same as doing `networks.ethereum.local.use_provider("test")`. +with networks.parse_network_choice("ethereum:local:test") as provider: + print(provider) +``` + +**A note about disconnect**: Providers do not disconnect until the very end of your Python session. +This is so you can easily switch network contexts in a bridge or multi-chain environment, which happens in fixtures and other sessions out of Ape's control. +However, sometimes you may definitely want your temporary network session to end before continuing, in which case you can use the `disconnect_after=True` kwarg: + +```python +from ape import networks + +with networks.parse_network_choice("ethereum:local:foundry", disconnect_after=True) as provider: + print(provider) +``` + +### Forked Context + +Using the `networks.fork()` method, you can achieve similar effects to using a forked network with `disconnect_after=True`. +For example, let's say we are running the following script on the network `ethereum:mainnet`. +We can switch to a forked network by doing this: + +```python +from ape import networks + +def main(): + with networks.fork("foundry"): + ... + # Do stuff on a local, forked version of mainnet + + # Switch back to mainnet. +``` diff --git a/src/ape/api/networks.py b/src/ape/api/networks.py index a0a6fbe2b1..066fd9b452 100644 --- a/src/ape/api/networks.py +++ b/src/ape/api/networks.py @@ -566,6 +566,11 @@ class ProviderContextManager(ManagerAccessMixin): mainnet = networks.ethereum.mainnet # An instance of NetworkAPI with mainnet.use_provider("infura"): ... + + # Or, using choice-strings: + + with networks.parse_network_choice("ethereum:local:test"): + ... """ connected_providers: Dict[str, "ProviderAPI"] = {} diff --git a/src/ape/pytest/plugin.py b/src/ape/pytest/plugin.py index 0fbefe29cd..1bd95b63ac 100644 --- a/src/ape/pytest/plugin.py +++ b/src/ape/pytest/plugin.py @@ -4,6 +4,7 @@ import pytest from ape import networks, project +from ape.exceptions import ConfigError from ape.logging import LogLevel, logger from ape.pytest.config import ConfigWrapper from ape.pytest.coverage import CoverageTracker @@ -14,33 +15,44 @@ def pytest_addoption(parser): - parser.addoption( - "--showinternal", - action="store_true", - ) - parser.addoption( + def add_option(*names, **kwargs): + try: + parser.addoption(*names, **kwargs) + except ValueError as err: + name_str = ", ".join(names) + if "already added" in str(err): + raise ConfigError( + f"Another pytest plugin besides `ape_test` uses an option with " + f"one of '{name_str}'. Note that Ape does not support being " + f"installed alongside Brownie; please use separate environments!" + ) + + raise ConfigError(f"Failed adding option {name_str}: {err}") from err + + add_option("--showinternal", action="store_true") + add_option( "--network", action="store", default=networks.default_ecosystem.name, help="Override the default network and provider (see ``ape networks list`` for options).", ) - parser.addoption( + add_option( "--interactive", "-I", action="store_true", help="Open an interactive console each time a test fails.", ) - parser.addoption( + add_option( "--disable-isolation", action="store_true", help="Disable test and fixture isolation (see provider for info on snapshot availability).", ) - parser.addoption( + add_option( "--gas", action="store_true", help="Show a transaction gas report at the end of the test session.", ) - parser.addoption( + add_option( "--gas-exclude", action="store", help="A comma-separated list of contract:method-name glob-patterns to ignore.",