util: Some tweaks to StructStream and sized ints (#15090)

* Only define `StructStream` members, don't give them defaults

* `MAXIMUM_EXCLUSIVE` -> `MAXIMUM`

* Make `MINIMUM` and `MAXIMUM` instances of the underlying class

* Make the `StructStream` members `ClassVar`s

* More explicit and consistent `MINIMUM` and `MAXIMUM` assignments

* Comment about instantiation

* Use `MAXIMUM`
This commit is contained in:
dustinface 2023-06-16 16:32:17 +02:00 committed by GitHub
parent b2498d2917
commit 508087251c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 62 additions and 43 deletions

View File

@ -2926,10 +2926,7 @@ class WalletRpcApi:
async def get_coin_records(self, request: Dict[str, Any]) -> EndpointResult:
parsed_request = GetCoinRecords.from_json_dict(request)
if (
parsed_request.limit != uint32.MAXIMUM_EXCLUSIVE - 1
and parsed_request.limit > self.max_get_coin_records_limit
):
if parsed_request.limit != uint32.MAXIMUM and parsed_request.limit > self.max_get_coin_records_limit:
raise ValueError(f"limit of {self.max_get_coin_records_limit} exceeded: {parsed_request.limit}")
for filter_name, filter in {

View File

@ -1,51 +1,62 @@
from __future__ import annotations
from typing import ClassVar
from chia.util.struct_stream import StructStream, parse_metadata_from_name
@parse_metadata_from_name
class int8(StructStream):
pass
MINIMUM: ClassVar[int8]
MAXIMUM: ClassVar[int8]
@parse_metadata_from_name
class uint8(StructStream):
pass
MINIMUM: ClassVar[uint8]
MAXIMUM: ClassVar[uint8]
@parse_metadata_from_name
class int16(StructStream):
pass
MINIMUM: ClassVar[int16]
MAXIMUM: ClassVar[int16]
@parse_metadata_from_name
class uint16(StructStream):
pass
MINIMUM: ClassVar[uint16]
MAXIMUM: ClassVar[uint16]
@parse_metadata_from_name
class int32(StructStream):
pass
MINIMUM: ClassVar[int32]
MAXIMUM: ClassVar[int32]
@parse_metadata_from_name
class uint32(StructStream):
pass
MINIMUM: ClassVar[uint32]
MAXIMUM: ClassVar[uint32]
@parse_metadata_from_name
class int64(StructStream):
pass
MINIMUM: ClassVar[int64]
MAXIMUM: ClassVar[int64]
@parse_metadata_from_name
class uint64(StructStream):
pass
MINIMUM: ClassVar[uint64]
MAXIMUM: ClassVar[uint64]
@parse_metadata_from_name
class uint128(StructStream):
pass
MINIMUM: ClassVar[uint128]
MAXIMUM: ClassVar[uint128]
class int512(StructStream):
@ -59,5 +70,9 @@ class int512(StructStream):
# note that the boundaries for int512 is not what you might expect. We
# encode these with one extra byte, but only allow a range of
# [-INT512_MAX, INT512_MAX]
MAXIMUM_EXCLUSIVE = 2**BITS
MINIMUM = -(2**BITS) + 1
MINIMUM: ClassVar[int512] = -(2**BITS) + 1
MAXIMUM: ClassVar[int512] = (2**BITS) - 1
int512.MINIMUM = int512(int512.MINIMUM)
int512.MAXIMUM = int512(int512.MAXIMUM)

View File

@ -124,14 +124,14 @@ else:
@dataclasses.dataclass(frozen=True)
class UInt32Range(Streamable):
start: uint32 = uint32(0)
stop: uint32 = uint32(uint32.MAXIMUM_EXCLUSIVE - 1)
stop: uint32 = uint32.MAXIMUM
@streamable
@dataclasses.dataclass(frozen=True)
class UInt64Range(Streamable):
start: uint64 = uint64(0)
stop: uint64 = uint64(uint64.MAXIMUM_EXCLUSIVE - 1)
stop: uint64 = uint64.MAXIMUM
@dataclass(frozen=True)

View File

@ -1,6 +1,6 @@
from __future__ import annotations
from typing import BinaryIO, SupportsInt, Type, TypeVar, Union
from typing import BinaryIO, ClassVar, SupportsInt, Type, TypeVar, Union
from typing_extensions import Protocol, SupportsIndex
@ -37,24 +37,28 @@ def parse_metadata_from_name(cls: Type[_T_StructStream]) -> Type[_T_StructStream
raise ValueError(f"cls.BITS must be a multiple of 8: {cls.BITS}")
if cls.SIGNED:
cls.MAXIMUM_EXCLUSIVE = 2 ** (cls.BITS - 1)
cls.MINIMUM = -(2 ** (cls.BITS - 1))
cls.MAXIMUM = (2 ** (cls.BITS - 1)) - 1
else:
cls.MAXIMUM_EXCLUSIVE = 2**cls.BITS
cls.MINIMUM = 0
cls.MAXIMUM = (2**cls.BITS) - 1
cls.MINIMUM = cls(cls.MINIMUM)
cls.MAXIMUM = cls(cls.MAXIMUM)
return cls
class StructStream(int):
SIZE = 0
BITS = 0
SIGNED = False
MAXIMUM_EXCLUSIVE = 0
MINIMUM = 0
SIZE: ClassVar[int]
BITS: ClassVar[int]
SIGNED: ClassVar[bool]
MAXIMUM: ClassVar[int]
MINIMUM: ClassVar[int]
"""
Create a class that can parse and stream itself based on a struct.pack template string.
Create a class that can parse and stream itself based on a struct.pack template string. This is only meant to be
a base class for further derivation and it's not recommended to instantiate it directly.
"""
# This is just a partial exposure of the underlying int constructor. Liskov...
@ -65,7 +69,7 @@ class StructStream(int):
# additional special action to take here beyond verifying that the newly
# created instance satisfies the bounds limitations of the particular subclass.
super().__init__()
if not (self.MINIMUM <= self < self.MAXIMUM_EXCLUSIVE):
if not (self.MINIMUM <= self <= self.MAXIMUM):
raise ValueError(f"Value {self} does not fit into {type(self).__name__}")
@classmethod

View File

@ -29,7 +29,7 @@ class CoinRecordOrder(IntEnum):
@dataclass(frozen=True)
class GetCoinRecords(Streamable):
offset: uint32 = uint32(0)
limit: uint32 = uint32(uint32.MAXIMUM_EXCLUSIVE - 1)
limit: uint32 = uint32.MAXIMUM
wallet_id: Optional[uint32] = None
wallet_type: Optional[uint8] = None # WalletType
coin_type: Optional[uint8] = None # CoinType
@ -188,7 +188,7 @@ class WalletCoinStore:
self,
*,
offset: uint32 = uint32(0),
limit: uint32 = uint32(uint32.MAXIMUM_EXCLUSIVE - 1),
limit: uint32 = uint32.MAXIMUM,
wallet_id: Optional[uint32] = None,
wallet_type: Optional[WalletType] = None,
coin_type: Optional[CoinType] = None,
@ -237,7 +237,7 @@ class WalletCoinStore:
where_sql = "WHERE " + " AND ".join(conditions) if len(conditions) > 0 else ""
order_sql = f"ORDER BY {order.name} {'DESC' if reverse else 'ASC'}, rowid"
limit_sql = f"LIMIT {offset}, {limit}" if offset > 0 or limit < uint32.MAXIMUM_EXCLUSIVE - 1 else ""
limit_sql = f"LIMIT {offset}, {limit}" if offset > 0 or limit < uint32.MAXIMUM else ""
query_sql = f"{where_sql} {order_sql} {limit_sql}"
async with self.db_wrapper.reader_no_transaction() as conn:

View File

@ -41,7 +41,7 @@ class Good:
size: int
bits: int
signed: bool
maximum_exclusive: int
maximum: int
minimum: int
@classmethod
@ -50,7 +50,7 @@ class Good:
name: str,
size: int,
signed: bool,
maximum_exclusive: int,
maximum: int,
minimum: int,
) -> Good:
raw_class: Type[StructStream] = type(name, (StructStream,), {})
@ -61,30 +61,30 @@ class Good:
size=size,
bits=size * 8,
signed=signed,
maximum_exclusive=maximum_exclusive,
maximum=maximum,
minimum=minimum,
)
good_classes = [
Good.create(name="uint8", size=1, signed=False, maximum_exclusive=0xFF + 1, minimum=0),
Good.create(name="int8", size=1, signed=True, maximum_exclusive=0x80, minimum=-0x80),
Good.create(name="uint16", size=2, signed=False, maximum_exclusive=0xFFFF + 1, minimum=0),
Good.create(name="int16", size=2, signed=True, maximum_exclusive=0x8000, minimum=-0x8000),
Good.create(name="uint24", size=3, signed=False, maximum_exclusive=0xFFFFFF + 1, minimum=0),
Good.create(name="int24", size=3, signed=True, maximum_exclusive=0x800000, minimum=-0x800000),
Good.create(name="uint8", size=1, signed=False, maximum=0xFF, minimum=0),
Good.create(name="int8", size=1, signed=True, maximum=0x7F, minimum=-0x80),
Good.create(name="uint16", size=2, signed=False, maximum=0xFFFF, minimum=0),
Good.create(name="int16", size=2, signed=True, maximum=0x7FFF, minimum=-0x8000),
Good.create(name="uint24", size=3, signed=False, maximum=0xFFFFFF, minimum=0),
Good.create(name="int24", size=3, signed=True, maximum=0x7FFFFF, minimum=-0x800000),
Good.create(
name="uint128",
size=16,
signed=False,
maximum_exclusive=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + 1,
maximum=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,
minimum=0,
),
Good.create(
name="int128",
size=16,
signed=True,
maximum_exclusive=0x80000000000000000000000000000000,
maximum=0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,
minimum=-0x80000000000000000000000000000000,
),
]
@ -145,6 +145,9 @@ class TestStructStream:
with pytest.raises(struct.error):
struct.pack(struct_format, upper_boundary + 1)
assert type(cls.MINIMUM) == cls
assert type(cls.MAXIMUM) == cls
def test_int512(self) -> None:
# int512 is special. it uses 65 bytes to allow positive and negative
# "uint512"
@ -245,7 +248,7 @@ class TestStructStream:
assert uint32(b"273") == 273
def test_struct_stream_cannot_be_instantiated_directly(self) -> None:
with pytest.raises(ValueError, match="does not fit"):
with pytest.raises(AttributeError, match="object has no attribute"):
StructStream(0)
@pytest.mark.parametrize(
@ -281,7 +284,7 @@ class TestStructStream:
assert good.cls.SIGNED == good.signed
def test_parse_metadata_from_name_correct_maximum(self, good: Good) -> None:
assert good.cls.MAXIMUM_EXCLUSIVE == good.maximum_exclusive
assert good.cls.MAXIMUM == good.maximum
def test_parse_metadata_from_name_correct_minimum(self, good: Good) -> None:
assert good.cls.MINIMUM == good.minimum