From 9f7f14dd29e92cce08571c422ba57c3661e1edce Mon Sep 17 00:00:00 2001 From: slush Date: Tue, 11 Jun 2024 23:47:10 -0500 Subject: [PATCH 01/25] feat: adds signed and unsigned ints to list of types --- eth_pydantic_types/hash.py | 68 +++++++++++++++++++++++++++----- eth_pydantic_types/validators.py | 16 ++++++++ 2 files changed, 75 insertions(+), 9 deletions(-) diff --git a/eth_pydantic_types/hash.py b/eth_pydantic_types/hash.py index c18f846..71404e9 100644 --- a/eth_pydantic_types/hash.py +++ b/eth_pydantic_types/hash.py @@ -4,13 +4,14 @@ CoreSchema, ValidationInfo, bytes_schema, + int_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 +from eth_pydantic_types.validators import validate_bytes_size, validate_int_size, validate_str_size def _get_hash_pattern(str_size: int) -> str: @@ -87,23 +88,60 @@ def validate_size(cls, value: str) -> str: return validate_str_size(value, cls.size * 2) -def _make_hash_cls(size: int, base_type: Type): +class HashInt(int): + """ + Represents an integer. + This type is meant to be overridden by the larger hash types with a new size. + e.g. Int32, UInt64. + """ + + size: ClassVar[int] = 1 + signed: ClassVar[bool] = True + + @classmethod + def __get_pydantic_core_schema__(cls, value, handler=None) -> CoreSchema: + return with_info_before_validator_function(cls.__eth_pydantic_validate__, int_schema()) + + @classmethod + def __eth_pydantic_validate__(cls, value: Any, info: Optional[ValidationInfo] = None) -> int: + return cls(cls.validate_size(int(value))) + + @classmethod + def validate_size(cls, value: int) -> int: + return validate_int_size(value, cls.size, cls.signed) + + +def _make_hash_cls(size: int, base_type: Type, signed: bool = True): + + str_size = size * 2 if issubclass(base_type, bytes): suffix = "Bytes" base_type = HashBytes - else: + typeDict = dict( + size=size, + schema_pattern=_get_hash_pattern(str_size), + schema_examples=_get_hash_examples(str_size), + ) + elif issubclass(base_type, str): suffix = "Str" base_type = HashStr + typeDict = dict( + size=size, + schema_pattern=_get_hash_pattern(str_size), + schema_examples=_get_hash_examples(str_size), + ) + else: + suffix = "Int" if signed else "UInt" + base_type = HashInt + typeDict = dict( + size=size, + signed=signed, + ) - str_size = size * 2 return type( f"Hash{suffix}{size}", (base_type,), - dict( - size=size, - schema_pattern=_get_hash_pattern(str_size), - schema_examples=_get_hash_examples(str_size), - ), + typeDict, ) @@ -119,3 +157,15 @@ def _make_hash_cls(size: int, base_type: Type): HashStr20 = _make_hash_cls(20, str) HashStr32 = _make_hash_cls(32, str) HashStr64 = _make_hash_cls(64, str) +HashInt8 = _make_hash_cls(8, int, signed=True) +HashInt16 = _make_hash_cls(16, int, signed=True) +HashInt32 = _make_hash_cls(32, int, signed=True) +HashInt64 = _make_hash_cls(64, int, signed=True) +HashInt128 = _make_hash_cls(128, int, signed=True) +HashInt256 = _make_hash_cls(256, int, signed=True) +HashUInt8 = _make_hash_cls(8, int, signed=False) +HashUInt16 = _make_hash_cls(16, int, signed=False) +HashUInt32 = _make_hash_cls(32, int, signed=False) +HashUInt64 = _make_hash_cls(64, int, signed=False) +HashUInt128 = _make_hash_cls(128, int, signed=False) +HashUInt256 = _make_hash_cls(256, int, signed=False) diff --git a/eth_pydantic_types/validators.py b/eth_pydantic_types/validators.py index 4f01b5a..8c93d0b 100644 --- a/eth_pydantic_types/validators.py +++ b/eth_pydantic_types/validators.py @@ -25,6 +25,18 @@ def validate_size(value: __SIZED_T, size: int, coerce: Optional[Callable] = None raise SizeError(size, value) +def validate_in_range(value: __SIZED_T, size: int, signed: bool = True) -> __SIZED_T: + if signed: + if value >= 0 and value < 2**size: + return value + + else: + if value >= -(2**size) / 2 and value < (2**size) / 2: + return value + + raise SizeError(size, value) + + def validate_bytes_size(value: bytes, size: int) -> bytes: return validate_size(value, size, coerce=lambda v: _coerce_hexbytes_size(v, size)) @@ -37,6 +49,10 @@ def validate_str_size(value: str, size: int) -> str: return validate_size(value, size, coerce=lambda v: _coerce_hexstr_size(v, size)) +def validate_int_size(value: str, size: int, signed: bool) -> str: + return validate_in_range(value, size, signed) + + def _coerce_hexstr_size(val: str, length: int) -> str: val = val.replace("0x", "") if val.startswith("0x") else val if len(val) == length: From 492fd3e2ddb71d66b05af17942c753285e5d8268 Mon Sep 17 00:00:00 2001 From: slush Date: Tue, 11 Jun 2024 23:49:11 -0500 Subject: [PATCH 02/25] chore: begins tests for int --- tests/test_int.py | 76 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tests/test_int.py diff --git a/tests/test_int.py b/tests/test_int.py new file mode 100644 index 0000000..695c1cc --- /dev/null +++ b/tests/test_int.py @@ -0,0 +1,76 @@ +import pytest +from pydantic import BaseModel, ValidationError + +from eth_pydantic_types.hash import ( + HashInt8, + HashInt16, + HashInt32, + HashInt64, + HashInt128, + HashInt256, + HashUInt8, + HashUInt16, + HashUInt32, + HashUInt64, + HashUInt128, + HashUInt256, +) + + +class IntModel(BaseModel): + valueint8: HashInt8 + valueint16: HashInt16 + valueint32: HashInt32 + valueint64: HashInt64 + valueint128: HashInt128 + valueint256: HashInt256 + valueuint8: HashUInt8 + valueuint16: HashUInt16 + valueuint32: HashUInt32 + valueuint64: HashUInt64 + valueuint128: HashUInt128 + valueuint256: HashUInt256 + + @classmethod + def from_single(cls, value): + return cls( + valueint8=value, + valueint16=value, + valueint32=value, + valueint64=value, + valueint128=value, + valueint256=value, + valueuint8=value, + valueuint16=value, + valueuint32=value, + valueuint64=value, + valueuint128=value, + valueuint256=value, + ) + + @classmethod + def signed_from_single(cls, value): + return cls( + valueint8=value, + valueint16=value, + valueint32=value, + valueint64=value, + valueint128=value, + valueint256=value, + ) + + @classmethod + def unsigned_from_single(cls, value): + return cls( + valueuint8=value, + valueuint16=value, + valueuint32=value, + valueuint64=value, + valueuint128=value, + valueuint256=value, + ) + + +def test_invalid_int(): + with pytest.raises(ValidationError): + IntModel.unsigned_from_single(-1) From 91f0b123fde64787b5d7b7b77745b7b30e0fde70 Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 12 Jun 2024 00:06:31 -0500 Subject: [PATCH 03/25] fix: fixes mypy issues --- eth_pydantic_types/validators.py | 4 ++-- tests/test_int.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/eth_pydantic_types/validators.py b/eth_pydantic_types/validators.py index 8c93d0b..63c63a7 100644 --- a/eth_pydantic_types/validators.py +++ b/eth_pydantic_types/validators.py @@ -25,7 +25,7 @@ def validate_size(value: __SIZED_T, size: int, coerce: Optional[Callable] = None raise SizeError(size, value) -def validate_in_range(value: __SIZED_T, size: int, signed: bool = True) -> __SIZED_T: +def validate_in_range(value: int, size: int, signed: bool = True) -> int: if signed: if value >= 0 and value < 2**size: return value @@ -49,7 +49,7 @@ def validate_str_size(value: str, size: int) -> str: return validate_size(value, size, coerce=lambda v: _coerce_hexstr_size(v, size)) -def validate_int_size(value: str, size: int, signed: bool) -> str: +def validate_int_size(value: int, size: int, signed: bool) -> int: return validate_in_range(value, size, signed) diff --git a/tests/test_int.py b/tests/test_int.py index 695c1cc..0d76fc3 100644 --- a/tests/test_int.py +++ b/tests/test_int.py @@ -17,7 +17,7 @@ ) -class IntModel(BaseModel): +class Model(BaseModel): valueint8: HashInt8 valueint16: HashInt16 valueint32: HashInt32 @@ -73,4 +73,4 @@ def unsigned_from_single(cls, value): def test_invalid_int(): with pytest.raises(ValidationError): - IntModel.unsigned_from_single(-1) + Model.unsigned_from_single(-1) From 50c3bbcfa92d4bc6c0f92f7812ee305665876b6d Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 12 Jun 2024 00:24:43 -0500 Subject: [PATCH 04/25] chore: removes test --- tests/test_int.py | 43 ++++++++++++++----------------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/tests/test_int.py b/tests/test_int.py index 0d76fc3..92929b9 100644 --- a/tests/test_int.py +++ b/tests/test_int.py @@ -1,5 +1,4 @@ -import pytest -from pydantic import BaseModel, ValidationError +from pydantic import BaseModel from eth_pydantic_types.hash import ( HashInt8, @@ -17,19 +16,13 @@ ) -class Model(BaseModel): +class SignedModel(BaseModel): valueint8: HashInt8 valueint16: HashInt16 valueint32: HashInt32 valueint64: HashInt64 valueint128: HashInt128 valueint256: HashInt256 - valueuint8: HashUInt8 - valueuint16: HashUInt16 - valueuint32: HashUInt32 - valueuint64: HashUInt64 - valueuint128: HashUInt128 - valueuint256: HashUInt256 @classmethod def from_single(cls, value): @@ -40,27 +33,19 @@ def from_single(cls, value): valueint64=value, valueint128=value, valueint256=value, - valueuint8=value, - valueuint16=value, - valueuint32=value, - valueuint64=value, - valueuint128=value, - valueuint256=value, ) - @classmethod - def signed_from_single(cls, value): - return cls( - valueint8=value, - valueint16=value, - valueint32=value, - valueint64=value, - valueint128=value, - valueint256=value, - ) + +class UnsignedModel(BaseModel): + valueuint8: HashUInt8 + valueuint16: HashUInt16 + valueuint32: HashUInt32 + valueuint64: HashUInt64 + valueuint128: HashUInt128 + valueuint256: HashUInt256 @classmethod - def unsigned_from_single(cls, value): + def from_single(cls, value): return cls( valueuint8=value, valueuint16=value, @@ -71,6 +56,6 @@ def unsigned_from_single(cls, value): ) -def test_invalid_int(): - with pytest.raises(ValidationError): - Model.unsigned_from_single(-1) +# def test_invalid_int(): +# with pytest.raises(ValidationError): +# UnsignedModel.from_single(-1) From 2b2269c53915131a43a3fe123c5ed88fee1d83e4 Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 12 Jun 2024 11:40:41 -0500 Subject: [PATCH 05/25] fix: fixes issue with signed/unsigned validation check --- eth_pydantic_types/validators.py | 2 +- tests/test_int.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/eth_pydantic_types/validators.py b/eth_pydantic_types/validators.py index 63c63a7..67d8298 100644 --- a/eth_pydantic_types/validators.py +++ b/eth_pydantic_types/validators.py @@ -26,7 +26,7 @@ def validate_size(value: __SIZED_T, size: int, coerce: Optional[Callable] = None def validate_in_range(value: int, size: int, signed: bool = True) -> int: - if signed: + if not signed: if value >= 0 and value < 2**size: return value diff --git a/tests/test_int.py b/tests/test_int.py index 92929b9..71aa773 100644 --- a/tests/test_int.py +++ b/tests/test_int.py @@ -1,4 +1,5 @@ -from pydantic import BaseModel +import pytest +from pydantic import BaseModel, ValidationError from eth_pydantic_types.hash import ( HashInt8, @@ -56,6 +57,6 @@ def from_single(cls, value): ) -# def test_invalid_int(): -# with pytest.raises(ValidationError): -# UnsignedModel.from_single(-1) +def test_negative_unsigned_int(): + with pytest.raises(ValidationError): + UnsignedModel.from_single(-1) From 18e505f4a0065731ef66508d1ee893dba5fe2895 Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 12 Jun 2024 11:41:45 -0500 Subject: [PATCH 06/25] chore: proper snake casing --- eth_pydantic_types/hash.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eth_pydantic_types/hash.py b/eth_pydantic_types/hash.py index 71404e9..381c915 100644 --- a/eth_pydantic_types/hash.py +++ b/eth_pydantic_types/hash.py @@ -117,7 +117,7 @@ def _make_hash_cls(size: int, base_type: Type, signed: bool = True): if issubclass(base_type, bytes): suffix = "Bytes" base_type = HashBytes - typeDict = dict( + type_dict = dict( size=size, schema_pattern=_get_hash_pattern(str_size), schema_examples=_get_hash_examples(str_size), @@ -125,7 +125,7 @@ def _make_hash_cls(size: int, base_type: Type, signed: bool = True): elif issubclass(base_type, str): suffix = "Str" base_type = HashStr - typeDict = dict( + type_dict = dict( size=size, schema_pattern=_get_hash_pattern(str_size), schema_examples=_get_hash_examples(str_size), @@ -133,7 +133,7 @@ def _make_hash_cls(size: int, base_type: Type, signed: bool = True): else: suffix = "Int" if signed else "UInt" base_type = HashInt - typeDict = dict( + type_dict = dict( size=size, signed=signed, ) @@ -141,7 +141,7 @@ def _make_hash_cls(size: int, base_type: Type, signed: bool = True): return type( f"Hash{suffix}{size}", (base_type,), - typeDict, + type_dict, ) From 6dec17295bfa5ee46da728188f3fb7a517bee1da Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 12 Jun 2024 15:35:44 -0500 Subject: [PATCH 07/25] chore: fixes name on Int types to remove reference to Hash --- eth_pydantic_types/hash.py | 58 +++++++++++++++++++------------------- tests/test_int.py | 48 +++++++++++++++---------------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/eth_pydantic_types/hash.py b/eth_pydantic_types/hash.py index 381c915..ccfc70d 100644 --- a/eth_pydantic_types/hash.py +++ b/eth_pydantic_types/hash.py @@ -88,10 +88,10 @@ def validate_size(cls, value: str) -> str: return validate_str_size(value, cls.size * 2) -class HashInt(int): +class Int(int): """ Represents an integer. - This type is meant to be overridden by the larger hash types with a new size. + This type is meant to be overridden by the larger types with a new size. e.g. Int32, UInt64. """ @@ -111,7 +111,7 @@ def validate_size(cls, value: int) -> int: return validate_int_size(value, cls.size, cls.signed) -def _make_hash_cls(size: int, base_type: Type, signed: bool = True): +def _make_cls(size: int, base_type: Type, signed: bool = True, prefix: str = ""): str_size = size * 2 if issubclass(base_type, bytes): @@ -132,40 +132,40 @@ def _make_hash_cls(size: int, base_type: Type, signed: bool = True): ) else: suffix = "Int" if signed else "UInt" - base_type = HashInt + base_type = Int type_dict = dict( size=size, signed=signed, ) return type( - f"Hash{suffix}{size}", + f"{prefix}{suffix}{size}", (base_type,), type_dict, ) -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) -HashInt8 = _make_hash_cls(8, int, signed=True) -HashInt16 = _make_hash_cls(16, int, signed=True) -HashInt32 = _make_hash_cls(32, int, signed=True) -HashInt64 = _make_hash_cls(64, int, signed=True) -HashInt128 = _make_hash_cls(128, int, signed=True) -HashInt256 = _make_hash_cls(256, int, signed=True) -HashUInt8 = _make_hash_cls(8, int, signed=False) -HashUInt16 = _make_hash_cls(16, int, signed=False) -HashUInt32 = _make_hash_cls(32, int, signed=False) -HashUInt64 = _make_hash_cls(64, int, signed=False) -HashUInt128 = _make_hash_cls(128, int, signed=False) -HashUInt256 = _make_hash_cls(256, int, signed=False) +HashBytes4 = _make_cls(4, bytes, prefix="Hash") +HashBytes8 = _make_cls(8, bytes, prefix="Hash") +HashBytes16 = _make_cls(16, bytes, prefix="Hash") +HashBytes20 = _make_cls(20, bytes, prefix="Hash") +HashBytes32 = _make_cls(32, bytes, prefix="Hash") +HashBytes64 = _make_cls(64, bytes, prefix="Hash") +HashStr4 = _make_cls(4, str, prefix="Hash") +HashStr8 = _make_cls(8, str, prefix="Hash") +HashStr16 = _make_cls(16, str, prefix="Hash") +HashStr20 = _make_cls(20, str, prefix="Hash") +HashStr32 = _make_cls(32, str, prefix="Hash") +HashStr64 = _make_cls(64, str, prefix="Hash") +Int8 = _make_cls(8, int, signed=True) +Int16 = _make_cls(16, int, signed=True) +Int32 = _make_cls(32, int, signed=True) +Int64 = _make_cls(64, int, signed=True) +Int128 = _make_cls(128, int, signed=True) +Int256 = _make_cls(256, int, signed=True) +UInt8 = _make_cls(8, int, signed=False) +UInt16 = _make_cls(16, int, signed=False) +UInt32 = _make_cls(32, int, signed=False) +UInt64 = _make_cls(64, int, signed=False) +UInt128 = _make_cls(128, int, signed=False) +UInt256 = _make_cls(256, int, signed=False) diff --git a/tests/test_int.py b/tests/test_int.py index 71aa773..9fbdf4b 100644 --- a/tests/test_int.py +++ b/tests/test_int.py @@ -2,28 +2,28 @@ from pydantic import BaseModel, ValidationError from eth_pydantic_types.hash import ( - HashInt8, - HashInt16, - HashInt32, - HashInt64, - HashInt128, - HashInt256, - HashUInt8, - HashUInt16, - HashUInt32, - HashUInt64, - HashUInt128, - HashUInt256, + Int8, + Int16, + Int32, + Int64, + Int128, + Int256, + UInt8, + UInt16, + UInt32, + UInt64, + UInt128, + UInt256, ) class SignedModel(BaseModel): - valueint8: HashInt8 - valueint16: HashInt16 - valueint32: HashInt32 - valueint64: HashInt64 - valueint128: HashInt128 - valueint256: HashInt256 + valueint8: Int8 + valueint16: Int16 + valueint32: Int32 + valueint64: Int64 + valueint128: Int128 + valueint256: Int256 @classmethod def from_single(cls, value): @@ -38,12 +38,12 @@ def from_single(cls, value): class UnsignedModel(BaseModel): - valueuint8: HashUInt8 - valueuint16: HashUInt16 - valueuint32: HashUInt32 - valueuint64: HashUInt64 - valueuint128: HashUInt128 - valueuint256: HashUInt256 + valueuint8: UInt8 + valueuint16: UInt16 + valueuint32: UInt32 + valueuint64: UInt64 + valueuint128: UInt128 + valueuint256: UInt256 @classmethod def from_single(cls, value): From 2a5d8d65d5c29512c04b68262f3ea4287318f24f Mon Sep 17 00:00:00 2001 From: slush Date: Sat, 15 Jun 2024 01:10:39 -0500 Subject: [PATCH 08/25] feat!: cleaned up codebase around managing multiple sized types. disentangled previous byte and string types from hash logic, but changed naming convention in the process. added abi child type for abi specific data, like the name of the type. --- README.md | 10 +- eth_pydantic_types/__init__.py | 66 ++++++++----- eth_pydantic_types/abi.py | 50 ++++++++++ eth_pydantic_types/address.py | 4 +- eth_pydantic_types/bytes.py | 70 ++++++++++++++ eth_pydantic_types/hash.py | 171 --------------------------------- eth_pydantic_types/hashing.py | 13 +++ eth_pydantic_types/numbers.py | 72 ++++++++++++++ eth_pydantic_types/string.py | 70 ++++++++++++++ tests/test_hash.py | 39 +++----- tests/test_hex.py | 4 +- tests/test_int.py | 2 +- 12 files changed, 340 insertions(+), 231 deletions(-) create mode 100644 eth_pydantic_types/abi.py create mode 100644 eth_pydantic_types/bytes.py delete mode 100644 eth_pydantic_types/hash.py create mode 100644 eth_pydantic_types/hashing.py create mode 100644 eth_pydantic_types/numbers.py create mode 100644 eth_pydantic_types/string.py diff --git a/README.md b/README.md index bdd1d59..896cb0a 100644 --- a/README.md +++ b/README.md @@ -4,19 +4,19 @@ The types in this package are pydantic types for Ethereum inspired from [eth-typ ## Hash -`HashBytes{n}` and `HashStr{n}` are good types to use when your hex values are sized. +`Bytes{n}` and `String{n}` are good types to use when your hex values are sized. Both types serialize to `string` in the JSON schema. -Use `HashBytes` types when you want types to serialize to bytes in the Pydantic core schema and `HashStr` types when you want to serialize to `str` in the core Pydantic schema. +Use `Bytes` types when you want types to serialize to bytes in the Pydantic core schema and `String` types when you want to serialize to `str` in the core Pydantic schema. ```python from pydantic import BaseModel -from eth_pydantic_types import HashBytes32, HashStr20 +from eth_pydantic_types import Bytes32, String20 # When serializing to JSON, both types are hex strings. class Transaction(BaseModel): - tx_hash: HashBytes32 # Will be bytes - address: HashStr20 # Will be str + tx_hash: Bytes32 # Will be bytes + address: String20 # Will be str # NOTE: I am able to pass an int-hash as the value and it will diff --git a/eth_pydantic_types/__init__.py b/eth_pydantic_types/__init__.py index 4c74729..d85c278 100644 --- a/eth_pydantic_types/__init__.py +++ b/eth_pydantic_types/__init__.py @@ -1,37 +1,51 @@ 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 .bytes import Bytes4, Bytes8, Bytes16, Bytes20, Bytes32, Bytes64 from .hex import HexBytes, HexStr +from .numbers import ( + Int8, + Int16, + Int32, + Int64, + Int128, + Int256, + UInt8, + UInt16, + UInt32, + UInt64, + UInt128, + UInt256, +) +from .string import String4, String8, String16, String20, String32, String64 __all__ = [ "Address", "AddressType", "Bip122Uri", - "HashBytes4", - "HashBytes8", - "HashBytes16", - "HashBytes20", - "HashBytes32", - "HashBytes64", - "HashStr4", - "HashStr8", - "HashStr16", - "HashStr20", - "HashStr32", - "HashStr64", + "Bytes4", + "Bytes8", + "Bytes16", + "Bytes20", + "Bytes32", + "Bytes64", + "String4", + "String8", + "String16", + "String20", + "String32", + "String64", "HexBytes", "HexStr", + "Int8", + "Int16", + "Int32", + "Int64", + "Int128", + "Int256", + "UInt8", + "UInt16", + "UInt32", + "UInt64", + "UInt128", + "UInt256", ] diff --git a/eth_pydantic_types/abi.py b/eth_pydantic_types/abi.py new file mode 100644 index 0000000..f5c4d7e --- /dev/null +++ b/eth_pydantic_types/abi.py @@ -0,0 +1,50 @@ +from typing import ClassVar + +from eth_pydantic_types.numbers import Int as _Int +from eth_pydantic_types.numbers import UInt as _UInt +from eth_pydantic_types.numbers import _make_cls +from eth_pydantic_types.string import String as _String + + +class Int(_Int): + """ + Represents an integer. + This type is meant to be overridden by the larger types with a new size. + e.g. Int32, Int64. + """ + + abi_type: ClassVar[str] = "int256" + + +class UInt(_UInt): + """ + Represents an unsigned integer. + This type is meant to be overridden by the larger types with a new size. + e.g. UInt32, UInt64. + """ + + abi_type: ClassVar[str] = "uint256" + + +Int8 = _make_cls(8, int, dict_additions=dict(abi_type="int8")) +Int16 = _make_cls(16, int, dict_additions=dict(abi_type="int16")) +Int32 = _make_cls(32, int, dict_additions=dict(abi_type="int32")) +Int64 = _make_cls(64, int, dict_additions=dict(abi_type="int64")) +Int128 = _make_cls(128, int, dict_additions=dict(abi_type="int128")) +Int256 = _make_cls(256, int, dict_additions=dict(abi_type="int256")) +UInt8 = _make_cls(8, int, signed=False, dict_additions=dict(abi_type="uint8")) +UInt16 = _make_cls(16, int, signed=False, dict_additions=dict(abi_type="uint16")) +UInt32 = _make_cls(32, int, signed=False, dict_additions=dict(abi_type="uint32")) +UInt64 = _make_cls(64, int, signed=False, dict_additions=dict(abi_type="uint64")) +UInt128 = _make_cls(128, int, signed=False, dict_additions=dict(abi_type="uint128")) +UInt256 = _make_cls(256, int, signed=False, dict_additions=dict(abi_type="uint256")) + + +class String(_String): + """ + Represents a single-slot static hash as a str. + This type is meant to be overridden by the larger hash types with a new size. + e.g. String20, String32. + """ + + abi_type: ClassVar[str] = "string" diff --git a/eth_pydantic_types/address.py b/eth_pydantic_types/address.py index dfd462f..26e91b7 100644 --- a/eth_pydantic_types/address.py +++ b/eth_pydantic_types/address.py @@ -5,7 +5,7 @@ from pydantic_core.core_schema import ValidationInfo, str_schema from typing_extensions import Annotated -from eth_pydantic_types.hash import HashStr20 +from eth_pydantic_types.string import String20 ADDRESS_PATTERN = "^0x[a-fA-F0-9]{40}$" @@ -14,7 +14,7 @@ def address_schema(): return str_schema(min_length=42, max_length=42, pattern=ADDRESS_PATTERN) -class Address(HashStr20): +class Address(String20): """ Use for address-types. Validates as a checksummed address. Left-pads zeroes if necessary. diff --git a/eth_pydantic_types/bytes.py b/eth_pydantic_types/bytes.py new file mode 100644 index 0000000..b9b344c --- /dev/null +++ b/eth_pydantic_types/bytes.py @@ -0,0 +1,70 @@ +from typing import Any, ClassVar, Optional, Tuple, Type + +from pydantic_core.core_schema import ( + CoreSchema, + ValidationInfo, + bytes_schema, + with_info_before_validator_function, +) + +from eth_pydantic_types.hashing import get_hash_examples, get_hash_pattern +from eth_pydantic_types.hex import HexBytes +from eth_pydantic_types.serializers import hex_serializer +from eth_pydantic_types.validators import validate_bytes_size + + +class Bytes(HexBytes): + """ + Represents a single-slot static hash as bytes. + This type is meant to be overridden by the larger hash types with a new size. + The class variable "size" is overridden in subclasses for each byte-size, + e.g. Bytes20, Bytes32. + """ + + size: ClassVar[int] = 1 + schema_pattern: ClassVar[str] = get_hash_pattern(1) + schema_examples: ClassVar[Tuple[str, ...]] = get_hash_examples(1) + + @classmethod + 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), + ) + schema["serialization"] = hex_serializer + return schema + + @classmethod + def __eth_pydantic_validate__( + cls, value: Any, info: Optional[ValidationInfo] = None + ) -> HexBytes: + return cls(cls.validate_size(HexBytes(value))) + + @classmethod + def validate_size(cls, value: bytes) -> bytes: + return validate_bytes_size(value, cls.size) + + +def _make_cls(size: int, base_type: Type, prefix: str = ""): + str_size = size * 2 + if issubclass(base_type, bytes): + display = "Bytes" + base_type = Bytes + type_dict = dict( + size=size, + schema_pattern=get_hash_pattern(str_size), + schema_examples=get_hash_examples(str_size), + ) + return type( + f"{prefix}{display}{size}", + (base_type,), + type_dict, + ) + + +Bytes4 = _make_cls(4, bytes) +Bytes8 = _make_cls(8, bytes) +Bytes16 = _make_cls(16, bytes) +Bytes20 = _make_cls(20, bytes) +Bytes32 = _make_cls(32, bytes) +Bytes64 = _make_cls(64, bytes) diff --git a/eth_pydantic_types/hash.py b/eth_pydantic_types/hash.py deleted file mode 100644 index ccfc70d..0000000 --- a/eth_pydantic_types/hash.py +++ /dev/null @@ -1,171 +0,0 @@ -from typing import Any, ClassVar, Optional, Tuple, Type - -from pydantic_core.core_schema import ( - CoreSchema, - ValidationInfo, - bytes_schema, - int_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_int_size, validate_str_size - - -def _get_hash_pattern(str_size: int) -> str: - return f"^0x[a-fA-F0-9]{{{str_size}}}$" - - -def _get_hash_examples(str_size: int) -> Tuple[str, str, str, str]: - zero_hash = f"0x{'0' * str_size}" - leading_zero = f"0x01{'1e' * ((str_size - 1) // 2)}" - trailing_zero = f"0x{'1e' * ((str_size - 1) // 2)}10" - full_hash = f"0x{'1e' * (str_size // 2)}" - return zero_hash, leading_zero, trailing_zero, full_hash - - -class HashBytes(HexBytes): - """ - Represents a single-slot static hash as bytes. - This type is meant to be overridden by the larger hash types with a new size. - The class variable "size" is overridden in subclasses for each byte-size, - e.g. HashBytes20, HashBytes32. - """ - - size: ClassVar[int] = 1 - schema_pattern: ClassVar[str] = _get_hash_pattern(1) - schema_examples: ClassVar[Tuple[str, ...]] = _get_hash_examples(1) - - @classmethod - 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), - ) - schema["serialization"] = hex_serializer - return schema - - @classmethod - def __eth_pydantic_validate__( - cls, value: Any, info: Optional[ValidationInfo] = None - ) -> HexBytes: - return cls(cls.validate_size(HexBytes(value))) - - @classmethod - def validate_size(cls, value: bytes) -> bytes: - return validate_bytes_size(value, cls.size) - - -class HashStr(BaseHexStr): - """ - Represents a single-slot static hash as a str. - This type is meant to be overridden by the larger hash types with a new size. - e.g. HashStr20, HashStr32. - """ - - size: ClassVar[int] = 1 - schema_pattern: ClassVar[str] = _get_hash_pattern(1) - schema_examples: ClassVar[Tuple[str, ...]] = _get_hash_examples(1) - - @classmethod - 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: - 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) - return cls(f"0x{sized_value}") - - @classmethod - def validate_size(cls, value: str) -> str: - return validate_str_size(value, cls.size * 2) - - -class Int(int): - """ - Represents an integer. - This type is meant to be overridden by the larger types with a new size. - e.g. Int32, UInt64. - """ - - size: ClassVar[int] = 1 - signed: ClassVar[bool] = True - - @classmethod - def __get_pydantic_core_schema__(cls, value, handler=None) -> CoreSchema: - return with_info_before_validator_function(cls.__eth_pydantic_validate__, int_schema()) - - @classmethod - def __eth_pydantic_validate__(cls, value: Any, info: Optional[ValidationInfo] = None) -> int: - return cls(cls.validate_size(int(value))) - - @classmethod - def validate_size(cls, value: int) -> int: - return validate_int_size(value, cls.size, cls.signed) - - -def _make_cls(size: int, base_type: Type, signed: bool = True, prefix: str = ""): - - str_size = size * 2 - if issubclass(base_type, bytes): - suffix = "Bytes" - base_type = HashBytes - type_dict = dict( - size=size, - schema_pattern=_get_hash_pattern(str_size), - schema_examples=_get_hash_examples(str_size), - ) - elif issubclass(base_type, str): - suffix = "Str" - base_type = HashStr - type_dict = dict( - size=size, - schema_pattern=_get_hash_pattern(str_size), - schema_examples=_get_hash_examples(str_size), - ) - else: - suffix = "Int" if signed else "UInt" - base_type = Int - type_dict = dict( - size=size, - signed=signed, - ) - - return type( - f"{prefix}{suffix}{size}", - (base_type,), - type_dict, - ) - - -HashBytes4 = _make_cls(4, bytes, prefix="Hash") -HashBytes8 = _make_cls(8, bytes, prefix="Hash") -HashBytes16 = _make_cls(16, bytes, prefix="Hash") -HashBytes20 = _make_cls(20, bytes, prefix="Hash") -HashBytes32 = _make_cls(32, bytes, prefix="Hash") -HashBytes64 = _make_cls(64, bytes, prefix="Hash") -HashStr4 = _make_cls(4, str, prefix="Hash") -HashStr8 = _make_cls(8, str, prefix="Hash") -HashStr16 = _make_cls(16, str, prefix="Hash") -HashStr20 = _make_cls(20, str, prefix="Hash") -HashStr32 = _make_cls(32, str, prefix="Hash") -HashStr64 = _make_cls(64, str, prefix="Hash") -Int8 = _make_cls(8, int, signed=True) -Int16 = _make_cls(16, int, signed=True) -Int32 = _make_cls(32, int, signed=True) -Int64 = _make_cls(64, int, signed=True) -Int128 = _make_cls(128, int, signed=True) -Int256 = _make_cls(256, int, signed=True) -UInt8 = _make_cls(8, int, signed=False) -UInt16 = _make_cls(16, int, signed=False) -UInt32 = _make_cls(32, int, signed=False) -UInt64 = _make_cls(64, int, signed=False) -UInt128 = _make_cls(128, int, signed=False) -UInt256 = _make_cls(256, int, signed=False) diff --git a/eth_pydantic_types/hashing.py b/eth_pydantic_types/hashing.py new file mode 100644 index 0000000..5971b21 --- /dev/null +++ b/eth_pydantic_types/hashing.py @@ -0,0 +1,13 @@ +from typing import Tuple + + +def get_hash_pattern(str_size: int) -> str: + return f"^0x[a-fA-F0-9]{{{str_size}}}$" + + +def get_hash_examples(str_size: int) -> Tuple[str, str, str, str]: + zero_hash = f"0x{'0' * str_size}" + leading_zero = f"0x01{'1e' * ((str_size - 1) // 2)}" + trailing_zero = f"0x{'1e' * ((str_size - 1) // 2)}10" + full_hash = f"0x{'1e' * (str_size // 2)}" + return zero_hash, leading_zero, trailing_zero, full_hash diff --git a/eth_pydantic_types/numbers.py b/eth_pydantic_types/numbers.py new file mode 100644 index 0000000..322b413 --- /dev/null +++ b/eth_pydantic_types/numbers.py @@ -0,0 +1,72 @@ +from typing import Any, ClassVar, Optional, Type + +from pydantic_core.core_schema import ( + CoreSchema, + ValidationInfo, + int_schema, + with_info_before_validator_function, +) + +from eth_pydantic_types.validators import validate_int_size + + +class BaseInt(int): + size: ClassVar[int] = 256 + signed: ClassVar[bool] = True + + @classmethod + def __get_pydantic_core_schema__(cls, value, handler=None) -> CoreSchema: + return with_info_before_validator_function(cls.__eth_pydantic_validate__, int_schema()) + + @classmethod + def __eth_pydantic_validate__(cls, value: Any, info: Optional[ValidationInfo] = None) -> int: + return cls(cls.validate_size(int(value))) + + @classmethod + def validate_size(cls, value: int) -> int: + return validate_int_size(value, cls.size, cls.signed) + + +class Int(BaseInt): + """ + Represents an integer. + This type is meant to be overridden by the larger types with a new size. + e.g. Int32, Int64. + """ + + +class UInt(BaseInt): + """ + Represents an unsigned integer. + This type is meant to be overridden by the larger types with a new size. + e.g. UInt32, UInt64. + """ + + signed = False + + +def _make_cls( + size: int, base_type: Type, signed: bool = True, prefix: str = "", dict_additions: dict = {} +): + if issubclass(base_type, int): + display = "Int" if signed else "UInt" + + return type( + f"{prefix}{display}{size}", + (Int if signed else UInt,), + dict(size=size, signed=signed, **dict_additions), + ) + + +Int8 = _make_cls(8, int) +Int16 = _make_cls(16, int) +Int32 = _make_cls(32, int) +Int64 = _make_cls(64, int) +Int128 = _make_cls(128, int) +Int256 = _make_cls(256, int) +UInt8 = _make_cls(8, int, signed=False) +UInt16 = _make_cls(16, int, signed=False) +UInt32 = _make_cls(32, int, signed=False) +UInt64 = _make_cls(64, int, signed=False) +UInt128 = _make_cls(128, int, signed=False) +UInt256 = _make_cls(256, int, signed=False) diff --git a/eth_pydantic_types/string.py b/eth_pydantic_types/string.py new file mode 100644 index 0000000..487f4b6 --- /dev/null +++ b/eth_pydantic_types/string.py @@ -0,0 +1,70 @@ +from typing import Any, ClassVar, Optional, Tuple, Type + +from pydantic_core.core_schema import ( + CoreSchema, + ValidationInfo, + str_schema, + with_info_before_validator_function, +) + +from eth_pydantic_types.hashing import get_hash_examples, get_hash_pattern +from eth_pydantic_types.hex import BaseHexStr +from eth_pydantic_types.validators import validate_str_size + + +class String(BaseHexStr): + """ + Represents a single-slot static hash as a str. + This type is meant to be overridden by the larger hash types with a new size. + e.g. String20, String32. + """ + + bound: bool = False + size: ClassVar[int] = 0 + schema_pattern: ClassVar[str] = get_hash_pattern(1) + schema_examples: ClassVar[Tuple[str, ...]] = get_hash_examples(1) + + @classmethod + 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: + 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) if cls.bound else hex_value + return cls(f"0x{sized_value}") + + @classmethod + def validate_size(cls, value: str) -> str: + return validate_str_size(value, cls.size * 2) + + +def _make_cls(size: int, base_type: Type, prefix: str = ""): + str_size = size * 2 + if issubclass(base_type, str): + display = "Str" + base_type = String + type_dict = dict( + bound=True, + size=size, + schema_pattern=get_hash_pattern(str_size), + schema_examples=get_hash_examples(str_size), + ) + + return type( + f"{prefix}{display}{size}", + (base_type,), + type_dict, + ) + + +String4 = _make_cls(4, str) +String8 = _make_cls(8, str) +String16 = _make_cls(16, str) +String20 = _make_cls(20, str) +String32 = _make_cls(32, str) +String64 = _make_cls(64, str) diff --git a/tests/test_hash.py b/tests/test_hash.py index 0abe877..2d99eae 100644 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -1,30 +1,21 @@ import pytest from pydantic import BaseModel, ValidationError -from eth_pydantic_types.hash import ( - HashBytes8, - HashBytes16, - HashBytes20, - HashBytes32, - HashBytes64, - HashStr8, - HashStr16, - HashStr32, - HashStr64, -) +from eth_pydantic_types.bytes import Bytes8, Bytes16, Bytes20, Bytes32, Bytes64 from eth_pydantic_types.hex import HexBytes +from eth_pydantic_types.string import String8, String16, String32, String64 class Model(BaseModel): - valuebytes8: HashBytes8 - valuebytes16: HashBytes16 - valuebytes20: HashBytes20 - valuebytes32: HashBytes32 - valuebytes64: HashBytes64 - valuestr8: HashStr8 - valuestr16: HashStr16 - valuestr32: HashStr32 - valuestr64: HashStr64 + valuebytes8: Bytes8 + valuebytes16: Bytes16 + valuebytes20: Bytes20 + valuebytes32: Bytes32 + valuebytes64: Bytes64 + valuestr8: String8 + valuestr16: String16 + valuestr32: String32 + valuestr64: String64 @classmethod def from_single(cls, value): @@ -42,14 +33,14 @@ def from_single(cls, value): def test_hashbytes_fromhex(bytes32str): - actual_with_0x = HashBytes32.fromhex(bytes32str) - actual_without_0x = HashBytes32.fromhex(bytes32str[2:]) + actual_with_0x = Bytes32.fromhex(bytes32str) + actual_without_0x = Bytes32.fromhex(bytes32str[2:]) expected = HexBytes(bytes32str) assert actual_with_0x == actual_without_0x == expected def test_hashbytes_is_bytes(bytes32str): - assert isinstance(HashBytes32.fromhex(bytes32str), bytes) + assert isinstance(Bytes32.fromhex(bytes32str), bytes) @pytest.mark.parametrize("value", ("0x32", HexBytes("0x32"), b"2", 50)) @@ -84,7 +75,7 @@ def test_hash_removes_leading_zeroes_if_needed(): address = "0x000000000000000000000000cafac3dd18ac6c6e92c921884f9e4176737c052c" class MyModel(BaseModel): - my_address: HashBytes20 + my_address: Bytes20 # Test both str and bytes for input. for addr in (address, HexBytes(address)): diff --git a/tests/test_hex.py b/tests/test_hex.py index 4c55bd8..105813f 100644 --- a/tests/test_hex.py +++ b/tests/test_hex.py @@ -2,7 +2,7 @@ from hexbytes import HexBytes as BaseHexBytes from pydantic import BaseModel, ValidationError -from eth_pydantic_types import HashStr20 +from eth_pydantic_types import String20 from eth_pydantic_types.hex import HexBytes, HexStr @@ -122,7 +122,7 @@ def test_hex_removes_leading_zeroes_if_needed(): address = "0x000000000000000000000000cafac3dd18ac6c6e92c921884f9e4176737c052c" class MyModel(BaseModel): - my_address: HashStr20 + my_address: String20 # Test both str and bytes for input. for addr in (address, HexBytes(address)): diff --git a/tests/test_int.py b/tests/test_int.py index 9fbdf4b..b3b1bb0 100644 --- a/tests/test_int.py +++ b/tests/test_int.py @@ -1,7 +1,7 @@ import pytest from pydantic import BaseModel, ValidationError -from eth_pydantic_types.hash import ( +from eth_pydantic_types.numbers import ( Int8, Int16, Int32, From db7b546d5b6cc4f044eb34826ff3619609640891 Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 19 Jun 2024 13:38:23 -0500 Subject: [PATCH 09/25] refactor: in progress - combining hex.py and hash.py hex strings/bytes into unified pydantic model classes --- eth_pydantic_types/__init__.py | 23 +++++++----- eth_pydantic_types/abi.py | 17 ++++----- eth_pydantic_types/address.py | 4 +-- eth_pydantic_types/hex.py | 64 ++++++++++++++++++++++++++++++++-- eth_pydantic_types/string.py | 15 ++++---- tests/test_hash.py | 11 +++--- tests/test_hex.py | 5 ++- 7 files changed, 102 insertions(+), 37 deletions(-) diff --git a/eth_pydantic_types/__init__.py b/eth_pydantic_types/__init__.py index d85c278..94948fd 100644 --- a/eth_pydantic_types/__init__.py +++ b/eth_pydantic_types/__init__.py @@ -1,7 +1,7 @@ from .address import Address, AddressType from .bip122 import Bip122Uri from .bytes import Bytes4, Bytes8, Bytes16, Bytes20, Bytes32, Bytes64 -from .hex import HexBytes, HexStr +from .hex import HexBytes, HexStr, HexStr4, HexStr8, HexStr16, HexStr20, HexStr32, HexStr64 from .numbers import ( Int8, Int16, @@ -16,7 +16,8 @@ UInt128, UInt256, ) -from .string import String4, String8, String16, String20, String32, String64 + +# from .string import String4, String8, String16, String20, String32, String64 __all__ = [ "Address", @@ -28,14 +29,20 @@ "Bytes20", "Bytes32", "Bytes64", - "String4", - "String8", - "String16", - "String20", - "String32", - "String64", + # "String4", + # "String8", + # "String16", + # "String20", + # "String32", + # "String64", "HexBytes", "HexStr", + "HexStr4", + "HexStr8", + "HexStr16", + "HexStr20", + "HexStr32", + "HexStr64", "Int8", "Int16", "Int32", diff --git a/eth_pydantic_types/abi.py b/eth_pydantic_types/abi.py index f5c4d7e..7db64aa 100644 --- a/eth_pydantic_types/abi.py +++ b/eth_pydantic_types/abi.py @@ -3,7 +3,8 @@ from eth_pydantic_types.numbers import Int as _Int from eth_pydantic_types.numbers import UInt as _UInt from eth_pydantic_types.numbers import _make_cls -from eth_pydantic_types.string import String as _String + +# from eth_pydantic_types.string import String as _String class Int(_Int): @@ -40,11 +41,11 @@ class UInt(_UInt): UInt256 = _make_cls(256, int, signed=False, dict_additions=dict(abi_type="uint256")) -class String(_String): - """ - Represents a single-slot static hash as a str. - This type is meant to be overridden by the larger hash types with a new size. - e.g. String20, String32. - """ +# class String(_String): +# """ +# Represents a single-slot static hash as a str. +# This type is meant to be overridden by the larger hash types with a new size. +# e.g. String20, String32. +# """ - abi_type: ClassVar[str] = "string" +# abi_type: ClassVar[str] = "string" diff --git a/eth_pydantic_types/address.py b/eth_pydantic_types/address.py index 26e91b7..bc88ac5 100644 --- a/eth_pydantic_types/address.py +++ b/eth_pydantic_types/address.py @@ -5,7 +5,7 @@ from pydantic_core.core_schema import ValidationInfo, str_schema from typing_extensions import Annotated -from eth_pydantic_types.string import String20 +from eth_pydantic_types.hex import HexStr20 ADDRESS_PATTERN = "^0x[a-fA-F0-9]{40}$" @@ -14,7 +14,7 @@ def address_schema(): return str_schema(min_length=42, max_length=42, pattern=ADDRESS_PATTERN) -class Address(String20): +class Address(HexStr20): """ Use for address-types. Validates as a checksummed address. Left-pads zeroes if necessary. diff --git a/eth_pydantic_types/hex.py b/eth_pydantic_types/hex.py index ad6cbbf..6074570 100644 --- a/eth_pydantic_types/hex.py +++ b/eth_pydantic_types/hex.py @@ -1,4 +1,4 @@ -from typing import Any, ClassVar, Optional, Tuple, Union, cast +from typing import Any, ClassVar, Optional, Tuple, Type, Union, cast from eth_typing import HexStr as EthTypingHexStr from eth_utils import add_0x_prefix @@ -14,6 +14,7 @@ from eth_pydantic_types._error import HexValueError from eth_pydantic_types.serializers import hex_serializer +from eth_pydantic_types.validators import validate_str_size schema_pattern = "^0x([0-9a-f][0-9a-f])*$" schema_examples = ( @@ -101,9 +102,27 @@ def __bytes__(self) -> bytes: class HexStr(BaseHexStr): """A hex string value, typically from a hash.""" + bound: bool = False + size: ClassVar[int] = 0 + @classmethod - def __eth_pydantic_validate__(cls, value): - return cls.validate_hex(value) + 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) if cls.bound else str_schema(), + ) + + @classmethod + 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) if cls.bound else hex_value + return cls(f"0x{sized_value}") + + @classmethod + def validate_size(cls, value: str) -> str: + return validate_str_size(value, cls.size * 2) @classmethod def from_bytes(cls, data: bytes) -> "HexStr": @@ -122,3 +141,42 @@ def validate_hex_str(value: str) -> str: hex_value = f"0{hex_value}" return f"0x{hex_value}" + + +def get_hash_pattern(str_size: int) -> str: + return f"^0x[a-fA-F0-9]{{{str_size}}}$" + + +def get_hash_examples(str_size: int) -> Tuple[str, str, str, str]: + zero_hash = f"0x{'0' * str_size}" + leading_zero = f"0x01{'1e' * ((str_size - 1) // 2)}" + trailing_zero = f"0x{'1e' * ((str_size - 1) // 2)}10" + full_hash = f"0x{'1e' * (str_size // 2)}" + return zero_hash, leading_zero, trailing_zero, full_hash + + +def _make_cls(size: int, base_type: Type, prefix: str = "", dict_additions: dict = {}): + str_size = size * 2 + if issubclass(base_type, str): + display = "Str" + base_type = HexStr + type_dict = dict( + bound=True, + size=size, + schema_pattern=get_hash_pattern(str_size), + schema_examples=get_hash_examples(str_size), + **dict_additions, + ) + return type( + f"{prefix}{display}{size}", + (base_type,), + type_dict, + ) + + +HexStr4 = _make_cls(4, str, prefix="Hex") +HexStr8 = _make_cls(8, str, prefix="Hex") +HexStr16 = _make_cls(16, str, prefix="Hex") +HexStr20 = _make_cls(20, str, prefix="Hex") +HexStr32 = _make_cls(32, str, prefix="Hex") +HexStr64 = _make_cls(64, str, prefix="Hex") diff --git a/eth_pydantic_types/string.py b/eth_pydantic_types/string.py index 487f4b6..e956084 100644 --- a/eth_pydantic_types/string.py +++ b/eth_pydantic_types/string.py @@ -43,7 +43,7 @@ def validate_size(cls, value: str) -> str: return validate_str_size(value, cls.size * 2) -def _make_cls(size: int, base_type: Type, prefix: str = ""): +def _make_cls(size: int, base_type: Type, prefix: str = "", dict_additions: dict = {}): str_size = size * 2 if issubclass(base_type, str): display = "Str" @@ -53,6 +53,7 @@ def _make_cls(size: int, base_type: Type, prefix: str = ""): size=size, schema_pattern=get_hash_pattern(str_size), schema_examples=get_hash_examples(str_size), + **dict_additions, ) return type( @@ -62,9 +63,9 @@ def _make_cls(size: int, base_type: Type, prefix: str = ""): ) -String4 = _make_cls(4, str) -String8 = _make_cls(8, str) -String16 = _make_cls(16, str) -String20 = _make_cls(20, str) -String32 = _make_cls(32, str) -String64 = _make_cls(64, str) +# String4 = _make_cls(4, str) +# String8 = _make_cls(8, str) +# String16 = _make_cls(16, str) +# String20 = _make_cls(20, str) +# String32 = _make_cls(32, str) +# String64 = _make_cls(64, str) diff --git a/tests/test_hash.py b/tests/test_hash.py index 2d99eae..7d7526f 100644 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -2,8 +2,7 @@ from pydantic import BaseModel, ValidationError from eth_pydantic_types.bytes import Bytes8, Bytes16, Bytes20, Bytes32, Bytes64 -from eth_pydantic_types.hex import HexBytes -from eth_pydantic_types.string import String8, String16, String32, String64 +from eth_pydantic_types.hex import HexBytes, HexStr8, HexStr16, HexStr32, HexStr64 class Model(BaseModel): @@ -12,10 +11,10 @@ class Model(BaseModel): valuebytes20: Bytes20 valuebytes32: Bytes32 valuebytes64: Bytes64 - valuestr8: String8 - valuestr16: String16 - valuestr32: String32 - valuestr64: String64 + valuestr8: HexStr8 + valuestr16: HexStr16 + valuestr32: HexStr32 + valuestr64: HexStr64 @classmethod def from_single(cls, value): diff --git a/tests/test_hex.py b/tests/test_hex.py index 105813f..fcdc3ad 100644 --- a/tests/test_hex.py +++ b/tests/test_hex.py @@ -2,8 +2,7 @@ from hexbytes import HexBytes as BaseHexBytes from pydantic import BaseModel, ValidationError -from eth_pydantic_types import String20 -from eth_pydantic_types.hex import HexBytes, HexStr +from eth_pydantic_types.hex import HexBytes, HexStr, HexStr20 class BytesModel(BaseModel): @@ -122,7 +121,7 @@ def test_hex_removes_leading_zeroes_if_needed(): address = "0x000000000000000000000000cafac3dd18ac6c6e92c921884f9e4176737c052c" class MyModel(BaseModel): - my_address: String20 + my_address: HexStr20 # Test both str and bytes for input. for addr in (address, HexBytes(address)): From 4a92f24a5d6afc48ac1489fbfa2aaa0e86913e54 Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 19 Jun 2024 18:27:35 -0500 Subject: [PATCH 10/25] chore: cleans up abi types and stray comments from old hash string and byte types --- eth_pydantic_types/__init__.py | 25 +++++------- eth_pydantic_types/abi.py | 62 ++++++++++++----------------- eth_pydantic_types/bytes.py | 70 --------------------------------- eth_pydantic_types/hashing.py | 13 ------- eth_pydantic_types/hex.py | 38 ++++++++++++++---- eth_pydantic_types/string.py | 71 ---------------------------------- tests/test_hash.py | 32 +++++++++------ 7 files changed, 86 insertions(+), 225 deletions(-) delete mode 100644 eth_pydantic_types/bytes.py delete mode 100644 eth_pydantic_types/hashing.py delete mode 100644 eth_pydantic_types/string.py diff --git a/eth_pydantic_types/__init__.py b/eth_pydantic_types/__init__.py index 94948fd..d106141 100644 --- a/eth_pydantic_types/__init__.py +++ b/eth_pydantic_types/__init__.py @@ -1,14 +1,15 @@ from .address import Address, AddressType from .bip122 import Bip122Uri -from .bytes import Bytes4, Bytes8, Bytes16, Bytes20, Bytes32, Bytes64 from .hex import HexBytes, HexStr, HexStr4, HexStr8, HexStr16, HexStr20, HexStr32, HexStr64 from .numbers import ( + Int, Int8, Int16, Int32, Int64, Int128, Int256, + UInt, UInt8, UInt16, UInt32, @@ -17,25 +18,17 @@ UInt256, ) -# from .string import String4, String8, String16, String20, String32, String64 - __all__ = [ "Address", "AddressType", "Bip122Uri", - "Bytes4", - "Bytes8", - "Bytes16", - "Bytes20", - "Bytes32", - "Bytes64", - # "String4", - # "String8", - # "String16", - # "String20", - # "String32", - # "String64", "HexBytes", + "HexBytes4", + "HexBytes8", + "HexBytes16", + "HexBytes20", + "HexBytes32", + "HexBytes34", "HexStr", "HexStr4", "HexStr8", @@ -43,12 +36,14 @@ "HexStr20", "HexStr32", "HexStr64", + "Int", "Int8", "Int16", "Int32", "Int64", "Int128", "Int256", + "UInt", "UInt8", "UInt16", "UInt32", diff --git a/eth_pydantic_types/abi.py b/eth_pydantic_types/abi.py index 7db64aa..963bbb0 100644 --- a/eth_pydantic_types/abi.py +++ b/eth_pydantic_types/abi.py @@ -1,51 +1,37 @@ +""" +These models are used to match the lowercase type names used by the abi. +""" + from typing import ClassVar -from eth_pydantic_types.numbers import Int as _Int -from eth_pydantic_types.numbers import UInt as _UInt -from eth_pydantic_types.numbers import _make_cls +from .address import Address +from .numbers import _make_cls -# from eth_pydantic_types.string import String as _String +int8 = _make_cls(8, int, dict_additions=dict(abi_type="int8")) +int16 = _make_cls(16, int, dict_additions=dict(abi_type="int16")) +int32 = _make_cls(32, int, dict_additions=dict(abi_type="int32")) +int64 = _make_cls(64, int, dict_additions=dict(abi_type="int64")) +int128 = _make_cls(128, int, dict_additions=dict(abi_type="int128")) +int256 = _make_cls(256, int, dict_additions=dict(abi_type="int256")) +uint8 = _make_cls(8, int, signed=False, dict_additions=dict(abi_type="uint8")) +uint16 = _make_cls(16, int, signed=False, dict_additions=dict(abi_type="uint16")) +uint32 = _make_cls(32, int, signed=False, dict_additions=dict(abi_type="uint32")) +uint64 = _make_cls(64, int, signed=False, dict_additions=dict(abi_type="uint64")) +uint128 = _make_cls(128, int, signed=False, dict_additions=dict(abi_type="uint128")) +uint256 = _make_cls(256, int, signed=False, dict_additions=dict(abi_type="uint256")) -class Int(_Int): +class string(str): """ - Represents an integer. - This type is meant to be overridden by the larger types with a new size. - e.g. Int32, Int64. + Represents a string. """ - abi_type: ClassVar[str] = "int256" + abi_type: ClassVar[str] = "string" -class UInt(_UInt): +class address(Address): """ - Represents an unsigned integer. - This type is meant to be overridden by the larger types with a new size. - e.g. UInt32, UInt64. + Represents a 20 character hex string address. """ - abi_type: ClassVar[str] = "uint256" - - -Int8 = _make_cls(8, int, dict_additions=dict(abi_type="int8")) -Int16 = _make_cls(16, int, dict_additions=dict(abi_type="int16")) -Int32 = _make_cls(32, int, dict_additions=dict(abi_type="int32")) -Int64 = _make_cls(64, int, dict_additions=dict(abi_type="int64")) -Int128 = _make_cls(128, int, dict_additions=dict(abi_type="int128")) -Int256 = _make_cls(256, int, dict_additions=dict(abi_type="int256")) -UInt8 = _make_cls(8, int, signed=False, dict_additions=dict(abi_type="uint8")) -UInt16 = _make_cls(16, int, signed=False, dict_additions=dict(abi_type="uint16")) -UInt32 = _make_cls(32, int, signed=False, dict_additions=dict(abi_type="uint32")) -UInt64 = _make_cls(64, int, signed=False, dict_additions=dict(abi_type="uint64")) -UInt128 = _make_cls(128, int, signed=False, dict_additions=dict(abi_type="uint128")) -UInt256 = _make_cls(256, int, signed=False, dict_additions=dict(abi_type="uint256")) - - -# class String(_String): -# """ -# Represents a single-slot static hash as a str. -# This type is meant to be overridden by the larger hash types with a new size. -# e.g. String20, String32. -# """ - -# abi_type: ClassVar[str] = "string" + abi_type: ClassVar[str] = "address" diff --git a/eth_pydantic_types/bytes.py b/eth_pydantic_types/bytes.py deleted file mode 100644 index b9b344c..0000000 --- a/eth_pydantic_types/bytes.py +++ /dev/null @@ -1,70 +0,0 @@ -from typing import Any, ClassVar, Optional, Tuple, Type - -from pydantic_core.core_schema import ( - CoreSchema, - ValidationInfo, - bytes_schema, - with_info_before_validator_function, -) - -from eth_pydantic_types.hashing import get_hash_examples, get_hash_pattern -from eth_pydantic_types.hex import HexBytes -from eth_pydantic_types.serializers import hex_serializer -from eth_pydantic_types.validators import validate_bytes_size - - -class Bytes(HexBytes): - """ - Represents a single-slot static hash as bytes. - This type is meant to be overridden by the larger hash types with a new size. - The class variable "size" is overridden in subclasses for each byte-size, - e.g. Bytes20, Bytes32. - """ - - size: ClassVar[int] = 1 - schema_pattern: ClassVar[str] = get_hash_pattern(1) - schema_examples: ClassVar[Tuple[str, ...]] = get_hash_examples(1) - - @classmethod - 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), - ) - schema["serialization"] = hex_serializer - return schema - - @classmethod - def __eth_pydantic_validate__( - cls, value: Any, info: Optional[ValidationInfo] = None - ) -> HexBytes: - return cls(cls.validate_size(HexBytes(value))) - - @classmethod - def validate_size(cls, value: bytes) -> bytes: - return validate_bytes_size(value, cls.size) - - -def _make_cls(size: int, base_type: Type, prefix: str = ""): - str_size = size * 2 - if issubclass(base_type, bytes): - display = "Bytes" - base_type = Bytes - type_dict = dict( - size=size, - schema_pattern=get_hash_pattern(str_size), - schema_examples=get_hash_examples(str_size), - ) - return type( - f"{prefix}{display}{size}", - (base_type,), - type_dict, - ) - - -Bytes4 = _make_cls(4, bytes) -Bytes8 = _make_cls(8, bytes) -Bytes16 = _make_cls(16, bytes) -Bytes20 = _make_cls(20, bytes) -Bytes32 = _make_cls(32, bytes) -Bytes64 = _make_cls(64, bytes) diff --git a/eth_pydantic_types/hashing.py b/eth_pydantic_types/hashing.py deleted file mode 100644 index 5971b21..0000000 --- a/eth_pydantic_types/hashing.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Tuple - - -def get_hash_pattern(str_size: int) -> str: - return f"^0x[a-fA-F0-9]{{{str_size}}}$" - - -def get_hash_examples(str_size: int) -> Tuple[str, str, str, str]: - zero_hash = f"0x{'0' * str_size}" - leading_zero = f"0x01{'1e' * ((str_size - 1) // 2)}" - trailing_zero = f"0x{'1e' * ((str_size - 1) // 2)}10" - full_hash = f"0x{'1e' * (str_size // 2)}" - return zero_hash, leading_zero, trailing_zero, full_hash diff --git a/eth_pydantic_types/hex.py b/eth_pydantic_types/hex.py index 6074570..2d88136 100644 --- a/eth_pydantic_types/hex.py +++ b/eth_pydantic_types/hex.py @@ -14,7 +14,7 @@ from eth_pydantic_types._error import HexValueError from eth_pydantic_types.serializers import hex_serializer -from eth_pydantic_types.validators import validate_str_size +from eth_pydantic_types.validators import validate_bytes_size, validate_str_size schema_pattern = "^0x([0-9a-f][0-9a-f])*$" schema_examples = ( @@ -29,6 +29,8 @@ class BaseHex: + bound: bool = False + size: ClassVar[int] = 0 schema_pattern: ClassVar[str] = schema_pattern schema_examples: ClassVar[Tuple[str, ...]] = schema_examples @@ -49,7 +51,10 @@ class HexBytes(BaseHexBytes, BaseHex): @classmethod def __get_pydantic_core_schema__(cls, value, handle=None) -> CoreSchema: - schema = with_info_before_validator_function(cls.__eth_pydantic_validate__, bytes_schema()) + schema = with_info_before_validator_function( + cls.__eth_pydantic_validate__, + bytes_schema(max_length=cls.size, min_length=cls.size) if cls.bound else bytes_schema(), + ) schema["serialization"] = hex_serializer return schema @@ -62,7 +67,11 @@ def fromhex(cls, hex_str: str) -> "HexBytes": def __eth_pydantic_validate__( cls, value: Any, info: Optional[ValidationInfo] = None ) -> BaseHexBytes: - return BaseHexBytes(value) + return cls(cls.validate_size(HexBytes(value))) + + @classmethod + def validate_size(cls, value: bytes) -> bytes: + return validate_bytes_size(value, cls.size) if cls.bound else value class BaseHexStr(str, BaseHex): @@ -102,9 +111,6 @@ def __bytes__(self) -> bytes: class HexStr(BaseHexStr): """A hex string value, typically from a hash.""" - bound: bool = False - size: ClassVar[int] = 0 - @classmethod def __get_pydantic_core_schema__(cls, value, handler=None) -> CoreSchema: str_size = cls.size * 2 + 2 @@ -143,6 +149,9 @@ def validate_hex_str(value: str) -> str: return f"0x{hex_value}" +# Creates bound variations e.g. HexStr16, HexStr64 + + def get_hash_pattern(str_size: int) -> str: return f"^0x[a-fA-F0-9]{{{str_size}}}$" @@ -157,7 +166,16 @@ def get_hash_examples(str_size: int) -> Tuple[str, str, str, str]: def _make_cls(size: int, base_type: Type, prefix: str = "", dict_additions: dict = {}): str_size = size * 2 - if issubclass(base_type, str): + if issubclass(base_type, bytes): + display = "Bytes" + base_type = HexBytes + type_dict = dict( + bound=True, + size=size, + schema_pattern=get_hash_pattern(str_size), + schema_examples=get_hash_examples(str_size), + ) + elif issubclass(base_type, str): display = "Str" base_type = HexStr type_dict = dict( @@ -174,6 +192,12 @@ def _make_cls(size: int, base_type: Type, prefix: str = "", dict_additions: dict ) +HexBytes4 = _make_cls(4, bytes, prefix="Hex") +HexBytes8 = _make_cls(8, bytes, prefix="Hex") +HexBytes16 = _make_cls(16, bytes, prefix="Hex") +HexBytes20 = _make_cls(20, bytes, prefix="Hex") +HexBytes32 = _make_cls(32, bytes, prefix="Hex") +HexBytes64 = _make_cls(64, bytes, prefix="Hex") HexStr4 = _make_cls(4, str, prefix="Hex") HexStr8 = _make_cls(8, str, prefix="Hex") HexStr16 = _make_cls(16, str, prefix="Hex") diff --git a/eth_pydantic_types/string.py b/eth_pydantic_types/string.py deleted file mode 100644 index e956084..0000000 --- a/eth_pydantic_types/string.py +++ /dev/null @@ -1,71 +0,0 @@ -from typing import Any, ClassVar, Optional, Tuple, Type - -from pydantic_core.core_schema import ( - CoreSchema, - ValidationInfo, - str_schema, - with_info_before_validator_function, -) - -from eth_pydantic_types.hashing import get_hash_examples, get_hash_pattern -from eth_pydantic_types.hex import BaseHexStr -from eth_pydantic_types.validators import validate_str_size - - -class String(BaseHexStr): - """ - Represents a single-slot static hash as a str. - This type is meant to be overridden by the larger hash types with a new size. - e.g. String20, String32. - """ - - bound: bool = False - size: ClassVar[int] = 0 - schema_pattern: ClassVar[str] = get_hash_pattern(1) - schema_examples: ClassVar[Tuple[str, ...]] = get_hash_examples(1) - - @classmethod - 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: - 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) if cls.bound else hex_value - return cls(f"0x{sized_value}") - - @classmethod - def validate_size(cls, value: str) -> str: - return validate_str_size(value, cls.size * 2) - - -def _make_cls(size: int, base_type: Type, prefix: str = "", dict_additions: dict = {}): - str_size = size * 2 - if issubclass(base_type, str): - display = "Str" - base_type = String - type_dict = dict( - bound=True, - size=size, - schema_pattern=get_hash_pattern(str_size), - schema_examples=get_hash_examples(str_size), - **dict_additions, - ) - - return type( - f"{prefix}{display}{size}", - (base_type,), - type_dict, - ) - - -# String4 = _make_cls(4, str) -# String8 = _make_cls(8, str) -# String16 = _make_cls(16, str) -# String20 = _make_cls(20, str) -# String32 = _make_cls(32, str) -# String64 = _make_cls(64, str) diff --git a/tests/test_hash.py b/tests/test_hash.py index 7d7526f..647259c 100644 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -1,16 +1,26 @@ import pytest from pydantic import BaseModel, ValidationError -from eth_pydantic_types.bytes import Bytes8, Bytes16, Bytes20, Bytes32, Bytes64 -from eth_pydantic_types.hex import HexBytes, HexStr8, HexStr16, HexStr32, HexStr64 +from eth_pydantic_types.hex import ( + HexBytes, + HexBytes8, + HexBytes16, + HexBytes20, + HexBytes32, + HexBytes64, + HexStr8, + HexStr16, + HexStr32, + HexStr64, +) class Model(BaseModel): - valuebytes8: Bytes8 - valuebytes16: Bytes16 - valuebytes20: Bytes20 - valuebytes32: Bytes32 - valuebytes64: Bytes64 + valuebytes8: HexBytes8 + valuebytes16: HexBytes16 + valuebytes20: HexBytes20 + valuebytes32: HexBytes32 + valuebytes64: HexBytes64 valuestr8: HexStr8 valuestr16: HexStr16 valuestr32: HexStr32 @@ -32,14 +42,14 @@ def from_single(cls, value): def test_hashbytes_fromhex(bytes32str): - actual_with_0x = Bytes32.fromhex(bytes32str) - actual_without_0x = Bytes32.fromhex(bytes32str[2:]) + actual_with_0x = HexBytes32.fromhex(bytes32str) + actual_without_0x = HexBytes32.fromhex(bytes32str[2:]) expected = HexBytes(bytes32str) assert actual_with_0x == actual_without_0x == expected def test_hashbytes_is_bytes(bytes32str): - assert isinstance(Bytes32.fromhex(bytes32str), bytes) + assert isinstance(HexBytes32.fromhex(bytes32str), bytes) @pytest.mark.parametrize("value", ("0x32", HexBytes("0x32"), b"2", 50)) @@ -74,7 +84,7 @@ def test_hash_removes_leading_zeroes_if_needed(): address = "0x000000000000000000000000cafac3dd18ac6c6e92c921884f9e4176737c052c" class MyModel(BaseModel): - my_address: Bytes20 + my_address: HexBytes20 # Test both str and bytes for input. for addr in (address, HexBytes(address)): From 254102626d22f6eeea194f8da55f1f038e28ead7 Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 26 Jun 2024 10:33:36 -0500 Subject: [PATCH 11/25] feat: adds remaining int types and updates hexbytes version --- eth_pydantic_types/__init__.py | 21 +++++++- eth_pydantic_types/abi.py | 96 +++++++++++++++++++++++++++++----- eth_pydantic_types/address.py | 2 +- eth_pydantic_types/hex.py | 33 ++++-------- setup.py | 6 +-- tests/test_hash.py | 10 ++-- tests/test_hex.py | 4 +- 7 files changed, 123 insertions(+), 49 deletions(-) diff --git a/eth_pydantic_types/__init__.py b/eth_pydantic_types/__init__.py index d106141..49d418b 100644 --- a/eth_pydantic_types/__init__.py +++ b/eth_pydantic_types/__init__.py @@ -1,6 +1,22 @@ from .address import Address, AddressType from .bip122 import Bip122Uri -from .hex import HexBytes, HexStr, HexStr4, HexStr8, HexStr16, HexStr20, HexStr32, HexStr64 +from .hex import ( + HexBytes, + HexBytes1, + HexBytes4, + HexBytes8, + HexBytes16, + HexBytes20, + HexBytes32, + HexBytes64, + HexStr, + HexStr4, + HexStr8, + HexStr16, + HexStr20, + HexStr32, + HexStr64, +) from .numbers import ( Int, Int8, @@ -23,12 +39,13 @@ "AddressType", "Bip122Uri", "HexBytes", + "HexBytes1", "HexBytes4", "HexBytes8", "HexBytes16", "HexBytes20", "HexBytes32", - "HexBytes34", + "HexBytes64", "HexStr", "HexStr4", "HexStr8", diff --git a/eth_pydantic_types/abi.py b/eth_pydantic_types/abi.py index 963bbb0..b4df3ce 100644 --- a/eth_pydantic_types/abi.py +++ b/eth_pydantic_types/abi.py @@ -4,21 +4,19 @@ from typing import ClassVar +from pydantic_core.core_schema import CoreSchema, str_schema + from .address import Address -from .numbers import _make_cls - -int8 = _make_cls(8, int, dict_additions=dict(abi_type="int8")) -int16 = _make_cls(16, int, dict_additions=dict(abi_type="int16")) -int32 = _make_cls(32, int, dict_additions=dict(abi_type="int32")) -int64 = _make_cls(64, int, dict_additions=dict(abi_type="int64")) -int128 = _make_cls(128, int, dict_additions=dict(abi_type="int128")) -int256 = _make_cls(256, int, dict_additions=dict(abi_type="int256")) -uint8 = _make_cls(8, int, signed=False, dict_additions=dict(abi_type="uint8")) -uint16 = _make_cls(16, int, signed=False, dict_additions=dict(abi_type="uint16")) -uint32 = _make_cls(32, int, signed=False, dict_additions=dict(abi_type="uint32")) -uint64 = _make_cls(64, int, signed=False, dict_additions=dict(abi_type="uint64")) -uint128 = _make_cls(128, int, signed=False, dict_additions=dict(abi_type="uint128")) -uint256 = _make_cls(256, int, signed=False, dict_additions=dict(abi_type="uint256")) +from .hex import _make_cls as _make_hex_cls +from .numbers import _make_cls as _make_number_cls + +bytes1 = _make_hex_cls(1, bytes, dict_additions=dict(abi_type="bytes1")) +bytes4 = _make_hex_cls(4, bytes, dict_additions=dict(abi_type="bytes4")) +bytes8 = _make_hex_cls(8, bytes, dict_additions=dict(abi_type="bytes8")) +bytes16 = _make_hex_cls(16, bytes, dict_additions=dict(abi_type="bytes16")) +bytes20 = _make_hex_cls(20, bytes, dict_additions=dict(abi_type="bytes20")) +bytes32 = _make_hex_cls(32, bytes, dict_additions=dict(abi_type="bytes32")) +bytes64 = _make_hex_cls(64, bytes, dict_additions=dict(abi_type="bytes64")) class string(str): @@ -26,6 +24,10 @@ class string(str): Represents a string. """ + @classmethod + def __get_pydantic_core_schema__(cls, value, handler=None) -> CoreSchema: + return str_schema() + abi_type: ClassVar[str] = "string" @@ -35,3 +37,69 @@ class address(Address): """ abi_type: ClassVar[str] = "address" + + +int8 = _make_number_cls(8, int, dict_additions=dict(abi_type="int8")) +int16 = _make_number_cls(16, int, dict_additions=dict(abi_type="int16")) +int24 = _make_number_cls(24, int, dict_additions=dict(abi_type="int24")) +int32 = _make_number_cls(32, int, dict_additions=dict(abi_type="int32")) +int40 = _make_number_cls(40, int, dict_additions=dict(abi_type="int40")) +int48 = _make_number_cls(48, int, dict_additions=dict(abi_type="int48")) +int56 = _make_number_cls(56, int, dict_additions=dict(abi_type="int56")) +int64 = _make_number_cls(64, int, dict_additions=dict(abi_type="int64")) +int72 = _make_number_cls(72, int, dict_additions=dict(abi_type="int72")) +int80 = _make_number_cls(80, int, dict_additions=dict(abi_type="int80")) +int88 = _make_number_cls(88, int, dict_additions=dict(abi_type="int88")) +int96 = _make_number_cls(96, int, dict_additions=dict(abi_type="int96")) +int104 = _make_number_cls(104, int, dict_additions=dict(abi_type="int104")) +int112 = _make_number_cls(112, int, dict_additions=dict(abi_type="int112")) +int120 = _make_number_cls(120, int, dict_additions=dict(abi_type="int120")) +int128 = _make_number_cls(128, int, dict_additions=dict(abi_type="int128")) +int136 = _make_number_cls(136, int, dict_additions=dict(abi_type="int136")) +int144 = _make_number_cls(144, int, dict_additions=dict(abi_type="int144")) +int152 = _make_number_cls(152, int, dict_additions=dict(abi_type="int152")) +int160 = _make_number_cls(160, int, dict_additions=dict(abi_type="int160")) +int168 = _make_number_cls(168, int, dict_additions=dict(abi_type="int168")) +int176 = _make_number_cls(176, int, dict_additions=dict(abi_type="int176")) +int184 = _make_number_cls(184, int, dict_additions=dict(abi_type="int184")) +int192 = _make_number_cls(192, int, dict_additions=dict(abi_type="int192")) +int200 = _make_number_cls(200, int, dict_additions=dict(abi_type="int200")) +int208 = _make_number_cls(208, int, dict_additions=dict(abi_type="int208")) +int216 = _make_number_cls(216, int, dict_additions=dict(abi_type="int216")) +int224 = _make_number_cls(224, int, dict_additions=dict(abi_type="int224")) +int232 = _make_number_cls(232, int, dict_additions=dict(abi_type="int232")) +int240 = _make_number_cls(240, int, dict_additions=dict(abi_type="int240")) +int248 = _make_number_cls(248, int, dict_additions=dict(abi_type="int248")) +int256 = _make_number_cls(256, int, dict_additions=dict(abi_type="int256")) +uint8 = _make_number_cls(8, int, signed=False, dict_additions=dict(abi_type="uint8")) +uint16 = _make_number_cls(16, int, signed=False, dict_additions=dict(abi_type="uint16")) +uint24 = _make_number_cls(24, int, signed=False, dict_additions=dict(abi_type="uint24")) +uint32 = _make_number_cls(32, int, signed=False, dict_additions=dict(abi_type="uint32")) +uint40 = _make_number_cls(40, int, signed=False, dict_additions=dict(abi_type="uint40")) +uint48 = _make_number_cls(48, int, signed=False, dict_additions=dict(abi_type="uint48")) +uint56 = _make_number_cls(56, int, signed=False, dict_additions=dict(abi_type="uint56")) +uint64 = _make_number_cls(64, int, signed=False, dict_additions=dict(abi_type="uint64")) +uint72 = _make_number_cls(72, int, signed=False, dict_additions=dict(abi_type="uint72")) +uint80 = _make_number_cls(80, int, signed=False, dict_additions=dict(abi_type="uint80")) +uint88 = _make_number_cls(88, int, signed=False, dict_additions=dict(abi_type="uint88")) +uint96 = _make_number_cls(96, int, signed=False, dict_additions=dict(abi_type="uint96")) +uint104 = _make_number_cls(104, int, signed=False, dict_additions=dict(abi_type="uint104")) +uint112 = _make_number_cls(112, int, signed=False, dict_additions=dict(abi_type="uint112")) +uint120 = _make_number_cls(120, int, signed=False, dict_additions=dict(abi_type="uint120")) +uint128 = _make_number_cls(128, int, signed=False, dict_additions=dict(abi_type="uint128")) +uint136 = _make_number_cls(136, int, signed=False, dict_additions=dict(abi_type="uint136")) +uint144 = _make_number_cls(144, int, signed=False, dict_additions=dict(abi_type="uint144")) +uint152 = _make_number_cls(152, int, signed=False, dict_additions=dict(abi_type="uint152")) +uint160 = _make_number_cls(160, int, signed=False, dict_additions=dict(abi_type="uint160")) +uint168 = _make_number_cls(168, int, signed=False, dict_additions=dict(abi_type="uint168")) +uint176 = _make_number_cls(176, int, signed=False, dict_additions=dict(abi_type="uint176")) +uint184 = _make_number_cls(184, int, signed=False, dict_additions=dict(abi_type="uint184")) +uint192 = _make_number_cls(192, int, signed=False, dict_additions=dict(abi_type="uint192")) +uint200 = _make_number_cls(200, int, signed=False, dict_additions=dict(abi_type="uint200")) +uint208 = _make_number_cls(208, int, signed=False, dict_additions=dict(abi_type="uint208")) +uint216 = _make_number_cls(216, int, signed=False, dict_additions=dict(abi_type="uint216")) +uint224 = _make_number_cls(224, int, signed=False, dict_additions=dict(abi_type="uint224")) +uint232 = _make_number_cls(232, int, signed=False, dict_additions=dict(abi_type="uint232")) +uint240 = _make_number_cls(240, int, signed=False, dict_additions=dict(abi_type="uint240")) +uint248 = _make_number_cls(248, int, signed=False, dict_additions=dict(abi_type="uint248")) +uint256 = _make_number_cls(256, int, signed=False, dict_additions=dict(abi_type="uint256")) diff --git a/eth_pydantic_types/address.py b/eth_pydantic_types/address.py index bc88ac5..771e9e9 100644 --- a/eth_pydantic_types/address.py +++ b/eth_pydantic_types/address.py @@ -5,7 +5,7 @@ from pydantic_core.core_schema import ValidationInfo, str_schema from typing_extensions import Annotated -from eth_pydantic_types.hex import HexStr20 +from .hex import HexStr20 ADDRESS_PATTERN = "^0x[a-fA-F0-9]{40}$" diff --git a/eth_pydantic_types/hex.py b/eth_pydantic_types/hex.py index 2d88136..79f6639 100644 --- a/eth_pydantic_types/hex.py +++ b/eth_pydantic_types/hex.py @@ -149,9 +149,6 @@ def validate_hex_str(value: str) -> str: return f"0x{hex_value}" -# Creates bound variations e.g. HexStr16, HexStr64 - - def get_hash_pattern(str_size: int) -> str: return f"^0x[a-fA-F0-9]{{{str_size}}}$" @@ -166,25 +163,15 @@ def get_hash_examples(str_size: int) -> Tuple[str, str, str, str]: def _make_cls(size: int, base_type: Type, prefix: str = "", dict_additions: dict = {}): str_size = size * 2 - if issubclass(base_type, bytes): - display = "Bytes" - base_type = HexBytes - type_dict = dict( - bound=True, - size=size, - schema_pattern=get_hash_pattern(str_size), - schema_examples=get_hash_examples(str_size), - ) - elif issubclass(base_type, str): - display = "Str" - base_type = HexStr - type_dict = dict( - bound=True, - size=size, - schema_pattern=get_hash_pattern(str_size), - schema_examples=get_hash_examples(str_size), - **dict_additions, - ) + display = "Bytes" if issubclass(base_type, bytes) else "Str" + base_type = HexBytes if issubclass(base_type, bytes) else HexStr + type_dict = dict( + bound=True, + size=size, + schema_pattern=get_hash_pattern(str_size), + schema_examples=get_hash_examples(str_size), + **dict_additions, + ) return type( f"{prefix}{display}{size}", (base_type,), @@ -192,6 +179,8 @@ def _make_cls(size: int, base_type: Type, prefix: str = "", dict_additions: dict ) +# Creates bound variations e.g. HexStr16, HexStr64 +HexBytes1 = _make_cls(1, bytes, prefix="Hex") HexBytes4 = _make_cls(4, bytes, prefix="Hex") HexBytes8 = _make_cls(8, bytes, prefix="Hex") HexBytes16 = _make_cls(16, bytes, prefix="Hex") diff --git a/setup.py b/setup.py index 13e829a..ed47b29 100644 --- a/setup.py +++ b/setup.py @@ -73,10 +73,10 @@ url="https://github.com/ApeWorX/eth-pydantic-types", include_package_data=True, install_requires=[ - "hexbytes>=0.3.0,<1", + "hexbytes>=1.2.0,<2", "eth-hash[pycryptodome]>=0.5.2,<1", - "eth-utils>=2.2.0,<3", - "eth-typing>=3.5.0,<4", + "eth-utils>=2.2.0,<5", + "eth-typing>=3.5.0,<5", "pydantic>=2.4.2,<3", "typing_extensions>=4.8.0,<5", ], diff --git a/tests/test_hash.py b/tests/test_hash.py index 647259c..966c539 100644 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -122,14 +122,14 @@ def test_model_dump(bytes32str): model = Model.from_single(5) actual = model.model_dump() expected = { - "valuebytes8": "0x0000000000000005", + "valuebytes8": "0000000000000005", "valuestr8": "0x0000000000000005", - "valuebytes16": "0x00000000000000000000000000000005", - "valuebytes20": "0x0000000000000000000000000000000000000005", + "valuebytes16": "00000000000000000000000000000005", + "valuebytes20": "0000000000000000000000000000000000000005", "valuestr16": "0x00000000000000000000000000000005", - "valuebytes32": "0x0000000000000000000000000000000000000000000000000000000000000005", + "valuebytes32": "0000000000000000000000000000000000000000000000000000000000000005", "valuestr32": "0x0000000000000000000000000000000000000000000000000000000000000005", - "valuebytes64": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005", # noqa: E501 + "valuebytes64": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005", # noqa: E501 "valuestr64": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005", # noqa: E501 } assert actual == expected diff --git a/tests/test_hex.py b/tests/test_hex.py index fcdc3ad..d3279d4 100644 --- a/tests/test_hex.py +++ b/tests/test_hex.py @@ -19,7 +19,7 @@ def test_hexbytes(value): # The end result, the value is a hexbytes.HexBytes assert actual.value == BaseHexBytes(value) - assert actual.value.hex() == "0x0a" + assert actual.value.hex() == "0a" assert isinstance(actual.value, bytes) assert isinstance(actual.value, BaseHexBytes) @@ -56,7 +56,7 @@ def test_hexbytes_schema(): def test_hexbytes_model_dump(bytes32str): model = BytesModel(value=bytes32str) actual = model.model_dump() - expected = {"value": "0x9b70bd98ccb5b6434c2ead14d68d15f392435a06ff469f8d1f8cf38b2ae0b0e2"} + expected = {"value": "9b70bd98ccb5b6434c2ead14d68d15f392435a06ff469f8d1f8cf38b2ae0b0e2"} assert actual == expected From 88e24787fe4753a7f787608b2cc6dd2cf7b783aa Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 3 Jul 2024 05:56:56 -0500 Subject: [PATCH 12/25] test: broken build but need to get this approach reviewed - issue with defaulting size of hexbytes object when building out the bound sizes --- eth_pydantic_types/__init__.py | 57 +--------- eth_pydantic_types/abi.py | 189 ++++++++++++++++++--------------- eth_pydantic_types/hex.py | 73 +++++++------ eth_pydantic_types/numbers.py | 72 ------------- tests/test_hash.py | 44 +------- tests/test_int.py | 50 ++++----- 6 files changed, 167 insertions(+), 318 deletions(-) delete mode 100644 eth_pydantic_types/numbers.py diff --git a/eth_pydantic_types/__init__.py b/eth_pydantic_types/__init__.py index 49d418b..ec9c25b 100644 --- a/eth_pydantic_types/__init__.py +++ b/eth_pydantic_types/__init__.py @@ -1,70 +1,15 @@ from .address import Address, AddressType from .bip122 import Bip122Uri -from .hex import ( - HexBytes, - HexBytes1, - HexBytes4, - HexBytes8, - HexBytes16, - HexBytes20, - HexBytes32, - HexBytes64, - HexStr, - HexStr4, - HexStr8, - HexStr16, - HexStr20, - HexStr32, - HexStr64, -) -from .numbers import ( - Int, - Int8, - Int16, - Int32, - Int64, - Int128, - Int256, - UInt, - UInt8, - UInt16, - UInt32, - UInt64, - UInt128, - UInt256, -) +from .hex import HexBytes, HexBytes20, HexBytes32, HexStr, HexStr20, HexStr32 __all__ = [ "Address", "AddressType", "Bip122Uri", "HexBytes", - "HexBytes1", - "HexBytes4", - "HexBytes8", - "HexBytes16", "HexBytes20", "HexBytes32", - "HexBytes64", "HexStr", - "HexStr4", - "HexStr8", - "HexStr16", "HexStr20", "HexStr32", - "HexStr64", - "Int", - "Int8", - "Int16", - "Int32", - "Int64", - "Int128", - "Int256", - "UInt", - "UInt8", - "UInt16", - "UInt32", - "UInt64", - "UInt128", - "UInt256", ] diff --git a/eth_pydantic_types/abi.py b/eth_pydantic_types/abi.py index b4df3ce..d41ca7c 100644 --- a/eth_pydantic_types/abi.py +++ b/eth_pydantic_types/abi.py @@ -2,33 +2,50 @@ These models are used to match the lowercase type names used by the abi. """ -from typing import ClassVar +from typing import Annotated, ClassVar -from pydantic_core.core_schema import CoreSchema, str_schema +from hexbytes import HexBytes +from pydantic import Field +from typing_extensions import TypeAlias from .address import Address -from .hex import _make_cls as _make_hex_cls -from .numbers import _make_cls as _make_number_cls +from .hex import BoundHexBytes -bytes1 = _make_hex_cls(1, bytes, dict_additions=dict(abi_type="bytes1")) -bytes4 = _make_hex_cls(4, bytes, dict_additions=dict(abi_type="bytes4")) -bytes8 = _make_hex_cls(8, bytes, dict_additions=dict(abi_type="bytes8")) -bytes16 = _make_hex_cls(16, bytes, dict_additions=dict(abi_type="bytes16")) -bytes20 = _make_hex_cls(20, bytes, dict_additions=dict(abi_type="bytes20")) -bytes32 = _make_hex_cls(32, bytes, dict_additions=dict(abi_type="bytes32")) -bytes64 = _make_hex_cls(64, bytes, dict_additions=dict(abi_type="bytes64")) +bytes: TypeAlias = HexBytes +string: TypeAlias = str - -class string(str): - """ - Represents a string. - """ - - @classmethod - def __get_pydantic_core_schema__(cls, value, handler=None) -> CoreSchema: - return str_schema() - - abi_type: ClassVar[str] = "string" +bytes1 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=1))] +bytes2 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=2))] +bytes3 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=3))] +bytes4 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=4))] +bytes5 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=5))] +bytes6 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=6))] +bytes7 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=7))] +bytes8 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=8))] +bytes9 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=9))] +bytes10 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=10))] +bytes11 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=11))] +bytes12 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=12))] +bytes13 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=13))] +bytes14 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=14))] +bytes15 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=15))] +bytes16 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=16))] +bytes17 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=17))] +bytes18 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=18))] +bytes19 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=19))] +bytes20 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=20))] +bytes21 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=21))] +bytes22 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=22))] +bytes23 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=23))] +bytes24 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=24))] +bytes25 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=25))] +bytes26 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=26))] +bytes27 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=27))] +bytes28 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=28))] +bytes29 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=29))] +bytes30 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=30))] +bytes31 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=31))] +bytes32 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=32))] class address(Address): @@ -39,67 +56,67 @@ class address(Address): abi_type: ClassVar[str] = "address" -int8 = _make_number_cls(8, int, dict_additions=dict(abi_type="int8")) -int16 = _make_number_cls(16, int, dict_additions=dict(abi_type="int16")) -int24 = _make_number_cls(24, int, dict_additions=dict(abi_type="int24")) -int32 = _make_number_cls(32, int, dict_additions=dict(abi_type="int32")) -int40 = _make_number_cls(40, int, dict_additions=dict(abi_type="int40")) -int48 = _make_number_cls(48, int, dict_additions=dict(abi_type="int48")) -int56 = _make_number_cls(56, int, dict_additions=dict(abi_type="int56")) -int64 = _make_number_cls(64, int, dict_additions=dict(abi_type="int64")) -int72 = _make_number_cls(72, int, dict_additions=dict(abi_type="int72")) -int80 = _make_number_cls(80, int, dict_additions=dict(abi_type="int80")) -int88 = _make_number_cls(88, int, dict_additions=dict(abi_type="int88")) -int96 = _make_number_cls(96, int, dict_additions=dict(abi_type="int96")) -int104 = _make_number_cls(104, int, dict_additions=dict(abi_type="int104")) -int112 = _make_number_cls(112, int, dict_additions=dict(abi_type="int112")) -int120 = _make_number_cls(120, int, dict_additions=dict(abi_type="int120")) -int128 = _make_number_cls(128, int, dict_additions=dict(abi_type="int128")) -int136 = _make_number_cls(136, int, dict_additions=dict(abi_type="int136")) -int144 = _make_number_cls(144, int, dict_additions=dict(abi_type="int144")) -int152 = _make_number_cls(152, int, dict_additions=dict(abi_type="int152")) -int160 = _make_number_cls(160, int, dict_additions=dict(abi_type="int160")) -int168 = _make_number_cls(168, int, dict_additions=dict(abi_type="int168")) -int176 = _make_number_cls(176, int, dict_additions=dict(abi_type="int176")) -int184 = _make_number_cls(184, int, dict_additions=dict(abi_type="int184")) -int192 = _make_number_cls(192, int, dict_additions=dict(abi_type="int192")) -int200 = _make_number_cls(200, int, dict_additions=dict(abi_type="int200")) -int208 = _make_number_cls(208, int, dict_additions=dict(abi_type="int208")) -int216 = _make_number_cls(216, int, dict_additions=dict(abi_type="int216")) -int224 = _make_number_cls(224, int, dict_additions=dict(abi_type="int224")) -int232 = _make_number_cls(232, int, dict_additions=dict(abi_type="int232")) -int240 = _make_number_cls(240, int, dict_additions=dict(abi_type="int240")) -int248 = _make_number_cls(248, int, dict_additions=dict(abi_type="int248")) -int256 = _make_number_cls(256, int, dict_additions=dict(abi_type="int256")) -uint8 = _make_number_cls(8, int, signed=False, dict_additions=dict(abi_type="uint8")) -uint16 = _make_number_cls(16, int, signed=False, dict_additions=dict(abi_type="uint16")) -uint24 = _make_number_cls(24, int, signed=False, dict_additions=dict(abi_type="uint24")) -uint32 = _make_number_cls(32, int, signed=False, dict_additions=dict(abi_type="uint32")) -uint40 = _make_number_cls(40, int, signed=False, dict_additions=dict(abi_type="uint40")) -uint48 = _make_number_cls(48, int, signed=False, dict_additions=dict(abi_type="uint48")) -uint56 = _make_number_cls(56, int, signed=False, dict_additions=dict(abi_type="uint56")) -uint64 = _make_number_cls(64, int, signed=False, dict_additions=dict(abi_type="uint64")) -uint72 = _make_number_cls(72, int, signed=False, dict_additions=dict(abi_type="uint72")) -uint80 = _make_number_cls(80, int, signed=False, dict_additions=dict(abi_type="uint80")) -uint88 = _make_number_cls(88, int, signed=False, dict_additions=dict(abi_type="uint88")) -uint96 = _make_number_cls(96, int, signed=False, dict_additions=dict(abi_type="uint96")) -uint104 = _make_number_cls(104, int, signed=False, dict_additions=dict(abi_type="uint104")) -uint112 = _make_number_cls(112, int, signed=False, dict_additions=dict(abi_type="uint112")) -uint120 = _make_number_cls(120, int, signed=False, dict_additions=dict(abi_type="uint120")) -uint128 = _make_number_cls(128, int, signed=False, dict_additions=dict(abi_type="uint128")) -uint136 = _make_number_cls(136, int, signed=False, dict_additions=dict(abi_type="uint136")) -uint144 = _make_number_cls(144, int, signed=False, dict_additions=dict(abi_type="uint144")) -uint152 = _make_number_cls(152, int, signed=False, dict_additions=dict(abi_type="uint152")) -uint160 = _make_number_cls(160, int, signed=False, dict_additions=dict(abi_type="uint160")) -uint168 = _make_number_cls(168, int, signed=False, dict_additions=dict(abi_type="uint168")) -uint176 = _make_number_cls(176, int, signed=False, dict_additions=dict(abi_type="uint176")) -uint184 = _make_number_cls(184, int, signed=False, dict_additions=dict(abi_type="uint184")) -uint192 = _make_number_cls(192, int, signed=False, dict_additions=dict(abi_type="uint192")) -uint200 = _make_number_cls(200, int, signed=False, dict_additions=dict(abi_type="uint200")) -uint208 = _make_number_cls(208, int, signed=False, dict_additions=dict(abi_type="uint208")) -uint216 = _make_number_cls(216, int, signed=False, dict_additions=dict(abi_type="uint216")) -uint224 = _make_number_cls(224, int, signed=False, dict_additions=dict(abi_type="uint224")) -uint232 = _make_number_cls(232, int, signed=False, dict_additions=dict(abi_type="uint232")) -uint240 = _make_number_cls(240, int, signed=False, dict_additions=dict(abi_type="uint240")) -uint248 = _make_number_cls(248, int, signed=False, dict_additions=dict(abi_type="uint248")) -uint256 = _make_number_cls(256, int, signed=False, dict_additions=dict(abi_type="uint256")) +int8 = Annotated[int, Field(lt=2**7, ge=-(2**7))] +int16 = Annotated[int, Field(lt=2**15, ge=-(2**15))] +int24 = Annotated[int, Field(lt=2**23, ge=-(2**23))] +int32 = Annotated[int, Field(lt=2**31, ge=-(2**31))] +int40 = Annotated[int, Field(lt=2**39, ge=-(2**39))] +int48 = Annotated[int, Field(lt=2**47, ge=-(2**47))] +int56 = Annotated[int, Field(lt=2**55, ge=-(2**55))] +int64 = Annotated[int, Field(lt=2**63, ge=-(2**63))] +int72 = Annotated[int, Field(lt=2**71, ge=-(2**71))] +int80 = Annotated[int, Field(lt=2**79, ge=-(2**79))] +int88 = Annotated[int, Field(lt=2**87, ge=-(2**87))] +int96 = Annotated[int, Field(lt=2**95, ge=-(2**95))] +int104 = Annotated[int, Field(lt=2**103, ge=-(2**103))] +int112 = Annotated[int, Field(lt=2**111, ge=-(2**111))] +int120 = Annotated[int, Field(lt=2**119, ge=-(2**119))] +int128 = Annotated[int, Field(lt=2**127, ge=-(2**127))] +int136 = Annotated[int, Field(lt=2**135, ge=-(2**135))] +int144 = Annotated[int, Field(lt=2**143, ge=-(2**143))] +int152 = Annotated[int, Field(lt=2**151, ge=-(2**151))] +int160 = Annotated[int, Field(lt=2**159, ge=-(2**159))] +int168 = Annotated[int, Field(lt=2**167, ge=-(2**167))] +int176 = Annotated[int, Field(lt=2**175, ge=-(2**175))] +int184 = Annotated[int, Field(lt=2**183, ge=-(2**183))] +int192 = Annotated[int, Field(lt=2**191, ge=-(2**191))] +int200 = Annotated[int, Field(lt=2**199, ge=-(2**199))] +int208 = Annotated[int, Field(lt=2**207, ge=-(2**207))] +int216 = Annotated[int, Field(lt=2**215, ge=-(2**215))] +int224 = Annotated[int, Field(lt=2**223, ge=-(2**223))] +int232 = Annotated[int, Field(lt=2**231, ge=-(2**231))] +int240 = Annotated[int, Field(lt=2**239, ge=-(2**239))] +int248 = Annotated[int, Field(lt=2**247, ge=-(2**247))] +int256 = Annotated[int, Field(lt=2**255, ge=-(2**255))] +uint8 = Annotated[int, Field(lt=2**8, ge=0)] +uint16 = Annotated[int, Field(lt=2**16, ge=0)] +uint24 = Annotated[int, Field(lt=2**24, ge=0)] +uint32 = Annotated[int, Field(lt=2**32, ge=0)] +uint40 = Annotated[int, Field(lt=2**40, ge=0)] +uint48 = Annotated[int, Field(lt=2**48, ge=0)] +uint56 = Annotated[int, Field(lt=2**56, ge=0)] +uint64 = Annotated[int, Field(lt=2**64, ge=0)] +uint72 = Annotated[int, Field(lt=2**72, ge=0)] +uint80 = Annotated[int, Field(lt=2**80, ge=0)] +uint88 = Annotated[int, Field(lt=2**88, ge=0)] +uint96 = Annotated[int, Field(lt=2**96, ge=0)] +uint104 = Annotated[int, Field(lt=2**104, ge=0)] +uint112 = Annotated[int, Field(lt=2**112, ge=0)] +uint120 = Annotated[int, Field(lt=2**120, ge=0)] +uint128 = Annotated[int, Field(lt=2**128, ge=0)] +uint136 = Annotated[int, Field(lt=2**136, ge=0)] +uint144 = Annotated[int, Field(lt=2**144, ge=0)] +uint152 = Annotated[int, Field(lt=2**152, ge=0)] +uint160 = Annotated[int, Field(lt=2**160, ge=0)] +uint168 = Annotated[int, Field(lt=2**168, ge=0)] +uint176 = Annotated[int, Field(lt=2**176, ge=0)] +uint184 = Annotated[int, Field(lt=2**184, ge=0)] +uint192 = Annotated[int, Field(lt=2**192, ge=0)] +uint200 = Annotated[int, Field(lt=2**200, ge=0)] +uint208 = Annotated[int, Field(lt=2**208, ge=0)] +uint216 = Annotated[int, Field(lt=2**216, ge=0)] +uint224 = Annotated[int, Field(lt=2**224, ge=0)] +uint232 = Annotated[int, Field(lt=2**232, ge=0)] +uint240 = Annotated[int, Field(lt=2**240, ge=0)] +uint248 = Annotated[int, Field(lt=2**248, ge=0)] +uint256 = Annotated[int, Field(lt=2**256, ge=0)] diff --git a/eth_pydantic_types/hex.py b/eth_pydantic_types/hex.py index 79f6639..fc546cc 100644 --- a/eth_pydantic_types/hex.py +++ b/eth_pydantic_types/hex.py @@ -1,8 +1,9 @@ -from typing import Any, ClassVar, Optional, Tuple, Type, Union, cast +from typing import Annotated, Any, ClassVar, Optional, Tuple, TypeAlias, Union, cast from eth_typing import HexStr as EthTypingHexStr from eth_utils import add_0x_prefix -from hexbytes import HexBytes as BaseHexBytes +from hexbytes.main import HexBytes as BaseHexBytes +from pydantic import Field from pydantic_core import CoreSchema from pydantic_core.core_schema import ( ValidationInfo, @@ -53,7 +54,7 @@ class HexBytes(BaseHexBytes, BaseHex): def __get_pydantic_core_schema__(cls, value, handle=None) -> CoreSchema: schema = with_info_before_validator_function( cls.__eth_pydantic_validate__, - bytes_schema(max_length=cls.size, min_length=cls.size) if cls.bound else bytes_schema(), + bytes_schema(), ) schema["serialization"] = hex_serializer return schema @@ -74,6 +75,34 @@ def validate_size(cls, value: bytes) -> bytes: return validate_bytes_size(value, cls.size) if cls.bound else value +class BoundHexBytes(HexBytes): + size: ClassVar[int] = 32 + bound: bool = True + + @classmethod + def __get_pydantic_core_schema__(cls, value, handle=None) -> CoreSchema: + schema = with_info_before_validator_function( + cls.__eth_pydantic_validate__, + bytes_schema(max_length=cls.size, min_length=cls.size) if cls.bound else bytes_schema(), + ) + schema["serialization"] = hex_serializer + return schema + + @classmethod + def __eth_pydantic_validate__( + cls, value: Any, info: Optional[ValidationInfo] = None + ) -> BaseHexBytes: + return cls(cls.validate_size(HexBytes(value))) + + @classmethod + def validate_size(cls, value: bytes) -> bytes: + return validate_bytes_size(value, cls.size) if cls.bound else value + + +HexBytes20: TypeAlias = Annotated[BoundHexBytes, Field(max_length=20)] +HexBytes32: TypeAlias = Annotated[BoundHexBytes, Field(max_length=32)] + + class BaseHexStr(str, BaseHex): @classmethod def __get_pydantic_core_schema__(cls, value, handler=None): @@ -137,6 +166,10 @@ def from_bytes(cls, data: bytes) -> "HexStr": return HexStr(value) +HexStr20: TypeAlias = Annotated[HexStr, Field(max_length=20)] +HexStr32: TypeAlias = Annotated[HexStr, Field(max_length=32)] + + def validate_hex_str(value: str) -> str: hex_value = (value[2:] if value.startswith("0x") else value).lower() if set(hex_value) - set("1234567890abcdef"): @@ -159,37 +192,3 @@ def get_hash_examples(str_size: int) -> Tuple[str, str, str, str]: trailing_zero = f"0x{'1e' * ((str_size - 1) // 2)}10" full_hash = f"0x{'1e' * (str_size // 2)}" return zero_hash, leading_zero, trailing_zero, full_hash - - -def _make_cls(size: int, base_type: Type, prefix: str = "", dict_additions: dict = {}): - str_size = size * 2 - display = "Bytes" if issubclass(base_type, bytes) else "Str" - base_type = HexBytes if issubclass(base_type, bytes) else HexStr - type_dict = dict( - bound=True, - size=size, - schema_pattern=get_hash_pattern(str_size), - schema_examples=get_hash_examples(str_size), - **dict_additions, - ) - return type( - f"{prefix}{display}{size}", - (base_type,), - type_dict, - ) - - -# Creates bound variations e.g. HexStr16, HexStr64 -HexBytes1 = _make_cls(1, bytes, prefix="Hex") -HexBytes4 = _make_cls(4, bytes, prefix="Hex") -HexBytes8 = _make_cls(8, bytes, prefix="Hex") -HexBytes16 = _make_cls(16, bytes, prefix="Hex") -HexBytes20 = _make_cls(20, bytes, prefix="Hex") -HexBytes32 = _make_cls(32, bytes, prefix="Hex") -HexBytes64 = _make_cls(64, bytes, prefix="Hex") -HexStr4 = _make_cls(4, str, prefix="Hex") -HexStr8 = _make_cls(8, str, prefix="Hex") -HexStr16 = _make_cls(16, str, prefix="Hex") -HexStr20 = _make_cls(20, str, prefix="Hex") -HexStr32 = _make_cls(32, str, prefix="Hex") -HexStr64 = _make_cls(64, str, prefix="Hex") diff --git a/eth_pydantic_types/numbers.py b/eth_pydantic_types/numbers.py deleted file mode 100644 index 322b413..0000000 --- a/eth_pydantic_types/numbers.py +++ /dev/null @@ -1,72 +0,0 @@ -from typing import Any, ClassVar, Optional, Type - -from pydantic_core.core_schema import ( - CoreSchema, - ValidationInfo, - int_schema, - with_info_before_validator_function, -) - -from eth_pydantic_types.validators import validate_int_size - - -class BaseInt(int): - size: ClassVar[int] = 256 - signed: ClassVar[bool] = True - - @classmethod - def __get_pydantic_core_schema__(cls, value, handler=None) -> CoreSchema: - return with_info_before_validator_function(cls.__eth_pydantic_validate__, int_schema()) - - @classmethod - def __eth_pydantic_validate__(cls, value: Any, info: Optional[ValidationInfo] = None) -> int: - return cls(cls.validate_size(int(value))) - - @classmethod - def validate_size(cls, value: int) -> int: - return validate_int_size(value, cls.size, cls.signed) - - -class Int(BaseInt): - """ - Represents an integer. - This type is meant to be overridden by the larger types with a new size. - e.g. Int32, Int64. - """ - - -class UInt(BaseInt): - """ - Represents an unsigned integer. - This type is meant to be overridden by the larger types with a new size. - e.g. UInt32, UInt64. - """ - - signed = False - - -def _make_cls( - size: int, base_type: Type, signed: bool = True, prefix: str = "", dict_additions: dict = {} -): - if issubclass(base_type, int): - display = "Int" if signed else "UInt" - - return type( - f"{prefix}{display}{size}", - (Int if signed else UInt,), - dict(size=size, signed=signed, **dict_additions), - ) - - -Int8 = _make_cls(8, int) -Int16 = _make_cls(16, int) -Int32 = _make_cls(32, int) -Int64 = _make_cls(64, int) -Int128 = _make_cls(128, int) -Int256 = _make_cls(256, int) -UInt8 = _make_cls(8, int, signed=False) -UInt16 = _make_cls(16, int, signed=False) -UInt32 = _make_cls(32, int, signed=False) -UInt64 = _make_cls(64, int, signed=False) -UInt128 = _make_cls(128, int, signed=False) -UInt256 = _make_cls(256, int, signed=False) diff --git a/tests/test_hash.py b/tests/test_hash.py index 966c539..237812a 100644 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -1,43 +1,20 @@ import pytest from pydantic import BaseModel, ValidationError -from eth_pydantic_types.hex import ( - HexBytes, - HexBytes8, - HexBytes16, - HexBytes20, - HexBytes32, - HexBytes64, - HexStr8, - HexStr16, - HexStr32, - HexStr64, -) +from eth_pydantic_types.hex import HexBytes, HexBytes20, HexBytes32, HexStr32 class Model(BaseModel): - valuebytes8: HexBytes8 - valuebytes16: HexBytes16 valuebytes20: HexBytes20 valuebytes32: HexBytes32 - valuebytes64: HexBytes64 - valuestr8: HexStr8 - valuestr16: HexStr16 valuestr32: HexStr32 - valuestr64: HexStr64 @classmethod def from_single(cls, value): return cls( - valuebytes8=value, - valuebytes16=value, valuebytes20=value, valuebytes32=value, - valuebytes64=value, - valuestr8=value, - valuestr16=value, valuestr32=value, - valuestr64=value, ) @@ -55,23 +32,12 @@ def test_hashbytes_is_bytes(bytes32str): @pytest.mark.parametrize("value", ("0x32", HexBytes("0x32"), b"2", 50)) def test_hash(value): model = Model.from_single(value) - assert len(model.valuebytes8) == 8 - assert len(model.valuebytes16) == 16 assert len(model.valuebytes20) == 20 assert len(model.valuebytes32) == 32 - assert len(model.valuebytes64) == 64 - assert len(model.valuestr8) == 18 - assert len(model.valuestr16) == 34 assert len(model.valuestr32) == 66 - assert len(model.valuestr64) == 130 - assert model.valuebytes8.hex().endswith("32") - assert model.valuebytes16.hex().endswith("32") + assert model.valuebytes20.hex().endswith("32") assert model.valuebytes32.hex().endswith("32") - assert model.valuestr64.endswith("32") - assert model.valuestr8.endswith("32") - assert model.valuestr16.endswith("32") assert model.valuestr32.endswith("32") - assert model.valuestr64.endswith("32") @pytest.mark.parametrize("value", ("foo", -35, "0x" + ("F" * 100))) @@ -122,14 +88,8 @@ def test_model_dump(bytes32str): model = Model.from_single(5) actual = model.model_dump() expected = { - "valuebytes8": "0000000000000005", - "valuestr8": "0x0000000000000005", - "valuebytes16": "00000000000000000000000000000005", "valuebytes20": "0000000000000000000000000000000000000005", - "valuestr16": "0x00000000000000000000000000000005", "valuebytes32": "0000000000000000000000000000000000000000000000000000000000000005", "valuestr32": "0x0000000000000000000000000000000000000000000000000000000000000005", - "valuebytes64": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005", # noqa: E501 - "valuestr64": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005", # noqa: E501 } assert actual == expected diff --git a/tests/test_int.py b/tests/test_int.py index b3b1bb0..ca9ee32 100644 --- a/tests/test_int.py +++ b/tests/test_int.py @@ -1,29 +1,29 @@ import pytest from pydantic import BaseModel, ValidationError -from eth_pydantic_types.numbers import ( - Int8, - Int16, - Int32, - Int64, - Int128, - Int256, - UInt8, - UInt16, - UInt32, - UInt64, - UInt128, - UInt256, +from eth_pydantic_types.abi import ( + int8, + int16, + int32, + int64, + int128, + int256, + uint8, + uint16, + uint32, + uint64, + uint128, + uint256, ) class SignedModel(BaseModel): - valueint8: Int8 - valueint16: Int16 - valueint32: Int32 - valueint64: Int64 - valueint128: Int128 - valueint256: Int256 + valueint8: int8 + valueint16: int16 + valueint32: int32 + valueint64: int64 + valueint128: int128 + valueint256: int256 @classmethod def from_single(cls, value): @@ -38,12 +38,12 @@ def from_single(cls, value): class UnsignedModel(BaseModel): - valueuint8: UInt8 - valueuint16: UInt16 - valueuint32: UInt32 - valueuint64: UInt64 - valueuint128: UInt128 - valueuint256: UInt256 + valueuint8: uint8 + valueuint16: uint16 + valueuint32: uint32 + valueuint64: uint64 + valueuint128: uint128 + valueuint256: uint256 @classmethod def from_single(cls, value): From 6440db5e3d30c3f6a562313843a0d87c2058406a Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 10 Jul 2024 20:47:23 -0500 Subject: [PATCH 13/25] feat: adds in full collection of abi-allowed int and bytes sizes --- eth_pydantic_types/abi.py | 308 +++++++++++------- eth_pydantic_types/address.py | 15 +- eth_pydantic_types/hex.py | 89 +++-- .../{validators.py => utils.py} | 28 +- tests/test_hash.py | 1 + 5 files changed, 293 insertions(+), 148 deletions(-) rename eth_pydantic_types/{validators.py => utils.py} (69%) diff --git a/eth_pydantic_types/abi.py b/eth_pydantic_types/abi.py index d41ca7c..39eb919 100644 --- a/eth_pydantic_types/abi.py +++ b/eth_pydantic_types/abi.py @@ -6,117 +6,205 @@ from hexbytes import HexBytes from pydantic import Field -from typing_extensions import TypeAlias +from typing_extensions import TypeAliasType from .address import Address from .hex import BoundHexBytes -bytes: TypeAlias = HexBytes -string: TypeAlias = str - -bytes1 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=1))] -bytes2 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=2))] -bytes3 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=3))] -bytes4 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=4))] -bytes5 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=5))] -bytes6 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=6))] -bytes7 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=7))] -bytes8 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=8))] -bytes9 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=9))] -bytes10 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=10))] -bytes11 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=11))] -bytes12 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=12))] -bytes13 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=13))] -bytes14 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=14))] -bytes15 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=15))] -bytes16 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=16))] -bytes17 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=17))] -bytes18 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=18))] -bytes19 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=19))] -bytes20 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=20))] -bytes21 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=21))] -bytes22 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=22))] -bytes23 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=23))] -bytes24 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=24))] -bytes25 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=25))] -bytes26 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=26))] -bytes27 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=27))] -bytes28 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=28))] -bytes29 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=29))] -bytes30 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=30))] -bytes31 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=31))] -bytes32 = Annotated[BoundHexBytes, Field(default=BoundHexBytes(size=32))] - - -class address(Address): - """ - Represents a 20 character hex string address. - """ - - abi_type: ClassVar[str] = "address" - - -int8 = Annotated[int, Field(lt=2**7, ge=-(2**7))] -int16 = Annotated[int, Field(lt=2**15, ge=-(2**15))] -int24 = Annotated[int, Field(lt=2**23, ge=-(2**23))] -int32 = Annotated[int, Field(lt=2**31, ge=-(2**31))] -int40 = Annotated[int, Field(lt=2**39, ge=-(2**39))] -int48 = Annotated[int, Field(lt=2**47, ge=-(2**47))] -int56 = Annotated[int, Field(lt=2**55, ge=-(2**55))] -int64 = Annotated[int, Field(lt=2**63, ge=-(2**63))] -int72 = Annotated[int, Field(lt=2**71, ge=-(2**71))] -int80 = Annotated[int, Field(lt=2**79, ge=-(2**79))] -int88 = Annotated[int, Field(lt=2**87, ge=-(2**87))] -int96 = Annotated[int, Field(lt=2**95, ge=-(2**95))] -int104 = Annotated[int, Field(lt=2**103, ge=-(2**103))] -int112 = Annotated[int, Field(lt=2**111, ge=-(2**111))] -int120 = Annotated[int, Field(lt=2**119, ge=-(2**119))] -int128 = Annotated[int, Field(lt=2**127, ge=-(2**127))] -int136 = Annotated[int, Field(lt=2**135, ge=-(2**135))] -int144 = Annotated[int, Field(lt=2**143, ge=-(2**143))] -int152 = Annotated[int, Field(lt=2**151, ge=-(2**151))] -int160 = Annotated[int, Field(lt=2**159, ge=-(2**159))] -int168 = Annotated[int, Field(lt=2**167, ge=-(2**167))] -int176 = Annotated[int, Field(lt=2**175, ge=-(2**175))] -int184 = Annotated[int, Field(lt=2**183, ge=-(2**183))] -int192 = Annotated[int, Field(lt=2**191, ge=-(2**191))] -int200 = Annotated[int, Field(lt=2**199, ge=-(2**199))] -int208 = Annotated[int, Field(lt=2**207, ge=-(2**207))] -int216 = Annotated[int, Field(lt=2**215, ge=-(2**215))] -int224 = Annotated[int, Field(lt=2**223, ge=-(2**223))] -int232 = Annotated[int, Field(lt=2**231, ge=-(2**231))] -int240 = Annotated[int, Field(lt=2**239, ge=-(2**239))] -int248 = Annotated[int, Field(lt=2**247, ge=-(2**247))] -int256 = Annotated[int, Field(lt=2**255, ge=-(2**255))] -uint8 = Annotated[int, Field(lt=2**8, ge=0)] -uint16 = Annotated[int, Field(lt=2**16, ge=0)] -uint24 = Annotated[int, Field(lt=2**24, ge=0)] -uint32 = Annotated[int, Field(lt=2**32, ge=0)] -uint40 = Annotated[int, Field(lt=2**40, ge=0)] -uint48 = Annotated[int, Field(lt=2**48, ge=0)] -uint56 = Annotated[int, Field(lt=2**56, ge=0)] -uint64 = Annotated[int, Field(lt=2**64, ge=0)] -uint72 = Annotated[int, Field(lt=2**72, ge=0)] -uint80 = Annotated[int, Field(lt=2**80, ge=0)] -uint88 = Annotated[int, Field(lt=2**88, ge=0)] -uint96 = Annotated[int, Field(lt=2**96, ge=0)] -uint104 = Annotated[int, Field(lt=2**104, ge=0)] -uint112 = Annotated[int, Field(lt=2**112, ge=0)] -uint120 = Annotated[int, Field(lt=2**120, ge=0)] -uint128 = Annotated[int, Field(lt=2**128, ge=0)] -uint136 = Annotated[int, Field(lt=2**136, ge=0)] -uint144 = Annotated[int, Field(lt=2**144, ge=0)] -uint152 = Annotated[int, Field(lt=2**152, ge=0)] -uint160 = Annotated[int, Field(lt=2**160, ge=0)] -uint168 = Annotated[int, Field(lt=2**168, ge=0)] -uint176 = Annotated[int, Field(lt=2**176, ge=0)] -uint184 = Annotated[int, Field(lt=2**184, ge=0)] -uint192 = Annotated[int, Field(lt=2**192, ge=0)] -uint200 = Annotated[int, Field(lt=2**200, ge=0)] -uint208 = Annotated[int, Field(lt=2**208, ge=0)] -uint216 = Annotated[int, Field(lt=2**216, ge=0)] -uint224 = Annotated[int, Field(lt=2**224, ge=0)] -uint232 = Annotated[int, Field(lt=2**232, ge=0)] -uint240 = Annotated[int, Field(lt=2**240, ge=0)] -uint248 = Annotated[int, Field(lt=2**248, ge=0)] -uint256 = Annotated[int, Field(lt=2**256, ge=0)] +bytes = TypeAliasType("bytes", HexBytes) +string = TypeAliasType("string", str) +address = TypeAliasType("address", Address) + + +class bytes1(BoundHexBytes): + size: ClassVar[int] = 1 + + +class bytes2(BoundHexBytes): + size: ClassVar[int] = 2 + + +class bytes3(BoundHexBytes): + size: ClassVar[int] = 3 + + +class bytes4(BoundHexBytes): + size: ClassVar[int] = 4 + + +class bytes5(BoundHexBytes): + size: ClassVar[int] = 5 + + +class bytes6(BoundHexBytes): + size: ClassVar[int] = 6 + + +class bytes7(BoundHexBytes): + size: ClassVar[int] = 7 + + +class bytes8(BoundHexBytes): + size: ClassVar[int] = 8 + + +class bytes9(BoundHexBytes): + size: ClassVar[int] = 9 + + +class bytes10(BoundHexBytes): + size: ClassVar[int] = 10 + + +class bytes11(BoundHexBytes): + size: ClassVar[int] = 11 + + +class bytes12(BoundHexBytes): + size: ClassVar[int] = 12 + + +class bytes13(BoundHexBytes): + size: ClassVar[int] = 13 + + +class bytes14(BoundHexBytes): + size: ClassVar[int] = 14 + + +class bytes15(BoundHexBytes): + size: ClassVar[int] = 15 + + +class bytes16(BoundHexBytes): + size: ClassVar[int] = 16 + + +class bytes17(BoundHexBytes): + size: ClassVar[int] = 17 + + +class bytes18(BoundHexBytes): + size: ClassVar[int] = 18 + + +class bytes19(BoundHexBytes): + size: ClassVar[int] = 19 + + +class bytes20(BoundHexBytes): + size: ClassVar[int] = 20 + + +class bytes21(BoundHexBytes): + size: ClassVar[int] = 21 + + +class bytes22(BoundHexBytes): + size: ClassVar[int] = 22 + + +class bytes23(BoundHexBytes): + size: ClassVar[int] = 23 + + +class bytes24(BoundHexBytes): + size: ClassVar[int] = 24 + + +class bytes25(BoundHexBytes): + size: ClassVar[int] = 25 + + +class bytes26(BoundHexBytes): + size: ClassVar[int] = 26 + + +class bytes27(BoundHexBytes): + size: ClassVar[int] = 27 + + +class bytes28(BoundHexBytes): + size: ClassVar[int] = 28 + + +class bytes29(BoundHexBytes): + size: ClassVar[int] = 29 + + +class bytes30(BoundHexBytes): + size: ClassVar[int] = 30 + + +class bytes31(BoundHexBytes): + size: ClassVar[int] = 31 + + +class bytes32(BoundHexBytes): + size: ClassVar[int] = 32 + + +int8 = TypeAliasType("int8", Annotated[int, Field(lt=2**7, ge=-(2**7))]) +int16 = TypeAliasType("int16", Annotated[int, Field(lt=2**15, ge=-(2**15))]) +int24 = TypeAliasType("int24", Annotated[int, Field(lt=2**23, ge=-(2**23))]) +int32 = TypeAliasType("int32", Annotated[int, Field(lt=2**31, ge=-(2**31))]) +int40 = TypeAliasType("int40", Annotated[int, Field(lt=2**39, ge=-(2**39))]) +int48 = TypeAliasType("int48", Annotated[int, Field(lt=2**47, ge=-(2**47))]) +int56 = TypeAliasType("int56", Annotated[int, Field(lt=2**55, ge=-(2**55))]) +int64 = TypeAliasType("int64", Annotated[int, Field(lt=2**63, ge=-(2**63))]) +int72 = TypeAliasType("int72", Annotated[int, Field(lt=2**71, ge=-(2**71))]) +int80 = TypeAliasType("int80", Annotated[int, Field(lt=2**79, ge=-(2**79))]) +int88 = TypeAliasType("int88", Annotated[int, Field(lt=2**87, ge=-(2**87))]) +int96 = TypeAliasType("int96", Annotated[int, Field(lt=2**95, ge=-(2**95))]) +int104 = TypeAliasType("int104", Annotated[int, Field(lt=2**103, ge=-(2**103))]) +int112 = TypeAliasType("int112", Annotated[int, Field(lt=2**111, ge=-(2**111))]) +int120 = TypeAliasType("int120", Annotated[int, Field(lt=2**119, ge=-(2**119))]) +int128 = TypeAliasType("int128", Annotated[int, Field(lt=2**127, ge=-(2**127))]) +int136 = TypeAliasType("int136", Annotated[int, Field(lt=2**135, ge=-(2**135))]) +int144 = TypeAliasType("int144", Annotated[int, Field(lt=2**143, ge=-(2**143))]) +int152 = TypeAliasType("int152", Annotated[int, Field(lt=2**151, ge=-(2**151))]) +int160 = TypeAliasType("int160", Annotated[int, Field(lt=2**159, ge=-(2**159))]) +int168 = TypeAliasType("int168", Annotated[int, Field(lt=2**167, ge=-(2**167))]) +int176 = TypeAliasType("int176", Annotated[int, Field(lt=2**175, ge=-(2**175))]) +int184 = TypeAliasType("int184", Annotated[int, Field(lt=2**183, ge=-(2**183))]) +int192 = TypeAliasType("int192", Annotated[int, Field(lt=2**191, ge=-(2**191))]) +int200 = TypeAliasType("int200", Annotated[int, Field(lt=2**199, ge=-(2**199))]) +int208 = TypeAliasType("int208", Annotated[int, Field(lt=2**207, ge=-(2**207))]) +int216 = TypeAliasType("int216", Annotated[int, Field(lt=2**215, ge=-(2**215))]) +int224 = TypeAliasType("int224", Annotated[int, Field(lt=2**223, ge=-(2**223))]) +int232 = TypeAliasType("int232", Annotated[int, Field(lt=2**231, ge=-(2**231))]) +int240 = TypeAliasType("int240", Annotated[int, Field(lt=2**239, ge=-(2**239))]) +int248 = TypeAliasType("int248", Annotated[int, Field(lt=2**247, ge=-(2**247))]) +int256 = TypeAliasType("int256", Annotated[int, Field(lt=2**255, ge=-(2**255))]) +uint8 = TypeAliasType("uint8", Annotated[int, Field(lt=2**8, ge=0)]) +uint16 = TypeAliasType("uint16", Annotated[int, Field(lt=2**16, ge=0)]) +uint24 = TypeAliasType("uint24", Annotated[int, Field(lt=2**24, ge=0)]) +uint32 = TypeAliasType("uint32", Annotated[int, Field(lt=2**32, ge=0)]) +uint40 = TypeAliasType("uint40", Annotated[int, Field(lt=2**40, ge=0)]) +uint48 = TypeAliasType("uint48", Annotated[int, Field(lt=2**48, ge=0)]) +uint56 = TypeAliasType("uint56", Annotated[int, Field(lt=2**56, ge=0)]) +uint64 = TypeAliasType("uint64", Annotated[int, Field(lt=2**64, ge=0)]) +uint72 = TypeAliasType("uint72", Annotated[int, Field(lt=2**72, ge=0)]) +uint80 = TypeAliasType("uint80", Annotated[int, Field(lt=2**80, ge=0)]) +uint88 = TypeAliasType("uint88", Annotated[int, Field(lt=2**88, ge=0)]) +uint96 = TypeAliasType("uint96", Annotated[int, Field(lt=2**96, ge=0)]) +uint104 = TypeAliasType("uint104", Annotated[int, Field(lt=2**104, ge=0)]) +uint112 = TypeAliasType("uint112", Annotated[int, Field(lt=2**112, ge=0)]) +uint120 = TypeAliasType("uint120", Annotated[int, Field(lt=2**120, ge=0)]) +uint128 = TypeAliasType("uint128", Annotated[int, Field(lt=2**128, ge=0)]) +uint136 = TypeAliasType("uint136", Annotated[int, Field(lt=2**136, ge=0)]) +uint144 = TypeAliasType("uint144", Annotated[int, Field(lt=2**144, ge=0)]) +uint152 = TypeAliasType("uint152", Annotated[int, Field(lt=2**152, ge=0)]) +uint160 = TypeAliasType("uint160", Annotated[int, Field(lt=2**160, ge=0)]) +uint168 = TypeAliasType("uint168", Annotated[int, Field(lt=2**168, ge=0)]) +uint176 = TypeAliasType("uint176", Annotated[int, Field(lt=2**176, ge=0)]) +uint184 = TypeAliasType("uint184", Annotated[int, Field(lt=2**184, ge=0)]) +uint192 = TypeAliasType("uint192", Annotated[int, Field(lt=2**192, ge=0)]) +uint200 = TypeAliasType("uint200", Annotated[int, Field(lt=2**200, ge=0)]) +uint208 = TypeAliasType("uint208", Annotated[int, Field(lt=2**208, ge=0)]) +uint216 = TypeAliasType("uint216", Annotated[int, Field(lt=2**216, ge=0)]) +uint224 = TypeAliasType("uint224", Annotated[int, Field(lt=2**224, ge=0)]) +uint232 = TypeAliasType("uint232", Annotated[int, Field(lt=2**232, ge=0)]) +uint240 = TypeAliasType("uint240", Annotated[int, Field(lt=2**240, ge=0)]) +uint248 = TypeAliasType("uint248", Annotated[int, Field(lt=2**248, ge=0)]) +uint256 = TypeAliasType("uint256", Annotated[int, Field(lt=2**256, ge=0)]) diff --git a/eth_pydantic_types/address.py b/eth_pydantic_types/address.py index 771e9e9..391884f 100644 --- a/eth_pydantic_types/address.py +++ b/eth_pydantic_types/address.py @@ -2,7 +2,12 @@ from eth_typing import ChecksumAddress from eth_utils import is_checksum_address, to_checksum_address -from pydantic_core.core_schema import ValidationInfo, str_schema +from pydantic_core import CoreSchema +from pydantic_core.core_schema import ( + ValidationInfo, + str_schema, + with_info_before_validator_function, +) from typing_extensions import Annotated from .hex import HexStr20 @@ -27,6 +32,14 @@ class Address(HexStr20): "0xa5a13f62ce1113838e0d9b4559b8caf5f76463c0", # Trailing zero "0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C", ) + calculate_schema: ClassVar[bool] = False # use schema defined here, not one calculated by size + + @classmethod + def __get_pydantic_core_schema__(cls, value, handler=None) -> CoreSchema: + return with_info_before_validator_function( + cls.__eth_pydantic_validate__, + address_schema(), + ) @classmethod def __eth_pydantic_validate__(cls, value: Any, info: Optional[ValidationInfo] = None) -> str: diff --git a/eth_pydantic_types/hex.py b/eth_pydantic_types/hex.py index fc546cc..79fd8d5 100644 --- a/eth_pydantic_types/hex.py +++ b/eth_pydantic_types/hex.py @@ -1,9 +1,8 @@ -from typing import Annotated, Any, ClassVar, Optional, Tuple, TypeAlias, Union, cast +from typing import Any, ClassVar, Optional, Tuple, TypeAlias, Union, cast from eth_typing import HexStr as EthTypingHexStr from eth_utils import add_0x_prefix from hexbytes.main import HexBytes as BaseHexBytes -from pydantic import Field from pydantic_core import CoreSchema from pydantic_core.core_schema import ( ValidationInfo, @@ -15,7 +14,13 @@ from eth_pydantic_types._error import HexValueError from eth_pydantic_types.serializers import hex_serializer -from eth_pydantic_types.validators import validate_bytes_size, validate_str_size +from eth_pydantic_types.utils import ( + get_hash_examples, + get_hash_pattern, + validate_bytes_size, + validate_hex_str, + validate_str_size, +) schema_pattern = "^0x([0-9a-f][0-9a-f])*$" schema_examples = ( @@ -30,7 +35,6 @@ class BaseHex: - bound: bool = False size: ClassVar[int] = 0 schema_pattern: ClassVar[str] = schema_pattern schema_examples: ClassVar[Tuple[str, ...]] = schema_examples @@ -72,18 +76,17 @@ def __eth_pydantic_validate__( @classmethod def validate_size(cls, value: bytes) -> bytes: - return validate_bytes_size(value, cls.size) if cls.bound else value + return value class BoundHexBytes(HexBytes): size: ClassVar[int] = 32 - bound: bool = True @classmethod def __get_pydantic_core_schema__(cls, value, handle=None) -> CoreSchema: schema = with_info_before_validator_function( cls.__eth_pydantic_validate__, - bytes_schema(max_length=cls.size, min_length=cls.size) if cls.bound else bytes_schema(), + bytes_schema(max_length=cls.size, min_length=cls.size), ) schema["serialization"] = hex_serializer return schema @@ -96,11 +99,20 @@ def __eth_pydantic_validate__( @classmethod def validate_size(cls, value: bytes) -> bytes: - return validate_bytes_size(value, cls.size) if cls.bound else value + str_size = cls.size * 2 + cls.schema_pattern = get_hash_pattern(str_size) + cls.schema_examples = get_hash_examples(str_size) + return validate_bytes_size(value, cls.size) + + +class HexBytes20(BoundHexBytes): + size: ClassVar[int] = 20 + +HexBytes32: TypeAlias = BoundHexBytes -HexBytes20: TypeAlias = Annotated[BoundHexBytes, Field(max_length=20)] -HexBytes32: TypeAlias = Annotated[BoundHexBytes, Field(max_length=32)] +# HexBytes20: TypeAlias = Annotated[BoundHexBytes, Field(max_length=20)] +# HexBytes32: TypeAlias = Annotated[BoundHexBytes, Field(max_length=32)] class BaseHexStr(str, BaseHex): @@ -142,23 +154,18 @@ class HexStr(BaseHexStr): @classmethod 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) if cls.bound else str_schema(), + str_schema(), ) @classmethod 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) if cls.bound else hex_value + sized_value = hex_value return cls(f"0x{sized_value}") - @classmethod - def validate_size(cls, value: str) -> str: - return validate_str_size(value, cls.size * 2) - @classmethod def from_bytes(cls, data: bytes) -> "HexStr": value_str = super().from_bytes(data) @@ -166,29 +173,41 @@ def from_bytes(cls, data: bytes) -> "HexStr": return HexStr(value) -HexStr20: TypeAlias = Annotated[HexStr, Field(max_length=20)] -HexStr32: TypeAlias = Annotated[HexStr, Field(max_length=32)] +class BoundHexStr(BaseHexStr): + """A hex string value, typically from a hash.""" + + size: ClassVar[int] = 32 + calculate_schema: ClassVar[bool] = True + @classmethod + 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), + ) -def validate_hex_str(value: str) -> str: - hex_value = (value[2:] if value.startswith("0x") else value).lower() - if set(hex_value) - set("1234567890abcdef"): - raise HexValueError(value) + @classmethod + 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) + return cls(f"0x{sized_value}") - # Missing zero padding. - if len(hex_value) % 2 != 0: - hex_value = f"0{hex_value}" + @classmethod + def validate_size(cls, value: str) -> str: + if cls.calculate_schema: + str_size = cls.size * 2 + cls.schema_pattern = get_hash_pattern(str_size) + cls.schema_examples = get_hash_examples(str_size) + return validate_str_size(value, cls.size * 2) - return f"0x{hex_value}" +class HexStr20(BoundHexStr): + size: ClassVar[int] = 20 -def get_hash_pattern(str_size: int) -> str: - return f"^0x[a-fA-F0-9]{{{str_size}}}$" +HexStr32: TypeAlias = BoundHexStr -def get_hash_examples(str_size: int) -> Tuple[str, str, str, str]: - zero_hash = f"0x{'0' * str_size}" - leading_zero = f"0x01{'1e' * ((str_size - 1) // 2)}" - trailing_zero = f"0x{'1e' * ((str_size - 1) // 2)}10" - full_hash = f"0x{'1e' * (str_size // 2)}" - return zero_hash, leading_zero, trailing_zero, full_hash +# HexStr20: TypeAlias = Annotated[HexStr, Field(max_length=20)] +# HexStr32: TypeAlias = Annotated[HexStr, Field(max_length=32)] diff --git a/eth_pydantic_types/validators.py b/eth_pydantic_types/utils.py similarity index 69% rename from eth_pydantic_types/validators.py rename to eth_pydantic_types/utils.py index 67d8298..d5dd776 100644 --- a/eth_pydantic_types/validators.py +++ b/eth_pydantic_types/utils.py @@ -1,9 +1,9 @@ -from typing import Any, Callable, Dict, Optional, Sized, TypeVar, cast +from typing import Any, Callable, Dict, Optional, Sized, Tuple, TypeVar, cast from pydantic import WithJsonSchema from pydantic_core.core_schema import bytes_schema -from eth_pydantic_types._error import SizeError +from eth_pydantic_types._error import HexValueError, SizeError __SIZED_T = TypeVar("__SIZED_T", bound=Sized) @@ -72,3 +72,27 @@ def _coerce_hexbytes_size(val: bytes, num_bytes: int) -> bytes: num_zeroes = max(0, num_bytes - len(val_stripped)) zeroes = b"\x00" * num_zeroes return zeroes + val_stripped + + +def validate_hex_str(value: str) -> str: + hex_value = (value[2:] if value.startswith("0x") else value).lower() + if set(hex_value) - set("1234567890abcdef"): + raise HexValueError(value) + + # Missing zero padding. + if len(hex_value) % 2 != 0: + hex_value = f"0{hex_value}" + + return f"0x{hex_value}" + + +def get_hash_pattern(str_size: int) -> str: + return f"^0x[a-fA-F0-9]{{{str_size}}}$" + + +def get_hash_examples(str_size: int) -> Tuple[str, str, str, str]: + zero_hash = f"0x{'0' * str_size}" + leading_zero = f"0x01{'1e' * ((str_size - 1) // 2)}" + trailing_zero = f"0x{'1e' * ((str_size - 1) // 2)}10" + full_hash = f"0x{'1e' * (str_size // 2)}" + return zero_hash, leading_zero, trailing_zero, full_hash diff --git a/tests/test_hash.py b/tests/test_hash.py index 237812a..cc5c4bb 100644 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -76,6 +76,7 @@ def test_schema(): assert prop["minLength"] == expected_length assert prop["type"] == "string" assert prop["format"] == "binary" + breakpoint() assert prop["pattern"] == f"^0x[a-fA-F0-9]{{{hex_value_str_size}}}$" assert prop["examples"][0] == f"0x{'0' * hex_value_str_size}" From 5c0a1bec24b12b1f0022e18cde368d70350d3cf1 Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 10 Jul 2024 22:29:42 -0500 Subject: [PATCH 14/25] fix: typo fix --- eth_pydantic_types/hex.py | 6 ------ tests/test_hash.py | 1 - 2 files changed, 7 deletions(-) diff --git a/eth_pydantic_types/hex.py b/eth_pydantic_types/hex.py index 79fd8d5..c273bc5 100644 --- a/eth_pydantic_types/hex.py +++ b/eth_pydantic_types/hex.py @@ -111,9 +111,6 @@ class HexBytes20(BoundHexBytes): HexBytes32: TypeAlias = BoundHexBytes -# HexBytes20: TypeAlias = Annotated[BoundHexBytes, Field(max_length=20)] -# HexBytes32: TypeAlias = Annotated[BoundHexBytes, Field(max_length=32)] - class BaseHexStr(str, BaseHex): @classmethod @@ -208,6 +205,3 @@ class HexStr20(BoundHexStr): HexStr32: TypeAlias = BoundHexStr - -# HexStr20: TypeAlias = Annotated[HexStr, Field(max_length=20)] -# HexStr32: TypeAlias = Annotated[HexStr, Field(max_length=32)] diff --git a/tests/test_hash.py b/tests/test_hash.py index cc5c4bb..237812a 100644 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -76,7 +76,6 @@ def test_schema(): assert prop["minLength"] == expected_length assert prop["type"] == "string" assert prop["format"] == "binary" - breakpoint() assert prop["pattern"] == f"^0x[a-fA-F0-9]{{{hex_value_str_size}}}$" assert prop["examples"][0] == f"0x{'0' * hex_value_str_size}" From 39813956dbc0fc8b6a81ed2a2b73ec24dca62930 Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 10 Jul 2024 22:46:02 -0500 Subject: [PATCH 15/25] fix: uses typing_extensions.TypeAlias in stead of typing.TypeAlias --- eth_pydantic_types/hex.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eth_pydantic_types/hex.py b/eth_pydantic_types/hex.py index c273bc5..ebd8567 100644 --- a/eth_pydantic_types/hex.py +++ b/eth_pydantic_types/hex.py @@ -1,4 +1,4 @@ -from typing import Any, ClassVar, Optional, Tuple, TypeAlias, Union, cast +from typing import Any, ClassVar, Optional, Tuple, Union, cast from eth_typing import HexStr as EthTypingHexStr from eth_utils import add_0x_prefix @@ -11,6 +11,7 @@ str_schema, with_info_before_validator_function, ) +from typing_extensions import TypeAlias from eth_pydantic_types._error import HexValueError from eth_pydantic_types.serializers import hex_serializer From 1440c29ee64a4a43f5431e99bcf950f82a1aa3f2 Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 10 Jul 2024 22:52:11 -0500 Subject: [PATCH 16/25] fix: uses typing_extensions.Annotated instead of typing.Annotated --- eth_pydantic_types/abi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth_pydantic_types/abi.py b/eth_pydantic_types/abi.py index 39eb919..4c0f8f0 100644 --- a/eth_pydantic_types/abi.py +++ b/eth_pydantic_types/abi.py @@ -2,11 +2,11 @@ These models are used to match the lowercase type names used by the abi. """ -from typing import Annotated, ClassVar +from typing import ClassVar from hexbytes import HexBytes from pydantic import Field -from typing_extensions import TypeAliasType +from typing_extensions import Annotated, TypeAliasType from .address import Address from .hex import BoundHexBytes From 8501431ad54742d23eb018ed66ff597da8feab2c Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 21 Aug 2024 03:34:32 -0500 Subject: [PATCH 17/25] chore: updating doc strings and removing unnecessary method --- eth_pydantic_types/hex.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/eth_pydantic_types/hex.py b/eth_pydantic_types/hex.py index ebd8567..0329362 100644 --- a/eth_pydantic_types/hex.py +++ b/eth_pydantic_types/hex.py @@ -81,6 +81,11 @@ def validate_size(cls, value: bytes) -> bytes: class BoundHexBytes(HexBytes): + """ + Use when receiving ``hexbytes.HexBytes`` values and a specific size is required. + Includes a pydantic validator and serializer. + """ + size: ClassVar[int] = 32 @classmethod @@ -92,12 +97,6 @@ def __get_pydantic_core_schema__(cls, value, handle=None) -> CoreSchema: schema["serialization"] = hex_serializer return schema - @classmethod - def __eth_pydantic_validate__( - cls, value: Any, info: Optional[ValidationInfo] = None - ) -> BaseHexBytes: - return cls(cls.validate_size(HexBytes(value))) - @classmethod def validate_size(cls, value: bytes) -> bytes: str_size = cls.size * 2 @@ -172,7 +171,7 @@ def from_bytes(cls, data: bytes) -> "HexStr": class BoundHexStr(BaseHexStr): - """A hex string value, typically from a hash.""" + """A hex string value, typically from a hash, that is required to be a specific size.""" size: ClassVar[int] = 32 calculate_schema: ClassVar[bool] = True From 5ba7b410e315db77fa26219555e4aa6ec68f8399 Mon Sep 17 00:00:00 2001 From: slush Date: Sun, 15 Dec 2024 23:36:40 -0600 Subject: [PATCH 18/25] feat: use enriched HexBytes instead of imported one --- eth_pydantic_types/abi.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/eth_pydantic_types/abi.py b/eth_pydantic_types/abi.py index 4c0f8f0..fccfc58 100644 --- a/eth_pydantic_types/abi.py +++ b/eth_pydantic_types/abi.py @@ -4,12 +4,11 @@ from typing import ClassVar -from hexbytes import HexBytes from pydantic import Field from typing_extensions import Annotated, TypeAliasType from .address import Address -from .hex import BoundHexBytes +from .hex import BoundHexBytes, HexBytes bytes = TypeAliasType("bytes", HexBytes) string = TypeAliasType("string", str) From 0ddc42d1fc4a604f4f2256dbc97e723069530e00 Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 18 Dec 2024 00:33:59 -0600 Subject: [PATCH 19/25] chore: lint --- eth_pydantic_types/__init__.py | 17 +++-------------- eth_pydantic_types/_main.py | 32 +++++--------------------------- eth_pydantic_types/address.py | 10 ++++++---- eth_pydantic_types/hex.py | 4 ++-- 4 files changed, 16 insertions(+), 47 deletions(-) diff --git a/eth_pydantic_types/__init__.py b/eth_pydantic_types/__init__.py index ec9c25b..7ee0362 100644 --- a/eth_pydantic_types/__init__.py +++ b/eth_pydantic_types/__init__.py @@ -1,15 +1,4 @@ -from .address import Address, AddressType -from .bip122 import Bip122Uri -from .hex import HexBytes, HexBytes20, HexBytes32, HexStr, HexStr20, HexStr32 +def __getattr__(name: str): + import eth_pydantic_types._main as module -__all__ = [ - "Address", - "AddressType", - "Bip122Uri", - "HexBytes", - "HexBytes20", - "HexBytes32", - "HexStr", - "HexStr20", - "HexStr32", -] + return getattr(module, name) diff --git a/eth_pydantic_types/_main.py b/eth_pydantic_types/_main.py index 4c74729..ec9c25b 100644 --- a/eth_pydantic_types/_main.py +++ b/eth_pydantic_types/_main.py @@ -1,37 +1,15 @@ 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 +from .hex import HexBytes, HexBytes20, HexBytes32, HexStr, HexStr20, HexStr32 __all__ = [ "Address", "AddressType", "Bip122Uri", - "HashBytes4", - "HashBytes8", - "HashBytes16", - "HashBytes20", - "HashBytes32", - "HashBytes64", - "HashStr4", - "HashStr8", - "HashStr16", - "HashStr20", - "HashStr32", - "HashStr64", "HexBytes", + "HexBytes20", + "HexBytes32", "HexStr", + "HexStr20", + "HexStr32", ] diff --git a/eth_pydantic_types/address.py b/eth_pydantic_types/address.py index 0fc61e2..b295280 100644 --- a/eth_pydantic_types/address.py +++ b/eth_pydantic_types/address.py @@ -1,16 +1,18 @@ from functools import cached_property -from typing import Annotated, Any, ClassVar, Optional +from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Optional from cchecksum import to_checksum_address from eth_typing import ChecksumAddress -from pydantic_core import CoreSchema from pydantic_core.core_schema import ( ValidationInfo, str_schema, with_info_before_validator_function, ) -from .hex import HexStr20 +from eth_pydantic_types.hex import HexStr20 + +if TYPE_CHECKING: + from pydantic_core import CoreSchema ADDRESS_PATTERN = "^0x[a-fA-F0-9]{40}$" @@ -35,7 +37,7 @@ class Address(HexStr20): calculate_schema: ClassVar[bool] = False # use schema defined here, not one calculated by size @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( cls.__eth_pydantic_validate__, address_schema(), diff --git a/eth_pydantic_types/hex.py b/eth_pydantic_types/hex.py index 18559fd..6252ed9 100644 --- a/eth_pydantic_types/hex.py +++ b/eth_pydantic_types/hex.py @@ -8,7 +8,6 @@ str_schema, with_info_before_validator_function, ) -from typing_extensions import TypeAlias from eth_pydantic_types._error import HexValueError from eth_pydantic_types.serializers import hex_serializer @@ -21,6 +20,7 @@ ) if TYPE_CHECKING: + from typing_extensions import TypeAlias from pydantic_core import CoreSchema @@ -107,7 +107,7 @@ class HexBytes20(BoundHexBytes): size: ClassVar[int] = 20 -HexBytes32: TypeAlias = BoundHexBytes +HexBytes32: "TypeAlias" = BoundHexBytes class BaseHexStr(str, BaseHex): From 02ecb4559c6998ba087ab78d762dd976c6acfe7a Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 18 Dec 2024 00:41:33 -0600 Subject: [PATCH 20/25] chore: lin[d]t truffles --- eth_pydantic_types/hex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth_pydantic_types/hex.py b/eth_pydantic_types/hex.py index 6252ed9..8feaf31 100644 --- a/eth_pydantic_types/hex.py +++ b/eth_pydantic_types/hex.py @@ -20,8 +20,8 @@ ) if TYPE_CHECKING: - from typing_extensions import TypeAlias from pydantic_core import CoreSchema + from typing_extensions import TypeAlias schema_pattern = "^0x([0-9a-f][0-9a-f])*$" From b9aa930e0fa64a74fe443a10003b8d41f441a68a Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 18 Dec 2024 00:45:08 -0600 Subject: [PATCH 21/25] chore: typo fix --- eth_pydantic_types/hex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth_pydantic_types/hex.py b/eth_pydantic_types/hex.py index 8feaf31..61c45e1 100644 --- a/eth_pydantic_types/hex.py +++ b/eth_pydantic_types/hex.py @@ -177,7 +177,7 @@ class BoundHexStr(BaseHexStr): calculate_schema: ClassVar[bool] = True @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__, From 8ddb9cc98a0185d7ca982dfae936a0045949a22a Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 18 Dec 2024 00:49:32 -0600 Subject: [PATCH 22/25] feat: fix tests --- eth_pydantic_types/hex.py | 2 +- tests/test_hash.py | 4 ++-- tests/test_hex.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/eth_pydantic_types/hex.py b/eth_pydantic_types/hex.py index 61c45e1..3d4557c 100644 --- a/eth_pydantic_types/hex.py +++ b/eth_pydantic_types/hex.py @@ -204,4 +204,4 @@ class HexStr20(BoundHexStr): size: ClassVar[int] = 20 -HexStr32: TypeAlias = BoundHexStr +HexStr32: "TypeAlias" = BoundHexStr diff --git a/tests/test_hash.py b/tests/test_hash.py index 237812a..075113a 100644 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -88,8 +88,8 @@ def test_model_dump(bytes32str): model = Model.from_single(5) actual = model.model_dump() expected = { - "valuebytes20": "0000000000000000000000000000000000000005", - "valuebytes32": "0000000000000000000000000000000000000000000000000000000000000005", + "valuebytes20": "0x0000000000000000000000000000000000000005", + "valuebytes32": "0x0000000000000000000000000000000000000000000000000000000000000005", "valuestr32": "0x0000000000000000000000000000000000000000000000000000000000000005", } assert actual == expected diff --git a/tests/test_hex.py b/tests/test_hex.py index 76b69c8..3a32f6c 100644 --- a/tests/test_hex.py +++ b/tests/test_hex.py @@ -57,7 +57,7 @@ def test_hexbytes_schema(): def test_hexbytes_model_dump(bytes32str): model = BytesModel(value=bytes32str) actual = model.model_dump() - expected = {"value": "9b70bd98ccb5b6434c2ead14d68d15f392435a06ff469f8d1f8cf38b2ae0b0e2"} + expected = {"value": "0x9b70bd98ccb5b6434c2ead14d68d15f392435a06ff469f8d1f8cf38b2ae0b0e2"} assert actual == expected From 4fe3cfdf41dd9de7bd16801313863d2449e13b58 Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 18 Dec 2024 22:58:22 -0600 Subject: [PATCH 23/25] chore: updates pins --- .pre-commit-config.yaml | 4 ++-- setup.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8aca554..25f459b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,8 +4,8 @@ repos: hooks: - id: check-yaml -- repo: https://github.com/pre-commit/mirrors-isort - rev: v5.10.1 +- repo: https://github.com/PyCQA/isort + rev: 5.13.2 hooks: - id: isort diff --git a/setup.py b/setup.py index 533b2ff..df02119 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,7 @@ extras_require = { "test": [ # `test` GitHub Action jobs uses this + "pytest>=6.0", # Core testing package "pytest-xdist", # Multi-process runner "pytest-cov>=4.0.0,<5", # Coverage analyzer plugin "pytest-mock", # For creating mocks @@ -23,7 +24,7 @@ "flake8-print>=5.0.0,<6", # Detect print statements left in code "flake8-pydantic", # For detecting issues with Pydantic models "flake8-type-checking", # Detect imports to move in/out of type-checking blocks - "isort>=5.10.1,<6", # Import sorting linter + "isort>=5.13.2,<6", # Import sorting linter "mdformat>=0.7.19", # Auto-formatter for markdown "mdformat-gfm>=0.3.5", # Needed for formatting GitHub-flavored markdown "mdformat-frontmatter>=0.4.1", # Needed for frontmatters-style headers in issue templates From ff72ed7c95bf76e448f9c587ff30ae0181dfd059 Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 18 Dec 2024 23:05:43 -0600 Subject: [PATCH 24/25] feat: flip the evaluation of signed/unsigned range to do true condition first --- eth_pydantic_types/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eth_pydantic_types/utils.py b/eth_pydantic_types/utils.py index eebfe93..944bb98 100644 --- a/eth_pydantic_types/utils.py +++ b/eth_pydantic_types/utils.py @@ -18,12 +18,12 @@ def validate_size(value: "__SIZED_T", size: int, coerce: Optional[Callable] = No def validate_in_range(value: int, size: int, signed: bool = True) -> int: - if not signed: - if value >= 0 and value < 2**size: + if signed: + if value >= -(2**size) / 2 and value < (2**size) / 2: return value else: - if value >= -(2**size) / 2 and value < (2**size) / 2: + if value >= 0 and value < 2**size: return value raise SizeError(size, value) From 3ec3cfe5b1bbeaf3cdb5ab267ace913f9b60d646 Mon Sep 17 00:00:00 2001 From: slush Date: Thu, 19 Dec 2024 12:44:06 -0600 Subject: [PATCH 25/25] refactor: cleaner way to have addresses keep a static schema pattern/examples --- eth_pydantic_types/address.py | 6 +++++- eth_pydantic_types/hex.py | 12 +++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/eth_pydantic_types/address.py b/eth_pydantic_types/address.py index b295280..09fc835 100644 --- a/eth_pydantic_types/address.py +++ b/eth_pydantic_types/address.py @@ -34,7 +34,6 @@ class Address(HexStr20): "0xa5a13f62ce1113838e0d9b4559b8caf5f76463c0", # Trailing zero "0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C", ) - calculate_schema: ClassVar[bool] = False # use schema defined here, not one calculated by size @classmethod def __get_pydantic_core_schema__(cls, value, handler=None) -> "CoreSchema": @@ -48,6 +47,11 @@ def __eth_pydantic_validate__(cls, value: Any, info: Optional[ValidationInfo] = value = super().__eth_pydantic_validate__(value) return cls.to_checksum_address(value) + @classmethod + def update_schema(cls): + # Already set statically in the class + return + @classmethod def to_checksum_address(cls, value: str) -> ChecksumAddress: return to_checksum_address(value) diff --git a/eth_pydantic_types/hex.py b/eth_pydantic_types/hex.py index 3d4557c..2011849 100644 --- a/eth_pydantic_types/hex.py +++ b/eth_pydantic_types/hex.py @@ -174,7 +174,6 @@ class BoundHexStr(BaseHexStr): """A hex string value, typically from a hash, that is required to be a specific size.""" size: ClassVar[int] = 32 - calculate_schema: ClassVar[bool] = True @classmethod def __get_pydantic_core_schema__(cls, value, handler=None) -> "CoreSchema": @@ -193,12 +192,15 @@ def __eth_pydantic_validate__(cls, value: Any, info: Optional[ValidationInfo] = @classmethod def validate_size(cls, value: str) -> str: - if cls.calculate_schema: - str_size = cls.size * 2 - cls.schema_pattern = get_hash_pattern(str_size) - cls.schema_examples = get_hash_examples(str_size) + cls.update_schema() return validate_str_size(value, cls.size * 2) + @classmethod + def update_schema(cls): + str_size = cls.size * 2 + cls.schema_pattern = get_hash_pattern(str_size) + cls.schema_examples = get_hash_examples(str_size) + class HexStr20(BoundHexStr): size: ClassVar[int] = 20