Skip to content

Commit 4241e7b

Browse files
authored
Merge branch 'main' into codex/fix-authentication-for-ape-foundry
2 parents 584d996 + d34fbdc commit 4241e7b

7 files changed

Lines changed: 60 additions & 21 deletions

File tree

src/ape/api/providers.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from ethpm_types import MethodABI
2222
from pydantic import Field, computed_field, field_serializer, model_validator
2323

24-
from ape.api.networks import NetworkAPI
24+
from ape.api.networks import NetworkAPI, ProviderContextManager
2525
from ape.api.query import BlockTransactionQuery
2626
from ape.api.transactions import ReceiptAPI, TransactionAPI
2727
from ape.exceptions import (
@@ -311,6 +311,38 @@ def disconnect(self):
311311
Disconnect from a provider, such as tear-down a process or quit an HTTP session.
312312
"""
313313

314+
def connection(
315+
self,
316+
disconnect_after: bool = False,
317+
disconnect_on_exit: bool = True,
318+
) -> ProviderContextManager:
319+
"""
320+
Use this provider in a temporary context. Connects on enter and disconnects
321+
on exit. Equivalent to :meth:`~ape.managers.networks.NetworkManager.parse_network_choice`
322+
but for a provider instance that has already been resolved.
323+
324+
Usage example::
325+
326+
provider = networks.ethereum.mainnet.get_provider("infura")
327+
with provider.connection():
328+
...
329+
330+
Args:
331+
disconnect_after (bool): Set to ``True`` to force a disconnect after ending
332+
the context. Defaults to ``False`` so the connection can be reused, such
333+
as in a multi-chain scenario.
334+
disconnect_on_exit (bool): Whether to disconnect on the exit of the python
335+
session. Defaults to ``True``.
336+
337+
Returns:
338+
:class:`~ape.api.networks.ProviderContextManager`
339+
"""
340+
return ProviderContextManager(
341+
provider=self,
342+
disconnect_after=disconnect_after,
343+
disconnect_on_exit=disconnect_on_exit,
344+
)
345+
314346
@property
315347
def ipc_path(self) -> Path | None:
316348
"""

src/ape/cli/commands.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,19 @@ def get_param_from_ctx(ctx: "Context", param: str) -> Any | None:
2828

2929
def parse_network(ctx: "Context") -> "ProviderContextManager | None":
3030
from ape.api.providers import ProviderAPI
31+
from ape.cli.options import NETWORK_META_KEY
3132
from ape.utils.basemodel import ManagerAccessMixin as access
3233

3334
interactive = get_param_from_ctx(ctx, "interactive")
3435

36+
# Handle if already parsed by the network_option callback. Preferred
37+
# location is ``ctx.meta`` so we don't collide with user-owned ``ctx.obj``
38+
# (e.g. when a command chains ``click.make_pass_decorator(...)`` that
39+
# replaces ``ctx.obj`` with a typed container).
40+
network_meta = ctx.meta.get(NETWORK_META_KEY)
41+
if network_meta and (provider := network_meta.get("provider")) is not None:
42+
return provider.network.use_provider(provider, disconnect_on_exit=not interactive)
43+
3544
# Handle if already parsed (as when using network-option)
3645
if ctx.obj and "provider" in ctx.obj:
3746
provider = ctx.obj["provider"]

src/ape/cli/options.py

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,9 @@ def _get_requested_networks(function, network_object_names):
322322
return [x for x in command_kwargs if x in network_object_names]
323323

324324

325+
NETWORK_META_KEY = "__ape_network__"
326+
327+
325328
def _update_context_with_network(ctx, provider, requested_network_objects):
326329
choice_classes = {
327330
"ecosystem": provider.network.ecosystem,
@@ -336,22 +339,12 @@ def _update_context_with_network(ctx, provider, requested_network_objects):
336339
ctx.params[item] = instance
337340

338341
if isinstance(ctx.command, ConnectedProviderCommand):
339-
# Place all values, regardless of request in
340-
# the context. This helps the Ape CLI backend.
341-
if ctx.obj is None:
342-
# Happens when using commands that don't use the
343-
# Ape context or any context.
344-
ctx.obj = {}
345-
346-
for choice, obj in choice_classes.items():
347-
try:
348-
ctx.obj[choice] = obj
349-
except Exception:
350-
# This would only happen if using an unusual context object.
351-
raise Abort(
352-
"Cannot use connected-provider command type(s) "
353-
"with non key-settable context object."
354-
)
342+
# Place all values, regardless of request, in ``ctx.meta`` so that
343+
# ``parse_network`` (and ``ctx.obj`` consumers that use
344+
# :meth:`click.Context.ensure_object`) can all coexist. ``ctx.obj`` is
345+
# owned by the caller's code; ``ctx.meta`` is Click's cross-cutting
346+
# scratch space and is safe for us to write to.
347+
ctx.meta.setdefault(NETWORK_META_KEY, {}).update(choice_classes)
355348

356349

357350
def _get_provider(value, default, keep_as_choice_str):

tests/functional/test_accounts.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,9 +330,9 @@ def test_deploy(owner, contract_container, clean_contract_caches):
330330

331331
@explorer_test
332332
@pytest.mark.timeout(360)
333-
def test_deploy_and_publish_local_network(owner, contract_container):
333+
def test_deploy_and_publish_local_network(owner, minimal_proxy_container):
334334
with pytest.raises(ProjectError, match="Can only publish deployments on a live network"):
335-
owner.deploy(contract_container, 0, publish=True)
335+
owner.deploy(minimal_proxy_container, publish=True)
336336

337337

338338
@explorer_test

tests/functional/test_cli.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,7 @@ def test_get_param_from_ctx(mocker):
873873

874874
def test_parse_network_when_interactive_and_no_param(mocker):
875875
ctx = mocker.MagicMock()
876+
ctx.meta = {}
876877
ctx.params = {"interactive": True}
877878
ctx.parent = None
878879
network_ctx = parse_network(ctx)
@@ -883,6 +884,7 @@ def test_parse_network_when_interactive_and_no_param(mocker):
883884

884885
def test_parse_network_when_interactive_and_str_param(mocker):
885886
ctx = mocker.MagicMock()
887+
ctx.meta = {}
886888
ctx.params = {"interactive": True, "network": "ethereum:local:test"}
887889
network_ctx = parse_network(ctx)
888890
assert network_ctx is not None
@@ -892,6 +894,7 @@ def test_parse_network_when_interactive_and_str_param(mocker):
892894

893895
def test_parse_network_when_interactive_and_class_param(mocker, eth_tester_provider):
894896
ctx = mocker.MagicMock()
897+
ctx.meta = {}
895898
ctx.params = {"interactive": True, "network": eth_tester_provider}
896899
network_ctx = parse_network(ctx)
897900
assert network_ctx is not None
@@ -901,6 +904,7 @@ def test_parse_network_when_interactive_and_class_param(mocker, eth_tester_provi
901904

902905
def test_parse_network_when_explicit_none(mocker):
903906
ctx = mocker.MagicMock()
907+
ctx.meta = {}
904908
ctx.params = {"network": _NONE_NETWORK}
905909
network_ctx = parse_network(ctx)
906910
assert network_ctx is None

tests/functional/test_contract_container.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ def test_deploy_wrong_number_of_arguments(
4545
contract_container.deploy(sender=not_owner)
4646

4747

48-
def test_deploy_and_publish_local_network(owner, contract_container):
48+
def test_deploy_and_publish_local_network(owner, minimal_proxy_container):
4949
with pytest.raises(ProjectError, match="Can only publish deployments on a live network"):
50-
contract_container.deploy(0, sender=owner, publish=True)
50+
minimal_proxy_container.deploy(sender=owner, publish=True)
5151

5252

5353
def test_deploy_and_publish_live_network_no_explorer(owner, contract_container, dummy_live_network):

tests/functional/test_provider.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ def test_chain_id_when_disconnected(eth_tester_provider):
146146
assert actual == expected
147147

148148

149+
@pytest.mark.skip("Use stable rpc")
149150
def test_chain_id_adhoc_http(networks):
150151
with networks.parse_network_choice("https://www.shibrpc.com") as bor:
151152
assert bor.chain_id == 109

0 commit comments

Comments
 (0)