Example #1
0
 def solve_aliases():
     '''Solve keyword arguments that have aliases.'''
     from xoutil import Unset
     for par, ps in iteritems(self.scheme):
         for alias in ps['aliases']:
             value = kwargs.pop(alias, Unset)
             if value is not Unset:
                 settle(par, value)
Example #2
0
def dict_merge(*dicts, **others):

    '''Merges several dicts into a single one.

    Merging is similar to updating a dict, but if values are non-scalars they
    are also merged is this way:

    - Any two :class:`sequences <collection.Sequence>` or :class:`sets
      <collections.Set>` are joined together.

    - Any two mappings are recursively merged.

    - Other types are just replaced like in :func:`update`.

    If for a single key two values of incompatible types are found, raise a
    TypeError.  If the values for a single key are compatible but different
    (i.e a list an a tuple) the resultant type will be the type of the first
    apparition of the key, unless for mappings which are always cast to dicts.

    No matter the types of `dicts` the result is always a dict.

    Without arguments, return the empty dict.

    '''
    from collections import Mapping, Sequence, Set
    from xoutil.eight import iteritems
    from xoutil.objects import get_first_of
    from xoutil.types import are_instances, no_instances
    if others:
        dicts = dicts + (others, )
    dicts = list(dicts)
    result = {}
    collections = (Set, Sequence)
    while dicts:
        current = dicts.pop(0)
        for key, val in iteritems(current):
            if isinstance(val, Mapping):
                val = {key: val[key] for key in val}
            value = result.setdefault(key, val)
            if value is not val:
                if are_instances(value, val, collections):
                    join = get_first_of((value, ), '__add__', '__or__')
                    if join:
                        constructor = type(value)
                        value = join(constructor(val))
                    else:
                        raise ValueError("Invalid value for key '%s'"
                                         % key)
                elif are_instances(value, val, Mapping):
                    value = dict_merge(value, val)
                elif no_instances(value, val, (Set, Sequence, Mapping)):
                    value = val
                else:
                    raise TypeError("Found incompatible values for key '%s'"
                                    % key)
                result[key] = value
    return result
Example #3
0
 def solve_results():
     '''Assign default values for missing arguments.'''
     from xoutil.values import valid
     for par, ps in iteritems(self.scheme):
         if clean(par):
             default = ps['default']
             if valid(default):
                 kwargs[str(par)] = default
             else:
                 msg = 'Missing required argument "{}"!'
                 raise TypeError(msg.format(par))
Example #4
0
 def _normalize_positions(self):
     '''Update the `positions` dictionaries.'''
     from xoutil.eight import range, iteritems
     aux = {}
     for par, ps in iteritems(self.scheme):
         for pos in ps['pos']:
             l = aux.setdefault(pos, [])
             l.append(par)
     res, pivot = {}, 0
     for pos in range(min(aux), max(aux) + 1):
         if pos in aux:
             res[pivot] = sorted(aux[pos])
             pivot += 1
     self.positions = res
Example #5
0
 def _check_duplicate_aliases(self):
     '''Check if there are duplicate aliases and parameter names.'''
     from xoutil.eight import iteritems
     used = set(self.scheme)
     duplicated = set()
     for par, ps in iteritems(self.scheme):
         for alias in ps['aliases']:
             if alias in used:
                 duplicated.add(alias)
             else:
                 used.add(alias)
     if duplicated:
         msg = 'Duplicate identifiers detected: "{}"'
         raise TypeError(msg.format(', '.join(duplicated)))
Example #6
0
 def check_kwargs():
     '''Check all formal keyword arguments.'''
     from xoutil.values import valid
     for key, arg in iteritems(kwargs):
         if key in self.scheme:
             checker = self.scheme[key]['checker']
             value = checker(arg)
             if valid(value):
                 kwargs[str(key)] = value
             else:
                 msg = 'Invalid argument value "{}": "{}"!'
                 raise ValueError(msg.format(key, arg))
         elif self.strict:
             msg = 'Invalid keyword argument "{}": "{}"!'
             raise ValueError(msg.format(key, arg))
Example #7
0
 def wrapper(target):
     if isinstance(target, decorables):
         if isinstance(target, (staticmethod, classmethod)):
             target = target.__func__
         for name in (mod_key, name_key, doc_key):
             if name in source:
                 value = source.pop(name)
                 if name in safes:
                     value = safe_str(value)
                 setattr(target, str(name), value)
             d = source.pop('__dict__', Unset)
             if d:
                 target.__dict__.update(d)
         for key, value in iteritems(source):
             setattr(target, key, value)
         return target
     else:
         from xoutil.eight import typeof
         msg = 'Only decorate functions, not {}'
         raise TypeError(msg.format(typeof(target).__name__))
Example #8
0
 def foobar(**kwargs):
     from xoutil.eight import iteritems
     res = {key: ps['default'] for key, ps in iteritems(conformer.scheme)}
     for key, value in iteritems(kwargs):
         res[key] = value
     return res
Example #9
0
 def inner(target):
     from xoutil.eight import iteritems
     for key, value in iteritems(kwargs):
         setattr(target, key, value)
     return target
Example #10
0
def copy_class(cls, meta=None, ignores=None, new_attrs=None, new_name=None):
    '''Copies a class definition to a new class.

    The returned class will have the same name, bases and module of `cls`.

    :param meta: If None, the `type(cls)` of the class is used to build the
                 new class, otherwise this must be a *proper* metaclass.


    :param ignores: A sequence of attributes names that should not be copied
        to the new class.

        An item may be callable accepting a single argument `attr` that must
        return a non-null value if the the `attr` should be ignored.

    :param new_attrs: New attributes the class must have. These will take
                      precedence over the attributes in the original class.

    :type new_attrs: dict

    :param new_name: The name for the copy.  If not provided the name will
                     copied.

    .. versionadded:: 1.4.0

    .. versionchanged:: 1.7.1 The `ignores` argument must an iterable of
       strings or callables.  Removed the glob-pattern and regular expressions
       as possible values.  They are all possible via the callable variant.

    .. versionadded:: 1.7.1 The `new_name` argument.

    '''
    from xoutil.eight import iteritems, callable
    from xoutil.eight._types import new_class
    from xoutil.eight.types import MemberDescriptorType
    from xoutil.string import safe_str

    def _get_ignored(what):
        if callable(what):
            return what
        else:
            return lambda s: s == what

    if not meta:
        meta = type(cls)
    if ignores:
        ignores = tuple(_get_ignored(i) for i in ignores)
        ignored = lambda name: any(ignore(name) for ignore in ignores)
    else:
        ignored = None
    valids = ('__class__', '__mro__', '__name__', '__weakref__', '__dict__')
    attrs = {name: value
             for name, value in iteritems(cls.__dict__)
             if name not in valids
             # Must remove member descriptors, otherwise the old's class
             # descriptor will override those that must be created here.
             if not isinstance(value, MemberDescriptorType)
             if ignored is None or not ignored(name)}
    if new_attrs:
        attrs.update(new_attrs)
    exec_body = lambda ns: ns.update(attrs)
    if new_name:
        name = safe_str(new_name)
    else:
        name = cls.__name__
    result = new_class(name, cls.__bases__, {'metaclass': meta}, exec_body)
    return result
Example #11
0
    def __call__(self, args, kwargs):
        '''Consolidate in `kwargs` all actual parameters.

        :param args: The positional arguments received by the calling function.

        :param kwargs: The keyword arguments received by the calling function.

        '''
        from xoutil.eight import iteritems
        assert isinstance(args, tuple) and isinstance(kwargs, dict)

        def clean(name):
            '''If argument with name is not yet assigned.'''
            return name not in kwargs

        def settle(name, value):
            '''Settle a value if not yet assigned, raises an error if not.'''
            if clean(name):
                kwargs[str(name)] = value
            else:
                msg = 'Got multiple values for "{}" argument: "{}" and "{}"!'
                raise TypeError(msg.format(name, value, kwargs[name]))

        def solve_aliases():
            '''Solve keyword arguments that have aliases.'''
            from xoutil import Unset
            for par, ps in iteritems(self.scheme):
                for alias in ps['aliases']:
                    value = kwargs.pop(alias, Unset)
                    if value is not Unset:
                        settle(par, value)

        def check_kwargs():
            '''Check all formal keyword arguments.'''
            from xoutil.values import valid
            for key, arg in iteritems(kwargs):
                if key in self.scheme:
                    checker = self.scheme[key]['checker']
                    value = checker(arg)
                    if valid(value):
                        kwargs[str(key)] = value
                    else:
                        msg = 'Invalid argument value "{}": "{}"!'
                        raise ValueError(msg.format(key, arg))
                elif self.strict:
                    msg = 'Invalid keyword argument "{}": "{}"!'
                    raise ValueError(msg.format(key, arg))

        def solve_results():
            '''Assign default values for missing arguments.'''
            from xoutil.values import valid
            for par, ps in iteritems(self.scheme):
                if clean(par):
                    default = ps['default']
                    if valid(default):
                        kwargs[str(par)] = default
                    else:
                        msg = 'Missing required argument "{}"!'
                        raise TypeError(msg.format(par))

        def get_valid():
            '''Get the valid parameter name in current position pivot.

            Return a tuple (name, value) if valid.

            '''
            from xoutil.values import valid
            names = positions[pivot]
            i, count = 0, len(names)
            res = ()
            while not res and i < count:
                name = names[i]
                if clean(name):
                    checker = self.scheme[name]['checker']
                    value = checker(arg)
                    if valid(value):
                        res = (name, value)
                i += 1
            return res

        def get_duplicate():
            '''Get a possible all not settled valid parameter names.'''
            from xoutil.values import valid
            res = None
            pos = last_pivot
            while not res and pos < len(positions):
                names = positions[pos]
                i = 0
                while not res and i < len(names):
                    name = names[i]
                    if name not in settled:
                        checker = self.scheme[name]['checker']
                        value = checker(arg)
                        if valid(value):
                            res = name
                    i += 1
                pos += 1
            return res

        solve_aliases()
        check_kwargs()
        # Solve positional arguments
        settled = set()
        positions = self.positions
        positionals = {p for p, ps in iteritems(self.scheme) if ps['pos']}
        max_args = len({name for name in positionals if clean(name)})
        i, count = 0, len(args)
        pivot = last_pivot = 0
        if count <= max_args:
            while i < count and pivot < len(positions):
                arg = args[i]
                res = get_valid()
                if res:
                    name, value = res
                    settle(name, value)
                    settled.add(name)
                    last_pivot = pivot
                    i += 1
                else:
                    pivot += 1
            if i == count:
                solve_results()
            else:
                from xoutil.eight import typeof
                dup = get_duplicate()
                extra = 'duplicate "{}" '.format(dup) if dup else ''
                msg = ('Invalid {}argument "{}" at position "{}" of type '
                       '"{}".')
                tname = typeof(arg).__name__
                raise TypeError(msg.format(extra, arg, i, tname))
        else:
            msg = 'Expecting at most {} positional arguments ({} given)!'
            raise TypeError(msg.format(max_args, count))
Example #12
0
def lwraps(*args, **kwargs):
    '''Lambda wrapper.

    Useful for decorate lambda functions with name and documentation.

    As positional arguments could be passed the function to be decorated and
    the name in any order.  So the next two ``identity`` definitions are
    equivalents::

      >>> from xoutil.functools import lwraps as lw

      >>> identity = lw('identity', lambda arg: arg)

      >>> identity = lw(lambda arg: arg, 'identity')

    As keyword arguments could be passed some special values, and any number
    of literal values to be assigned:

    - **name**: The name of the function (``__name__``); only valid if not
      given as positional argument.

    - **doc**: The documentation (``__doc__`` field).

    - **wrapped**: An object to extract all values not yet assigned.  These
      values are ('__module__', '__name__' and '__doc__') to be assigned, and
      '__dict__' to be updated.

    If the function to decorate is present in the positional arguments, this
    same argument function is directly returned after decorated; if not a
    decorator is returned similar to standard `wraps`:func:.

    For example::

      >>> from xoutil.validators.connote import lwraps as lw

      >>> is_valid_age = lw('is-valid-human-age', lambda age: 0 < age <= 120,
      ...                   doc=('A predicate to evaluate if an age is '
      ...                        'valid for a human being.')

      >>> @lw(wrapped=is_valid_age)
      ... def is_valid_working_age(age):
      ...     return 18 < age <= 70

      >>> is_valid_age(16)
      True

      >>> is_valid_age(200)
      False

      >>> is_valid_working_age(16)
      False

    .. versionadded:: 1.7.0

    '''
    from types import FunctionType
    from xoutil import Unset
    from xoutil.eight import string_types, iteritems
    from xoutil.string import safe_str

    def repeated(name):
        msg = "lwraps() got multiple values for argument '{}'"
        raise TypeError(msg.format(name))

    def settle_str(name, value):
        if value is not Unset:
            if isinstance(value, string_types):
                if name not in source:
                    source[name] = value
                else:
                    repeated(name)
            else:
                from xoutil.eight import typeof
                msg = 'lwraps() expecting string for "{}", {} found'
                raise TypeError(msg.format(name, typeof(value).__name__))

    decorables = (FunctionType, staticmethod, classmethod)
    name_key = str('__name__')
    doc_key = str('__doc__')
    mod_key = str('__module__')
    safes = {name_key, mod_key}
    source = {}
    target = Unset
    count = len(args)
    if count <= 2:
        i = 0
        while i < count:
            arg = args[i]
            if isinstance(arg, string_types):
                settle_str(name_key, arg)
            elif isinstance(arg, decorables):
                if target is Unset:
                    target = arg
                else:
                    repeated('target-function')
            else:
                msg = 'lwraps() arg {} must be a string or decorable function'
                raise TypeError(msg.format(i))
            i += 1
        wrapped = kwargs.pop('wrapped', Unset)
        settle_str(name_key, kwargs.pop('name', Unset))
        settle_str(name_key, kwargs.pop(name_key, Unset))
        settle_str(doc_key, kwargs.pop('doc', Unset))
        settle_str(doc_key, kwargs.pop(doc_key, Unset))
        for key, value in iteritems(kwargs):
            source[key] = value
        if wrapped is not Unset:
            for name in (mod_key, '__name__', '__doc__'):
                if name not in source:
                    source[str(name)] = getattr(wrapped, name)
            d = source.setdefault('__dict__', {})
            d.update(wrapped.__dict__)

        def wrapper(target):
            if isinstance(target, decorables):
                if isinstance(target, (staticmethod, classmethod)):
                    target = target.__func__
                for name in (mod_key, name_key, doc_key):
                    if name in source:
                        value = source.pop(name)
                        if name in safes:
                            value = safe_str(value)
                        setattr(target, str(name), value)
                    d = source.pop('__dict__', Unset)
                    if d:
                        target.__dict__.update(d)
                for key, value in iteritems(source):
                    setattr(target, key, value)
                return target
            else:
                from xoutil.eight import typeof
                msg = 'Only decorate functions, not {}'
                raise TypeError(msg.format(typeof(target).__name__))

        return wrapper(target) if target else wrapper
    else:
        msg = 'lwraps() takes at most 2 arguments ({} given)'
        raise TypeError(msg.format(len(args)))