Skip to content

Improves typing in Hash classes #146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 54 additions & 12 deletions xrpl/binarycodec/types/hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"""
from __future__ import annotations # Requires Python 3.7+

from abc import ABC

from typing_extensions import Final
from abc import ABC, abstractmethod
from typing import Optional, Type

from xrpl.binarycodec.binary_wrappers.binary_parser import BinaryParser
from xrpl.binarycodec.exceptions import XRPLBinaryCodecException
from xrpl.binarycodec.types.serialized_type import SerializedType

Expand All @@ -15,24 +15,66 @@ class Hash(SerializedType, ABC):
"""
Base class for XRPL Hash types.
`See Hash Fields <https://xrpl.org/serialization.html#hash-fields>`_

Attributes:
_LENGTH: The length of this hash in bytes.
"""

_LENGTH: Final[int]

def __init__(self: Hash, buffer: bytes) -> None:
def __init__(self: Hash, buffer: Optional[bytes]) -> None:
"""
Construct a Hash.

:param buffer: The byte buffer that will be used to store
the serialized encoding of this field.
Args:
buffer: The byte buffer that will be used to store the serialized
encoding of this field.
"""
if len(buffer) != self._LENGTH:
buffer = buffer if buffer is not None else bytes(self._get_length())

if len(buffer) != self._get_length():
raise XRPLBinaryCodecException("Invalid hash length {}".format(len(buffer)))
super().__init__(buffer)

def __str__(self: Hash) -> str:
"""Returns a hex-encoded string representation of the bytes buffer."""
return self.to_hex()

@classmethod
def from_value(cls: Type[Hash], value: str) -> Hash:
"""
Construct a Hash object from a hex string.

Args:
value: The value to construct a Hash from.

Returns:
The Hash object constructed from value.

Raises:
XRPLBinaryCodecException: If the supplied value is of the wrong type.
"""
if not isinstance(value, str):
raise XRPLBinaryCodecException(
"Invalid type to construct a {}: expected str,"
" received {}.".format(cls.__name__, value.__class__.__name__)
)

return cls(bytes.fromhex(value))

@classmethod
def from_parser(
cls: Type[Hash], parser: BinaryParser, length_hint: Optional[int] = None
) -> Hash:
"""
Construct a Hash object from an existing BinaryParser.

Args:
parser: The parser to construct the Hash object from.
length_hint: The number of bytes to consume from the parser.

Returns:
The Hash object constructed from a parser.
"""
num_bytes = length_hint if length_hint is not None else cls._get_length()
return cls(parser.read(num_bytes))

@classmethod
@abstractmethod
def _get_length(cls: Type[Hash]) -> int:
raise NotImplementedError("Hash._get_length not implemented")
Comment on lines +79 to +80
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a better way to do this would probably use some combination of @property, @abstractmethod, and @classmethod, so that the base class implements an abstract getter for a class property and the subclasses can just use the length = 20 syntax

unfortunately, this isn't officially supported by mypy: python/mypy#8532
as such, i went with using a non-property getter. let me know what you think

54 changes: 3 additions & 51 deletions xrpl/binarycodec/types/hash128.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
"""
from __future__ import annotations

from typing import Optional
from typing import Type

from xrpl.binarycodec import XRPLBinaryCodecException
from xrpl.binarycodec.binary_wrappers.binary_parser import BinaryParser
from xrpl.binarycodec.types.hash import Hash


Expand All @@ -17,54 +15,8 @@ class Hash128(Hash):
Codec for serializing and deserializing a hash field with a width
of 128 bits (16 bytes).
`See Hash Fields <https://xrpl.org/serialization.html#hash-fields>`_


Attributes:
_LENGTH: The length of this hash in bytes.
"""

_LENGTH = 16

def __init__(self: Hash128, buffer: bytes = None) -> None:
"""Construct a Hash128."""
buffer = buffer if buffer is not None else bytes(self._LENGTH)
super().__init__(buffer)

@classmethod
def from_value(cls: Hash128, value: str) -> Hash128:
"""
Construct a Hash128 object from a hex string.

Args:
value: The value to construct a Hash128 from.

Returns:
The Hash128 object constructed from value.

Raises:
XRPLBinaryCodecException: If the supplied value is of the wrong type.
"""
if not isinstance(value, str):
raise XRPLBinaryCodecException(
"Invalid type to construct a Hash128: expected str,"
" received {}.".format(value.__class__.__name__)
)

return cls(bytes.fromhex(value))

@classmethod
def from_parser(
cls: Hash128, parser: BinaryParser, length_hint: Optional[int] = None
) -> Hash128:
"""
Construct a Hash128 object from an existing BinaryParser.

Args:
parser: The parser to construct the Hash128 object from.
length_hint: The number of bytes to consume from the parser.

Returns:
The Hash128 object constructed from a parser.
"""
num_bytes = length_hint if length_hint is not None else cls._LENGTH
return cls(parser.read(num_bytes))
def _get_length(cls: Type[Hash128]) -> int:
return 16
54 changes: 3 additions & 51 deletions xrpl/binarycodec/types/hash160.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
"""
from __future__ import annotations

from typing import Optional, Type
from typing import Type

from xrpl.binarycodec import XRPLBinaryCodecException
from xrpl.binarycodec.binary_wrappers.binary_parser import BinaryParser
from xrpl.binarycodec.types.hash import Hash


Expand All @@ -16,54 +14,8 @@ class Hash160(Hash):
Codec for serializing and deserializing a hash field with a width
of 160 bits (20 bytes).
`See Hash Fields <https://xrpl.org/serialization.html#hash-fields>`_


Attributes:
_LENGTH: The length of this hash in bytes.
"""

_LENGTH = 20

def __init__(self: Hash160, buffer: bytes = None) -> None:
"""Construct a Hash160."""
buffer = buffer if buffer is not None else bytes(self._width)
super().__init__(buffer)

@classmethod
def from_value(cls: Hash160, value: str) -> Hash160:
"""
Construct a Hash160 object from a hex string.

Args:
value: The string to construct a Hash160 from.

Returns:
The Hash160 constructed from value.

Raises:
XRPLBinaryCodecException: If the supplied value is of the wrong type.
"""
if not isinstance(value, str):
raise XRPLBinaryCodecException(
"Invalid type to construct a Hash160: expected str,"
" received {}.".format(value.__class__.__name__)
)

return cls(bytes.fromhex(value))

@classmethod
def from_parser(
cls: Type[Hash160], parser: BinaryParser, length_hint: Optional[int] = None
) -> Hash160:
"""
Construct a Hash160 object from an existing BinaryParser.

Args:
parser: The parser to construct Hash160 from.
length_hint: The number of bytes to consume from the parser.

Returns:
The Hash160 constructed from the parser.
"""
num_bytes = length_hint if length_hint is not None else cls._LENGTH
return cls(parser.read(num_bytes))
def _get_length(cls: Type[Hash160]) -> int:
return 20
53 changes: 3 additions & 50 deletions xrpl/binarycodec/types/hash256.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
"""
from __future__ import annotations

from typing import Optional
from typing import Type

from xrpl.binarycodec import XRPLBinaryCodecException
from xrpl.binarycodec.binary_wrappers.binary_parser import BinaryParser
from xrpl.binarycodec.types.hash import Hash


Expand All @@ -17,53 +15,8 @@ class Hash256(Hash):
Codec for serializing and deserializing a hash field with a width
of 256 bits (32 bytes).
`See Hash Fields <https://xrpl.org/serialization.html#hash-fields>`_


Attributes:
: The length of this hash in bytes.
"""

_LENGTH = 32

def __init__(self: Hash256, buffer: bytes = None) -> None:
"""Construct a Hash256."""
buffer = buffer if buffer is not None else bytes(self._width)
super().__init__(buffer)

@classmethod
def from_value(cls: Hash256, value: str) -> Hash256:
"""
Construct a Hash256 object from a hex string.

Args:
value: The string to construct a Hash256 object from.

Returns:
The Hash256 constructed from value.

Raises:
XRPLBinaryCodecException: If the supplied value is of the wrong type.
"""
if not isinstance(value, str):
raise XRPLBinaryCodecException(
"Invalid type to construct a Hash256: expected str,"
" received {}.".format(value.__class__.__name__)
)
return cls(bytes.fromhex(value))

@classmethod
def from_parser(
cls: Hash256, parser: BinaryParser, length_hint: Optional[int] = None
) -> Hash256:
"""
Construct a Hash256 object from an existing BinaryParser.

Args:
parser: The parser to construct a Hash256 from.
length_hint: The number of bytes to consume from the parser.

Returns:
The Hash256 constructed from parser.
"""
num_bytes = length_hint if length_hint is not None else cls._LENGTH
return cls(parser.read(num_bytes))
def _get_length(cls: Type[Hash256]) -> int:
return 32