Skip to content

Commit

Permalink
Merge branch 'main' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey authored Dec 10, 2024
2 parents 74aec24 + a492a6b commit 0db4f4a
Show file tree
Hide file tree
Showing 13 changed files with 168 additions and 111 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: [3.9, "3.10", "3.11", "3.12"]
python-version: [3.9, "3.10", "3.11", "3.12", "3.13"]

env:
GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
9 changes: 5 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v5.0.0
hooks:
- id: check-yaml

Expand All @@ -10,7 +10,7 @@ repos:
- id: isort

- repo: https://github.com/psf/black
rev: 24.8.0
rev: 24.10.0
hooks:
- id: black
name: black
Expand All @@ -19,15 +19,16 @@ repos:
rev: 7.1.1
hooks:
- id: flake8
additional_dependencies: [flake8-breakpoint, flake8-print, flake8-pydantic, flake8-type-checking]

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.1
rev: v1.13.0
hooks:
- id: mypy
additional_dependencies: [types-setuptools, pydantic]

- repo: https://github.com/executablebooks/mdformat
rev: 0.7.17
rev: 0.7.19
hooks:
- id: mdformat
additional_dependencies: [mdformat-gfm, mdformat-frontmatter, mdformat-pyproject]
Expand Down
39 changes: 3 additions & 36 deletions eth_pydantic_types/__init__.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,4 @@
from .address import Address, AddressType
from .bip122 import Bip122Uri
from .hash import (
HashBytes4,
HashBytes8,
HashBytes16,
HashBytes20,
HashBytes32,
HashBytes64,
HashStr4,
HashStr8,
HashStr16,
HashStr20,
HashStr32,
HashStr64,
)
from .hex import HexBytes, HexStr
def __getattr__(name: str):
import eth_pydantic_types._main as module

__all__ = [
"Address",
"AddressType",
"Bip122Uri",
"HashBytes4",
"HashBytes8",
"HashBytes16",
"HashBytes20",
"HashBytes32",
"HashBytes64",
"HashStr4",
"HashStr8",
"HashStr16",
"HashStr20",
"HashStr32",
"HashStr64",
"HexBytes",
"HexStr",
]
return getattr(module, name)
17 changes: 11 additions & 6 deletions eth_pydantic_types/_error.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
from collections.abc import Callable
from typing import Any
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
from pydantic_core._pydantic_core import PydanticCustomError

from pydantic_core import PydanticCustomError

# NOTE: We use the factory approach because PydanticCustomError is a final class.
# That is also why this module is internal.


def CustomError(fn: Callable, invalid_tag: str, **kwargs) -> PydanticCustomError:
def CustomError(fn: Callable, invalid_tag: str, **kwargs) -> "PydanticCustomError":
# perf: keep module loading super fast by localizing this import.
from pydantic_core._pydantic_core import PydanticCustomError

return PydanticCustomError(fn.__name__, f"Invalid {invalid_tag}", kwargs)


def HexValueError(value: Any) -> PydanticCustomError:
def HexValueError(value: Any) -> "PydanticCustomError":
return CustomError(HexValueError, "hex value", value=value)


def SizeError(size: Any, value: Any) -> PydanticCustomError:
def SizeError(size: Any, value: Any) -> "PydanticCustomError":
return CustomError(SizeError, "size of value", size=size, value=value)


def Bip122UriFormatError(value: str) -> PydanticCustomError:
def Bip122UriFormatError(value: str) -> "PydanticCustomError":
return CustomError(
Bip122UriFormatError,
"BIP-122 URI format",
Expand Down
37 changes: 37 additions & 0 deletions eth_pydantic_types/_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from .address import Address, AddressType
from .bip122 import Bip122Uri
from .hash import (
HashBytes4,
HashBytes8,
HashBytes16,
HashBytes20,
HashBytes32,
HashBytes64,
HashStr4,
HashStr8,
HashStr16,
HashStr20,
HashStr32,
HashStr64,
)
from .hex import HexBytes, HexStr

__all__ = [
"Address",
"AddressType",
"Bip122Uri",
"HashBytes4",
"HashBytes8",
"HashBytes16",
"HashBytes20",
"HashBytes32",
"HashBytes64",
"HashStr4",
"HashStr8",
"HashStr16",
"HashStr20",
"HashStr32",
"HashStr64",
"HexBytes",
"HexStr",
]
49 changes: 39 additions & 10 deletions eth_pydantic_types/address.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from typing import Annotated, Any, ClassVar, Optional
from functools import cached_property
from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Optional

from cchecksum import to_checksum_address
from eth_typing import ChecksumAddress
from pydantic_core.core_schema import ValidationInfo, str_schema

from eth_pydantic_types.hash import HashStr20

if TYPE_CHECKING:
from eth_typing import ChecksumAddress

ADDRESS_PATTERN = "^0x[a-fA-F0-9]{40}$"


Expand Down Expand Up @@ -34,14 +36,41 @@ def __eth_pydantic_validate__(cls, value: Any, info: Optional[ValidationInfo] =

@classmethod
def to_checksum_address(cls, value: str) -> ChecksumAddress:
# perf: localized import for module loading performance reasons.
from cchecksum import to_checksum_address

return to_checksum_address(value)


"""
A type that can be used in place of ``eth_typing.ChecksumAddress``.
class _AddressTypeFactory:
@cached_property
def address_type(self):
from eth_typing import ChecksumAddress

# Lazy define for performance reasons.
AddressType = Annotated[ChecksumAddress, Address]
AddressType.__doc__ = """
A type that can be used in place of ``eth_typing.ChecksumAddress``.
**NOTE**: We are unable to subclass ``eth_typing.ChecksumAddress``
in :class:`~eth_pydantic_types.address.Address` because it is
a NewType; that is why we offer this annotated approach.
"""
return AddressType


_factory = _AddressTypeFactory()


def __getattr__(name: str):
if name == "Address":
return Address

elif name == "AddressType":
return _factory.address_type


**NOTE**: We are unable to subclass ``eth_typing.ChecksumAddress``
in :class:`~eth_pydantic_types.address.Address` because it is
a NewType; that is why we offer this annotated approach.
"""
AddressType = Annotated[ChecksumAddress, Address]
__all__ = [
"AddressType",
"Address",
]
8 changes: 5 additions & 3 deletions eth_pydantic_types/bip122.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from enum import Enum
from functools import cached_property
from typing import Any, Optional
from typing import TYPE_CHECKING, Any, Optional

from pydantic_core import CoreSchema
from pydantic_core.core_schema import (
ValidationInfo,
str_schema,
Expand All @@ -12,6 +11,9 @@
from eth_pydantic_types._error import Bip122UriFormatError
from eth_pydantic_types.hex import validate_hex_str

if TYPE_CHECKING:
from pydantic_core import CoreSchema


class Bip122UriType(Enum):
TX = "tx"
Expand All @@ -35,7 +37,7 @@ def __get_pydantic_json_schema__(cls, core_schema, handler):
return json_schema

@classmethod
def __get_pydantic_core_schema__(cls, value, handler=None) -> CoreSchema:
def __get_pydantic_core_schema__(cls, value, handler=None) -> "CoreSchema":
return with_info_before_validator_function(
value.__eth_pydantic_validate__,
str_schema(),
Expand Down
64 changes: 40 additions & 24 deletions eth_pydantic_types/hash.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
from typing import Any, ClassVar, Optional
from typing import TYPE_CHECKING, Any, ClassVar, Optional

from pydantic_core.core_schema import (
CoreSchema,
ValidationInfo,
bytes_schema,
str_schema,
with_info_before_validator_function,
)
from pydantic_core.core_schema import bytes_schema, str_schema, with_info_before_validator_function

from eth_pydantic_types.hex import BaseHexStr, HexBytes
from eth_pydantic_types.serializers import hex_serializer
from eth_pydantic_types.validators import validate_bytes_size, validate_str_size

if TYPE_CHECKING:
from pydantic_core.core_schema import CoreSchema, ValidationInfo


def _get_hash_pattern(str_size: int) -> str:
return f"^0x[a-fA-F0-9]{{{str_size}}}$"
Expand All @@ -38,7 +35,7 @@ class HashBytes(HexBytes):
schema_examples: ClassVar[tuple[str, ...]] = _get_hash_examples(1)

@classmethod
def __get_pydantic_core_schema__(cls, value, handler=None) -> CoreSchema:
def __get_pydantic_core_schema__(cls, value, handler=None) -> "CoreSchema":
schema = with_info_before_validator_function(
cls.__eth_pydantic_validate__,
bytes_schema(max_length=cls.size, min_length=cls.size),
Expand All @@ -48,7 +45,7 @@ def __get_pydantic_core_schema__(cls, value, handler=None) -> CoreSchema:

@classmethod
def __eth_pydantic_validate__(
cls, value: Any, info: Optional[ValidationInfo] = None
cls, value: Any, info: Optional["ValidationInfo"] = None
) -> HexBytes:
return cls(cls.validate_size(HexBytes(value)))

Expand All @@ -69,14 +66,14 @@ class HashStr(BaseHexStr):
schema_examples: ClassVar[tuple[str, ...]] = _get_hash_examples(1)

@classmethod
def __get_pydantic_core_schema__(cls, value, handler=None) -> CoreSchema:
def __get_pydantic_core_schema__(cls, value, handler=None) -> "CoreSchema":
str_size = cls.size * 2 + 2
return with_info_before_validator_function(
cls.__eth_pydantic_validate__, str_schema(max_length=str_size, min_length=str_size)
)

@classmethod
def __eth_pydantic_validate__(cls, value: Any, info: Optional[ValidationInfo] = None) -> str:
def __eth_pydantic_validate__(cls, value: Any, info: Optional["ValidationInfo"] = None) -> str:
hex_str = cls.validate_hex(value)
hex_value = hex_str[2:] if hex_str.startswith("0x") else hex_str
sized_value = cls.validate_size(hex_value)
Expand Down Expand Up @@ -107,15 +104,34 @@ def _make_hash_cls(size: int, base_type: type):
)


HashBytes4 = _make_hash_cls(4, bytes)
HashBytes8 = _make_hash_cls(8, bytes)
HashBytes16 = _make_hash_cls(16, bytes)
HashBytes20 = _make_hash_cls(20, bytes)
HashBytes32 = _make_hash_cls(32, bytes)
HashBytes64 = _make_hash_cls(64, bytes)
HashStr4 = _make_hash_cls(4, str)
HashStr8 = _make_hash_cls(8, str)
HashStr16 = _make_hash_cls(16, str)
HashStr20 = _make_hash_cls(20, str)
HashStr32 = _make_hash_cls(32, str)
HashStr64 = _make_hash_cls(64, str)
def __getattr__(name: str):
_type: type
if name.startswith("HashBytes"):
number = name.replace("HashBytes", "")
_type = bytes
elif name.startswith("HashStr"):
number = name.replace("HashStr", "")
_type = str
else:
raise AttributeError(name)

if not number.isnumeric():
raise AttributeError(name)

return _make_hash_cls(int(number), _type)


__all__ = [
"HashBytes4",
"HashBytes8",
"HashBytes16",
"HashBytes20",
"HashBytes32",
"HashBytes64",
"HashStr4",
"HashStr8",
"HashStr16",
"HashStr20",
"HashStr32",
"HashStr64",
]
Loading

0 comments on commit 0db4f4a

Please sign in to comment.