Source code for bases.alphabet

"""
    Module containing classes for alphabets.
"""

from __future__ import annotations

import re
from typing import Collection, Dict, Iterator, Optional, overload, Tuple, Union
from typing_extensions import Literal
from typing_validation import validate

from .abstract import Alphabet as Alphabet
from .string_alphabet import StringAlphabet as StringAlphabet
from .range_alphabet import RangeAlphabet as RangeAlphabet

_alphabets: Dict[str, Alphabet] = {}

[docs] def get(name: str) -> Alphabet: """ Gets an alphabet by name, raising `KeyError` if none exists. Example usage: >>> alphabet.get("base16") StringAlphabet('0123456789ABCDEF') :param name: the alphabet name :type name: :obj:`str` """ validate(name, str) if name not in _alphabets: raise KeyError(f"Alphabet named {repr(name)} does not exist.") return _alphabets[name]
[docs] def has(name: str) -> bool: """ Checks whether an alphabet with the given name exists. Example usage: >>> from bases import alphabet >>> alphabet.has("base32") True :param name: the alphabet name :type name: :obj:`str` """ validate(name, str) return name in _alphabets
[docs] def register(**alphabets: Alphabet) -> None: r""" Registers any number of new alphabets by name. Example usage: >>> from bases import alphabet >>> alphabet.register(base16lower=alphabet.base16.lower()) >>> alphabet.get("base16lower") StringAlphabet('0123456789abcdef') Alphabet names must conform with: .. code-block:: re.match(r"^base[0-9][a-zA-Z0-9_]*$", name) :param alphabets: the alphabets to register, passed by desired registration name :type alphabets: :obj:`~typing.Dict`\ [:obj:`str`, :class:`~bases.alphabet.abstract.Alphabet`] :raises KeyError: if an alphabet with one of the given names already exists """ for arg in alphabets.values(): validate(arg, Alphabet) for name, alphabet in alphabets.items(): if not re.match(r"^base[0-9][a-zA-Z0-9_]*$", name): raise ValueError(f"Invalid alphabet name {repr(name)}") if not isinstance(alphabet, Alphabet): raise TypeError() if name in _alphabets: raise ValueError(f"Alphabet named {repr(name)} already exists.") _alphabets[name] = alphabet
[docs] def unregister(*names: str) -> None: r""" Unregisters any number of existing alphabets by name. Example usage: >>> from bases import alphabet >>> alphabet.unregister("base16", "base32") >>> alphabet.has("base16") False >>> alphabet.get("base16") KeyError: "Alphabet named 'base16' does not exist." Note that pre-defined constants are unaffected by this: >>> alphabet.base16 StringAlphabet('0123456789ABCDEF') :param names: the alphabet names :type names: :obj:`~typing.Tuple`\ [:obj:`str`, ...] :raises KeyError: if an alphabet with one of the given names does not exists """ for name in names: validate(name, str) for name in names: if name not in _alphabets: raise KeyError(f"Alphabet named {repr(name)} does not exist.") del _alphabets[name]
[docs] def table(*, prefix: str = "") -> Iterator[Tuple[str, Alphabet]]: """ Iterates over all registered alphabets, optionally restricting to those with given prefix. Example usage: >>> from bases import alphabet >>> dict(alphabet.table(prefix="base32")) {'base32': StringAlphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', case_sensitive=False), 'base32hex': StringAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUV', case_sensitive=False), 'base32z': StringAlphabet('ybndrfg8ejkmcpqxot1uwisza345h769', case_sensitive=False)} :param name: optional prefix to filter by when listing alphabets :type name: :obj:`str`, *optional* """ validate(prefix, str) alphabets = [(name, alphabet) for name, alphabet in _alphabets.items() if name.startswith(prefix)] alphabets = sorted(alphabets, key=lambda pair: pair[0]) return iter(alphabets)
@overload def make(chars: str, *, case_sensitive: bool = True, name: Optional[str] = None) -> StringAlphabet: ... @overload def make(chars: range, *, case_sensitive: bool = True, name: Optional[str] = None) -> RangeAlphabet: ...
[docs] def make(chars: Union[str, range], *, case_sensitive: bool = True, name: Optional[str] = None) -> Union[StringAlphabet, RangeAlphabet]: """ Utility function to create custom alphabets. Automatically creates an instance of :class:`~bases.alphabet.string_alphabet.StringAlphabet` or :class:`~bases.alphabet.range_alphabet.RangeAlphabet` based on whether a string or range is passed. Example usage with string (case-insensitive base-16): >>> alphabet.make("0123456789ABCDEF", case_sensitive=False) StringAlphabet('0123456789ABCDEF', case_sensitive=False) Example usage with range (extended ASCII): >>> alphabet.make(range(0x00, 0x100)) RangeAlphabet(range(0x00, 0x100)) If the optional keyword argument ``name`` is specified, the alphabet is automatically registered using :func:`register`. :param chars: the alphabet characters or codepoint range :type chars: :obj:`str` or :obj:`range` :param case_sensitive: whether the alphabet is case-sensitive :type case_sensitive: :obj:`bool`, *optional* :param name: if specified, the newly created alphabet is registered with this name :type name: :obj:`str` or :obj:`None`, *optional* """ validate(chars, Union[str, range]) validate(case_sensitive, bool) validate(name, Optional[str]) if isinstance(chars, str): string_alphabet = StringAlphabet(chars, case_sensitive=case_sensitive) if name is not None: register(**{name: string_alphabet}) return string_alphabet if isinstance(chars, range): range_alphabet = RangeAlphabet(chars, case_sensitive=case_sensitive) if name is not None: register(**{name: range_alphabet}) return range_alphabet raise NotImplementedError(f"Character type {type(chars)} not supported.")
base2 = make("01", name="base2") """ Base-2 alphabet. """ base8 = make("01234567", name="base8") """ Base-8 alphabet. """ base16 = make("0123456789ABCDEF", case_sensitive=False, name="base16") """ Uppercase case-insensitive base-16 alphabet. """ base32 = make("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", case_sensitive=False, name="base32") """ Uppercase case-insensitive base-32 alphabet from `rfc4648 <https://datatracker.ietf.org/doc/html/rfc4648>`_. """ base32hex = make("0123456789ABCDEFGHIJKLMNOPQRSTUV", case_sensitive=False, name="base32hex") """ Uppercase case-insensitive hex base-32 alphabet from `rfc4648 <https://datatracker.ietf.org/doc/html/rfc4648>`_. """ base32z = make("ybndrfg8ejkmcpqxot1uwisza345h769", case_sensitive=False, name="base32z") """ Lowercase case-insensitive human-oriented base-32 alphabet from https://philzimmermann.com/docs/human-oriented-base-32-encoding.txt """ base64 = make("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", name="base64") """ Uppercase case-insensitive base-64 alphabet from `rfc4648 <https://datatracker.ietf.org/doc/html/rfc4648>`_. """ base64url = make("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", name="base64url") """ Uppercase case-insensitive url-safe base-64 alphabet from `rfc4648 <https://datatracker.ietf.org/doc/html/rfc4648>`_. """ base10 = make("0123456789", name="base10") """ Base-10 alphabet. """ base36 = make("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", case_sensitive=False, name="base36") """ Uppercase case-insensitive base-36 alphabet. """ base58btc = make("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", name="base58btc") """ Case-sensitive base-58 alphabet used by Bitcoin. """ base58flickr = make("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ", name="base58flickr") """ Case-sensitive base-58 alphabet used by Flickr. """ base58ripple = make("rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz", name="base58ripple") """ Case-sensitive base-58 alphabet used by Ripple. """ base45 = make("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:", case_sensitive=False, name="base45") """ Uppercase case-insensitive base-45 alphabet from https://datatracker.ietf.org/doc/draft-faltstrom-base45/ """