# Copyright (c) 2020-2023, Andrea Zoppi.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
r"""Common stuff, shared across modules."""
import abc
import collections.abc
from typing import Any
from typing import ByteString
from typing import Callable
from typing import Iterable
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union
try:
from typing import TypeAlias
except ImportError: # pragma: no cover
TypeAlias = Any # Python < 3.10
BytesLike: TypeAlias = Union[ByteString, memoryview]
BytesFactory: TypeAlias = Callable[..., BytesLike]
def _inherit_extend_docstring(src_class): # pragma: no cover
def wrapper(dst_method): # pragma: no cover
if hasattr(src_class, dst_method.__name__):
docstring_after = r"""
Warnings:
This method documentation is just a stub, copied directly from
:meth:`{0}.{1}`. It may be incomplete, or with different arguments.
The behavior should be very similar though.
""".format(src_class.__name__, dst_method.__name__)
src_method = getattr(src_class, dst_method.__name__)
dst_method.__doc__ = src_method.__doc__ + docstring_after
return dst_method
return wrapper
[docs]class BaseBytesMethods(ByteString, collections.abc.Sequence):
r"""Provides useful methods to a byte buffer.
Python's :obj:`memoryview` and most *byte-like* objects do not provide many
useful methods found instead within the :obj:`bytes` or :obj:`str` APIs.
This wrapper class adds a low-level implementation of those methods to
anything supporting the *buffer protocol*.
Arguments:
wrapped (*byte-like*): The target object supporting the
*buffer protocol*.
Examples:
>>> from cbytesparse import BytesMethods
>>> import numpy
>>> numbers = list(b'ABC')
>>> numbers
[65, 66, 67]
>>> data = numpy.array(numbers, dtype=numpy.ubyte)
>>> data
array([65, 66, 67], dtype=uint8)
>>> data.lower() # noqa
Traceback (most recent call last):
...
AttributeError: 'numpy.ndarray' object has no attribute 'lower'
>>> wrapped = BytesMethods(data) # noqa
>>> bytes(wrapped.lower())
b'abc'
>>> wrapped = BytesMethods(memoryview(data))
>>> bytes(wrapped.lower())
b'abc'
"""
[docs] @abc.abstractmethod
def __bool__(
self,
) -> bool:
r"""Has any items.
Returns:
bool: Has any items.
Examples:
>>> from cbytesparse import BytesMethods
>>> instance = BytesMethods(b'')
>>> bool(instance)
False
>>> instance = BytesMethods(b'Hello, World!')
>>> bool(instance)
True
"""
...
[docs] @abc.abstractmethod
def __bytes__(
self,
) -> bytes:
r"""Creates a bytes clone.
Returns:
:obj:`bytes`: Cloned data.
Examples:
>>> from cbytesparse import BytesMethods
>>> instance = BytesMethods(bytearray(b''))
>>> bytes(instance)
b''
>>> instance = BytesMethods(bytearray(b'Hello, World!'))
>>> bytes(instance)
b'Hello, World!'
"""
...
[docs] @abc.abstractmethod
def __contains__(
self,
token: Union[BytesLike, int],
) -> bool:
r"""Checks if some items are contained.
Arguments:
token (byte-like):
Token to find.
Returns:
bool: Token is contained.
Examples:
>>> from cbytesparse import BytesMethods
>>> instance = BytesMethods(b'Hello, World!')
>>> b'World' in instance
True
>>> b'$' in instance
False
>>> ord('o') in instance
True
>>> ord('$') in instance
False
"""
...
@abc.abstractmethod
def __delitem__(
self,
key: Any,
) -> None:
...
[docs] @abc.abstractmethod
def __eq__(
self,
other: Any,
) -> bool:
r"""Equality comparison.
Arguments:
other (byte-like):
Data to compare with `self`.
Returns:
bool: `self` is equal to `other`.
Examples:
>>> from cbytesparse import BytesMethods
>>> data = bytearray(b'Hello, World!')
>>> instance = BytesMethods(data)
>>> instance == data
True
>>> instance == memoryview(data)
True
>>> instance == b'Something else'
False
"""
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def __ge__(
self,
other: Any,
) -> bool:
...
[docs] @abc.abstractmethod
def __getitem__(
self,
key: Any,
) -> Any:
r"""Gets data.
Arguments:
key (slice or int):
Selection range or address.
If it is a :obj:`slice` with bytes-like `step`, the latter is
interpreted as the filling pattern.
Returns:
items: Items from the requested range.
Examples:
>>> from cbytesparse import BytesMethods
>>> instance = BytesMethods(b'Hello, World!')
>>> instance[7] # -> ord('W') = 87
121
>>> bytes(instance[:3])
b'Hel'
>>> bytes(instance[3:10])
b'lo, Wor'
>>> bytes(instance[-1:])
b'!'
>>> bytes(instance[2:10:3])
b'l,o'
>>> bytes(instance[3:10:2])
b'l,Wr'
"""
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def __gt__(
self,
other: Any,
) -> bool:
r"""
See Also:
:meth:`bytes.__gt__`
"""
...
[docs] @abc.abstractmethod
def __init__(
self,
wrapped: Optional[BytesLike],
):
...
[docs] @abc.abstractmethod
def __iter__(
self,
) -> Iterable[int]:
r"""Iterates over values.
Yields:
int: Value as byte integer.
Examples:
>>> from cbytesparse import BytesMethods
>>> instance = BytesMethods(b'Hello, World!')
>>> list(instance)
[72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33]
"""
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def __le__(
self,
other: Any,
) -> bool:
...
[docs] @abc.abstractmethod
def __len__(
self,
) -> int:
r"""Actual length.
Computes the actual length of the wrapped data object.
Returns:
int: Data length.
Examples:
>>> from cbytesparse import BytesMethods
>>> instance = BytesMethods(b'')
>>> len(instance)
0
>>> instance = BytesMethods(bytes(7))
>>> len(instance)
7
>>> instance = BytesMethods(memoryview(b'Hello, World!'))
>>> len(instance)
13
"""
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def __lt__(
self,
other: Any,
) -> bool:
...
[docs] @abc.abstractmethod
def __ne__(
self,
other: Any,
) -> bool:
r"""Inquality comparison.
Arguments:
other (byte-like):
Data to compare with `self`.
Returns:
bool: `self` is not equal to `other`.
Examples:
>>> from cbytesparse import BytesMethods
>>> data = bytearray(b'Hello, World!')
>>> instance = BytesMethods(data)
>>> instance != data
False
>>> instance != memoryview(data)
False
>>> instance != b'Something else'
True
"""
...
[docs] @abc.abstractmethod
def __reversed__(
self,
) -> Iterable[int]:
r"""Iterates over values, reversed order.
Yields:
int: Value as byte integer.
Examples:
>>> from cbytesparse import BytesMethods
>>> instance = BytesMethods(b'Hello, World!')
>>> list(instance)
[72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33]
>>> list(reversed(instance))
[33, 100, 108, 114, 111, 87, 32, 44, 111, 108, 108, 101, 72]
"""
...
@abc.abstractmethod
def __setitem__(
self,
key: Any,
value: Any,
) -> None:
...
[docs] @abc.abstractmethod
def __sizeof__(
self,
) -> int:
r"""int: Allocated byte size."""
...
@property
@abc.abstractmethod
def c_contiguous(
self,
) -> bool:
r"""bool: Contiguous array, *C* language style."""
...
[docs] @abc.abstractmethod
def capitalize(
self,
) -> 'BaseBytesMethods':
r"""byte-like: First character capitalized, the rest lowercase."""
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def center(
self,
width: int,
fillchar: BytesLike = b' ',
factory: BytesFactory = bytes,
) -> BytesLike:
...
[docs] @abc.abstractmethod
def contains(
self,
token: Union[BytesLike, int],
start: Optional[int] = None,
endex: Optional[int] = None,
) -> bool:
r"""Contains a substring.
Arguments:
token (byte-like):
Token to search.
start (int):
Inclusive start of the searched range, ``None`` to ignore.
endex (int):
Exclusive end of the searched range, ``None`` to ignore.
Returns:
bool: Token contained within the wrapped object.
See Also:
:meth:`__contains__`
Examples:
>>> from cbytesparse import BytesMethods
>>> instance = BytesMethods(b'Hello, World!')
>>> instance.contains(b'World')
True
>>> instance.contains(b'$')
False
>>> instance.contains(ord('o'))
True
>>> instance.contains(ord('$'))
False
>>> instance.contains(b'Hello', endex=10)
True
>>> instance.contains(b'Hello', endex=3)
False
>>> instance.contains(b'World', start=3)
True
>>> instance.contains(b'World', start=10)
False
>>> instance.contains(b',', start=3, endex=10)
True
>>> instance.contains(b',', start=8, endex=10)
False
"""
...
@property
@abc.abstractmethod
def contiguous(
self,
) -> bool:
r"""bool: Contiguous array."""
...
[docs] @abc.abstractmethod
def count(
self,
token: Union[BytesLike, int],
start: Optional[int] = None,
endex: Optional[int] = None,
) -> int:
r"""Counts token occurrences.
Arguments:
token (byte-like):
Token to count.
start (int):
Inclusive start of the searched range, ``None`` to ignore.
endex (int):
Exclusive end of the searched range, ``None`` to ignore.
Returns:
int: The number of items equal to `token`.
Examples:
>>> from cbytesparse import BytesMethods
>>> instance = BytesMethods(b'Hello, World!')
>>> instance.count(b'o')
2
>>> instance.count(b'l')
3
>>> instance.count(b'll')
3
>>> instance.count(b'World')
1
>>> instance.count(b'$')
0
>>> instance.count(ord('o'))
1
>>> instance.count(ord('$'))
0
>>> instance.count(b'Hello', endex=10)
1
>>> instance.count(b'Hello', endex=3)
0
>>> instance.count(b'World', start=3)
1
>>> instance.count(b'World', start=10)
0
>>> instance.count(b',', start=3, endex=10)
1
>>> instance.count(b',', start=8, endex=10)
0
"""
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def decode(
self,
encoding: str = 'utf-8',
errors: str = 'strict',
) -> str:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def endswith(
self,
token: BytesLike,
) -> bool:
...
@property
@abc.abstractmethod
def f_contiguous(
self,
) -> bool:
r"""bool: Contiguous array, *Fortran* language style."""
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def find(
self,
token: Union[BytesLike, int],
start: Optional[int] = None,
endex: Optional[int] = None,
) -> int:
...
@property
@abc.abstractmethod
@_inherit_extend_docstring(memoryview) # TODO
def format(
self,
) -> str:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def index(
self,
token: Union[BytesLike, int],
start: Optional[int] = None,
endex: Optional[int] = None,
) -> int:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def isalnum(
self,
) -> bool:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def isalpha(
self,
) -> bool:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def isascii(
self,
) -> bool:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(str) # TODO
def isdecimal(
self,
) -> bool:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def isdigit(
self,
) -> bool:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(str) # TODO
def isidentifier(
self,
) -> bool:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def islower(
self,
) -> bool:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(str) # TODO
def isnumeric(
self,
) -> bool:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(str) # TODO
def isprintable(
self,
) -> bool:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def isspace(
self,
) -> bool:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def istitle(
self,
) -> bool:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def isupper(
self,
) -> bool:
...
@property
@abc.abstractmethod
@_inherit_extend_docstring(memoryview) # TODO
def itemsize(
self,
) -> int:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def ljust(
self,
width: int,
fillchar: BytesLike = b' ',
factory: BytesFactory = bytes,
) -> BytesLike:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def lstrip(
self,
chars: Optional[BytesLike] = None,
factory: BytesFactory = bytes,
) -> BytesLike:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def lower(
self,
) -> 'BaseBytesMethods':
...
[docs] @staticmethod
@abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def maketrans(
chars_from: BytesLike,
chars_to: BytesLike,
) -> bytes:
...
@property
@abc.abstractmethod
@_inherit_extend_docstring(memoryview) # TODO
def nbytes(
self,
) -> int:
...
@property
@abc.abstractmethod
@_inherit_extend_docstring(memoryview) # TODO
def ndim(
self,
) -> int:
...
@property
@abc.abstractmethod
@_inherit_extend_docstring(memoryview) # TODO
def obj(
self,
) -> Optional[BytesLike]:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def partition(
self,
sep: BytesLike,
factory: BytesFactory = bytes,
) -> Tuple[BytesLike, BytesLike, BytesLike]:
...
@property
@abc.abstractmethod
@_inherit_extend_docstring(memoryview) # TODO
def readonly(
self,
) -> bool:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(memoryview) # TODO
def release(
self,
) -> None:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def removeprefix(
self,
prefix: BytesLike,
factory: BytesFactory = bytes,
) -> BytesLike:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def removesuffix(
self,
suffix: BytesLike,
factory: BytesFactory = bytes,
) -> BytesLike:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def replace(
self,
old: BytesLike,
new: BytesLike,
count: Optional[int] = None,
start: Optional[int] = None,
endex: Optional[int] = None,
) -> 'BaseBytesMethods':
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def rfind(
self,
token: Union[BytesLike, int],
start: Optional[int] = None,
endex: Optional[int] = None,
) -> int:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def rindex(
self,
token: Union[BytesLike, int],
start: Optional[int] = None,
endex: Optional[int] = None,
) -> int:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def rjust(
self,
width: int,
fillchar: BytesLike = b' ',
factory: BytesFactory = bytes,
) -> BytesLike:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def rpartition(
self,
sep: BytesLike,
factory: BytesFactory = bytes,
) -> Tuple[BytesLike, BytesLike, BytesLike]:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def rstrip(
self,
chars: Optional[BytesLike] = None,
factory: BytesFactory = bytes,
) -> BytesLike:
...
@property
@abc.abstractmethod
@_inherit_extend_docstring(memoryview) # TODO
def shape(
self,
) -> Tuple[int]:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def startswith(
self,
token: BytesLike,
) -> bool:
r"""bool: Data beginning with *token*."""
...
@property
@abc.abstractmethod
@_inherit_extend_docstring(memoryview) # TODO
def strides(
self,
) -> Tuple[int]:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def strip(
self,
chars: Optional[BytesLike] = None,
factory: BytesFactory = bytes,
) -> BytesLike:
...
@property
@abc.abstractmethod
@_inherit_extend_docstring(memoryview) # TODO
def suboffsets(
self,
) -> Tuple:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def swapcase(
self,
) -> 'BaseBytesMethods':
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def title(
self,
) -> 'BaseBytesMethods':
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(memoryview) # TODO
def tobytes(
self,
) -> bytes:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(memoryview) # TODO
def tolist(
self,
) -> List[int]:
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def translate(
self,
table: BytesLike,
) -> 'BaseBytesMethods':
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def upper(
self,
) -> 'BaseBytesMethods':
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(bytes) # TODO
def zfill(
self,
width: int,
factory: BytesFactory = bytes,
) -> BytesLike:
...
[docs]class BaseInplaceView(BaseBytesMethods):
r"""Provides inplace methods to a byte buffer.
Standard Python *byte-like* objects (e.g. :obj:`memoryview` and
:obj:`bytearray`) only provide a very small set of methods for
*inplace* editing of their underlying bytes buffer.
This wrapper class adds a low-level implementation of those methods to
anything supporting the *buffer protocol* with a mutable buffer.
Note:
Editing support is only limited to the existing buffer items, i.e. the
wrapper and its underlying buffer cannot be resized via the methods
provided by this class, just like with standard memory views.
Arguments:
wrapped (*byte-like*): The target object supporting the
*buffer protocol*.
Examples:
>>> from cbytesparse import InplaceView
>>> buffer = bytearray(b'Hello, World!')
>>> wrapped = InplaceView(buffer)
>>> wrapped.lower()
>>> buffer
bytearray(b'hello, world!')
>>> wrapped.replace(b'l', b'p')
>>> buffer
bytearray(b'heppo, worpd!')
>>> wrapped.find(b'w')
7
>>> wrapped.rfind(b'o')
8
>>> wrapped.count(b'o')
2
"""
[docs] @abc.abstractmethod
def __init__(
self,
wrapped: Optional[BytesLike],
):
...
[docs] @abc.abstractmethod
@_inherit_extend_docstring(memoryview) # TODO
def toreadonly(
self,
) -> 'BaseInplaceView':
...