Skip to content

Commit

Permalink
feat: pydantic v2
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey committed Sep 25, 2023
1 parent d3ba065 commit 363c233
Show file tree
Hide file tree
Showing 56 changed files with 555 additions and 560 deletions.
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ exclude =
docs
build
.eggs
tests/integration/cli/projects
per-file-ignores =
# Need signal handler before imports
src/ape/__init__.py: E402
Expand Down
7 changes: 4 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@
"packaging>=23.0,<24",
"pandas>=1.3.0,<2",
"pluggy>=1.3,<2",
"pydantic>=1.10.8,<2",
"pydantic>=2.3,<3",
"pydantic-settings>=2.0.3,<3",
"PyGithub>=1.59,<2",
"pytest>=6.0,<8.0",
"python-dateutil>=2.8.2,<3",
Expand All @@ -123,8 +124,8 @@
"web3[tester]>=6.7.0,<7",
# ** Dependencies maintained by ApeWorX **
"eip712>=0.2.1,<0.3",
"ethpm-types>=0.5.6,<0.6",
"evm-trace>=0.1.0a23",
"ethpm-types>=0.6.0,<0.7",
"evm-trace>=0.1.0a26",
],
entry_points={
"console_scripts": ["ape=ape._cli:cli"],
Expand Down
2 changes: 1 addition & 1 deletion src/ape/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def display_config(ctx, param, value):
from ape import project

click.echo("# Current configuration")
click.echo(yaml.dump(project.config_manager.dict()))
click.echo(yaml.dump(project.config_manager.model_dump(mode="json")))

ctx.exit() # NOTE: Must exit to bypass running ApeCLI

Expand Down
6 changes: 1 addition & 5 deletions src/ape/api/address.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from ape.exceptions import ConversionError
from ape.types import AddressType, ContractCode
from ape.utils import BaseInterface, abstractmethod, cached_property
from ape.utils.abi import _convert_kwargs

if TYPE_CHECKING:
from ape.api.transactions import ReceiptAPI, TransactionAPI
Expand Down Expand Up @@ -167,10 +166,7 @@ def history(self) -> "AccountHistory":
return self.chain_manager.history[self.address]

def as_transaction(self, **kwargs) -> "TransactionAPI":
converted_kwargs = _convert_kwargs(kwargs, self.conversion_manager.convert)
return self.provider.network.ecosystem.create_transaction(
receiver=self.address, **converted_kwargs
)
return self.provider.network.ecosystem.create_transaction(receiver=self.address, **kwargs)

def estimate_gas_cost(self, **kwargs) -> int:
txn = self.as_transaction(**kwargs)
Expand Down
17 changes: 7 additions & 10 deletions src/ape/api/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from enum import Enum
from typing import Any, Dict, Optional, TypeVar

from pydantic import BaseModel, BaseSettings
from pydantic import ConfigDict
from pydantic_settings import BaseSettings

T = TypeVar("T")

Expand All @@ -14,10 +15,6 @@ class ConfigEnum(str, Enum):
"""


class ConfigDict(BaseModel):
__root__: dict = {}


class PluginConfig(BaseSettings):
"""
A base plugin configuration class. Each plugin that includes
Expand All @@ -26,7 +23,7 @@ class PluginConfig(BaseSettings):

@classmethod
def from_overrides(cls, overrides: Dict) -> "PluginConfig":
default_values = cls().dict()
default_values = cls().model_dump()

def update(root: Dict, value_map: Dict):
for key, val in value_map.items():
Expand All @@ -42,7 +39,9 @@ def update(root: Dict, value_map: Dict):
def __getattr__(self, attr_name: str) -> Any:
# allow hyphens in plugin config files
attr_name = attr_name.replace("-", "_")
return super().__getattribute__(attr_name)

private = self.__pydantic_private__ or {}
return private[attr_name] if attr_name in private else super().__getattribute__(attr_name)

def __getitem__(self, item: str) -> Any:
return self.__dict__[item]
Expand All @@ -54,9 +53,7 @@ def get(self, key: str, default: Optional[T] = None) -> T:
return self.__dict__.get(key, default)


class GenericConfig(PluginConfig):
class GenericConfig(ConfigDict):
"""
The default class used when no specialized class is used.
"""

__root__: dict = {}
22 changes: 11 additions & 11 deletions src/ape/api/networks.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@
serializable_unsigned_transaction_from_dict,
)
from eth_utils import keccak, to_int
from ethpm_types import ContractType, HexBytes
from ethpm_types import BaseModel, ContractType, HexBytes
from ethpm_types.abi import ABIType, ConstructorABI, EventABI, MethodABI
from pydantic import BaseModel

from ape.exceptions import (
NetworkError,
Expand Down Expand Up @@ -115,7 +114,7 @@ def encode_contract_blueprint( # type: ignore[empty-body]
or Starknet's ``Declare`` transaction type.
Args:
contract (``ContractType``): The type of contract to create a blueprint for.
contract_type (``ContractType``): The type of contract to create a blueprint for.
This is the type of contract that will get created by factory contracts.
*args: Calldata, if applicable.
**kwargs: Transaction specifications, such as ``value``.
Expand All @@ -124,21 +123,18 @@ def encode_contract_blueprint( # type: ignore[empty-body]
:class:`~ape.ape.transactions.TransactionAPI`
"""

def serialize_transaction(self, transaction: "TransactionAPI") -> bytes:
def serialize_transaction(self) -> bytes:
"""
Serialize a transaction to bytes.
Args:
transaction (:class:`~ape.api.transactions.TransactionAPI`): The transaction to encode.
Returns:
bytes
"""

if not self.signature:
raise SignatureError("The transaction is not signed.")

txn_data = self.dict(exclude={"sender"})
txn_data = self.model_dump(exclude={"sender"})

unsigned_txn = serializable_unsigned_transaction_from_dict(txn_data)
signature = (
Expand Down Expand Up @@ -256,6 +252,10 @@ def __getattr__(self, network_name: str) -> "NetworkAPI":
Returns:
:class:`~ape.api.networks.NetworkAPI`
"""
private_attrs = self.__pydantic_private__ or {}
if network_name in private_attrs:
return private_attrs[network_name]

try:
return self.get_network(network_name.replace("_", "-"))
except NetworkNotFoundError:
Expand Down Expand Up @@ -383,7 +383,7 @@ def create_transaction(self, **kwargs) -> "TransactionAPI":
"""

@abstractmethod
def decode_calldata(self, abi: Union[ConstructorABI, MethodABI], calldata: bytes) -> Dict:
def decode_calldata(self, abi: Union[ConstructorABI, MethodABI], calldata: HexBytes) -> Dict:
"""
Decode method calldata.
Expand Down Expand Up @@ -411,7 +411,7 @@ def encode_calldata(self, abi: Union[ConstructorABI, MethodABI], *args: Any) ->
"""

@abstractmethod
def decode_returndata(self, abi: MethodABI, raw_data: bytes) -> Any:
def decode_returndata(self, abi: MethodABI, raw_data: HexBytes) -> Any:
"""
Get the result of a contract call.
Expand Down Expand Up @@ -686,7 +686,7 @@ def create_adhoc_network(cls) -> "NetworkAPI":
return cls(
name="adhoc",
ecosystem=ethereum,
data_folder=data_folder,
data_folder=Path(data_folder),
request_header=request_header,
_default_provider="geth",
)
Expand Down
22 changes: 14 additions & 8 deletions src/ape/api/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@

from ethpm_types import Checksum, ContractType, PackageManifest, Source
from ethpm_types.manifest import PackageName
from ethpm_types.utils import AnyUrl, compute_checksum
from ethpm_types.source import Content
from ethpm_types.utils import Algorithm, compute_checksum
from packaging.version import InvalidVersion, Version
from pydantic import ValidationError
from pydantic import AnyUrl, ValidationError

from ape.exceptions import ApeAttributeError
from ape.logging import logger
Expand Down Expand Up @@ -110,7 +111,7 @@ def cached_manifest(self) -> Optional[PackageManifest]:
continue

path = self._cache_folder / f"{contract_type.name}.json"
path.write_text(contract_type.json())
path.write_text(contract_type.model_dump_json())

# Rely on individual cache files.
self._contracts = manifest.contract_types
Expand Down Expand Up @@ -143,7 +144,7 @@ def contracts(self) -> Dict[str, ContractType]:
continue

contract_name = p.stem
contract_type = ContractType().parse_file(p)
contract_type = ContractType.model_validate_json(p.read_text())
if contract_type.name is None:
contract_type.name = contract_name

Expand Down Expand Up @@ -210,11 +211,11 @@ def _create_source_dict(

source_dict[key] = Source(
checksum=Checksum(
algorithm="md5",
algorithm=Algorithm.MD5,
hash=compute_checksum(source_path.read_bytes()),
),
urls=[],
content=text,
content=Content(root={i + 1: x for i, x in enumerate(text.splitlines())}),
imports=source_imports.get(key, []),
references=source_references.get(key, []),
)
Expand Down Expand Up @@ -333,6 +334,10 @@ def __getitem__(self, contract_name: str) -> "ContractContainer":
return container

def __getattr__(self, contract_name: str) -> "ContractContainer":
private_attributes = self.__pydantic_private__ or {}
if attr := private_attributes.get(contract_name):
return attr

try:
return self.__getattribute__(contract_name)
except AttributeError:
Expand Down Expand Up @@ -449,16 +454,17 @@ def _get_sources(self, project: ProjectAPI) -> List[Path]:
def _write_manifest_to_cache(self, manifest: PackageManifest):
self._target_manifest_cache_file.unlink(missing_ok=True)
self._target_manifest_cache_file.parent.mkdir(exist_ok=True, parents=True)
self._target_manifest_cache_file.write_text(manifest.json())
self._target_manifest_cache_file.write_text(manifest.model_dump_json())
self._cached_manifest = manifest


def _load_manifest_from_file(file_path: Path) -> Optional[PackageManifest]:
if not file_path.is_file():
return None

text = file_path.read_text()
try:
return PackageManifest.parse_file(file_path)
return PackageManifest.model_validate_json(text)
except ValidationError as err:
logger.warning(f"Existing manifest file '{file_path}' corrupted. Re-building.")
logger.debug(str(err))
Expand Down
Loading

0 comments on commit 363c233

Please sign in to comment.