Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-funcy for openSUSE:Factory checked in at 2021-09-06 15:58:09 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-funcy (Old) and /work/SRC/openSUSE:Factory/.python-funcy.new.1899 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-funcy" Mon Sep 6 15:58:09 2021 rev:8 rq:917116 version:1.16 Changes: -------- --- /work/SRC/openSUSE:Factory/python-funcy/python-funcy.changes 2020-08-18 15:05:25.579901082 +0200 +++ /work/SRC/openSUSE:Factory/.python-funcy.new.1899/python-funcy.changes 2021-09-06 15:58:25.905289643 +0200 @@ -1,0 +2,19 @@ +Tue Aug 31 10:08:03 UTC 2021 - John Paul Adrian Glaubitz <adrian.glaub...@suse.com> + +- Update to 1.16 + * support Python 3.9 officially + * unify @memoize() and @cache(): both have .skip/.memory/.invalidate/.invalidate_all now + * support dynamic resulting exception in @reraise() (Laurens Duijvesteijn) + * made () optional for @decorator-made decorators with kw-only args + * added @throttle() + * added has_path() (Denys Zorinets) + * fixed autocurry kwargs handling +- from version 1.15 + * made rpartial accept keyworded arguments (Ruan Comelli) + * made `@cache.invalidate()` idempotent (Dmitry Vasilyanov) + * made raiser() accept a string as a shortcut + * fixed cheatsheat description for 'distinct' helper (tsouvarev) + * fixed some seqs docstrings + * fixed some typos (Tim Gates) + +------------------------------------------------------------------- Old: ---- funcy-1.14.tar.gz New: ---- funcy-1.16.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-funcy.spec ++++++ --- /var/tmp/diff_new_pack.Sy7DxL/_old 2021-09-06 15:58:26.321289546 +0200 +++ /var/tmp/diff_new_pack.Sy7DxL/_new 2021-09-06 15:58:26.321289546 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-funcy # -# Copyright (c) 2020 SUSE LLC +# Copyright (c) 2021 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-funcy -Version: 1.14 +Version: 1.16 Release: 0 Summary: Functional tools for Python License: BSD-3-Clause ++++++ funcy-1.14.tar.gz -> funcy-1.16.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/CHANGELOG new/funcy-1.16/CHANGELOG --- old/funcy-1.14/CHANGELOG 2019-11-28 20:08:47.000000000 +0100 +++ new/funcy-1.16/CHANGELOG 2021-05-10 07:38:06.000000000 +0200 @@ -1,3 +1,20 @@ +1.16 +- support Python 3.9 officially +- unify @memoize() and @cache(): both have .skip/.memory/.invalidate/.invalidate_all now +- support dynamic resulting exception in @reraise() (Laurens Duijvesteijn) +- made () optional for @decorator-made decorators with kw-only args +- added @throttle() +- added has_path() (Denys Zorinets) +- fixed autocurry kwargs handling + +1.15 +- made rpartial accept keyworded arguments (Ruan Comelli) +- made `@cache.invalidate()` idempotent (Dmitry Vasilyanov) +- made raiser() accept a string as a shortcut +- fixed cheatsheat description for 'distinct' helper (tsouvarev) +- fixed some seqs docstrings +- fixed some typos (Tim Gates) + 1.14 - stated Python 3.7 and 3.8 support - dropped Python 2.6 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/LICENSE new/funcy-1.16/LICENSE --- old/funcy-1.14/LICENSE 2018-02-08 14:01:12.000000000 +0100 +++ new/funcy-1.16/LICENSE 2020-07-09 17:41:29.000000000 +0200 @@ -1,4 +1,4 @@ -Copyright (c) 2012-2018, Alexander Schepanovski. +Copyright (c) 2012-2020, Alexander Schepanovski. All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/PKG-INFO new/funcy-1.16/PKG-INFO --- old/funcy-1.14/PKG-INFO 2019-11-28 20:41:42.000000000 +0100 +++ new/funcy-1.16/PKG-INFO 2021-05-10 09:17:36.870961700 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: funcy -Version: 1.14 +Version: 1.16 Summary: A fancy and practical functional tools Home-page: http://github.com/Suor/funcy Author: Alexander Schepanovski @@ -219,6 +219,7 @@ Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries :: Python Modules diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/VERSION new/funcy-1.16/VERSION --- old/funcy-1.14/VERSION 2019-11-28 20:15:30.000000000 +0100 +++ new/funcy-1.16/VERSION 2021-05-10 07:38:09.000000000 +0200 @@ -1 +1 @@ -1.14 +1.16 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/funcy/_inspect.py new/funcy-1.16/funcy/_inspect.py --- old/funcy-1.14/funcy/_inspect.py 2018-10-03 14:49:11.000000000 +0200 +++ new/funcy-1.16/funcy/_inspect.py 2021-05-08 11:10:10.000000000 +0200 @@ -1,5 +1,6 @@ from __future__ import absolute_import -from inspect import CO_VARARGS +from inspect import CO_VARARGS, CO_VARKEYWORDS +from collections import namedtuple import types import re @@ -94,6 +95,7 @@ type_classes = (type, types.ClassType) if hasattr(types, 'ClassType') else type +Spec = namedtuple("Spec", "max_n names req_n req_names kw") def get_spec(func, _cache={}): @@ -107,35 +109,40 @@ if mod in STD_MODULES or mod in ARGS and func.__name__ in ARGS[mod]: _spec = ARGS[mod].get(func.__name__, '*') required, _, optional = _spec.partition('-') - required_names = re.findall(r'\w+|\*', required) - spec = set(required_names), len(required_names), len(required_names) + len(optional) + req_names = re.findall(r'\w+|\*', required) # a list with dups of * + max_n = len(req_names) + len(optional) + req_n = len(req_names) + spec = Spec(max_n=max_n, names=set(), req_n=req_n, req_names=set(req_names), kw=False) _cache[func] = spec return spec elif isinstance(func, type_classes): # Old style classes without base if not hasattr(func, '__init__'): - return set(), 0, 0 + return Spec(max_n=0, names=set(), req_n=0, req_names=set(), kw=False) # __init__ inherited from builtin classes objclass = getattr(func.__init__, '__objclass__', None) if objclass and objclass is not func: return get_spec(objclass) # Introspect constructor and remove self - _required_names, _required_n, _max_n = get_spec(func.__init__) - self_name = func.__init__.__code__.co_varnames[0] - return _required_names - set([self_name]), _required_n - 1, _max_n - 1 + spec = get_spec(func.__init__) + self_set = set([func.__init__.__code__.co_varnames[0]]) + return spec._replace(max_n=spec.max_n - 1, names=spec.names - self_set, + req_n=spec.req_n - 1, req_names=spec.req_names - self_set) else: try: defaults_n = len(func.__defaults__) except (AttributeError, TypeError): defaults_n = 0 try: - names = func.__code__.co_varnames + varnames = func.__code__.co_varnames n = func.__code__.co_argcount - required_n = n - defaults_n - required_names = set(names[:required_n]) + names = set(varnames[:n]) + req_n = n - defaults_n + req_names = set(varnames[:req_n]) + kw = bool(func.__code__.co_flags & CO_VARKEYWORDS) # If there are varargs they could be required, but all keywords args can't be - max_n = required_n + 1 if func.__code__.co_flags & CO_VARARGS else n - return required_names, required_n, max_n + max_n = req_n + 1 if func.__code__.co_flags & CO_VARARGS else n + return Spec(max_n=max_n, names=names, req_n=req_n, req_names=req_names, kw=kw) except AttributeError: raise ValueError('Unable to introspect %s() arguments' % getattr(func, '__name__', func)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/funcy/calc.py new/funcy-1.16/funcy/calc.py --- old/funcy-1.14/funcy/calc.py 2019-07-03 16:30:38.000000000 +0200 +++ new/funcy-1.16/funcy/calc.py 2021-05-09 05:12:04.000000000 +0200 @@ -1,5 +1,8 @@ -from datetime import datetime, timedelta +from datetime import timedelta +import time import inspect +from collections import deque +from bisect import bisect from .decorators import wraps from .compat import PY2 @@ -8,9 +11,11 @@ -class SkipMemoization(Exception): +class SkipMemory(Exception): pass +SkipMemoization = SkipMemory # Old name + # TODO: use real kwonly once in Python 3 only def memoize(*args, **kwargs): """@memoize(key_func=None). Makes decorated function memoize its results. @@ -28,13 +33,26 @@ if kwargs: raise TypeError('memoize() got unexpected keyword arguments: %s', ', '.join(kwargs)) - def decorator(func): - memory = {} + return _memory_decorator({}, key_func) + +memoize.skip = SkipMemory + + +def cache(timeout, key_func=None): + """Caches a function results for timeout seconds.""" + if isinstance(timeout, timedelta): + timeout = timeout.total_seconds() + + return _memory_decorator(CacheMemory(timeout), key_func) + +cache.skip = SkipMemory + +def _memory_decorator(memory, key_func): + def decorator(func): @wraps(func) def wrapper(*args, **kwargs): - # NOTE: we inline this here but not in @cache, - # since @memoize also targets microoptimizations. + # We inline this here since @memoize also targets microoptimizations key = key_func(*args, **kwargs) if key_func else \ args + tuple(sorted(kwargs.items())) if kwargs else args try: @@ -46,11 +64,48 @@ except SkipMemoization as e: return e.args[0] if e.args else None + def invalidate(*args, **kwargs): + key = key_func(*args, **kwargs) if key_func else \ + args + tuple(sorted(kwargs.items())) if kwargs else args + memory.pop(key, None) + wrapper.invalidate = invalidate + + def invalidate_all(): + memory.clear() + wrapper.invalidate_all = invalidate_all + wrapper.memory = memory return wrapper return decorator -memoize.skip = SkipMemoization +class CacheMemory(dict): + def __init__(self, timeout): + self.timeout = timeout + self.clear() + + def __setitem__(self, key, value): + expires_at = time.time() + self.timeout + dict.__setitem__(self, key, (value, expires_at)) + self._keys.append(key) + self._expires.append(expires_at) + + def __getitem__(self, key): + value, expires_at = dict.__getitem__(self, key) + if expires_at <= time.time(): + self.expire() + raise KeyError(key) + return value + + def expire(self): + i = bisect(self._expires, time.time()) + for _ in range(i): + self._expires.popleft() + self.pop(self._keys.popleft(), None) + + def clear(self): + dict.clear(self) + self._keys = deque() + self._expires = deque() def _make_lookuper(silent): @@ -97,43 +152,6 @@ silent_lookuper.__name__ = 'silent_lookuper' -def cache(timeout, key_func=None): - """Caches a function results for timeout seconds.""" - if isinstance(timeout, int): - timeout = timedelta(seconds=timeout) - - if key_func is None: - key_func = lambda *a, **kw: a + tuple(sorted(kw.items())) if kw else a - - def decorator(func): - cache = {} - - @wraps(func) - def wrapper(*args, **kwargs): - key = key_func(*args, **kwargs) - if key in cache: - result, timestamp = cache[key] - if datetime.now() - timestamp < timeout: - return result - else: - del cache[key] - - result = func(*args, **kwargs) - cache[key] = result, datetime.now() - return result - - def invalidate(*args, **kwargs): - cache.pop(key_func(*args, **kwargs)) - wrapper.invalidate = invalidate - - def invalidate_all(): - cache.clear() - wrapper.invalidate_all = invalidate_all - - return wrapper - return decorator - - if PY2: def has_arg_types(func): spec = inspect.getargspec(func) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/funcy/colls.py new/funcy-1.16/funcy/colls.py --- old/funcy-1.14/funcy/colls.py 2019-05-21 10:13:31.000000000 +0200 +++ new/funcy-1.16/funcy/colls.py 2021-03-21 06:53:51.000000000 +0100 @@ -19,7 +19,7 @@ 'is_distinct', 'all', 'any', 'none', 'one', 'some', 'zipdict', 'flip', 'project', 'omit', 'zip_values', 'zip_dicts', 'where', 'pluck', 'pluck_attr', 'invoke', 'lwhere', 'lpluck', 'lpluck_attr', 'linvoke', - 'get_in', 'set_in', 'update_in'] + 'get_in', 'set_in', 'update_in', 'has_path'] ### Generic ops @@ -32,7 +32,7 @@ def _factory(coll, mapper=None): coll_type = type(coll) - # Hack for defaultdicts overriden constructor + # Hack for defaultdicts overridden constructor if isinstance(coll, defaultdict): item_factory = compose(mapper, coll.default_factory) if mapper and coll.default_factory \ else coll.default_factory @@ -265,7 +265,6 @@ for key in keys: yield key, tuple(d[key] for d in dicts) - def get_in(coll, path, default=None): """Returns a value at path in the given nested collection.""" for key in path: @@ -294,6 +293,14 @@ copy[path[0]] = update_in(copy.get(path[0], current_default), path[1:], update, default) return copy +def has_path(coll, path): + """Checks if path exists in the given nested collection.""" + for p in path: + try: + coll = coll[p] + except (KeyError, IndexError): + return False + return True def lwhere(mappings, **cond): """Selects mappings containing all pairs in cond.""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/funcy/decorators.py new/funcy-1.16/funcy/decorators.py --- old/funcy-1.14/funcy/decorators.py 2019-04-17 17:29:31.000000000 +0200 +++ new/funcy-1.16/funcy/decorators.py 2021-05-08 07:56:40.000000000 +0200 @@ -23,12 +23,18 @@ """ if has_single_arg(deco): return make_decorator(deco) - else: + elif has_1pos_and_kwonly(deco): # Any arguments after first become decorator arguments # And a decorator with arguments is essentialy a decorator fab + def decorator_fab(_func=None, **dkwargs): # TODO: make _func pos only in Python 3 + if _func is not None: + return make_decorator(deco, (), dkwargs)(_func) + return make_decorator(deco, (), dkwargs) + else: def decorator_fab(*dargs, **dkwargs): return make_decorator(deco, dargs, dkwargs) - return wraps(deco)(decorator_fab) + + return wraps(deco)(decorator_fab) def make_decorator(deco, dargs=(), dkwargs={}): @@ -73,14 +79,27 @@ def has_single_arg(func): spec = inspect.getargspec(func) return len(spec.args) == 1 and not spec.varargs and not spec.keywords + + def has_1pos_and_kwonly(func): + spec = inspect.getargspec(func) + return len(spec.args) == 1 and not spec.varargs else: + from collections import Counter + from inspect import Parameter as P + def has_single_arg(func): - spec = inspect.signature(func) - if len(spec.parameters) != 1: + sig = inspect.signature(func) + if len(sig.parameters) != 1: return False - arg = next(iter(spec.parameters.values())) + arg = next(iter(sig.parameters.values())) return arg.kind not in (arg.VAR_POSITIONAL, arg.VAR_KEYWORD) + def has_1pos_and_kwonly(func): + sig = inspect.signature(func) + kinds = Counter(p.kind for p in sig.parameters.values()) + return kinds[P.POSITIONAL_ONLY] + kinds[P.POSITIONAL_OR_KEYWORD] == 1 \ + and kinds[P.VAR_POSITIONAL] == 0 + def get_argnames(func): func = getattr(func, '__original__', None) or unwrap(func) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/funcy/flow.py new/funcy-1.16/funcy/flow.py --- old/funcy-1.14/funcy/flow.py 2019-11-28 19:29:12.000000000 +0100 +++ new/funcy-1.16/funcy/flow.py 2021-05-09 04:59:09.000000000 +0200 @@ -7,7 +7,7 @@ __all__ = ['raiser', 'ignore', 'silent', 'suppress', 'nullcontext', 'reraise', 'retry', 'fallback', - 'limit_error_rate', 'ErrorRateExceeded', + 'limit_error_rate', 'ErrorRateExceeded', 'throttle', 'post_processing', 'collecting', 'joining', 'once', 'once_per', 'once_per_args', 'wrap_with'] @@ -18,6 +18,9 @@ def raiser(exception_or_class=Exception, *args, **kwargs): """Constructs function that raises the given exception with given arguments on any invocation.""" + if isinstance(exception_or_class, str): + exception_or_class = Exception(exception_or_class) + def _raiser(*a, **kw): if args or kwargs: raise exception_or_class(*args, **kwargs) @@ -108,6 +111,8 @@ try: yield except errors as e: + if callable(into) and not _is_exception_type(into): + into = into(e) raise_from(into, e) @@ -147,8 +152,11 @@ def _ensure_exceptable(errors): """Ensures that errors are passable to except clause. I.e. should be BaseException subclass or a tuple.""" - is_exception = isinstance(errors, type) and issubclass(errors, BaseException) - return errors if is_exception else tuple(errors) + return errors if _is_exception_type(errors) else tuple(errors) + + +def _is_exception_type(value): + return isinstance(value, type) and issubclass(value, BaseException) class ErrorRateExceeded(Exception): @@ -186,6 +194,28 @@ return decorator +def throttle(period): + """Allows only one run in a period, the rest is skipped""" + if isinstance(period, timedelta): + period = period.total_seconds() + + def decorator(func): + func.blocked_until = None + + @wraps(func) + def wrapper(*args, **kwargs): + now = time.time() + if func.blocked_until and func.blocked_until > now: + return + func.blocked_until = now + period + + return func(*args, **kwargs) + + return wrapper + + return decorator + + ### Post processing decorators @decorator diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/funcy/funcs.py new/funcy-1.16/funcy/funcs.py --- old/funcy-1.14/funcy/funcs.py 2018-10-03 14:49:11.000000000 +0200 +++ new/funcy-1.16/funcy/funcs.py 2021-05-08 11:08:25.000000000 +0200 @@ -2,7 +2,7 @@ from functools import partial, reduce from .compat import map -from ._inspect import get_spec +from ._inspect import get_spec, Spec from .primitives import EMPTY from .funcmakers import make_func, make_pred @@ -31,15 +31,16 @@ Can be used to construct methods.""" return lambda *a, **kw: func(*(args + a), **dict(kwargs, **kw)) -def rpartial(func, *args): - """Partially applies last arguments.""" - return lambda *a: func(*(a + args)) +def rpartial(func, *args, **kwargs): + """Partially applies last arguments. + New keyworded arguments extend and override kwargs.""" + return lambda *a, **kw: func(*(a + args), **dict(kwargs, **kw)) def curry(func, n=EMPTY): """Curries func into a chain of one argument functions.""" if n is EMPTY: - _, n, _ = get_spec(func) + n = get_spec(func).max_n if n <= 1: return func @@ -53,7 +54,7 @@ """Curries func into a chain of one argument functions. Arguments are passed from right to left.""" if n is EMPTY: - _, n, _ = get_spec(func) + n = get_spec(func).max_n if n <= 1: return func @@ -67,17 +68,18 @@ def autocurry(func, n=EMPTY, _spec=None, _args=(), _kwargs={}): """Creates a version of func returning its partial applications until sufficient arguments are passed.""" - spec = _spec or (get_spec(func) if n is EMPTY else (set(), n, n)) - required_names, required_n, max_n = spec + spec = _spec or (get_spec(func) if n is EMPTY else Spec(n, set(), n, set(), False)) def autocurried(*a, **kw): args = _args + a kwargs = _kwargs.copy() kwargs.update(kw) - if len(args) + len(kwargs) >= max_n: + if not spec.kw and len(args) + len(kwargs) >= spec.max_n: return func(*args, **kwargs) - elif len(args) + len(set(kwargs) & required_names) >= required_n: + elif len(args) + len(set(kwargs) & spec.names) >= spec.max_n: + return func(*args, **kwargs) + elif len(args) + len(set(kwargs) & spec.req_names) >= spec.req_n: try: return func(*args, **kwargs) except TypeError: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/funcy/objects.py new/funcy-1.16/funcy/objects.py --- old/funcy-1.14/funcy/objects.py 2019-11-28 14:14:52.000000000 +0100 +++ new/funcy-1.16/funcy/objects.py 2020-01-23 11:23:37.000000000 +0100 @@ -100,7 +100,7 @@ class LazyObject(object): """ A simplistic lazy init object. - Rewrites itself when any attribute is accesssed. + Rewrites itself when any attribute is accessed. """ # NOTE: we can add lots of magic methods here to intercept on more events, # this is postponed. As well as metaclass to support isinstance() check. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/funcy/seqs.py new/funcy-1.16/funcy/seqs.py --- old/funcy-1.14/funcy/seqs.py 2018-10-03 14:49:11.000000000 +0200 +++ new/funcy-1.16/funcy/seqs.py 2020-03-06 05:42:42.000000000 +0100 @@ -109,22 +109,22 @@ # TODO: tree-seq equivalent def lmap(f, *seqs): - """An extended version of builtin map(). + """An extended version of builtin map() returning a list. Derives a mapper from string, int, slice, dict or set.""" return _lmap(make_func(f, builtin=PY2), *seqs) def lfilter(pred, seq): - """An extended version of builtin filter(). + """An extended version of builtin filter() returning a list. Derives a predicate from string, int, slice, dict or set.""" return _lfilter(make_pred(pred, builtin=PY2), seq) def map(f, *seqs): - """An extended version of builtin imap(). + """An extended version of builtin map(). Derives a mapper from string, int, slice, dict or set.""" return _map(make_func(f, builtin=PY2), *seqs) def filter(pred, seq): - """An extended version of builtin ifilter(). + """An extended version of builtin filter(). Derives a predicate from string, int, slice, dict or set.""" return _filter(make_pred(pred, builtin=PY2), seq) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/funcy.egg-info/PKG-INFO new/funcy-1.16/funcy.egg-info/PKG-INFO --- old/funcy-1.14/funcy.egg-info/PKG-INFO 2019-11-28 20:41:41.000000000 +0100 +++ new/funcy-1.16/funcy.egg-info/PKG-INFO 2021-05-10 09:17:36.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: funcy -Version: 1.14 +Version: 1.16 Summary: A fancy and practical functional tools Home-page: http://github.com/Suor/funcy Author: Alexander Schepanovski @@ -219,6 +219,7 @@ Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries :: Python Modules diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/funcy.egg-info/SOURCES.txt new/funcy-1.16/funcy.egg-info/SOURCES.txt --- old/funcy-1.14/funcy.egg-info/SOURCES.txt 2019-11-28 20:41:41.000000000 +0100 +++ new/funcy-1.16/funcy.egg-info/SOURCES.txt 2021-05-10 09:17:36.000000000 +0200 @@ -56,6 +56,7 @@ tests/__pycache__/test_calc.cpython-37-PYTEST.pyc tests/__pycache__/test_calc.cpython-37-pytest-5.0.1.pyc tests/__pycache__/test_calc.cpython-38-pytest-5.3.1.pyc +tests/__pycache__/test_calc.cpython-38-pytest-5.4.3.pyc tests/__pycache__/test_calc.pypy-41-PYTEST.pyc tests/__pycache__/test_colls.cpython-27-PYTEST.pyc tests/__pycache__/test_colls.cpython-35-PYTEST.pyc @@ -64,6 +65,7 @@ tests/__pycache__/test_colls.cpython-37-PYTEST.pyc tests/__pycache__/test_colls.cpython-37-pytest-5.0.1.pyc tests/__pycache__/test_colls.cpython-38-pytest-5.3.1.pyc +tests/__pycache__/test_colls.cpython-38-pytest-5.4.3.pyc tests/__pycache__/test_colls.pypy-41-PYTEST.pyc tests/__pycache__/test_debug.cpython-27-PYTEST.pyc tests/__pycache__/test_debug.cpython-35-PYTEST.pyc @@ -72,6 +74,7 @@ tests/__pycache__/test_debug.cpython-37-PYTEST.pyc tests/__pycache__/test_debug.cpython-37-pytest-5.0.1.pyc tests/__pycache__/test_debug.cpython-38-pytest-5.3.1.pyc +tests/__pycache__/test_debug.cpython-38-pytest-5.4.3.pyc tests/__pycache__/test_debug.pypy-41-PYTEST.pyc tests/__pycache__/test_decorators.cpython-27-PYTEST.pyc tests/__pycache__/test_decorators.cpython-35-PYTEST.pyc @@ -80,6 +83,7 @@ tests/__pycache__/test_decorators.cpython-37-PYTEST.pyc tests/__pycache__/test_decorators.cpython-37-pytest-5.0.1.pyc tests/__pycache__/test_decorators.cpython-38-pytest-5.3.1.pyc +tests/__pycache__/test_decorators.cpython-38-pytest-5.4.3.pyc tests/__pycache__/test_decorators.pypy-41-PYTEST.pyc tests/__pycache__/test_flow.cpython-27-PYTEST.pyc tests/__pycache__/test_flow.cpython-35-PYTEST.pyc @@ -88,6 +92,7 @@ tests/__pycache__/test_flow.cpython-37-PYTEST.pyc tests/__pycache__/test_flow.cpython-37-pytest-5.0.1.pyc tests/__pycache__/test_flow.cpython-38-pytest-5.3.1.pyc +tests/__pycache__/test_flow.cpython-38-pytest-5.4.3.pyc tests/__pycache__/test_flow.pypy-41-PYTEST.pyc tests/__pycache__/test_funcmakers.cpython-27-PYTEST.pyc tests/__pycache__/test_funcmakers.cpython-35-PYTEST.pyc @@ -96,6 +101,7 @@ tests/__pycache__/test_funcmakers.cpython-37-PYTEST.pyc tests/__pycache__/test_funcmakers.cpython-37-pytest-5.0.1.pyc tests/__pycache__/test_funcmakers.cpython-38-pytest-5.3.1.pyc +tests/__pycache__/test_funcmakers.cpython-38-pytest-5.4.3.pyc tests/__pycache__/test_funcmakers.pypy-41-PYTEST.pyc tests/__pycache__/test_funcolls.cpython-27-PYTEST.pyc tests/__pycache__/test_funcolls.cpython-35-PYTEST.pyc @@ -104,6 +110,7 @@ tests/__pycache__/test_funcolls.cpython-37-PYTEST.pyc tests/__pycache__/test_funcolls.cpython-37-pytest-5.0.1.pyc tests/__pycache__/test_funcolls.cpython-38-pytest-5.3.1.pyc +tests/__pycache__/test_funcolls.cpython-38-pytest-5.4.3.pyc tests/__pycache__/test_funcolls.pypy-41-PYTEST.pyc tests/__pycache__/test_funcs.cpython-27-PYTEST.pyc tests/__pycache__/test_funcs.cpython-35-PYTEST.pyc @@ -112,6 +119,7 @@ tests/__pycache__/test_funcs.cpython-37-PYTEST.pyc tests/__pycache__/test_funcs.cpython-37-pytest-5.0.1.pyc tests/__pycache__/test_funcs.cpython-38-pytest-5.3.1.pyc +tests/__pycache__/test_funcs.cpython-38-pytest-5.4.3.pyc tests/__pycache__/test_funcs.pypy-41-PYTEST.pyc tests/__pycache__/test_interface.cpython-27-PYTEST.pyc tests/__pycache__/test_interface.cpython-35-PYTEST.pyc @@ -120,6 +128,7 @@ tests/__pycache__/test_interface.cpython-37-PYTEST.pyc tests/__pycache__/test_interface.cpython-37-pytest-5.0.1.pyc tests/__pycache__/test_interface.cpython-38-pytest-5.3.1.pyc +tests/__pycache__/test_interface.cpython-38-pytest-5.4.3.pyc tests/__pycache__/test_interface.pypy-41-PYTEST.pyc tests/__pycache__/test_objects.cpython-27-PYTEST.pyc tests/__pycache__/test_objects.cpython-35-PYTEST.pyc @@ -128,6 +137,7 @@ tests/__pycache__/test_objects.cpython-37-PYTEST.pyc tests/__pycache__/test_objects.cpython-37-pytest-5.0.1.pyc tests/__pycache__/test_objects.cpython-38-pytest-5.3.1.pyc +tests/__pycache__/test_objects.cpython-38-pytest-5.4.3.pyc tests/__pycache__/test_objects.pypy-41-PYTEST.pyc tests/__pycache__/test_seqs.cpython-27-PYTEST.pyc tests/__pycache__/test_seqs.cpython-35-PYTEST.pyc @@ -136,6 +146,7 @@ tests/__pycache__/test_seqs.cpython-37-PYTEST.pyc tests/__pycache__/test_seqs.cpython-37-pytest-5.0.1.pyc tests/__pycache__/test_seqs.cpython-38-pytest-5.3.1.pyc +tests/__pycache__/test_seqs.cpython-38-pytest-5.4.3.pyc tests/__pycache__/test_seqs.pypy-41-PYTEST.pyc tests/__pycache__/test_strings.cpython-27-PYTEST.pyc tests/__pycache__/test_strings.cpython-35-PYTEST.pyc @@ -144,6 +155,7 @@ tests/__pycache__/test_strings.cpython-37-PYTEST.pyc tests/__pycache__/test_strings.cpython-37-pytest-5.0.1.pyc tests/__pycache__/test_strings.cpython-38-pytest-5.3.1.pyc +tests/__pycache__/test_strings.cpython-38-pytest-5.4.3.pyc tests/__pycache__/test_strings.pypy-41-PYTEST.pyc tests/__pycache__/test_tree.cpython-27-PYTEST.pyc tests/__pycache__/test_tree.cpython-35-PYTEST.pyc @@ -152,6 +164,7 @@ tests/__pycache__/test_tree.cpython-37-PYTEST.pyc tests/__pycache__/test_tree.cpython-37-pytest-5.0.1.pyc tests/__pycache__/test_tree.cpython-38-pytest-5.3.1.pyc +tests/__pycache__/test_tree.cpython-38-pytest-5.4.3.pyc tests/__pycache__/test_tree.pypy-41-PYTEST.pyc tests/__pycache__/test_types.cpython-27-PYTEST.pyc tests/__pycache__/test_types.cpython-35-PYTEST.pyc @@ -160,4 +173,5 @@ tests/__pycache__/test_types.cpython-37-PYTEST.pyc tests/__pycache__/test_types.cpython-37-pytest-5.0.1.pyc tests/__pycache__/test_types.cpython-38-pytest-5.3.1.pyc +tests/__pycache__/test_types.cpython-38-pytest-5.4.3.pyc tests/__pycache__/test_types.pypy-41-PYTEST.pyc \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/setup.py new/funcy-1.16/setup.py --- old/funcy-1.14/setup.py 2019-11-28 20:15:42.000000000 +0100 +++ new/funcy-1.16/setup.py 2021-05-07 13:22:59.000000000 +0200 @@ -34,6 +34,7 @@ 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', Binary files old/funcy-1.14/tests/__pycache__/test_calc.cpython-27-PYTEST.pyc and new/funcy-1.16/tests/__pycache__/test_calc.cpython-27-PYTEST.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_calc.cpython-38-pytest-5.4.3.pyc and new/funcy-1.16/tests/__pycache__/test_calc.cpython-38-pytest-5.4.3.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_colls.cpython-27-PYTEST.pyc and new/funcy-1.16/tests/__pycache__/test_colls.cpython-27-PYTEST.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_colls.cpython-38-pytest-5.4.3.pyc and new/funcy-1.16/tests/__pycache__/test_colls.cpython-38-pytest-5.4.3.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_debug.cpython-38-pytest-5.4.3.pyc and new/funcy-1.16/tests/__pycache__/test_debug.cpython-38-pytest-5.4.3.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_decorators.cpython-27-PYTEST.pyc and new/funcy-1.16/tests/__pycache__/test_decorators.cpython-27-PYTEST.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_decorators.cpython-38-pytest-5.4.3.pyc and new/funcy-1.16/tests/__pycache__/test_decorators.cpython-38-pytest-5.4.3.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_flow.cpython-27-PYTEST.pyc and new/funcy-1.16/tests/__pycache__/test_flow.cpython-27-PYTEST.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_flow.cpython-37-pytest-5.0.1.pyc and new/funcy-1.16/tests/__pycache__/test_flow.cpython-37-pytest-5.0.1.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_flow.cpython-38-pytest-5.4.3.pyc and new/funcy-1.16/tests/__pycache__/test_flow.cpython-38-pytest-5.4.3.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_funcmakers.cpython-38-pytest-5.4.3.pyc and new/funcy-1.16/tests/__pycache__/test_funcmakers.cpython-38-pytest-5.4.3.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_funcolls.cpython-38-pytest-5.4.3.pyc and new/funcy-1.16/tests/__pycache__/test_funcolls.cpython-38-pytest-5.4.3.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_funcs.cpython-27-PYTEST.pyc and new/funcy-1.16/tests/__pycache__/test_funcs.cpython-27-PYTEST.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_funcs.cpython-37-pytest-5.0.1.pyc and new/funcy-1.16/tests/__pycache__/test_funcs.cpython-37-pytest-5.0.1.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_funcs.cpython-38-pytest-5.4.3.pyc and new/funcy-1.16/tests/__pycache__/test_funcs.cpython-38-pytest-5.4.3.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_interface.cpython-38-pytest-5.4.3.pyc and new/funcy-1.16/tests/__pycache__/test_interface.cpython-38-pytest-5.4.3.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_objects.cpython-38-pytest-5.4.3.pyc and new/funcy-1.16/tests/__pycache__/test_objects.cpython-38-pytest-5.4.3.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_seqs.cpython-38-pytest-5.4.3.pyc and new/funcy-1.16/tests/__pycache__/test_seqs.cpython-38-pytest-5.4.3.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_strings.cpython-38-pytest-5.4.3.pyc and new/funcy-1.16/tests/__pycache__/test_strings.cpython-38-pytest-5.4.3.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_tree.cpython-38-pytest-5.4.3.pyc and new/funcy-1.16/tests/__pycache__/test_tree.cpython-38-pytest-5.4.3.pyc differ Binary files old/funcy-1.14/tests/__pycache__/test_types.cpython-38-pytest-5.4.3.pyc and new/funcy-1.16/tests/__pycache__/test_types.cpython-38-pytest-5.4.3.pyc differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/tests/test_calc.py new/funcy-1.16/tests/test_calc.py --- old/funcy-1.14/tests/test_calc.py 2018-07-01 08:09:34.000000000 +0200 +++ new/funcy-1.16/tests/test_calc.py 2021-05-09 05:06:16.000000000 +0200 @@ -1,4 +1,5 @@ from math import sin, cos +from datetime import timedelta import pytest from funcy.calc import * @@ -111,10 +112,12 @@ assert function_table(cos)(-1) is None -def test_cache(): +@pytest.mark.parametrize('typ', + [pytest.param(int, id='int'), pytest.param(lambda s: timedelta(seconds=s), id='timedelta')]) +def test_cache(typ): calls = [] - @cache(timeout=60) + @cache(timeout=typ(60)) def inc(x): calls.append(x) return x + 1 @@ -122,9 +125,7 @@ assert inc(0) == 1 assert inc(1) == 2 assert inc(0) == 1 - inc.invalidate(0) - assert inc(0) == 1 - assert calls == [0, 1, 0] + assert calls == [0, 1] def test_cache_mixed_args(): @@ -144,5 +145,37 @@ return x + 1 assert inc(0) == 1 + assert inc(1) == 2 assert inc(0) == 1 - assert calls == [0, 0] + assert calls == [0, 1, 0] + assert len(inc.memory) == 1 # Both call should be erased then one added + + +def test_cache_invalidate(): + calls = [] + + @cache(timeout=60) + def inc(x): + calls.append(x) + return x + 1 + + assert inc(0) == 1 + assert inc(1) == 2 + assert inc(0) == 1 + assert calls == [0, 1] + + inc.invalidate_all() + assert inc(0) == 1 + assert inc(1) == 2 + assert inc(0) == 1 + assert calls == [0, 1, 0, 1] + + inc.invalidate(1) + assert inc(0) == 1 + assert inc(1) == 2 + assert inc(0) == 1 + assert calls == [0, 1, 0, 1, 1] + + # ensure invalidate() is idempotent (doesn't raise KeyError on the 2nd call) + inc.invalidate(0) + inc.invalidate(0) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/tests/test_colls.py new/funcy-1.16/tests/test_colls.py --- old/funcy-1.14/tests/test_colls.py 2019-11-28 20:14:12.000000000 +0100 +++ new/funcy-1.16/tests/test_colls.py 2021-03-21 07:01:39.000000000 +0100 @@ -246,7 +246,6 @@ assert get_in([1, 2], [3]) is None assert get_in({'x': [1, 2]}, ['x', 1]) == 2 - def test_set_in(): d = { 'a': { @@ -281,6 +280,29 @@ assert d2['a']['b'] == 1 assert d2['c'] is d['c'] +def test_has_path(): + d = { + "a": { + "b": "c", + "d": "e", + "f": { + "g": "h" + } + }, + "i": "j" + } + + assert has_path(d, []) + assert not has_path(d, ["m"]) + assert not has_path(d, ["m", "n"]) + assert has_path(d, ("i",)) + assert has_path(d, ("a", "b")) + assert has_path(d, ["a", "f", "g"]) + +def test_has_path_list(): + assert has_path([1, 2], [0]) + assert not has_path([1, 2], [3]) + assert has_path({'x': [1, 2]}, ['x', 1]) def test_where(): data = [{'a': 1, 'b': 2}, {'a': 10, 'b': 2}] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/tests/test_decorators.py new/funcy-1.16/tests/test_decorators.py --- old/funcy-1.14/tests/test_decorators.py 2019-03-17 05:36:21.000000000 +0100 +++ new/funcy-1.16/tests/test_decorators.py 2021-05-08 07:58:29.000000000 +0200 @@ -26,6 +26,20 @@ assert ten() == 12 +def test_decorator_kw_only_args(): + @decorator + def add(call, **kwargs): # TODO: use real kw-only args in Python 3 + return call() + kwargs.get("n", 1) + + def ten(): + return 10 + + # Should work with or without parentheses + assert add(n=2)(ten)() == 12 + assert add()(ten)() == 11 + assert add(ten)() == 11 + + def test_decorator_access_arg(): @decorator def multiply(call): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/tests/test_flow.py new/funcy-1.16/tests/test_flow.py --- old/funcy-1.14/tests/test_flow.py 2019-11-28 19:56:27.000000000 +0100 +++ new/funcy-1.16/tests/test_flow.py 2021-05-09 05:03:27.000000000 +0200 @@ -1,3 +1,4 @@ +from datetime import timedelta import pytest from funcy.flow import * @@ -29,11 +30,16 @@ with pytest.raises(Exception) as e: raiser()() assert e.type is Exception - with pytest.raises(MyError): raiser(MyError)() - with pytest.raises(MyError) as e: raiser(MyError, 'some message')() - assert e.value.args == ('some message',) + with pytest.raises(Exception, match="text") as e: raiser("text")() + assert e.type is Exception + + with pytest.raises(MyError): + raiser(MyError)() + with pytest.raises(MyError, match="some message"): + raiser(MyError('some message'))() + with pytest.raises(MyError, match="some message") as e: + raiser(MyError, 'some message')() - with pytest.raises(MyError): raiser(MyError('some message'))() with pytest.raises(MyError): raiser(MyError)('junk', keyword='junk') @@ -67,6 +73,10 @@ with reraise(ValueError, MyError): raise TypeError + with pytest.raises(MyError, match="heyhey"): + with reraise(ValueError, lambda e: MyError(str(e) * 2)): + raise ValueError("hey") + def test_retry(): with pytest.raises(MyError): @@ -143,6 +153,25 @@ assert calls == [1, 2] +@pytest.mark.parametrize('typ', + [pytest.param(int, id='int'), pytest.param(lambda s: timedelta(seconds=s), id='timedelta')]) +def test_throttle(monkeypatch, typ): + timestamps = iter([0, 0.01, 1, 1.000025]) + monkeypatch.setattr('time.time', lambda: next(timestamps)) + + calls = [] + + @throttle(typ(1)) + def throttled(x): + calls.append(x) + + throttled(1) + throttled(2) + throttled(3) + throttled(4) + assert calls == [1, 3] + + def test_post_processing(): @post_processing(max) def my_max(l): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/funcy-1.14/tests/test_funcs.py new/funcy-1.16/tests/test_funcs.py --- old/funcy-1.14/tests/test_funcs.py 2018-10-03 14:49:11.000000000 +0200 +++ new/funcy-1.16/tests/test_funcs.py 2021-05-08 10:33:58.000000000 +0200 @@ -32,6 +32,9 @@ assert rpartial(__sub__, 10)(1) == -9 assert rpartial(pow, 2, 85)(10) == 15 + merge = lambda a, b, c='bra': a + b + c + assert rpartial(merge, a='abra')(b='cada') == 'abracadabra' + assert rpartial(merge, 'cada', c='fancy')('abra', c='funcy') == 'abracadafuncy' def test_curry(): assert curry(lambda: 42)() == 42 @@ -73,6 +76,22 @@ assert at(1)(2, 3) == (1, 2, 3) assert at(a=1)(b=2) == (1, 2, 9) assert at(c=3)(1)(2) == (1, 2, 3) + assert at(c=3, a=1, b=2) == (1, 2, 3) + + with pytest.raises(TypeError): at(b=2, c=9, d=42)(1) + +def test_autocurry_kwargs(): + at = autocurry(lambda a, b, **kw: (a, b, kw)) + assert at(1, 2) == (1, 2, {}) + assert at(1)(c=9)(2) == (1, 2, {'c': 9}) + assert at(c=9, d=5)(e=7)(1, 2) == (1, 2, {'c': 9, 'd': 5, 'e': 7}) + + at = autocurry(lambda a, b=2, c=3: (a, b, c)) + assert at(1) == (1, 2, 3) + assert at(a=1) == (1, 2, 3) + assert at(c=9)(1) == (1, 2, 9) + assert at(b=3, c=9)(1) == (1, 3, 9) + with pytest.raises(TypeError): at(b=2, d=3, e=4)(a=1, c=1) def test_autocurry_builtin(): assert autocurry(complex)(imag=1)(0) == 1j