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):
        if 'help' not in self.kwargs:
            return

        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:
        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 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):
        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,
        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}}.'
        return cls(*args, group=group, help=help, default=default, nargs=nargs, dest=dest, type=sliceobj, 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:
        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:
        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:
        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)
Expand source code Browse git
@staticmethod
def AsOption(value: Optional[Any], cls: Enum) -> Enum:
    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 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()
Expand source code Browse git
@classmethod
def Delete(cls):
    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, 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,
    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}}.'
    return cls(*args, group=group, help=help, default=default, nargs=nargs, dest=dest, type=sliceobj, 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
Expand source code Browse git
@property
def positional(self) -> bool:
    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)
Expand source code Browse git
def update_help(self):
    if 'help' not in self.kwargs:
        return

    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)
Expand source code Browse git
def merge_args(self, them: Argument) -> None:
    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)
Expand source code Browse git
def merge_all(self, them: Arg) -> None:
    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