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 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
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 _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
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)))
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 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__))
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
def inner(target): from xoutil.eight import iteritems for key, value in iteritems(kwargs): setattr(target, key, value) return target
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
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))
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)))