Skip to content

Commit

Permalink
fix: issues around parsing network choice with custom networks (#1792)
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey authored Dec 20, 2023
1 parent cf8c0eb commit 0cd52d2
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 14 deletions.
25 changes: 17 additions & 8 deletions src/ape/api/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,15 @@ def is_connected(self) -> bool:
``True`` if currently connected to the provider. ``False`` otherwise.
"""

@property
def connection_str(self) -> str:
"""
The str representing how to connect
to the node, such as an HTTP URL
or an IPC path.
"""
return ""

@abstractmethod
def connect(self):
"""
Expand Down Expand Up @@ -235,6 +244,14 @@ def network_choice(self) -> str:
"""
The connected network choice string.
"""
if self.network.name == "custom" and self.connection_str:
# `custom` is not a real network and is same
# as using raw connection str
return self.connection_str

elif self.network.name == "custom":
raise ProviderError("Custom network provider missing `connection_str`.")

return f"{self.network.choice}:{self.name}"

def get_storage_at(self, *args, **kwargs) -> HexBytes:
Expand Down Expand Up @@ -821,14 +838,6 @@ class UpstreamProvider(ProviderAPI):
A provider that can also be set as another provider's upstream.
"""

@property
@abstractmethod
def connection_str(self) -> str:
"""
The str used by downstream providers to connect to this one.
For example, the URL for HTTP-based providers.
"""


class SubprocessProvider(ProviderAPI):
"""
Expand Down
20 changes: 14 additions & 6 deletions src/ape/cli/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from typing import Any, Callable, Iterator, List, Optional, Sequence, Type, Union

import click
from click import BadParameter, Choice, Context, Parameter
from click import BadOptionUsage, BadParameter, Choice, Context, Parameter

from ape import accounts, networks
from ape.api.accounts import AccountAPI
Expand Down Expand Up @@ -355,7 +355,19 @@ def convert(self, value: Any, param: Optional[Parameter], ctx: Optional[Context]
if not value or value in ("None", "none"):
choice = None

elif not self.is_custom_value(value):
elif self.is_custom_value(value):
# By-pass choice constraints when using custom network.
choice = value

elif "custom" in value:
raise BadOptionUsage(
"--network",
"Custom network options must include connection str as well, "
"such as the URI. Check `provider.network_choice` (if possible).",
)

else:
# Regular conditions.
try:
# Validate result.
choice = super().convert(value, param, ctx)
Expand All @@ -366,10 +378,6 @@ def convert(self, value: Any, param: Optional[Parameter], ctx: Optional[Context]
"Invalid network choice. Use `ape networks list` to see options."
) from err

else:
# By-pass choice constraints when using custom network.
choice = value

if choice is not None and issubclass(self.base_type, ProviderAPI):
# Return the provider.
choice = networks.get_provider_from_choice(network_choice=value)
Expand Down
19 changes: 19 additions & 0 deletions tests/functional/geth/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,22 @@ def test_send_transaction_when_no_error_and_receipt_fails(

finally:
geth_provider._web3 = start_web3


@geth_process_test
def test_network_choice(geth_provider):
actual = geth_provider.network_choice
expected = "ethereum:local:geth"
assert actual == expected


@geth_process_test
def test_network_choice_when_custom(geth_provider):
name = geth_provider.network.name
geth_provider.network.name = "custom"
try:
actual = geth_provider.network_choice
finally:
geth_provider.network.name = name

assert actual == "http://127.0.0.1:5550"
36 changes: 36 additions & 0 deletions tests/functional/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import click
import pytest
from click import BadOptionUsage

from ape.cli import (
AccountAliasPromptChoice,
ConnectedProviderCommand,
NetworkBoundCommand,
NetworkChoice,
PromptChoice,
account_option,
contract_file_paths_argument,
Expand Down Expand Up @@ -604,3 +606,37 @@ def test_parse_network_when_interactive_and_class_param(mocker, eth_tester_provi
network_ctx = parse_network(ctx)
assert network_ctx.provider.name == "test"
assert network_ctx._disconnect_on_exit is False # Because of interactive: True


def test_network_choice():
network_choice = NetworkChoice()
actual = network_choice.convert("ethereum:local:test", None, None)
assert actual.name == "test"
assert actual.network.name == "local"


@pytest.mark.parametrize("prefix", ("", "ethereum:custom:"))
def test_network_choice_when_custom_network(prefix):
network_choice = NetworkChoice()
uri = "https://example.com"
actual = network_choice.convert(f"{prefix}{uri}", None, None)
assert actual.uri == uri
assert actual.network.name == "custom"


def test_network_choice_when_custom_local_network():
network_choice = NetworkChoice()
uri = "https://example.com"
actual = network_choice.convert(f"ethereum:local:{uri}", None, None)
assert actual.uri == uri
assert actual.network.name == "local"


def test_network_choice_when_custom_and_missing_provider():
network_choice = NetworkChoice()
expected = (
r"Custom network options must include connection str as well, such as the URI\. "
r"Check `provider.network_choice` \(if possible\).*"
)
with pytest.raises(BadOptionUsage, match=expected):
network_choice.convert("ethereum:custom", None, None)
21 changes: 21 additions & 0 deletions tests/functional/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from ape.exceptions import (
BlockNotFoundError,
ContractLogicError,
ProviderError,
TransactionError,
TransactionNotFoundError,
)
Expand Down Expand Up @@ -321,3 +322,23 @@ def test_send_transaction_when_no_error_and_receipt_fails(

finally:
eth_tester_provider._web3 = start_web3


def test_network_choice(eth_tester_provider):
actual = eth_tester_provider.network_choice
expected = "ethereum:local:test"
assert actual == expected


def test_network_choice_when_custom(eth_tester_provider):
name = eth_tester_provider.network.name
eth_tester_provider.network.name = "custom"
try:
# NOTE: Raises this error because EthTester does not support custom
# connections.
with pytest.raises(
ProviderError, match=".*Custom network provider missing `connection_str`.*"
):
_ = eth_tester_provider.network_choice
finally:
eth_tester_provider.network.name = name

0 comments on commit 0cd52d2

Please sign in to comment.