from __future__ import annotations
import dataclasses
from math import ceil, log2
from bytemaker.typing_redirect import (
Any,
Dict,
Hashable,
ItemsView,
Iterable,
Iterator,
Mapping,
Optional,
Sequence,
TypeVar,
Union,
get_args,
get_origin,
)
# General Python functionality
[docs]class classproperty:
"Emulate class-level property similar to instance-level property"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if objtype is None:
objtype = type(obj)
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget.__get__(None, objtype)()
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
objtype = type(obj)
return self.fset.__get__(None, objtype)(value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
objtype = type(obj)
return self.fdel.__get__(None, objtype)()
[docs] def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
[docs] def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
[docs] def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
[docs]class Trie:
def __init__(self):
self.children = {}
self.is_end_of_suffix = False
self.is_start_of_prefix = False
[docs] @staticmethod
def build_suffix_trie(suffixes: Iterable[Sequence[int]]) -> Trie:
root = Trie()
for suffix in suffixes:
current = root
# Insert reversed suffix into trie
for bit in reversed(suffix):
if bit not in current.children:
current.children[bit] = Trie()
current = current.children[bit]
current.is_end_of_suffix = True
return root
[docs] @staticmethod
def build_prefix_trie(prefixes: Iterable[Iterable[int]]) -> Trie:
root = Trie()
for prefix in prefixes:
current = root
for bit in prefix:
if bit not in current.children:
current.children[bit] = Trie()
current = current.children[bit]
current.is_start_of_prefix = True
return root
[docs]def is_instance_of_union(obj, union_type: type):
"""
Determines if an object is an instance of a union type
(to support Python versions <3.10).
Args:
obj (object): The object to check
union_type (type): The union type to check against
Returns:
bool: Whether the object is an instance of the union type
"""
# Try the default isinstance method
try:
return isinstance(obj, union_type)
# If that does not work, try to process the union type
# instance recursively or as generic type instance
except TypeError:
type_origin = get_origin(union_type)
# If the type is non-generic and non-union
# use the default isinstance method with the type origin
if type_origin is None:
return isinstance(obj, union_type)
type_args = get_args(union_type)
# If the type is a union type or its instances are iterable
# check if the object is an instance of any
# of the constituent types
# or if the object is an iterable and its first element
# is an instance of the first type argument
if type_origin is Union:
return any(is_instance_of_union(obj, type_arg) for type_arg in type_args)
elif isinstance(obj, type_origin):
if len(type_args) == 1 and isinstance(obj, Iterable):
return bool(obj) or is_instance_of_union(next(iter(obj)), type_args[0])
# If the type is a multi-arg, non-union, non-generic type
else:
raise ValueError(
f"(Generic?) type {union_type} has origin {type_origin}"
f" and type args {type_args}."
f" Non-union types with multiple subscripts are not"
f" supported."
)
else:
return False
[docs]def is_subclass_of_union(subtype: type, supertype):
"""
Determines if an object is a subclass of a union type
(to support Python versions <3.10).
Args:
subtype (type): The object type to check
supertype (type): The union type to check against
Returns:
bool: Whether the object is a subclass of the union type
"""
# Try the default issubclass method
try:
return issubclass(subtype, supertype)
# If that does not work, try to process the union type recursively
# or as a generic type
except TypeError:
supertype_origin = get_origin(supertype)
# If the supertype is a single-arged, non-generic, non-union type
if supertype_origin is None:
return issubclass(subtype, supertype)
supertype_args = get_args(supertype)
# If the supertype is a union type or an iterable generic
if supertype_origin is Union:
return any(
is_subclass_of_union(subtype, union_type_part)
for union_type_part in supertype_args
)
elif issubclass(supertype_origin, Iterable) and len(supertype_args) == 1:
return issubclass(subtype, supertype_origin) and is_subclass_of_union(
subtype, supertype_args[0]
)
# If the supertype is a multi-arg, non-union, non-generic type
raise ValueError(
f"Supertype {supertype} has origin {supertype_origin}"
f" and type args {supertype_args}."
"Non-union supertypes with multiple subscripts are not supported."
)
K = TypeVar("K")
V = TypeVar("V")
[docs]class HashableMapping(Mapping[K, V], Hashable):
pass
[docs]class FrozenDict(HashableMapping[K, V]):
def __init__(self, input_dict: Dict[K, V]):
self._data = dict(input_dict)
self._hash: Optional[int] = None
def __getitem__(self, key: K) -> V:
return self._data[key]
def __iter__(self) -> Iterator[K]:
return iter(self._data)
def __len__(self) -> int:
return len(self._data)
[docs] def items(self) -> ItemsView[K, V]:
return self._data.items()
def __hash__(self) -> int:
if self._hash is None:
self._hash = hash(tuple(sorted(self.items())))
return self._hash
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self._data})"
def __eq__(self, other: object) -> bool:
if isinstance(other, Mapping):
return dict(self.items()) == dict(other.items())
return False
# Byte-related operations
class _ByteConvertibleMeta(type):
"""
This is used to create IsByteConvertible, a type to allow checking\
whether an object or instances of a class can be converted to a\
bytes object using isinstance or issubclass.
"""
def __instancecheck__(self, __instance: Any) -> bool:
try:
bytes(__instance)
return True
except Exception:
return False
def __subclasscheck__(self, __subclass: Any) -> bool:
return (
hasattr(__subclass, "__bytes__")
or is_subclass_of_union(__subclass, Union[bytes, bytearray, memoryview])
or (
hasattr(__subclass, "__getitem__")
and hasattr(__subclass, "format")
and hasattr(__subclass, "shape")
and hasattr(__subclass, "strides")
)
)
[docs]class ByteConvertible(metaclass=_ByteConvertibleMeta):
"""
Has __instancecheck__ and __subclasscheck__ methods\
to allow checking whether an object can be converted to a bytes\
object using :class:`python:bytes`
"""
# Aggregate type to bytes
[docs]class DataClassType(metaclass=DataClassTypeMeta):
pass
[docs]def twos_complement_bit_length(n: int):
"""
Determines the number of bits required to represent a signed\
integer in two's complement.
Args:
n (int): The (signed) integer for which to determine the number\
of bits required
Returns:
int: The number of bits required to represent the integer in\
two's-complement notation
"""
if n == 0:
return 1 # Technically can represent 0 with 0 bits in
# two's complement, but this is not useful
is_greq_than_zero = n >= 0
abs_val = abs(n)
is_power_of_two = (abs_val & (abs_val - 1)) == 0
if is_greq_than_zero or not is_power_of_two:
return ceil(log2(abs_val + 1)) + 1 # Account for extra
# at negative extreme
else:
return int(log2(abs_val)) + 1
[docs]def twos_complement(number, n_bits=32):
"""
Convert an integer to its two's complement representation.
:param number: The integer to convert.
:param bits: The bit width for the two's complement representation.
:return: A string representing the two's complement of the number.
"""
if number < 0:
number = (1 << n_bits) + number
format_string = "{:0" + str(n_bits) + "b}"
return format_string.format(number)