Skip to content

Commit 7923685

Browse files
committed
feat: custom converter types
1 parent 2a43d65 commit 7923685

File tree

4 files changed

+61
-3
lines changed

4 files changed

+61
-3
lines changed

src/ape/api/convert.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from abc import abstractmethod
2-
from typing import Any, Generic, TypeVar
2+
from typing import Any, Generic, Optional, TypeVar
33

4+
from ape.exceptions import APINotImplementedError
45
from ape.utils.basemodel import BaseInterfaceModel
56

67
ConvertedType = TypeVar("ConvertedType")
@@ -56,3 +57,18 @@ def name(self) -> str:
5657
class_name = self.__class__.__name__
5758
name = class_name.replace("Converter", "").replace("Conversions", "")
5859
return name.lower()
60+
61+
62+
class ConvertibleAPI:
63+
"""
64+
Use this base-class mixin if you want your custom class to be convertible to a more basic type
65+
without having to register a converter plugin for it.
66+
"""
67+
68+
def is_convertible(self, to_type: type) -> bool:
69+
return False
70+
71+
def convert_to(self, to_type: type) -> Optional[Any]:
72+
raise APINotImplementedError(
73+
f"Unable to convert '{self.__class__.__name__}' to type '{to_type.__class__.__name__}'."
74+
)

src/ape/managers/converters.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from eth_utils import is_0x_prefixed, is_checksum_address, is_hex, is_hex_address, to_int
1313

1414
from ape.api.address import BaseAddress
15-
from ape.api.convert import ConverterAPI
15+
from ape.api.convert import ConverterAPI, ConvertibleAPI
1616
from ape.api.transactions import TransactionAPI
1717
from ape.exceptions import ConversionError
1818
from ape.logging import logger
@@ -365,6 +365,12 @@ def convert(self, value: Any, to_type: Union[type, tuple, list]) -> Any:
365365
# NOTE: Always process lists and tuples
366366
return value
367367

368+
if isinstance(value, ConvertibleAPI) is value.is_convertible(to_type):
369+
return value.convert_to(to_type)
370+
371+
return self._convert_using_converter_apis(value, to_type)
372+
373+
def _convert_using_converter_apis(self, value: Any, to_type: type) -> Any:
368374
for converter in self._converters[to_type]:
369375
try:
370376
is_convertible = converter.is_convertible(value)

src/ape/utils/basemodel.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
from pydantic import BaseModel as RootBaseModel
1616
from pydantic import ConfigDict
1717

18-
from ape.exceptions import ApeAttributeError, ApeIndexError, ProviderNotConnectedError
18+
from ape.exceptions import (
19+
ApeAttributeError,
20+
ApeIndexError,
21+
ProviderNotConnectedError,
22+
)
1923
from ape.logging import logger
2024
from ape.utils.misc import log_instead_of_fail, raises_not_implemented
2125

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from typing import Any
2+
3+
import pytest
4+
5+
from ape.api.convert import ConvertibleAPI
6+
from ape.types.address import AddressType
7+
8+
9+
@pytest.fixture(scope="module")
10+
def custom_type(accounts):
11+
class MyAccountWrapper(ConvertibleAPI):
12+
def __init__(self, acct):
13+
self.acct = acct
14+
15+
def is_convertible(self, to_type: type) -> bool:
16+
return to_type is AddressType
17+
18+
def convert_to(self, to_type: type) -> Any:
19+
if to_type is AddressType:
20+
return self.acct.address
21+
22+
raise NotImplementedError()
23+
24+
return MyAccountWrapper(accounts[0])
25+
26+
27+
def test_convert(custom_type, conversion_manager, accounts):
28+
"""
29+
You can use the regular conversion manager to convert the custom type.
30+
"""
31+
actual = conversion_manager.convert(custom_type, AddressType)
32+
assert actual == accounts[0].address

0 commit comments

Comments
 (0)