Commit e1e97ae3 authored by HacKan's avatar HacKan
Browse files

📝 Implement google style docstrings

parent da0d31f1
[pydocstyle]
convention=google
This diff is collapsed.
......@@ -10,11 +10,26 @@ class ZlibCompressor(CompressorInterface):
"""Zlib compressor."""
def compress(self, data: bytes, *, level: int) -> bytes:
"""Compress given data using zlib."""
"""Compress given data using zlib.
Args:
data: Data to compress.
level: Desired compression level.
Returns:
Raw compressed data.
"""
return zlib.compress(data, level=level)
def decompress(self, data: bytes) -> bytes:
"""Decompress given compressed data compressed with zlib."""
"""Decompress given compressed data compressed with zlib.
Args:
data: Compressed data to decompress.
Returns:
Original data.
"""
return zlib.decompress(data)
......@@ -22,9 +37,24 @@ class GzipCompressor(CompressorInterface):
"""Gzip compressor."""
def compress(self, data: bytes, *, level: int) -> bytes:
"""Compress given data using gzip."""
"""Compress given data using gzip.
Args:
data: Data to compress.
level: Desired compression level.
Returns:
Raw compressed data.
"""
return gzip.compress(data, compresslevel=level)
def decompress(self, data: bytes) -> bytes:
"""Decompress given compressed data compressed with gzip."""
"""Decompress given compressed data compressed with gzip.
Args:
data: Compressed data to decompress.
Returns:
Original data.
"""
return gzip.decompress(data)
......@@ -18,11 +18,25 @@ class B64URLEncoder(EncoderInterface):
return b'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-'
def encode(self, data: bytes) -> bytes:
"""Encode given data to base64 URL safe without padding."""
"""Encode given data to base64 URL safe without padding.
Args:
data: Data to encode.
Returns:
Encoded data.
"""
return b64encode(data)
def decode(self, data: bytes) -> bytes:
"""Decode given encoded data from base64 URL safe without padding."""
"""Decode given encoded data from base64 URL safe without padding.
Args:
data: Data to decode.
Returns:
Original data.
"""
return b64decode(data)
......@@ -35,11 +49,25 @@ class B32Encoder(EncoderInterface):
return b'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
def encode(self, data: bytes) -> bytes:
"""Encode given data to base32 without padding."""
"""Encode given data to base32 without padding.
Args:
data: Data to encode.
Returns:
Encoded data.
"""
return b32encode(data)
def decode(self, data: bytes) -> bytes:
"""Decode given encoded data from base32 without padding."""
"""Decode given encoded data from base32 without padding.
Args:
data: Data to decode.
Returns:
Original data.
"""
return b32decode(data)
......@@ -52,9 +80,23 @@ class HexEncoder(EncoderInterface):
return b'ABCDEF0123456789'
def encode(self, data: bytes) -> bytes:
"""Encode given data to hexadecimal."""
"""Encode given data to hexadecimal.
Args:
data: Data to encode.
Returns:
Encoded data.
"""
return hexencode(data)
def decode(self, data: bytes) -> bytes:
"""Decode given encoded data from hexadecimal."""
"""Decode given encoded data from hexadecimal.
Args:
data: Data to decode.
Returns:
Original data.
"""
return hexdecode(data)
"""Errors."""
"""Errors: contains all errors and exceptions raised by this lib.
Note:
Here's the hierarchy tree:
SignerError
|
|-- InvalidOptionError: given option value is out of bounds, has the wrong
| format or type.
|
|-- DataError: generic data error.
|
|-- SignedDataError: error that occurred for *signed data*.
| |
| |-- SignatureError: error encountered while dealing with
| | | the signature.
| | |
| | |-- InvalidSignatureError: the signature is not
| | | valid.
| | |
| | |-- ExpiredSignatureError: the signature
| | has expired.
| |
| |-- UnserializationError: given data could not be
| | unserialized.
| |
| |-- DecompressionError: given data could not be decompressed.
| |
| |-- DecodeError: given data could not be decoded.
| |
| |-- ConversionError: given data could not be converted
| | to bytes.
| |
| |-- FileError: error while reading the file.
|
|-- UnsignedDataError: error that occurred for *data to be signed*.
|
|-- SerializationError: given data could not be serialized.
|
|-- CompressionError: given data could not be compressed.
|
|-- EncodeError: given data could not be encoded.
|
|-- ConversionError: given data could not be converted
| to bytes.
|
|-- FileError: error while writing the file.
"""
import typing
from datetime import datetime
......@@ -72,8 +119,12 @@ class ExpiredSignatureError(InvalidSignatureError):
def __init__(self, *args: typing.Any, timestamp: datetime) -> None:
"""Initialize self.
:param timestamp: An aware datetime object indicating when the signature
was done.
Args:
*args: Additional positional arguments, see `Exception.__init__`.
timestamp: An aware datetime object indicating when the signature was done.
Returns:
None.
"""
super().__init__(*args)
......
......@@ -13,11 +13,27 @@ class SerializerInterface(ABC):
@abstractmethod
def serialize(self, data: typing.Any, **kwargs: typing.Any) -> bytes:
"""Serialize given data."""
"""Serialize given data.
Args:
data: Data to serialize.
**kwargs: Additional arguments for the serializer.
Returns:
Serialized data
"""
@abstractmethod
def unserialize(self, data: bytes, **kwargs: typing.Any) -> typing.Any:
"""Unserialize given serialized data."""
"""Unserialize given serialized data.
Args:
data: Serialized data to unserialize.
**kwargs: Additional arguments for the serializer.
Returns:
Original data.
"""
class CompressorInterface(ABC):
......@@ -28,11 +44,26 @@ class CompressorInterface(ABC):
@abstractmethod
def compress(self, data: bytes, *, level: int) -> bytes:
"""Compress given data."""
"""Compress given data.
Args:
data: Data to compress.
level: Desired compression level.
Returns:
Raw compressed data.
"""
@abstractmethod
def decompress(self, data: bytes) -> bytes:
"""Decompress given compressed data."""
"""Decompress given compressed data.
Args:
data: Compressed data to decompress.
Returns:
Original data.
"""
class EncoderInterface(ABC):
......@@ -40,8 +71,8 @@ class EncoderInterface(ABC):
Implement your own encoder inheriting from this class.
Important note: verify that the separator character is out of the encoder
alphabet (a check is enforced nevertheless).
Note:
Verify that the encoder alphabet is ASCII (a check is enforced nevertheless).
"""
@property
......@@ -55,8 +86,22 @@ class EncoderInterface(ABC):
@abstractmethod
def encode(self, data: bytes) -> bytes:
"""Encode given data."""
"""Encode given data.
Args:
data: Data to encode.
Returns:
Encoded data.
"""
@abstractmethod
def decode(self, data: bytes) -> bytes:
"""Decode given encoded data."""
"""Decode given encoded data.
Args:
data: Encoded data to decode.
Returns:
Original data.
"""
......@@ -23,7 +23,14 @@ class Mixin(ABC):
def _force_bytes(value: typing.Any) -> bytes:
"""Force given value into bytes.
:raise ConversionError: Can't force value into bytes.
Args:
value: Value to convert to bytes.
Returns:
Converted value into bytes.
Raises:
ConversionError: Can't force value into bytes.
"""
try:
return force_bytes(value)
......@@ -43,7 +50,17 @@ class SerializerMixin(Mixin, ABC):
serializer: typing.Type[SerializerInterface] = JSONSerializer,
**kwargs: typing.Any,
) -> None:
"""Add serializing capabilities."""
"""Add serializing capabilities.
Args:
*args: Additional positional arguments.
serializer (optional): Serializer class to use (defaults to a JSON
serializer).
**kwargs: Additional keyword only arguments.
Returns:
None.
"""
self._serializer = serializer()
personalisation = self._force_bytes(kwargs.get('personalisation', b''))
......@@ -55,7 +72,8 @@ class SerializerMixin(Mixin, ABC):
def _serialize(self, data: typing.Any, **kwargs: typing.Any) -> bytes:
"""Serialize given data. Additional kwargs are passed to the serializer.
:raise SerializationError: Data can't be serialized.
Raises:
SerializationError: Data can't be serialized.
"""
try:
return self._serializer.serialize(data, **kwargs)
......@@ -65,7 +83,8 @@ class SerializerMixin(Mixin, ABC):
def _unserialize(self, data: bytes) -> typing.Any:
"""Unserialize given data.
:raise UnserializationError: Data can't be unserialized.
Raises:
UnserializationError: Data can't be unserialized.
"""
try:
return self._serializer.unserialize(data)
......@@ -89,18 +108,21 @@ class CompressorMixin(Mixin, ABC):
) -> None:
"""Add compressing capabilities.
:param compressor: [optional] Compressor class to use (defaults to a
Zlib compressor).
:param compression_flag: [optional] Character to mark the payload as
compressed. It must be ASCII (defaults to ".").
:param compression_ratio: [optional] Desired minimal compression ratio,
between 0 and below 100 (defaults to 5).
It is used to calculate when to consider a payload
sufficiently compressed so as to detect detrimental
compression. By default if compression achieves
less than 5% of size reduction, it is considered
detrimental.
Args:
*args: Additional positional arguments.
compressor (optional): Compressor class to use (defaults to a Zlib
compressor).
compression_flag (optional): Character to mark the payload as compressed.
It must be ASCII (defaults to ".").
compression_ratio (optional): Desired minimal compression ratio, between
0 and below 100 (defaults to 5). It is used to calculate when
to consider a payload sufficiently compressed to detect detrimental
compression. By default, if compression achieves less than 5% of
size reduction, it is considered detrimental.
**kwargs: Additional keyword only arguments.
Returns:
None.
"""
self._compressor = compressor()
......@@ -172,14 +194,17 @@ class CompressorMixin(Mixin, ABC):
given data and if not then it returns given data as-is, unless compression
is forced.
:param data: Data to compress.
:param level: Compression level wanted.
:param force: Force compression without checking if convenient.
Args:
data: Data to compress.
level: Compression level wanted.
force (optional): Force compression without checking if convenient.
:return: A tuple containing data and a flag indicating if data is
compressed (True) or not.
Returns:
A tuple containing data, and a flag indicating if data is compressed
(True) or not.
:raise CompressionError: Data can't be compressed.
Raises
CompressionError: Data can't be compressed.
"""
try:
compressed = self._compressor.compress(data, level=level)
......@@ -195,7 +220,8 @@ class CompressorMixin(Mixin, ABC):
def _decompress(self, data: bytes) -> bytes:
"""Decompress given data if it is compressed, otherwise do nothing.
:raise DecompressionError: Data can't be decompressed.
Raises:
DecompressionError: Data can't be decompressed.
"""
if not self._is_compressed(data):
return data
......@@ -219,7 +245,17 @@ class EncoderMixin(Mixin, ABC):
encoder: typing.Type[EncoderInterface] = B64URLEncoder,
**kwargs: typing.Any,
) -> None:
"""Add encoding capabilities."""
"""Add encoding capabilities.
Args:
*args: Additional positional arguments.
encoder (optional): Encoder class to use (defaults to a Base64 URL
safe encoder).
**kwargs: Additional keyword only arguments.
Returns:
None.
"""
self._encoder = self._validate_encoder(encoder)
personalisation = self._force_bytes(kwargs.get('personalisation', b''))
......@@ -246,7 +282,8 @@ class EncoderMixin(Mixin, ABC):
def _encode(self, data: bytes) -> bytes:
"""Encode given data.
:raise EncodeError: Data can't be encoded.
Raises:
EncodeError: Data can't be encoded.
"""
try:
return self._encoder.encode(data)
......@@ -256,7 +293,8 @@ class EncoderMixin(Mixin, ABC):
def _decode(self, data: bytes) -> bytes:
"""Decode given encoded data.
:raise DecodeError: Data can't be decoded.
Raises:
DecodeError: Data can't be decoded.
"""
try:
return self._encoder.decode(data)
......
......@@ -11,12 +11,28 @@ class JSONSerializer(SerializerInterface):
"""JSON serializer."""
def serialize(self, data: typing.Any, **kwargs: typing.Any) -> bytes:
"""Serialize given data to JSON."""
"""Serialize given data to JSON.
Args:
data: Data to serialize.
**kwargs: Additional arguments for `json.dumps`.
Returns:
Serialized data
"""
kwargs.setdefault('separators', (',', ':')) # Use JSON compact encoding
return json.dumps(data, **kwargs).encode()
def unserialize(self, data: bytes, **kwargs: typing.Any) -> typing.Any:
"""Unserialize given JSON data."""
"""Unserialize given JSON data.
Args:
data: Serialized data to unserialize.
**kwargs: Additional arguments for `json.loads`.
Returns:
Original data.
"""
return json.loads(data, **kwargs)
......@@ -24,9 +40,25 @@ class NullSerializer(SerializerInterface):
"""Null serializer that doesn't serializes anything."""
def serialize(self, data: typing.Any, **kwargs: typing.Any) -> bytes:
"""Null serialize data (it just converts it to bytes)."""
"""Null serialize data (it just converts it to bytes).
Args:
data: Data to serialize.
**kwargs: Ignored.
Returns:
Serialized data
"""
return force_bytes(data)
def unserialize(self, data: bytes, **kwargs: typing.Any) -> typing.Any:
"""Return given data as-is."""
"""Return given data as-is.
Args:
data: Serialized data to unserialize.
**kwargs: Ignored.
Returns:
Original data.
"""
return data
This diff is collapsed.
......@@ -8,7 +8,14 @@ from datetime import timezone
def force_bytes(value: typing.Any) -> bytes:
"""Force a given value into bytes."""
"""Force a given value into bytes.
Args:
value: Value to convert to bytes.
Returns:
Converted value into bytes.
"""
if isinstance(value, bytes):
return value
elif isinstance(value, str):
......@@ -18,44 +25,97 @@ def force_bytes(value: typing.Any) -> bytes:
def b64encode(data: bytes) -> bytes:
"""Encode data as Base 64 URL safe, stripping padding."""
"""Encode data as Base 64 URL safe, stripping padding.
Args:
data: Data to encode.
Returns:
Encoded data.
"""
return base64.urlsafe_b64encode(data).rstrip(b'=')
def b64decode(data: bytes) -> bytes:
"""Decode data encoded as Base 64 URL safe without padding."""
"""Decode data encoded as Base 64 URL safe without padding.
Args:
data: Data to decode.
Returns:
Original data.
"""
return base64.urlsafe_b64decode(data + (b'=' * (len(data) % 4)))
def b32encode(data: bytes) -> bytes:
"""Encode data as Base 32, stripping padding."""
"""Encode data as Base 32, stripping padding.
Args:
data: Data to encode.
Returns:
Encoded data.
"""
return base64.b32encode(data).rstrip(b'=')
def b32decode(data: bytes) -> bytes:
"""Decode data encoded as Base 32 without padding."""
"""Decode data encoded as Base 32 without padding.
Args:
data: Data to decode.
Returns:
Original data.
"""
return base64.b32decode(data + (b'=' * ((8 - (len(data) % 8)) % 8)))
def hexencode(data: bytes) -> bytes:
"""Encode data as hexadecimal (uppercase)."""
"""Encode data as hexadecimal (uppercase).
Args:
data: Data to encode.
Returns:
Encoded data.
"""
return base64.b16encode(data)
def hexdecode(data: bytes) -> bytes:
"""Decode data encoded as hexadecimal (uppercase)."""
"""Decode data encoded as hexadecimal (uppercase).
Args:
data: Data to decode.
Returns:
Original data.
"""
return base64.b16decode(data)
def timestamp_to_aware_datetime(timestamp: typing.Union[int, float]) -> datetime:
"""Convert a UNIX timestamp into an aware datetime in UTC."""
"""Convert a UNIX timestamp into an aware datetime in UTC.
Args:
timestamp: UNIX timestamp to convert.
Returns:
Converted timestamp into an aware datetime in UTC.
"""
return datetime.fromtimestamp(timestamp, tz=timezone.utc)
def file_mode_is_text(file: typing.IO) -> bool:
"""Check if given file is opened in text mode, or otherwise in binary mode.
:return: True if file is opened in text mode, False otherwise.
Args:
file: File to check its mode.
Returns:
True if file is opened in text mode, False otherwise.
"""