def encode_coerce(arg): """Encode string objects.""" import locale from . import Invalid from xoutil.eight import callable encoding = locale.getpreferredencoding() or "UTF-8" encode = getattr(arg, "encode", None) if callable(encode): try: res = encode(encoding, "replace") if not isinstance(res, bytes): res = None except BaseException: res = None else: res = None if res is None: try: import codecs res = codecs.encode(arg, encoding, "replace") except BaseException: res = Invalid return res
def decode_coerce(arg): """Decode objects implementing the buffer protocol.""" import locale from . import Invalid from xoutil.eight import text_type, callable encoding = locale.getpreferredencoding() or "UTF-8" decode = getattr(arg, "decode", None) if callable(decode): try: res = decode(encoding, "replace") if not isinstance(res, text_type): res = None except BaseException: res = None else: res = None if res is None: try: # TODO: All arrays are decoded, and not only those containing # valid byte or unicode characters. import codecs res = codecs.decode(arg, encoding, "replace") except BaseException: res = Invalid return res
def fix_class_documentation(cls, ignore=None, min_length=10, deep=1, default=None): '''Fix the documentation for the given class using its super-classes. This function may be useful for shells or Python Command Line Interfaces (CLI). If `cls` has an invalid documentation, super-classes are recursed in MRO until a documentation definition was made at any level. :param ignore: could be used to specify which classes to ignore by specifying its name in this list. :param min_length: specify that documentations with less that a number of characters, also are ignored. ''' assert isinstance(cls, type), _INVALID_CLASS_TYPE_MSG if _len(cls.__doc__) < min_length: ignore = ignore or () def get_doc(c): if (c.__name__ not in ignore) and _len(c.__doc__) >= min_length: return c.__doc__ else: return None doc = build_documentation(cls, get_doc, deep) if doc: cls.__doc__ = doc elif default: cls.__doc__ = default(cls) if callable(default) else default
def valid(chk, stack=True): if isinstance(chk, (bool, Logical)): res = bool(chk) elif isinstance(chk, type): res = isinstance(obj, chk) elif isinstance(chk, tuple): if not chk: res = False elif all(isinstance(c, type) for c in chk): res = isinstance(obj, chk) else: res = any(valid(c, stack=False) for c in chk) elif isinstance(chk, list): res = all(valid(c) for c in chk) elif isinstance(chk, Set): res = obj in chk elif isinstance(chk, Mapping): res = chk.get(obj, False) elif callable(chk): res = chk(obj) else: res = False if not res and stack: self.failed_stack.append(chk) return res
def power(*args): '''Returns the "power" composition of several functions. Examples:: >>> import operator >>> f = power(partial(operator.mul, 3), 3) >>> f(23) == 3*(3*(3*23)) True >>> power(operator.neg) Traceback (most recent call last): ... TypeError: Function `power` requires at least two arguments ''' try: funcs, times = args[:-1], args[-1] except IndexError: msg = "Function `power` requires at least two arguments" raise TypeError(msg) if not funcs: raise TypeError('Function `power` requires at least two arguments') if any(not callable(func) for func in funcs): raise TypeError('First arguments of `power` must be callables') if not isinstance(times, int): raise TypeError('Last argument of `power` must be int') if len(funcs) > 1: base = (compose(funcs), ) else: base = (funcs[0], ) return compose(*(base * times))
def is_file_like(obj): '''Return if `obj` is a valid file type or not.''' from xoutil.eight import _py2, callable types = (file, IOBase) if _py2 else (IOBase, ) if isinstance(obj, types): return True else: methods = ('close', 'write', 'read') return all(callable(getattr(obj, name, None)) for name in methods)
def get_doc(c): if (c.__name__ not in ignore): method = c.__dict__.get(method_name) if callable(method) and _len(method.__doc__) >= min_length: return method.__doc__ else: return None else: return None
def __parse_arguments(self, *args, **kwargs): '''Assign parsed arguments to the just created instance.''' from xoutil.validators import (is_valid_identifier, predicate) self.attr_name = Unset self.init = Unset self.default = Unset self.do_assigning = True self.validator = True for i, arg in enumerate(args): if self.attr_name is Unset and is_valid_identifier(arg): self.attr_name = arg elif self.init is Unset and callable(arg): self.init = arg else: msg = ('Invalid positional arguments: %s at %s\n' 'Valid arguments are the attribute name and a ' 'callable constructor for initial value.') raise ValueError(msg % (args[i:], i)) bads = {} for key in kwargs: value = kwargs[key] if (self.default is Unset and self.init is Unset and key in ('default', 'value', 'initial_value')): self.default = value elif (self.validator is True and key in ('validator', 'checker', 'check')): self.validator = value elif (self.do_assigning is True and key == 'do_assigning' and value is False): self.do_assigning = False else: bads[key] = value self.validator = predicate(self.validator) if bads: msg = ('Invalid keyword arguments: %s\n' 'See constructor documentation for more info.') raise ValueError(msg % bads) if self.attr_name is Unset: from xoutil.names import nameof if self.init is not Unset: if isinstance(self.init, type): self.attr_name = str('_%s' % self.init.__name__) else: self.attr_name = nameof(self.init, safe=True) else: self.attr_name = self._unique_name() self.inner_name = str('__%s__' % self.attr_name.strip('_'))
def get_method_function(cls, method_name): '''Get definition function given in its `method_name`. There is a difference between the result of this function and ``getattr(cls, method_name)`` because the last one return the unbound method and this a python function. ''' if not isinstance(cls, type): cls = cls.__class__ mro = cls.mro() i, res = 0, None while not res and (i < len(mro)): sc = mro[i] method = sc.__dict__.get(method_name) if callable(method): res = method else: i += 1 return res
def compose(*callables, **kwargs): '''Returns a function that is the composition of several `callables`. By default `compose` behaves like mathematical function composition: this is to say that ``compose(f1, ... fn)`` is equivalent to ``lambda _x: fn(...(f1(_x))...)``. If any "intermediate" function returns a :class:`ctuple` it is expanded as several positional arguments to the next function. .. versionchanged:: 1.5.5 At least a callable must be passed, otherwise a TypeError is raised. If a single callable is passed it is returned without change. :param math: Indicates if `compose` should behave like mathematical function composition: last function in `funcs` is applied last. If False, then the last function in `func` is applied first. ''' if not callables: raise TypeError('At least a function must be provided') if not all(callable(func) for func in callables): raise TypeError('Every func must a callable') if len(callables) == 1: return callables[0] math = kwargs.get('math', True) if not math: callables = list(reversed(callables)) def _inner(*args): f, functions = callables[0], callables[1:] result = f(*args) for f in functions: if isinstance(result, ctuple): result = f(*result) else: result = f(result) return result return _inner
def __call__(self): res = self.value if callable(res): return res(*self.args, **self.kwargs) else: return res
def smart_copy(*args, **kwargs): '''Copies the first apparition of attributes (or keys) from `sources` to `target`. :param sources: The objects from which to extract keys or attributes. :param target: The object to fill. :param defaults: Default values for the attributes to be copied as explained below. Defaults to False. :type defaults: Either a bool, a dictionary, an iterable or a callable. Every `sources` and `target` are always positional arguments. There should be at least one source. `target` will always be the last positional argument. If `defaults` is a dictionary or an iterable then only the names provided by itering over `defaults` will be copied. If `defaults` is a dictionary, and one of its key is not found in any of the `sources`, then the value of the key in the dictionary is copied to `target` unless: - It's the value :class:`xoutil.types.Required` or an instance of Required. - An exception object - A sequence with is first value being a subclass of Exception. In which case :class:`xoutil.data.adapt_exception` is used. In these cases a KeyError is raised if the key is not found in the sources. If `default` is an iterable and a key is not found in any of the sources, None is copied to `target`. If `defaults` is a callable then it should receive one positional arguments for the current `attribute name` and several keyword arguments (we pass ``source``) and return either True or False if the attribute should be copied. If `defaults` is False (or None) only the attributes that do not start with a "_" are copied, if it's True all attributes are copied. When `target` is not a mapping only valid Python identifiers will be copied. Each `source` is considered a mapping if it's an instance of `collections.Mapping` or a `MappingProxyType`. The `target` is considered a mapping if it's an instance of `collections.MutableMapping`. :returns: `target`. .. versionchanged:: 1.7.0 `defaults` is now keyword only. ''' from collections import MutableMapping from xoutil.types import is_collection, is_mapping, Required from xoutil.data import adapt_exception from xoutil.validators.identifiers import is_valid_identifier defaults = kwargs.pop('defaults', False) if kwargs: raise TypeError('smart_copy does not accept a "%s" keyword argument' % kwargs.keys()[0]) sources, target = args[:-1], args[-1] if not sources: raise TypeError('smart_copy requires at least one source') if isinstance(target, (bool, type(None), int, float, str_base)): raise TypeError('target should be a mutable object, not %s' % type(target)) if isinstance(target, MutableMapping): def setter(key, val): target[key] = val else: def setter(key, val): if is_valid_identifier(key): setattr(target, key, val) _mapping = is_mapping(defaults) if _mapping or is_collection(defaults): for key, val in ((key, get_first_of(sources, key, default=Unset)) for key in defaults): if val is Unset: if _mapping: val = defaults.get(key, None) else: val = None exc = adapt_exception(val, key=key) if exc or val is Required or isinstance(val, Required): raise KeyError(key) setter(key, val) else: keys = [] for source in sources: get = smart_getter(source) if is_mapping(source): items = (name for name in source) else: items = dir(source) for key in items: private = isinstance(key, str_base) and key.startswith('_') if (defaults is False or defaults is None) and private: copy = False elif callable(defaults): copy = defaults(key, source=source) else: copy = True if key not in keys: keys.append(key) if copy: setter(key, get(key)) return target
def _get_ignored(what): if callable(what): return what else: return lambda s: s == what
def _get_checker_name(checker, full=False): '''Return a nice name for a `checker`. :param full: If True, return a detailed representation of the checker. See :class:`Predicate` for possible checker kinds. ''' from xoutil.logical import Logical from xoutil.collections import Set, Mapping, PascalSet from xoutil.eight import callable from xoutil.inspect import type_name from xoutil.string import safe_str as sstr # , safe_repr as srepr srepr = repr if isinstance(checker, (bool, Logical)): return str(checker) elif isinstance(checker, Predicate): return str('p(%s)') % checker.get_name() elif isinstance(checker, type): return type_name(checker, affirm=True) elif isinstance(checker, list): if not checker: return str(True) else: return str(' & ').join(_get_checker_name(c) for c in checker) elif isinstance(checker, tuple): if not checker: return str(False) elif all(isinstance(c, type) for c in checker): return type_name(checker, affirm=True) else: res = str(' | ').join(_get_checker_name(c) for c in checker) return str('(%s)' % res) elif isinstance(checker, PascalSet): return str(checker) elif isinstance(checker, Set): if checker: aux = srepr(next(iter(checker))) if len(checker) > 1: aux += str(', ...') else: aux = str() return str('{%s}') % aux elif isinstance(checker, Mapping): if checker: key = next(iter(checker)) aux = str('%s: %s') % (srepr(key), srepr(checker[key])) if len(checker) > 1: aux += str(', ...') else: aux = str() return str('{%s}') % aux elif callable(checker): res = type_name(checker, affirm=True) if 'lambda' in res: from inspect import getargspec res = res.replace('<lambda>', '<λ>') args = getargspec(checker).args res = sstr('%s(%s)' % (res, ', '.join(args))) return res else: return str('False(%)' % srepr(checker))
def callable_coerce(arg): """Check if `arg` is a callable object.""" from xoutil.eight import callable return arg if callable(arg) else Invalid