From db491852cf17a7b1308937fed00bf8a1b9e01d5a Mon Sep 17 00:00:00 2001 From: banteg <4562643+banteg@users.noreply.github.com> Date: Thu, 28 Sep 2023 19:19:19 +0400 Subject: [PATCH] feat: support pydantic v2 [APE-1374] (#1651) --------- Co-authored-by: Juliya Smith --- setup.py | 3 +- src/ape/_pydantic_compat.py | 47 +++++++++++++++++++ src/ape/api/config.py | 2 +- src/ape/api/networks.py | 2 +- src/ape/api/projects.py | 7 +-- src/ape/api/providers.py | 2 +- src/ape/api/query.py | 2 +- src/ape/api/transactions.py | 3 +- src/ape/managers/chain.py | 8 ++-- src/ape/managers/config.py | 3 +- src/ape/managers/project/dependency.py | 2 +- src/ape/managers/project/manager.py | 1 - src/ape/types/__init__.py | 4 +- src/ape/types/coverage.py | 2 +- src/ape/types/trace.py | 2 +- src/ape/utils/abi.py | 6 ++- src/ape_compile/__init__.py | 3 +- src/ape_ethereum/ecosystem.py | 2 +- src/ape_ethereum/transactions.py | 2 +- src/ape_geth/provider.py | 2 +- src/ape_plugins/utils.py | 3 +- src/ape_test/__init__.py | 3 +- .../conversion/test_encode_structs.py | 2 +- tests/functional/test_contract_instance.py | 2 +- 24 files changed, 82 insertions(+), 33 deletions(-) create mode 100644 src/ape/_pydantic_compat.py diff --git a/setup.py b/setup.py index 28bf4c257c..77876ff758 100644 --- a/setup.py +++ b/setup.py @@ -33,6 +33,7 @@ "mdformat-gfm>=0.3.5", # Needed for formatting GitHub-flavored markdown "mdformat-frontmatter>=0.4.1", # Needed for frontmatters-style headers in issue templates "mdformat-pyproject>=0.0.1", # Allows configuring in pyproject.toml + "pydantic<2.0", # Needed for successful type check. TODO: Remove after full v2 support. ], "doc": [ "myst-parser>=1.0.0,<2", # Parse markdown docs @@ -101,7 +102,7 @@ "packaging>=23.0,<24", "pandas>=1.3.0,<2", "pluggy>=1.3,<2", - "pydantic>=1.10.8,<2", + "pydantic>=1.10.8,<3", "PyGithub>=1.59,<2", "pytest>=6.0,<8.0", "python-dateutil>=2.8.2,<3", diff --git a/src/ape/_pydantic_compat.py b/src/ape/_pydantic_compat.py new file mode 100644 index 0000000000..16bfe4b3a8 --- /dev/null +++ b/src/ape/_pydantic_compat.py @@ -0,0 +1,47 @@ +# support both pydantic v1 and v2 + +try: + from pydantic.v1 import ( # type: ignore + BaseModel, + BaseSettings, + Extra, + Field, + FileUrl, + HttpUrl, + NonNegativeInt, + PositiveInt, + ValidationError, + root_validator, + validator, + ) + from pydantic.v1.dataclasses import dataclass # type: ignore +except ImportError: + from pydantic import ( # type: ignore + BaseModel, + BaseSettings, + Extra, + Field, + FileUrl, + HttpUrl, + NonNegativeInt, + PositiveInt, + ValidationError, + root_validator, + validator, + ) + from pydantic.dataclasses import dataclass # type: ignore + +__all__ = ( + "BaseModel", + "BaseSettings", + "dataclass", + "Extra", + "Field", + "FileUrl", + "HttpUrl", + "NonNegativeInt", + "PositiveInt", + "ValidationError", + "root_validator", + "validator", +) diff --git a/src/ape/api/config.py b/src/ape/api/config.py index d683da2aea..b08a6a8086 100644 --- a/src/ape/api/config.py +++ b/src/ape/api/config.py @@ -1,7 +1,7 @@ from enum import Enum from typing import Any, Dict, Optional, TypeVar -from pydantic import BaseModel, BaseSettings +from ape._pydantic_compat import BaseModel, BaseSettings T = TypeVar("T") diff --git a/src/ape/api/networks.py b/src/ape/api/networks.py index 02339dc140..3252a6cd2a 100644 --- a/src/ape/api/networks.py +++ b/src/ape/api/networks.py @@ -11,8 +11,8 @@ from eth_utils import keccak, to_int from ethpm_types import ContractType, HexBytes from ethpm_types.abi import ABIType, ConstructorABI, EventABI, MethodABI -from pydantic import BaseModel +from ape._pydantic_compat import BaseModel from ape.exceptions import ( NetworkError, NetworkMismatchError, diff --git a/src/ape/api/projects.py b/src/ape/api/projects.py index 7ff8c899ae..b386257682 100644 --- a/src/ape/api/projects.py +++ b/src/ape/api/projects.py @@ -6,10 +6,11 @@ from ethpm_types import Checksum, ContractType, PackageManifest, Source from ethpm_types.manifest import PackageName +from ethpm_types.source import Content from ethpm_types.utils import Algorithm, AnyUrl, compute_checksum from packaging.version import InvalidVersion, Version -from pydantic import ValidationError +from ape._pydantic_compat import ValidationError from ape.logging import logger from ape.utils import ( BaseInterfaceModel, @@ -143,7 +144,7 @@ def contracts(self) -> Dict[str, ContractType]: continue contract_name = p.stem - contract_type = ContractType().parse_file(p) + contract_type = ContractType.parse_file(p) if contract_type.name is None: contract_type.name = contract_name @@ -214,7 +215,7 @@ def _create_source_dict( 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, []), ) diff --git a/src/ape/api/providers.py b/src/ape/api/providers.py index 30bc5bcf38..9da5605c48 100644 --- a/src/ape/api/providers.py +++ b/src/ape/api/providers.py @@ -21,12 +21,12 @@ from ethpm_types import HexBytes from evm_trace import CallTreeNode as EvmCallTreeNode from evm_trace import TraceFrame as EvmTraceFrame -from pydantic import Field, root_validator, validator from web3 import Web3 from web3.exceptions import ContractLogicError as Web3ContractLogicError from web3.exceptions import MethodUnavailable, TimeExhausted, TransactionNotFound from web3.types import RPCEndpoint, TxParams +from ape._pydantic_compat import Field, root_validator, validator from ape.api.config import PluginConfig from ape.api.networks import LOCAL_NETWORK_NAME, NetworkAPI from ape.api.query import BlockTransactionQuery diff --git a/src/ape/api/query.py b/src/ape/api/query.py index 19e1a98de5..3067d505bd 100644 --- a/src/ape/api/query.py +++ b/src/ape/api/query.py @@ -2,8 +2,8 @@ from typing import Any, Dict, Iterator, List, Optional, Sequence, Set, Type, Union from ethpm_types.abi import EventABI, MethodABI -from pydantic import BaseModel, NonNegativeInt, PositiveInt, root_validator +from ape._pydantic_compat import BaseModel, NonNegativeInt, PositiveInt, root_validator from ape.api.transactions import ReceiptAPI, TransactionAPI from ape.logging import logger from ape.types import AddressType diff --git a/src/ape/api/transactions.py b/src/ape/api/transactions.py index 1e3ef14074..71b82fe805 100644 --- a/src/ape/api/transactions.py +++ b/src/ape/api/transactions.py @@ -6,10 +6,9 @@ from eth_utils import is_0x_prefixed, is_hex, to_int from ethpm_types import HexBytes from ethpm_types.abi import EventABI, MethodABI -from pydantic import validator -from pydantic.fields import Field from tqdm import tqdm # type: ignore +from ape._pydantic_compat import Field, validator from ape.api.explorers import ExplorerAPI from ape.exceptions import ( NetworkError, diff --git a/src/ape/managers/chain.py b/src/ape/managers/chain.py index 537aaafc83..f46dea2726 100644 --- a/src/ape/managers/chain.py +++ b/src/ape/managers/chain.py @@ -159,14 +159,16 @@ def query( ) query = BlockQuery( - columns=columns, + columns=list(columns), start_block=start_block, stop_block=stop_block, step=step, ) blocks = self.query_manager.query(query, engine_to_use=engine_to_use) - columns = validate_and_expand_columns(columns, self.head.__class__) # type: ignore + columns: List[str] = validate_and_expand_columns( # type: ignore + columns, self.head.__class__ + ) blocks = map(partial(extract_fields, columns=columns), blocks) return pd.DataFrame(columns=columns, data=blocks) @@ -486,7 +488,7 @@ def query( ) query = AccountTransactionQuery( - columns=columns, + columns=list(columns), account=self.address, start_nonce=start_nonce, stop_nonce=stop_nonce, diff --git a/src/ape/managers/config.py b/src/ape/managers/config.py index 8512447474..f5883eb59a 100644 --- a/src/ape/managers/config.py +++ b/src/ape/managers/config.py @@ -3,8 +3,7 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Union -from pydantic import root_validator - +from ape._pydantic_compat import root_validator from ape.api import ConfigDict, DependencyAPI, PluginConfig from ape.exceptions import ConfigError, NetworkError from ape.logging import logger diff --git a/src/ape/managers/project/dependency.py b/src/ape/managers/project/dependency.py index 803064035d..d4bdf4ff29 100644 --- a/src/ape/managers/project/dependency.py +++ b/src/ape/managers/project/dependency.py @@ -7,9 +7,9 @@ from ethpm_types import PackageManifest from ethpm_types.utils import AnyUrl -from pydantic import FileUrl, HttpUrl, root_validator from semantic_version import NpmSpec, Version # type: ignore +from ape._pydantic_compat import FileUrl, HttpUrl, root_validator from ape.api import DependencyAPI from ape.exceptions import ProjectError, UnknownVersionError from ape.logging import logger diff --git a/src/ape/managers/project/manager.py b/src/ape/managers/project/manager.py index cb77f5b9f5..69dc1ec921 100644 --- a/src/ape/managers/project/manager.py +++ b/src/ape/managers/project/manager.py @@ -702,7 +702,6 @@ def track_deployment(self, contract: ContractInstance): if network == LOCAL_NETWORK_NAME or network.endswith("-fork"): raise ProjectError("Can only publish deployments on a live network.") - contract_name = contract.contract_type.name if not (contract_name := contract.contract_type.name): raise ProjectError("Contract name required when publishing.") diff --git a/src/ape/types/__init__.py b/src/ape/types/__init__.py index e083a3ad8a..8f110d2eee 100644 --- a/src/ape/types/__init__.py +++ b/src/ape/types/__init__.py @@ -29,9 +29,9 @@ ) from ethpm_types.abi import EventABI from ethpm_types.source import Closure -from pydantic import BaseModel, root_validator, validator from web3.types import FilterParams +from ape._pydantic_compat import BaseModel, root_validator, validator from ape.types.address import AddressType, RawAddress from ape.types.coverage import ( ContractCoverage, @@ -98,7 +98,7 @@ def validate_multiplier(cls, value): """ -TopicFilter = Sequence[Union[Optional[HexStr], List[Optional[HexStr]]]] +TopicFilter = Sequence[Union[Optional[HexStr], Sequence[Optional[HexStr]]]] @dataclass diff --git a/src/ape/types/coverage.py b/src/ape/types/coverage.py index dbb45b8f8e..462b6ca1b1 100644 --- a/src/ape/types/coverage.py +++ b/src/ape/types/coverage.py @@ -9,8 +9,8 @@ import requests from ethpm_types import BaseModel from ethpm_types.source import ContractSource, SourceLocation -from pydantic import NonNegativeInt, validator +from ape._pydantic_compat import NonNegativeInt, validator from ape.logging import logger from ape.utils.misc import get_current_timestamp_ms from ape.version import version as ape_version diff --git a/src/ape/types/trace.py b/src/ape/types/trace.py index b1c4a5b63b..ccc613f152 100644 --- a/src/ape/types/trace.py +++ b/src/ape/types/trace.py @@ -6,10 +6,10 @@ from ethpm_types.ast import SourceLocation from ethpm_types.source import Closure, Content, Function, SourceStatement, Statement from evm_trace.gas import merge_reports -from pydantic import Field from rich.table import Table from rich.tree import Tree +from ape._pydantic_compat import Field from ape.types.address import AddressType from ape.utils.basemodel import BaseInterfaceModel from ape.utils.misc import is_evm_precompile, is_zero_hex diff --git a/src/ape/utils/abi.py b/src/ape/utils/abi.py index 25d6a97d60..0a9b21ebde 100644 --- a/src/ape/utils/abi.py +++ b/src/ape/utils/abi.py @@ -124,7 +124,11 @@ def decode_output(self, values: Union[List, Tuple]) -> Any: return self._decode(self.abi.outputs, values) if isinstance(self.abi, MethodABI) else None - def _decode(self, _types: Sequence[ABIType], values: Union[Sequence, Dict[str, Any]]): + def _decode( + self, + _types: Union[Sequence[ABIType]], + values: Union[Sequence, Dict[str, Any]], + ): if is_struct(_types): return self._create_struct(_types[0], values) diff --git a/src/ape_compile/__init__.py b/src/ape_compile/__init__.py index 57406c3379..a829ae2bfc 100644 --- a/src/ape_compile/__init__.py +++ b/src/ape_compile/__init__.py @@ -1,8 +1,7 @@ from typing import Any, List, Optional -from pydantic import Field, validator - from ape import plugins +from ape._pydantic_compat import Field, validator from ape.api import PluginConfig from ape.logging import logger diff --git a/src/ape_ethereum/ecosystem.py b/src/ape_ethereum/ecosystem.py index 7cef8c6796..1b027ee88e 100644 --- a/src/ape_ethereum/ecosystem.py +++ b/src/ape_ethereum/ecosystem.py @@ -16,8 +16,8 @@ ) from ethpm_types import ContractType, HexBytes from ethpm_types.abi import ABIType, ConstructorABI, EventABI, MethodABI -from pydantic import Field, validator +from ape._pydantic_compat import Field, validator from ape.api import BlockAPI, EcosystemAPI, PluginConfig, ReceiptAPI, TransactionAPI from ape.api.networks import LOCAL_NETWORK_NAME from ape.contracts.base import ContractCall diff --git a/src/ape_ethereum/transactions.py b/src/ape_ethereum/transactions.py index 65d8f8bbcc..3d4c3e59e2 100644 --- a/src/ape_ethereum/transactions.py +++ b/src/ape_ethereum/transactions.py @@ -11,8 +11,8 @@ from eth_utils import decode_hex, encode_hex, keccak, to_hex, to_int from ethpm_types import ContractType, HexBytes from ethpm_types.abi import EventABI, MethodABI -from pydantic import BaseModel, Field, root_validator, validator +from ape._pydantic_compat import BaseModel, Field, root_validator, validator from ape.api import ReceiptAPI, TransactionAPI from ape.contracts import ContractEvent from ape.exceptions import OutOfGasError, SignatureError, TransactionError diff --git a/src/ape_geth/provider.py b/src/ape_geth/provider.py index 65d0e9ebc5..2d414c130f 100644 --- a/src/ape_geth/provider.py +++ b/src/ape_geth/provider.py @@ -25,7 +25,6 @@ from geth.chain import initialize_chain # type: ignore from geth.process import BaseGethProcess # type: ignore from geth.wrapper import construct_test_chain_kwargs # type: ignore -from pydantic import Extra from requests.exceptions import ConnectionError from web3 import HTTPProvider, Web3 from web3.exceptions import ExtraDataLengthError @@ -36,6 +35,7 @@ from web3.providers.auto import load_provider_from_environment from yarl import URL +from ape._pydantic_compat import Extra from ape.api import ( PluginConfig, SubprocessProvider, diff --git a/src/ape_plugins/utils.py b/src/ape_plugins/utils.py index a27965a158..8bcebb71a2 100644 --- a/src/ape_plugins/utils.py +++ b/src/ape_plugins/utils.py @@ -2,9 +2,8 @@ import sys from typing import List, Optional, Tuple -from pydantic import root_validator - from ape.__modules__ import __modules__ +from ape._pydantic_compat import root_validator from ape.logging import ApeLogger from ape.plugins import clean_plugin_name from ape.utils import BaseInterfaceModel, cached_property, get_package_version, github_client diff --git a/src/ape_test/__init__.py b/src/ape_test/__init__.py index 6a4507aecc..53bf1de479 100644 --- a/src/ape_test/__init__.py +++ b/src/ape_test/__init__.py @@ -1,8 +1,7 @@ from typing import Dict, List, NewType, Optional, Union -from pydantic import NonNegativeInt - from ape import plugins +from ape._pydantic_compat import NonNegativeInt from ape.api import PluginConfig from ape.api.networks import LOCAL_NETWORK_NAME from ape.utils import DEFAULT_HD_PATH, DEFAULT_NUMBER_OF_TEST_ACCOUNTS, DEFAULT_TEST_MNEMONIC diff --git a/tests/functional/conversion/test_encode_structs.py b/tests/functional/conversion/test_encode_structs.py index d2709ccaac..cd0373a9fa 100644 --- a/tests/functional/conversion/test_encode_structs.py +++ b/tests/functional/conversion/test_encode_structs.py @@ -3,8 +3,8 @@ import pytest from ethpm_types import HexBytes from ethpm_types.abi import MethodABI -from pydantic import BaseModel +from ape._pydantic_compat import BaseModel from ape.types import AddressType ABI = MethodABI.parse_obj( diff --git a/tests/functional/test_contract_instance.py b/tests/functional/test_contract_instance.py index 2514722591..2ee7180943 100644 --- a/tests/functional/test_contract_instance.py +++ b/tests/functional/test_contract_instance.py @@ -4,9 +4,9 @@ import pytest from eth_utils import is_checksum_address, to_hex from ethpm_types import ContractType, HexBytes -from pydantic import BaseModel from ape import Contract +from ape._pydantic_compat import BaseModel from ape.api import TransactionAPI from ape.contracts import ContractInstance from ape.exceptions import (