def source(self): '''The source of objects from this token. If then token is a top-level one and is related with a ``is_instance(token, SomeClass)`` filter and ``SomeClass.this_instances`` is a collection, this collection will be the source. If the token is a top-level token but the test described above fails, the we use the `gc` module to get every possible object in the Python's memory. If the token is not a top-level token, we simply use the attribute's name from the parent token current value. ''' from xoutil.types import is_collection cls = self._token_class this_instances = getattr(cls, 'this_instances', None) if is_collection(this_instances): if not self.only or defined(cls, self.only): return iter(this_instances) else: return [] else: return self._build_source()
def getargvalues(frame): '''Inspects the given frame for arguments and returns a dictionary that maps parameters names to arguments values. If an `*` argument was passed then the key on the returning dictionary would be formatted as `<name-of-*-param>[index]`. For example in the function:: >>> def autocontained(a, limit, *margs, **ks): ... import sys ... return getargvalues(sys._getframe()) >>> autocontained(1, 12)['limit'] 12 >>> autocontained(1, 2, -10, -11)['margs[0]'] -10 ''' from xoutil.types import is_collection from xoutil.iterators import flatten pos, args, kwds, values = inspect.getargvalues(frame) res = {} for keys in pos: if not is_collection(keys): keys = (keys,) res.update({key: values[key] for key in flatten(keys)}) if args: i = 0 for item in values[args]: res['%s[%s]' % (args, i)] = item i += 1 if kwds: res.update(values[kwds]) return res
def iterate_over(source, *keys): '''Yields pairs of (key, value) for of all `keys` in `source`. If any `key` is missing from `source` is ignored (not yielded). If `source` is a `collection <xoutil.types.is_collection>`:func:, iterate over each of the items searching for any of keys. This is not recursive. If no `keys` are provided, return an "empty" iterator -- i.e will raise StopIteration upon calling `next`. .. versionadded:: 1.5.2 ''' from xoutil.types import is_collection def inner(source): get = smart_getter(source) for key in keys: val = get(key, Unset) if val is not Unset: yield key, val def when_collection(source): from xoutil.iterators import map for generator in map(inner, source): for key, val in generator: yield key, val if is_collection(source): res = when_collection(source) else: res = inner(source) return res
def push(queues, items): from xoutil.types import is_collection is_queryobject = IQueryObject.providedBy if not is_collection(items): items = (items, ) for item in items: if is_expression(item): queues.append([item]) elif is_queryobject(item): queues.extend([f] for f in item.filters)
def pop_first_of(source, *keys, **kwargs): '''Similar to :func:`get_first_of` using as `source` either an object or a mapping and deleting the first attribute or key. Examples:: >>> somedict = dict(bar='bar-dict', eggs='eggs-dict') >>> class Foo(object): pass >>> foo = Foo() >>> foo.bar = 'bar-obj' >>> foo.eggs = 'eggs-obj' >>> pop_first_of((somedict, foo), 'eggs') 'eggs-dict' >>> pop_first_of((somedict, foo), 'eggs') 'eggs-obj' >>> pop_first_of((somedict, foo), 'eggs') is None True >>> pop_first_of((foo, somedict), 'bar') 'bar-obj' >>> pop_first_of((foo, somedict), 'bar') 'bar-dict' >>> pop_first_of((foo, somedict), 'bar') is None True ''' from xoutil.types import is_collection def inner(source): get = smart_getter_and_deleter(source) res, i = Unset, 0 while (res is Unset) and (i < len(keys)): res = get(keys[i], Unset) i += 1 return res if is_collection(source): res = Unset source = iter(source) probe = next(source, None) while res is Unset and probe: res = inner(probe) probe = next(source, None) else: res = inner(source) return res if res is not Unset else kwargs.get('default', None)
def test_iscollection(): from xoutil.eight import range from xoutil.types import is_collection from xoutil.collections import UserList, UserDict assert is_collection('all strings are iterable') is False assert is_collection(1) is False assert is_collection(range(1)) is True assert is_collection({}) is False assert is_collection(tuple()) is True assert is_collection(set()) is True assert is_collection(a for a in range(100)) is True class Foobar(UserList): pass assert is_collection(Foobar()) is True class Foobar(UserDict): pass assert is_collection(Foobar()) is False
def concatfiles(*files): '''Concat several files to a single one. Each positional argument must be either: - a file-like object (ready to be passed to :func:`shutil.copyfileobj`) - a string, the file path. The last positional argument is the target. If it's file-like object it must be open for writing, and the caller is the responsible for closing it. Alternatively if there are only two positional arguments and the first is a collection, the sources will be the members of the first argument. ''' import shutil from xoutil.eight import string_types from xoutil.types import is_collection if len(files) < 2: raise TypeError('At least 2 files must be passed to concatfiles.') elif len(files) == 2: files, target = files[0], files[1] if not is_collection(files): files = [files] else: files, target = files[:-1], files[-1] if isinstance(target, string_types): target, opened = open(target, 'wb'), True else: opened = False try: for f in files: if isinstance(f, string_types): fh = open(f, 'rb') closefh = True else: fh = f closefh = False try: shutil.copyfileobj(fh, target) finally: if closefh: fh.close() finally: if opened: target.close()
def first_n(iterable, n=1, fill=Unset): '''Takes the first `n` items from iterable. If there are less than `n` items in the iterable and `fill` is :class:`~xoutil.types.Unset`, a StopIteration exception is raised; otherwise it's used as a filling pattern as explained below. :param iterable: An iterable from which the first `n` items should be collected. :param n: The number of items to collect :type n: int :param fill: The filling pattern to use. It may be: - a collection, in which case `first_n` fills the last items by cycling over `fill`. - anything else is used as the filling pattern by repeating. :returns: The first `n` items from `iterable`, probably with a filling pattern at the end. :rtype: generator object .. versionadded:: 1.2.0 .. versionchanged:: 1.4.0 The notion of collection for the `fill` argument uses :class:`xoutil.types.is_collection` instead of probing for the ``__iter__`` method. ''' if fill is not Unset: from xoutil.types import is_collection from itertools import cycle, repeat, chain if is_collection(fill): fill = cycle(fill) else: fill = repeat(fill) seq = chain(iterable, fill) else: seq = iter(iterable) while n > 0: yield next(seq) n -= 1
def slides(iterable, width=2, fill=None): '''Creates a sliding window of a given `width` over an iterable:: >>> list(slides(range(1, 11))) [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)] If the iterator does not yield a width-aligned number of items, the last slice returned is filled with `fill` (by default None):: >>> list(slides(range(1, 11), width=3)) # doctest: +ELLIPSIS [(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, None, None)] .. versionchanged:: 1.4.0 If the `fill` argument is a collection is cycled over to get the filling, just like in :func:`first_n`. .. versionchanged:: 1.4.2 The `fill` argument now defaults to None, instead of Unset. ''' from itertools import cycle, repeat from xoutil.types import is_collection pos = 0 res = [] iterator = iter(iterable) current = next(iterator, Unset) while current is not Unset: if pos < width: res.append(current) current = next(iterator, Unset) pos = pos + 1 else: yield tuple(res) res = [] pos = 0 if res: if is_collection(fill): fill = cycle(fill) else: fill = repeat(fill) while pos < width: res.append(next(fill)) pos += 1 yield tuple(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