def simple_name(item, join=True): '''Returns the simple name for the given object. :param join: If False, only the object inner name is returned; if it is a callable is used similar to a string join receiving a tuple of (module-name, inner-name) as argument; True means (is equivalent to):: join = lambda arg: '.'.join(arg).strip('.') For example, use ``lambda arg: arg`` to return the 2-tuple itself. See `module_name`:func: for more information when a not False value is used. Examples:: >>> simple_name(simple_name) 'xoutil.names.simple_name' >>> from xoutil import Unset >>> simple_name(Unset) 'xoutil.logical.Unset' This function is intended for named objects (those with the `__name__` attribute), if an object without standard name is used, the type name is returned instead; for example:: >>> simple_name(0) 'int' To get a name in a more precise way, use `nameof`:func:. ''' # TODO: Use this function in `nameof` from xoutil.inspect import type_name singletons = (None, True, False, Ellipsis, NotImplemented) res = next((str(s) for s in singletons if s is item), None) if res is None: res = type_name(item) if res is None: item = type(item) res = type_name(item) if join: if join is True: join = lambda arg: str('.'.join(arg).strip('.')) res = join((module_name(item), res)) return res
def check(value, validator, msg=None): '''Check a `value` with a `validator`. Argument `validator` could be a callable, a type, or a tuple of types. Return True if the value is valid. Examples:: >>> check(1, int) True >>> check(10, lambda x: x <= 100, 'must be less than or equal to 100') True >>> check(11/2, (int, float)) True ''' if isinstance(validator, (type, tuple)): checker = is_type(validator) else: checker = validator if checker(value): return True else: from xoutil.inspect import type_name if not msg: # TODO: Use the name of validator with `inspect.getattr_static` # when `xoutil.future` is ready msg = 'Invalid value "%s" of type "%s"' msg = msg.format(value=value, type=type_name(value, affirm=True)) raise ValueError(msg)
def _get_checker_name(checker): '''Return a nice name for a `checker`. A `checker` could be a type, a tuple of types, a callable or a list of other checkers. ''' l = lambda o: str('(%s)' % o.join(_get_checker_name(c) for c in checker)) if isinstance(checker, list): return l('_AND_') elif isinstance(checker, tuple): return l('_OR_') else: from xoutil.inspect import type_name res = type_name(checker, affirm=True) if not isinstance(checker, type): assert callable(checker) if 'lambda' in res: from inspect import getargspec args = getargspec(checker).args assert len(args) == 1 res = str('%s(%s)' % (res, args[0])) return res
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 ok(value, *checkers, **kwargs): '''Validate a value with several checkers. Return the value if it is Ok, or raises an `ValueError` exception if not. Arguments: :param value: the value to validate :param checkers: a variable number of checkers (at least one), each one could be a type, a tuple of types of a callable that receives the value and returns if the value is valid or not. In order the value is considered valid, all checkers must validate the value. :param message: keyword argument to be used in case of error; will be the argument of `ValueError` exception; could contain the placeholders ``{value}`` and ``{type}``; a default value is used if this argument is not given. :param msg: an alias for "message" :param extra_checkers: In order to create validators using `partial`. Must be a tuple. Keyword arguments are not validated to be correct. This function could be used with type-definitions for arguments, see :class:`TypeChecker`. Examples:: >>> ok(1, int) 1 >>> ok(10, int, lambda x: x < 100, message='Must be integer under 100') 10 >>> ok(11/2, (int, float)) 5.5 >>> ok(11/2, int, float) 5.5 >>> try: ... res = ok(11/2, int) ... except ValueError: ... res = '---' >>> res '---' ''' extra_checkers = kwargs.get('extra_checkers', ()) pred = predicate(*(checkers + extra_checkers)) if pred(value): return value else: from xoutil.iterators import multi_get as get from xoutil.inspect import type_name msg = next(get(kwargs, 'message', 'msg'), 'Invalid {type}: {value}!') msg = msg.format(value=value, type=type_name(value, affirm=True)) raise ValueError(msg)
def nameof(*args, **kwargs): '''Obtain the name of each one of a set of objects. .. versionadded:: 1.4.0 .. versionchanged:: 1.6.0 - Keyword arguments are now keyword-only arguments. - Support for several objects - Improved the semantics of parameter `full`. - Added the `safe` keyword argument. If no object is given, None is returned; if only one object is given, a single string is returned; otherwise a list of strings is returned. The name of an object is normally the variable name in the calling stack. If the object is not present calling frame, up to five frame levels are searched. Use the `depth` keyword argument to specify a different starting point and the search will proceed five levels from this frame up. If the same object has several good names a single one is arbitrarily chosen. Good names candidates are retrieved based on the keywords arguments `full`, `inner`, `safe` and `typed`. If `typed` is True and the object is not a type name or a callable (see `xoutil.inspect.type_name`:func:), then the `type` of the object is used instead. If `inner` is True we try to extract the name by introspection instead of looking for the object in the frame stack:: If `full` is True the full identifier of the object is preferred. In this case if `inner` is False the local-name for the object is found. If `inner` is True, find the import-name. If `safe` is True, returned value is converted -if it is not- into a valid Python identifier, though you should not trust this identifier resolves to the value. See `the examples in the documentation <name-of-narrative>`:ref:. ''' # XXX: The examples are stripped from here. Go the documentation page. from numbers import Number from xoutil.eight import range from xoutil.inspect import type_name arg_count = len(args) names = [[] for i in range(arg_count)] class vars: '`nonlocal` simulation' params = kwargs idx = 0 def grant(name=None, **again): if name: names[vars.idx].append(name) assert len(names[vars.idx]) < 5 if again: vars.params = dict(kwargs, **again) else: vars.params = kwargs vars.idx += 1 def param(name, default=False): return vars.params.get(name, default) while vars.idx < arg_count: item = args[vars.idx] if param('typed') and not type_name(item): item = type(item) if param('inner'): res = type_name(item) if res: if param('full'): head = module_name(item) if head: res = '.'.join((head, res)) grant(res) elif isinstance(item, (base_string, Number)): grant(str(item)) else: grant('@'.join(('%(next)s', hex(id(item)))), typed=True) else: import sys sf = sys._getframe(param('depth', 1)) try: i, LIMIT, res = 0, 5, _undef _full = param('full') while not res and sf and (i < LIMIT): key, mapping = _key_for_value(sf, item) if key and _full: head = module_name(_get_value(mapping, '__name__')) if not head: head = module_name(sf.f_code.co_name) if not head: head = module_name(item) or None else: head = None if key: res = key else: sf = sf.f_back i += 1 finally: # TODO: on "del sf" Python says "SyntaxError: can not delete # variable 'sf' referenced in nested scope". sf = None if res: grant('.'.join((head, res)) if head else res) else: res = type_name(item) if res: grant(res) else: grant(None, inner=True) for i in range(arg_count): names[i] = _get_best_name(names[i], safe=param('safe')) if arg_count == 0: return None elif arg_count == 1: return names[0] else: return names