Module refinery.units.crypto.keyderive

Implements key derivation routines. These are mostly meant to be used as modifiers for multibin expressions that can be passed as key arguments to modules in refinery.units.crypto.cipher.

Expand source code Browse git
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Implements key derivation routines. These are mostly meant to be used as
modifiers for multibin expressions that can be passed as key arguments to
modules in `refinery.units.crypto.cipher`.
"""
from __future__ import annotations

import importlib

from refinery.units import Arg, Unit
from refinery.lib.argformats import number
from refinery.lib.types import ByteStr

from enum import Enum
from typing import Callable, TYPE_CHECKING

if TYPE_CHECKING:
    from typing import Protocol

    class _Hash(Protocol):
        def update(self, data: ByteStr): ...
        def digest(self) -> ByteStr: ...
        def hexdigest(self) -> str: ...

    class _HashModule(Protocol):
        def new(self, data=None) -> _Hash: ...


__all__ = ['Arg', 'HASH', 'KeyDerivation']


class HASH(str, Enum):
    MD2 = 'MD2'
    MD4 = 'MD4'
    MD5 = 'MD5'
    SHA1 = 'SHA'
    SHA256 = 'SHA256'
    SHA512 = 'SHA512'
    SHA224 = 'SHA224'
    SHA384 = 'SHA384'


def multidecode(data: ByteStr, function: Callable[[str], ByteStr]) -> ByteStr:
    for codec in ['utf8', 'latin1', 'cp1252']:
        try:
            return function(data.decode(codec))
        except UnicodeError:
            continue
    else:
        return function(''.join(chr(t) for t in data))


class KeyDerivation(Unit, abstract=True):

    def __init__(
        self,
        size: Arg(help='The number of bytes to generate.', type=number),
        salt: Arg(help='Salt for the derivation.'),
        hash: Arg.Option(choices=HASH, metavar='hash',
            help='Specify one of these algorithms (default is {default}): {choices}') = None,
        iter: Arg.Number(metavar='iter', help='Number of iterations; default is {default}.') = None,
        **kw
    ):
        if hash is not None:
            hash = Arg.AsOption(hash, HASH)
        return super().__init__(salt=salt, size=size, iter=iter, hash=hash, **kw)

    @property
    def hash(self) -> _HashModule:
        name = self.args.hash.value
        hash = importlib.import_module(F'Cryptodome.Hash.{name}')
        return hash

Sub-modules

refinery.units.crypto.keyderive.deskd
refinery.units.crypto.keyderive.hkdf
refinery.units.crypto.keyderive.hmac
refinery.units.crypto.keyderive.kblob
refinery.units.crypto.keyderive.mscdk

Reference: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptderivekey

refinery.units.crypto.keyderive.mspdb
refinery.units.crypto.keyderive.pbkdf1
refinery.units.crypto.keyderive.pbkdf2
refinery.units.crypto.keyderive.unixcrypt

Classes

class Arg (*args, action=refinery.units.Arg.omit, choices=refinery.units.Arg.omit, const=refinery.units.Arg.omit, default=refinery.units.Arg.omit, dest=refinery.units.Arg.omit, help=refinery.units.Arg.omit, metavar=refinery.units.Arg.omit, nargs=refinery.units.Arg.omit, required=refinery.units.Arg.omit, type=refinery.units.Arg.omit, group=None, guessed=None)

This class is specifically an argument for the add_argument method of an ArgumentParser from the argparse module. It can also be used as a decorator or annotation for the constructor of a refinery unit to better control the argument parser of that unit's command line interface. Example:

class prefixer(Unit):
    def __init__(
        self,
        prefix: Arg.Binary(help='This data will be prepended to the input.')
    ): ...
    def process(self, data):
        return self.args.prefix + data

Note that when the init of a unit has a return annotation that is a base class of itself, then all its parameters will automatically be forwarded to that base class.

Expand source code Browse git
class Arg(Argument):
    """
    This class is specifically an argument for the `add_argument` method of an `ArgumentParser` from
    the `argparse` module. It can also be used as a decorator or annotation for the constructor of a
    refinery unit to better control the argument parser of that unit's command line interface.
    Example:
    ```
    class prefixer(Unit):
        def __init__(
            self,
            prefix: Arg.Binary(help='This data will be prepended to the input.')
        ): ...
        def process(self, data):
            return self.args.prefix + data
    ```
    Note that when the init of a unit has a return annotation that is a base class of itself, then
    all its parameters will automatically be forwarded to that base class.
    """

    class delete: pass
    class omit: pass

    args: List[str]

    def __init__(
        self, *args: str,
            action   : Union[omit, str]           = omit, # noqa
            choices  : Union[omit, Iterable[Any]] = omit, # noqa
            const    : Union[omit, Any]           = omit, # noqa
            default  : Union[omit, Any]           = omit, # noqa
            dest     : Union[omit, str]           = omit, # noqa
            help     : Union[omit, str]           = omit, # noqa
            metavar  : Union[omit, str]           = omit, # noqa
            nargs    : Union[omit, int, str]      = omit, # noqa
            required : Union[omit, bool]          = omit, # noqa
            type     : Union[omit, type]          = omit, # noqa
            group    : Optional[str]              = None, # noqa
            guessed  : Optional[Set[str]]         = None, # noqa
    ) -> None:
        kwargs = dict(action=action, choices=choices, const=const, default=default, dest=dest,
            help=help, metavar=metavar, nargs=nargs, required=required, type=type)
        kwargs = {key: value for key, value in kwargs.items() if value is not Arg.omit}
        self.group = group
        self.guessed = set(guessed or ())
        super().__init__(*args, **kwargs)

    def update_help(self):
        """
        This method is called to format the help text of the argument retroactively. The primary
        purpose is to fill in default arguments via the formatting symbol `{default}`. These
        default values are not necessarily part of the `refinery.units.Arg` object itself: They
        may be a default value in the `__init__` function of the `refinery.units.Unit` subclass.
        Therefore, it is necessary to format the help text after all information has been
        compiled.
        """
        class formatting(dict):
            arg = self

            def __missing__(self, key):
                if key == 'choices':
                    return ', '.join(self.arg.kwargs['choices'])
                if key == 'default':
                    default: Union[bytes, int, str, slice] = self.arg.kwargs['default']
                    if isinstance(default, (list, tuple, set)):
                        if not default:
                            return 'empty'
                        elif len(default) == 1:
                            default = default[0]
                    if isinstance(default, slice):
                        parts = [default.start or '', default.stop or '', default.step]
                        default = ':'.join(str(x) for x in parts if x is not None)
                    if isinstance(default, int):
                        return default
                    if not isbuffer(default):
                        return default
                    if default.isalnum():
                        return default.decode('latin-1')
                    return F'H:{default.hex()}'
                if key == 'varname':
                    return self.arg.kwargs.get('metavar', self.arg.destination)

        try:
            help_string: str = self.kwargs['help']
            self.kwargs.update(
                help=help_string.format_map(formatting()))
        except Exception:
            pass

    def __rmatmul__(self, method):
        self.update_help()
        return super().__rmatmul__(method)

    @staticmethod
    def AsOption(value: Optional[Any], cls: Enum) -> Enum:
        """
        This method converts the input `value` to an instance of the enum `cls`. It is intended to
        be used on values that are passed as an argument marked with the `refinery.units.Arg.Option`
        decorator. If the input value is `None` or already an instance of `cls`, it is returned
        unchanged. Otherwise, the function attempts to find an element of the enumeration that
        matches the input, either by name or by value.
        """
        if value is None or isinstance(value, cls):
            return value
        if isinstance(value, str):
            try:
                return cls[value]
            except KeyError:
                pass
            needle = normalize_to_identifier(value).casefold()
            for item in cls.__members__:
                if not isinstance(item, str):
                    break
                if item.casefold() == needle:
                    return cls[item]
        try:
            return cls(value)
        except Exception as E:
            choices = ', '.join(normalize_to_display(m) for m in cls.__members__)
            raise ValueError(F'Could not transform {value} into {cls.__name__}; the choices are: {choices}') from E

    @classmethod
    def Delete(cls):
        """
        This should be specified when the argument is present for a (potentially abstract) parent
        unit but should be removed on the child.
        """
        return cls(nargs=cls.delete)

    @classmethod
    def Counts(
        cls,
        *args   : str,
        help    : Union[omit, str] = omit,
        dest    : Union[omit, str] = omit,
        group   : Optional[str] = None,
    ):
        """
        A convenience method to add argparse arguments that introduce a counter.
        """
        return cls(*args, group=group, help=help, dest=dest, action='count')

    @classmethod
    def Switch(
        cls,
        *args   : str, off=False,
        help    : Union[omit, str] = omit,
        dest    : Union[omit, str] = omit,
        group   : Optional[str] = None,
    ):
        """
        A convenience method to add argparse arguments that change a boolean value from True to False or
        vice versa. By default, a switch will have a False default and change it to True when specified.
        """
        return cls(*args, group=group, help=help, dest=dest, action='store_false' if off else 'store_true')

    @classmethod
    def Binary(
        cls,
        *args   : str,
        help    : Union[omit, str] = omit,
        dest    : Union[omit, str] = omit,
        nargs   : Union[omit, int, str] = omit,
        metavar : Optional[str] = None,
        group   : Optional[str] = None,
    ):
        """
        Used to add argparse arguments that contain binary data.
        """
        if metavar is None and any('-' in a for a in args):
            metavar = 'B'
        return cls(*args, group=group, help=help, dest=dest, nargs=nargs, type=multibin, metavar=metavar)

    @classmethod
    def String(
        cls,
        *args   : str,
        help    : Union[omit, str] = omit,
        dest    : Union[omit, str] = omit,
        nargs   : Union[omit, int, str] = omit,
        metavar : Optional[str] = None,
        group   : Optional[str] = None,
    ):
        """
        Used to add argparse arguments that contain string data.
        """
        if metavar is None and any('-' in a for a in args):
            metavar = 'STR'
        return cls(*args, group=group, help=help, dest=dest, nargs=nargs, type=str, metavar=metavar)

    @classmethod
    def RegExp(
        cls,
        *args   : str,
        help    : Union[omit, str] = omit,
        dest    : Union[omit, str] = omit,
        nargs   : Union[omit, int, str] = omit,
        metavar : Optional[str] = None,
        group   : Optional[str] = None,
    ):
        """
        Used to add argparse arguments that contain a regular expression.
        """
        if metavar is None and any('-' in a for a in args):
            metavar = 'REGEX'
        return cls(*args, group=group, help=help, dest=dest, nargs=nargs, type=regexp, metavar=metavar)

    @classmethod
    def NumSeq(
        cls,
        *args   : str,
        help    : Union[omit, str] = omit,
        dest    : Union[omit, str] = omit,
        nargs   : Union[omit, int, str] = omit,
        metavar : Optional[str] = None,
        group   : Optional[str] = None,
    ):
        """
        Used to add argparse arguments that contain a numeric sequence.
        """
        return cls(*args, group=group, help=help, nargs=nargs, dest=dest, type=numseq, metavar=metavar)

    @classmethod
    def Bounds(
        cls,
        *args   : str,
        help    : Optional[Union[omit, str]] = None,
        dest    : Union[omit, str] = omit,
        nargs   : Union[omit, int, str] = omit,
        default : Union[omit, Any] = omit,
        range   : bool = False,
        metavar : Optional[str] = 'start:end:step',
        group   : Optional[str] = None,
    ):
        """
        Used to add argparse arguments that contain a slice.
        """
        if help is None:
            help = 'Specify start:end:step in Python slice syntax.'
            if default is not cls.omit:
                help = F'{help} The default is {{default}}.'
        parser = slicerange if range else sliceobj
        return cls(*args, group=group, help=help, default=default, nargs=nargs, dest=dest, type=parser, metavar=metavar)

    @classmethod
    def Number(
        cls,
        *args   : str,
        bound   : Union[omit, Tuple[int, int]] = omit,
        help    : Union[omit, str] = omit,
        dest    : Union[omit, str] = omit,
        metavar : Optional[str] = None,
        group   : Optional[str] = None,
    ):
        """
        Used to add argparse arguments that contain a number.
        """
        nt = number
        if bound is not cls.omit:
            lower, upper = bound
            nt = nt[lower:upper]
        return cls(*args, group=group, help=help, dest=dest, type=nt, metavar=metavar or 'N')

    @classmethod
    def Option(
        cls,
        *args   : str,
        choices : Enum,
        help    : Union[omit, str] = omit,
        dest    : Union[omit, str] = omit,
        metavar : Optional[str] = None,
        group   : Optional[str] = None,
    ):
        """
        Used to add argparse arguments with a fixed set of options, based on an enumeration.
        """
        cnames = [normalize_to_display(c).casefold() for c in choices.__members__]
        metavar = metavar or choices.__name__
        return cls(*args, group=group, help=help, metavar=metavar, dest=dest, choices=cnames, type=str.casefold)

    @classmethod
    def Choice(
        cls,
        *args   : str,
        choices : List[str],
        help    : Union[omit, str] = omit,
        metavar : Union[omit, str] = omit,
        dest    : Union[omit, str] = omit,
        type    : Type = str,
        nargs   : Union[omit, int, str] = omit,
        group   : Optional[str] = None,
    ):
        """
        Used to add argparse arguments with a fixed set of options, based on a list of strings.
        """
        return cls(*args, group=group, type=type, metavar=metavar, nargs=nargs,
            dest=dest, help=help, choices=choices)

    @property
    def positional(self) -> bool:
        """
        Indicates whether the argument is positional. This is crudely determined by whether it has
        a specifier that does not start with a dash.
        """
        return any(a[0] != '-' for a in self.args)

    @property
    def destination(self) -> str:
        """
        The name of the variable where the contents of this parsed argument will be stored.
        """
        for a in self.args:
            if a[0] != '-':
                return a
        try:
            return self.kwargs['dest']
        except KeyError:
            for a in self.args:
                if a.startswith('--'):
                    dest = normalize_to_identifier(a)
                    if dest.isidentifier():
                        return dest
            raise AttributeError(F'The argument with these values has no destination: {self!r}')

    @classmethod
    def Infer(cls, pt: inspect.Parameter, module: Optional[str] = None):
        """
        This class method can be used to infer the argparse argument for a Python function
        parameter. This guess is based on the annotation, name, and default value.
        """

        def needs_type(item: Dict[str, str]):
            try:
                return item['action'] == 'store'
            except KeyError:
                return True

        def get_argp_type(annotation_type):
            if issubclass(annotation_type, (bytes, bytearray, memoryview)):
                return multibin
            if issubclass(annotation_type, int):
                return number
            if issubclass(annotation_type, slice):
                return sliceobj
            return annotation_type

        name = normalize_to_display(pt.name, False)
        default = pt.default
        guessed_pos_args = []
        guessed_kwd_args = dict(dest=pt.name)
        guessed = set()
        annotation = pt.annotation

        def guess(key, value):
            try:
                return guessed_kwd_args[key]
            except KeyError:
                guessed_kwd_args[key] = value
                guessed.add(key)
                return value

        if isinstance(annotation, str):
            symbols = None
            while symbols is not False:
                try:
                    annotation = eval(annotation, symbols)
                except NameError:
                    if symbols is not None or module is None:
                        break
                    try:
                        import importlib
                        symbols = importlib.import_module(module).__dict__
                    except Exception:
                        symbols = False
                except Exception:
                    pass
                else:
                    break

        if annotation is not pt.empty:
            if isinstance(annotation, Arg):
                if annotation.kwargs.get('dest', pt.name) != pt.name:
                    raise ValueError(
                        F'Incompatible argument destination specified; parameter {pt.name} '
                        F'was annotated with {annotation!r}.')
                guessed_pos_args = annotation.args
                guessed_kwd_args.update(annotation.kwargs)
                guessed_kwd_args.update(group=annotation.group)
            elif isinstance(annotation, type):
                guessed.add('type')
                if not issubclass(annotation, bool) and needs_type(guessed_kwd_args):
                    guessed_kwd_args.update(type=get_argp_type(annotation))
                elif not isinstance(default, bool):
                    raise ValueError('Default value for boolean arguments must be provided.')

        if not guessed_pos_args:
            guessed_pos_args = guessed_pos_args or [F'--{name}' if pt.kind is pt.KEYWORD_ONLY else name]

        if pt.kind is pt.VAR_POSITIONAL:
            oldnargs = guess('nargs', ZERO_OR_MORE)
            if oldnargs not in (ONE_OR_MORE, ZERO_OR_MORE, REMAINDER):
                raise ValueError(F'Variadic positional arguments has nargs set to {oldnargs!r}')
            return cls(*guessed_pos_args, **guessed_kwd_args)

        if default is not pt.empty:
            if isinstance(default, Enum):
                default = default.name
            if isinstance(default, (list, tuple)):
                guess('nargs', ZERO_OR_MORE)
                if not pt.default:
                    default = pt.empty
                else:
                    guessed_kwd_args['default'] = pt.default
                    default = default[0]
            else:
                guessed_kwd_args['default'] = default
                if pt.kind is pt.POSITIONAL_ONLY:
                    guess('nargs', OPTIONAL)

        if default is not pt.empty:
            if isinstance(default, bool):
                action = 'store_false' if default else 'store_true'
                guessed_kwd_args['action'] = action
            elif needs_type(guessed_kwd_args):
                guess('type', get_argp_type(type(default)))

        return cls(*guessed_pos_args, **guessed_kwd_args, guessed=guessed)

    def merge_args(self, them: Argument) -> None:
        """
        Merge the `args` component of another `refinery.units.Argument` into this one without
        overwriting or removing any of the `args` in this instance.
        """
        def iterboth():
            yield from them.args
            yield from self.args
        if not self.args:
            self.args = list(them.args)
            return
        sflag = None
        lflag = None
        for a in iterboth():
            if a[:2] == '--':
                lflag = lflag or a
            elif a[0] == '-':
                sflag = sflag or a
        self.args = []
        if sflag:
            self.args.append(sflag)
        if lflag:
            self.args.append(lflag)
        if not self.args:
            self.args = list(them.args)

    def merge_all(self, them: Arg) -> None:
        """
        Merge another `refinery.units.Arg` into the current instance. This is an additive process
        where no data on the present instance is destroyed unless `refinery.units.Arg.Delete` was
        used on `them` to explicitly remove an option.
        """
        for key, value in them.kwargs.items():
            if value is Arg.delete:
                self.kwargs.pop(key, None)
                self.guessed.discard(key)
                continue
            if key in them.guessed:
                if key not in self.guessed:
                    if key == 'type' and self.kwargs.get('action', None) != 'store':
                        continue
                    if key in self.kwargs:
                        continue
                self.guessed.add(key)
            self.kwargs[key] = value
        self.merge_args(them)
        self.group = them.group or self.group

    def __copy__(self) -> Argument:
        cls = self.__class__
        clone = cls.__new__(cls)
        clone.kwargs = dict(self.kwargs)
        clone.args = list(self.args)
        clone.group = self.group
        clone.guessed = set(self.guessed)
        return clone

    def __repr__(self) -> str:
        return F'{self.__class__.__name__}({super().__repr__()})'

    def __call__(self, init: Callable) -> Callable:
        parameters = inspect.signature(init).parameters
        try:
            inferred = Arg.Infer(parameters[self.destination])
            inferred.merge_all(self)
            init.__annotations__[self.destination] = inferred
        except KeyError:
            raise ValueError(F'Unable to decorate because no parameter with name {self.destination} exists.')
        return init

Ancestors

Class variables

var delete
var omit

Static methods

def AsOption(value, cls)

This method converts the input value to an instance of the enum cls. It is intended to be used on values that are passed as an argument marked with the Arg.Option() decorator. If the input value is None or already an instance of cls, it is returned unchanged. Otherwise, the function attempts to find an element of the enumeration that matches the input, either by name or by value.

Expand source code Browse git
@staticmethod
def AsOption(value: Optional[Any], cls: Enum) -> Enum:
    """
    This method converts the input `value` to an instance of the enum `cls`. It is intended to
    be used on values that are passed as an argument marked with the `refinery.units.Arg.Option`
    decorator. If the input value is `None` or already an instance of `cls`, it is returned
    unchanged. Otherwise, the function attempts to find an element of the enumeration that
    matches the input, either by name or by value.
    """
    if value is None or isinstance(value, cls):
        return value
    if isinstance(value, str):
        try:
            return cls[value]
        except KeyError:
            pass
        needle = normalize_to_identifier(value).casefold()
        for item in cls.__members__:
            if not isinstance(item, str):
                break
            if item.casefold() == needle:
                return cls[item]
    try:
        return cls(value)
    except Exception as E:
        choices = ', '.join(normalize_to_display(m) for m in cls.__members__)
        raise ValueError(F'Could not transform {value} into {cls.__name__}; the choices are: {choices}') from E
def Delete()

This should be specified when the argument is present for a (potentially abstract) parent unit but should be removed on the child.

Expand source code Browse git
@classmethod
def Delete(cls):
    """
    This should be specified when the argument is present for a (potentially abstract) parent
    unit but should be removed on the child.
    """
    return cls(nargs=cls.delete)
def Counts(*args, help=refinery.units.Arg.omit, dest=refinery.units.Arg.omit, group=None)

A convenience method to add argparse arguments that introduce a counter.

Expand source code Browse git
@classmethod
def Counts(
    cls,
    *args   : str,
    help    : Union[omit, str] = omit,
    dest    : Union[omit, str] = omit,
    group   : Optional[str] = None,
):
    """
    A convenience method to add argparse arguments that introduce a counter.
    """
    return cls(*args, group=group, help=help, dest=dest, action='count')
def Switch(*args, off=False, help=refinery.units.Arg.omit, dest=refinery.units.Arg.omit, group=None)

A convenience method to add argparse arguments that change a boolean value from True to False or vice versa. By default, a switch will have a False default and change it to True when specified.

Expand source code Browse git
@classmethod
def Switch(
    cls,
    *args   : str, off=False,
    help    : Union[omit, str] = omit,
    dest    : Union[omit, str] = omit,
    group   : Optional[str] = None,
):
    """
    A convenience method to add argparse arguments that change a boolean value from True to False or
    vice versa. By default, a switch will have a False default and change it to True when specified.
    """
    return cls(*args, group=group, help=help, dest=dest, action='store_false' if off else 'store_true')
def Binary(*args, help=refinery.units.Arg.omit, dest=refinery.units.Arg.omit, nargs=refinery.units.Arg.omit, metavar=None, group=None)

Used to add argparse arguments that contain binary data.

Expand source code Browse git
@classmethod
def Binary(
    cls,
    *args   : str,
    help    : Union[omit, str] = omit,
    dest    : Union[omit, str] = omit,
    nargs   : Union[omit, int, str] = omit,
    metavar : Optional[str] = None,
    group   : Optional[str] = None,
):
    """
    Used to add argparse arguments that contain binary data.
    """
    if metavar is None and any('-' in a for a in args):
        metavar = 'B'
    return cls(*args, group=group, help=help, dest=dest, nargs=nargs, type=multibin, metavar=metavar)
def String(*args, help=refinery.units.Arg.omit, dest=refinery.units.Arg.omit, nargs=refinery.units.Arg.omit, metavar=None, group=None)

Used to add argparse arguments that contain string data.

Expand source code Browse git
@classmethod
def String(
    cls,
    *args   : str,
    help    : Union[omit, str] = omit,
    dest    : Union[omit, str] = omit,
    nargs   : Union[omit, int, str] = omit,
    metavar : Optional[str] = None,
    group   : Optional[str] = None,
):
    """
    Used to add argparse arguments that contain string data.
    """
    if metavar is None and any('-' in a for a in args):
        metavar = 'STR'
    return cls(*args, group=group, help=help, dest=dest, nargs=nargs, type=str, metavar=metavar)
def RegExp(*args, help=refinery.units.Arg.omit, dest=refinery.units.Arg.omit, nargs=refinery.units.Arg.omit, metavar=None, group=None)

Used to add argparse arguments that contain a regular expression.

Expand source code Browse git
@classmethod
def RegExp(
    cls,
    *args   : str,
    help    : Union[omit, str] = omit,
    dest    : Union[omit, str] = omit,
    nargs   : Union[omit, int, str] = omit,
    metavar : Optional[str] = None,
    group   : Optional[str] = None,
):
    """
    Used to add argparse arguments that contain a regular expression.
    """
    if metavar is None and any('-' in a for a in args):
        metavar = 'REGEX'
    return cls(*args, group=group, help=help, dest=dest, nargs=nargs, type=regexp, metavar=metavar)
def NumSeq(*args, help=refinery.units.Arg.omit, dest=refinery.units.Arg.omit, nargs=refinery.units.Arg.omit, metavar=None, group=None)

Used to add argparse arguments that contain a numeric sequence.

Expand source code Browse git
@classmethod
def NumSeq(
    cls,
    *args   : str,
    help    : Union[omit, str] = omit,
    dest    : Union[omit, str] = omit,
    nargs   : Union[omit, int, str] = omit,
    metavar : Optional[str] = None,
    group   : Optional[str] = None,
):
    """
    Used to add argparse arguments that contain a numeric sequence.
    """
    return cls(*args, group=group, help=help, nargs=nargs, dest=dest, type=numseq, metavar=metavar)
def Bounds(*args, help=None, dest=refinery.units.Arg.omit, nargs=refinery.units.Arg.omit, default=refinery.units.Arg.omit, range=False, metavar='start:end:step', group=None)

Used to add argparse arguments that contain a slice.

Expand source code Browse git
@classmethod
def Bounds(
    cls,
    *args   : str,
    help    : Optional[Union[omit, str]] = None,
    dest    : Union[omit, str] = omit,
    nargs   : Union[omit, int, str] = omit,
    default : Union[omit, Any] = omit,
    range   : bool = False,
    metavar : Optional[str] = 'start:end:step',
    group   : Optional[str] = None,
):
    """
    Used to add argparse arguments that contain a slice.
    """
    if help is None:
        help = 'Specify start:end:step in Python slice syntax.'
        if default is not cls.omit:
            help = F'{help} The default is {{default}}.'
    parser = slicerange if range else sliceobj
    return cls(*args, group=group, help=help, default=default, nargs=nargs, dest=dest, type=parser, metavar=metavar)
def Number(*args, bound=refinery.units.Arg.omit, help=refinery.units.Arg.omit, dest=refinery.units.Arg.omit, metavar=None, group=None)

Used to add argparse arguments that contain a number.

Expand source code Browse git
@classmethod
def Number(
    cls,
    *args   : str,
    bound   : Union[omit, Tuple[int, int]] = omit,
    help    : Union[omit, str] = omit,
    dest    : Union[omit, str] = omit,
    metavar : Optional[str] = None,
    group   : Optional[str] = None,
):
    """
    Used to add argparse arguments that contain a number.
    """
    nt = number
    if bound is not cls.omit:
        lower, upper = bound
        nt = nt[lower:upper]
    return cls(*args, group=group, help=help, dest=dest, type=nt, metavar=metavar or 'N')
def Option(*args, choices, help=refinery.units.Arg.omit, dest=refinery.units.Arg.omit, metavar=None, group=None)

Used to add argparse arguments with a fixed set of options, based on an enumeration.

Expand source code Browse git
@classmethod
def Option(
    cls,
    *args   : str,
    choices : Enum,
    help    : Union[omit, str] = omit,
    dest    : Union[omit, str] = omit,
    metavar : Optional[str] = None,
    group   : Optional[str] = None,
):
    """
    Used to add argparse arguments with a fixed set of options, based on an enumeration.
    """
    cnames = [normalize_to_display(c).casefold() for c in choices.__members__]
    metavar = metavar or choices.__name__
    return cls(*args, group=group, help=help, metavar=metavar, dest=dest, choices=cnames, type=str.casefold)
def Choice(*args, choices, help=refinery.units.Arg.omit, metavar=refinery.units.Arg.omit, dest=refinery.units.Arg.omit, type=builtins.str, nargs=refinery.units.Arg.omit, group=None)

Used to add argparse arguments with a fixed set of options, based on a list of strings.

Expand source code Browse git
@classmethod
def Choice(
    cls,
    *args   : str,
    choices : List[str],
    help    : Union[omit, str] = omit,
    metavar : Union[omit, str] = omit,
    dest    : Union[omit, str] = omit,
    type    : Type = str,
    nargs   : Union[omit, int, str] = omit,
    group   : Optional[str] = None,
):
    """
    Used to add argparse arguments with a fixed set of options, based on a list of strings.
    """
    return cls(*args, group=group, type=type, metavar=metavar, nargs=nargs,
        dest=dest, help=help, choices=choices)
def Infer(pt, module=None)

This class method can be used to infer the argparse argument for a Python function parameter. This guess is based on the annotation, name, and default value.

Expand source code Browse git
@classmethod
def Infer(cls, pt: inspect.Parameter, module: Optional[str] = None):
    """
    This class method can be used to infer the argparse argument for a Python function
    parameter. This guess is based on the annotation, name, and default value.
    """

    def needs_type(item: Dict[str, str]):
        try:
            return item['action'] == 'store'
        except KeyError:
            return True

    def get_argp_type(annotation_type):
        if issubclass(annotation_type, (bytes, bytearray, memoryview)):
            return multibin
        if issubclass(annotation_type, int):
            return number
        if issubclass(annotation_type, slice):
            return sliceobj
        return annotation_type

    name = normalize_to_display(pt.name, False)
    default = pt.default
    guessed_pos_args = []
    guessed_kwd_args = dict(dest=pt.name)
    guessed = set()
    annotation = pt.annotation

    def guess(key, value):
        try:
            return guessed_kwd_args[key]
        except KeyError:
            guessed_kwd_args[key] = value
            guessed.add(key)
            return value

    if isinstance(annotation, str):
        symbols = None
        while symbols is not False:
            try:
                annotation = eval(annotation, symbols)
            except NameError:
                if symbols is not None or module is None:
                    break
                try:
                    import importlib
                    symbols = importlib.import_module(module).__dict__
                except Exception:
                    symbols = False
            except Exception:
                pass
            else:
                break

    if annotation is not pt.empty:
        if isinstance(annotation, Arg):
            if annotation.kwargs.get('dest', pt.name) != pt.name:
                raise ValueError(
                    F'Incompatible argument destination specified; parameter {pt.name} '
                    F'was annotated with {annotation!r}.')
            guessed_pos_args = annotation.args
            guessed_kwd_args.update(annotation.kwargs)
            guessed_kwd_args.update(group=annotation.group)
        elif isinstance(annotation, type):
            guessed.add('type')
            if not issubclass(annotation, bool) and needs_type(guessed_kwd_args):
                guessed_kwd_args.update(type=get_argp_type(annotation))
            elif not isinstance(default, bool):
                raise ValueError('Default value for boolean arguments must be provided.')

    if not guessed_pos_args:
        guessed_pos_args = guessed_pos_args or [F'--{name}' if pt.kind is pt.KEYWORD_ONLY else name]

    if pt.kind is pt.VAR_POSITIONAL:
        oldnargs = guess('nargs', ZERO_OR_MORE)
        if oldnargs not in (ONE_OR_MORE, ZERO_OR_MORE, REMAINDER):
            raise ValueError(F'Variadic positional arguments has nargs set to {oldnargs!r}')
        return cls(*guessed_pos_args, **guessed_kwd_args)

    if default is not pt.empty:
        if isinstance(default, Enum):
            default = default.name
        if isinstance(default, (list, tuple)):
            guess('nargs', ZERO_OR_MORE)
            if not pt.default:
                default = pt.empty
            else:
                guessed_kwd_args['default'] = pt.default
                default = default[0]
        else:
            guessed_kwd_args['default'] = default
            if pt.kind is pt.POSITIONAL_ONLY:
                guess('nargs', OPTIONAL)

    if default is not pt.empty:
        if isinstance(default, bool):
            action = 'store_false' if default else 'store_true'
            guessed_kwd_args['action'] = action
        elif needs_type(guessed_kwd_args):
            guess('type', get_argp_type(type(default)))

    return cls(*guessed_pos_args, **guessed_kwd_args, guessed=guessed)

Instance variables

var positional

Indicates whether the argument is positional. This is crudely determined by whether it has a specifier that does not start with a dash.

Expand source code Browse git
@property
def positional(self) -> bool:
    """
    Indicates whether the argument is positional. This is crudely determined by whether it has
    a specifier that does not start with a dash.
    """
    return any(a[0] != '-' for a in self.args)
var destination

The name of the variable where the contents of this parsed argument will be stored.

Expand source code Browse git
@property
def destination(self) -> str:
    """
    The name of the variable where the contents of this parsed argument will be stored.
    """
    for a in self.args:
        if a[0] != '-':
            return a
    try:
        return self.kwargs['dest']
    except KeyError:
        for a in self.args:
            if a.startswith('--'):
                dest = normalize_to_identifier(a)
                if dest.isidentifier():
                    return dest
        raise AttributeError(F'The argument with these values has no destination: {self!r}')

Methods

def update_help(self)

This method is called to format the help text of the argument retroactively. The primary purpose is to fill in default arguments via the formatting symbol {default}. These default values are not necessarily part of the Arg object itself: They may be a default value in the __init__ function of the Unit subclass. Therefore, it is necessary to format the help text after all information has been compiled.

Expand source code Browse git
def update_help(self):
    """
    This method is called to format the help text of the argument retroactively. The primary
    purpose is to fill in default arguments via the formatting symbol `{default}`. These
    default values are not necessarily part of the `refinery.units.Arg` object itself: They
    may be a default value in the `__init__` function of the `refinery.units.Unit` subclass.
    Therefore, it is necessary to format the help text after all information has been
    compiled.
    """
    class formatting(dict):
        arg = self

        def __missing__(self, key):
            if key == 'choices':
                return ', '.join(self.arg.kwargs['choices'])
            if key == 'default':
                default: Union[bytes, int, str, slice] = self.arg.kwargs['default']
                if isinstance(default, (list, tuple, set)):
                    if not default:
                        return 'empty'
                    elif len(default) == 1:
                        default = default[0]
                if isinstance(default, slice):
                    parts = [default.start or '', default.stop or '', default.step]
                    default = ':'.join(str(x) for x in parts if x is not None)
                if isinstance(default, int):
                    return default
                if not isbuffer(default):
                    return default
                if default.isalnum():
                    return default.decode('latin-1')
                return F'H:{default.hex()}'
            if key == 'varname':
                return self.arg.kwargs.get('metavar', self.arg.destination)

    try:
        help_string: str = self.kwargs['help']
        self.kwargs.update(
            help=help_string.format_map(formatting()))
    except Exception:
        pass
def merge_args(self, them)

Merge the args component of another Argument into this one without overwriting or removing any of the args in this instance.

Expand source code Browse git
def merge_args(self, them: Argument) -> None:
    """
    Merge the `args` component of another `refinery.units.Argument` into this one without
    overwriting or removing any of the `args` in this instance.
    """
    def iterboth():
        yield from them.args
        yield from self.args
    if not self.args:
        self.args = list(them.args)
        return
    sflag = None
    lflag = None
    for a in iterboth():
        if a[:2] == '--':
            lflag = lflag or a
        elif a[0] == '-':
            sflag = sflag or a
    self.args = []
    if sflag:
        self.args.append(sflag)
    if lflag:
        self.args.append(lflag)
    if not self.args:
        self.args = list(them.args)
def merge_all(self, them)

Merge another Arg into the current instance. This is an additive process where no data on the present instance is destroyed unless Arg.Delete() was used on them to explicitly remove an option.

Expand source code Browse git
def merge_all(self, them: Arg) -> None:
    """
    Merge another `refinery.units.Arg` into the current instance. This is an additive process
    where no data on the present instance is destroyed unless `refinery.units.Arg.Delete` was
    used on `them` to explicitly remove an option.
    """
    for key, value in them.kwargs.items():
        if value is Arg.delete:
            self.kwargs.pop(key, None)
            self.guessed.discard(key)
            continue
        if key in them.guessed:
            if key not in self.guessed:
                if key == 'type' and self.kwargs.get('action', None) != 'store':
                    continue
                if key in self.kwargs:
                    continue
            self.guessed.add(key)
        self.kwargs[key] = value
    self.merge_args(them)
    self.group = them.group or self.group

Inherited members

class HASH (value, names=None, *, module=None, qualname=None, type=None, start=1)

An enumeration.

Expand source code Browse git
class HASH(str, Enum):
    MD2 = 'MD2'
    MD4 = 'MD4'
    MD5 = 'MD5'
    SHA1 = 'SHA'
    SHA256 = 'SHA256'
    SHA512 = 'SHA512'
    SHA224 = 'SHA224'
    SHA384 = 'SHA384'

Ancestors

  • builtins.str
  • enum.Enum

Class variables

var MD2
var MD4
var MD5
var SHA1
var SHA256
var SHA512
var SHA224
var SHA384
class KeyDerivation (size, salt, hash=None, iter=None, **kw)
Expand source code Browse git
class KeyDerivation(Unit, abstract=True):

    def __init__(
        self,
        size: Arg(help='The number of bytes to generate.', type=number),
        salt: Arg(help='Salt for the derivation.'),
        hash: Arg.Option(choices=HASH, metavar='hash',
            help='Specify one of these algorithms (default is {default}): {choices}') = None,
        iter: Arg.Number(metavar='iter', help='Number of iterations; default is {default}.') = None,
        **kw
    ):
        if hash is not None:
            hash = Arg.AsOption(hash, HASH)
        return super().__init__(salt=salt, size=size, iter=iter, hash=hash, **kw)

    @property
    def hash(self) -> _HashModule:
        name = self.args.hash.value
        hash = importlib.import_module(F'Cryptodome.Hash.{name}')
        return hash

Ancestors

Subclasses

Class variables

var required_dependencies
var optional_dependencies

Instance variables

var hash
Expand source code Browse git
@property
def hash(self) -> _HashModule:
    name = self.args.hash.value
    hash = importlib.import_module(F'Cryptodome.Hash.{name}')
    return hash

Inherited members