diff --git a/.coveragerc b/.coveragerc index f9b82ac7..5098075d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -7,4 +7,3 @@ omit = [report] exclude_lines = pragma: no cover - pragma: py$MAJOR_PYTHON_VERSION no cover diff --git a/.travis.yml b/.travis.yml index 6d8404eb..24fc8ad2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,13 @@ sudo: false language: python python: - - "2.7" - - "3.4" - "3.5" - "3.6" - - "3.7-dev" - - "pypy" - -# Enable 3.7 without globally enabling sudo and dist: xenial for other build jobs -matrix: - include: - - python: 3.7 - dist: xenial - sudo: true + - "3.7" + - "3.8" + - "3.9-dev" + - "pypy3.5-7.0" + - "pypy3.6-7.1.1" env: - PEP8_IGNORE="E731,W503,E402" diff --git a/README.rst b/README.rst index 099c3ff8..00ecc856 100644 --- a/README.rst +++ b/README.rst @@ -73,7 +73,7 @@ This builds a standard wordcount function from pieces within ``toolz``: Dependencies ------------ -``toolz`` supports Python 2.7 and Python 3.4+ with a common codebase. +``toolz`` supports Python 3.5+ with a common codebase. It is pure Python and requires no dependencies beyond the standard library. diff --git a/doc/source/install.rst b/doc/source/install.rst index cfba80aa..3a999259 100644 --- a/doc/source/install.rst +++ b/doc/source/install.rst @@ -11,4 +11,4 @@ three ways: 1. Toolz is pure Python 2. Toolz relies only on the standard library -3. Toolz simultaneously supports Python versions 2.7, 3.4+, PyPy +3. Toolz simultaneously supports Python versions 3.4+ and PyPy diff --git a/doc/source/references.rst b/doc/source/references.rst index 3cdb2db9..3da61a9a 100644 --- a/doc/source/references.rst +++ b/doc/source/references.rst @@ -7,9 +7,9 @@ References similar library for Ruby - `Clojure `__: A functional language whose standard library has several counterparts in ``toolz`` -- `itertools `__: The +- `itertools `__: The Python standard library for iterator tools -- `functools `__: The +- `functools `__: The Python standard library for function tools - `Functional Programming HOWTO `__: The description of functional programming features from the official diff --git a/setup.py b/setup.py index 9577b7fe..1adc0927 100755 --- a/setup.py +++ b/setup.py @@ -21,17 +21,15 @@ long_description=(open('README.rst').read() if exists('README.rst') else ''), zip_safe=False, - python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", + python_requires=">=3.5", classifiers=[ "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: BSD License", "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy"]) diff --git a/toolz/__init__.py b/toolz/__init__.py index 7fa86ab4..8e3b0b9a 100644 --- a/toolz/__init__.py +++ b/toolz/__init__.py @@ -6,12 +6,14 @@ from .recipes import * -from .compatibility import map, filter - from functools import partial, reduce sorted = sorted +map = map + +filter = filter + # Aliases comp = compose diff --git a/toolz/_signatures.py b/toolz/_signatures.py index c55a778b..328cea91 100644 --- a/toolz/_signatures.py +++ b/toolz/_signatures.py @@ -18,14 +18,10 @@ import operator from importlib import import_module -from .compatibility import PY3 from .functoolz import (is_partial_args, is_arity, has_varargs, has_keywords, num_required_args) -if PY3: # pragma: py2 no cover - import builtins -else: # pragma: py3 no cover - import __builtin__ as builtins +import builtins # We mock builtin callables using lists of tuples with lambda functions. # @@ -235,54 +231,33 @@ lambda source, globals: None, lambda source, globals, locals: None] -if PY3: # pragma: py2 no cover - module_info[builtins].update( - breakpoint=[ - lambda *args, **kws: None], - bytes=[ - lambda: None, - lambda int: None, - lambda string, encoding='utf8', errors='strict': None], - compile=[ - (0, lambda source, filename, mode, flags=0, - dont_inherit=False, optimize=-1: None)], - max=[ - (1, lambda iterable: None, ('default', 'key',)), - (1, lambda arg1, arg2, *args: None, ('key',))], - min=[ - (1, lambda iterable: None, ('default', 'key',)), - (1, lambda arg1, arg2, *args: None, ('key',))], - open=[ - (0, lambda file, mode='r', buffering=-1, encoding=None, - errors=None, newline=None, closefd=True, opener=None: None)], - sorted=[ - (1, lambda iterable: None, ('key', 'reverse'))], - str=[ - lambda object='', encoding='utf', errors='strict': None], - ) - module_info[builtins]['print'] = [ - (0, lambda *args: None, ('sep', 'end', 'file', 'flush',))] - -else: # pragma: py3 no cover - module_info[builtins].update( - bytes=[ - lambda object='': None], - compile=[ - (0, lambda source, filename, mode, flags=0, - dont_inherit=False: None)], - max=[ - (1, lambda iterable, *args: None, ('key',))], - min=[ - (1, lambda iterable, *args: None, ('key',))], - open=[ - (0, lambda file, mode='r', buffering=-1: None)], - sorted=[ - lambda iterable, cmp=None, key=None, reverse=False: None], - str=[ - lambda object='': None], - ) - module_info[builtins]['print'] = [ - (0, lambda *args: None, ('sep', 'end', 'file',))] +module_info[builtins].update( + breakpoint=[ + lambda *args, **kws: None], + bytes=[ + lambda: None, + lambda int: None, + lambda string, encoding='utf8', errors='strict': None], + compile=[ + (0, lambda source, filename, mode, flags=0, + dont_inherit=False, optimize=-1: None)], + max=[ + (1, lambda iterable: None, ('default', 'key',)), + (1, lambda arg1, arg2, *args: None, ('key',))], + min=[ + (1, lambda iterable: None, ('default', 'key',)), + (1, lambda arg1, arg2, *args: None, ('key',))], + open=[ + (0, lambda file, mode='r', buffering=-1, encoding=None, + errors=None, newline=None, closefd=True, opener=None: None)], + sorted=[ + (1, lambda iterable: None, ('key', 'reverse'))], + str=[ + lambda object='', encoding='utf', errors='strict': None], +) +module_info[builtins]['print'] = [ + (0, lambda *args: None, ('sep', 'end', 'file', 'flush',))] + module_info[functools] = dict( cmp_to_key=[ @@ -346,16 +321,11 @@ (0, lambda *iterables: None, ('fillvalue',))], ) -if PY3: # pragma: py2 no cover - module_info[itertools].update( - product=[ - (0, lambda *iterables: None, ('repeat',))], - ) -else: # pragma: py3 no cover - module_info[itertools].update( - product=[ - lambda *iterables: None], - ) +module_info[itertools].update( + product=[ + (0, lambda *iterables: None, ('repeat',))], +) + module_info[operator] = dict( __abs__=[ @@ -622,51 +592,31 @@ classval=None: None)], ) -if PY3: # pragma: py2 no cover - def num_pos_args(sigspec): - """ Return the number of positional arguments. ``f(x, y=1)`` has 1""" - return sum(1 for x in sigspec.parameters.values() - if x.kind == x.POSITIONAL_OR_KEYWORD - and x.default is x.empty) - - def get_exclude_keywords(num_pos_only, sigspec): - """ Return the names of position-only arguments if func has **kwargs""" - if num_pos_only == 0: - return () - has_kwargs = any(x.kind == x.VAR_KEYWORD - for x in sigspec.parameters.values()) - if not has_kwargs: - return () - pos_args = list(sigspec.parameters.values())[:num_pos_only] - return tuple(x.name for x in pos_args) - - def signature_or_spec(func): - try: - return inspect.signature(func) - except (ValueError, TypeError): - return None - -else: # pragma: py3 no cover - def num_pos_args(sigspec): - """ Return the number of positional arguments. ``f(x, y=1)`` has 1""" - if sigspec.defaults: - return len(sigspec.args) - len(sigspec.defaults) - return len(sigspec.args) - - def get_exclude_keywords(num_pos_only, sigspec): - """ Return the names of position-only arguments if func has **kwargs""" - if num_pos_only == 0: - return () - has_kwargs = sigspec.keywords is not None - if not has_kwargs: - return () - return tuple(sigspec.args[:num_pos_only]) - - def signature_or_spec(func): - try: - return inspect.getargspec(func) - except TypeError: - return None + +def num_pos_args(sigspec): + """ Return the number of positional arguments. ``f(x, y=1)`` has 1""" + return sum(1 for x in sigspec.parameters.values() + if x.kind == x.POSITIONAL_OR_KEYWORD + and x.default is x.empty) + + +def get_exclude_keywords(num_pos_only, sigspec): + """ Return the names of position-only arguments if func has **kwargs""" + if num_pos_only == 0: + return () + has_kwargs = any(x.kind == x.VAR_KEYWORD + for x in sigspec.parameters.values()) + if not has_kwargs: + return () + pos_args = list(sigspec.parameters.values())[:num_pos_only] + return tuple(x.name for x in pos_args) + + +def signature_or_spec(func): + try: + return inspect.signature(func) + except (ValueError, TypeError): + return None def expand_sig(sig): @@ -792,7 +742,7 @@ def _has_varargs(func): checks = [check_varargs(sig) for sig in sigs] if all(checks): return True - elif any(checks): # pragma: py2 no cover + elif any(checks): return None return False diff --git a/toolz/compatibility.py b/toolz/compatibility.py index af6acfbd..9a1242c1 100644 --- a/toolz/compatibility.py +++ b/toolz/compatibility.py @@ -1,34 +1,30 @@ +import warnings +warnings.warn("The toolz.compatibility module is no longer " + "needed in Python 3 and has been deprecated. Please " + "import these utilities directly from the standard library. " + "This module will be removed in a future release.", + category=DeprecationWarning) + import operator import sys + PY3 = sys.version_info[0] > 2 PY34 = sys.version_info[0] == 3 and sys.version_info[1] == 4 -PYPY = hasattr(sys, 'pypy_version_info') +PYPY = hasattr(sys, 'pypy_version_info') and PY3 __all__ = ('map', 'filter', 'range', 'zip', 'reduce', 'zip_longest', 'iteritems', 'iterkeys', 'itervalues', 'filterfalse', 'PY3', 'PY34', 'PYPY') -if PY3: - map = map - filter = filter - range = range - zip = zip - from functools import reduce - from itertools import zip_longest - from itertools import filterfalse - iteritems = operator.methodcaller('items') - iterkeys = operator.methodcaller('keys') - itervalues = operator.methodcaller('values') - from collections.abc import Sequence, Mapping -else: - range = xrange - reduce = reduce - from itertools import imap as map - from itertools import ifilter as filter - from itertools import ifilterfalse as filterfalse - from itertools import izip as zip - from itertools import izip_longest as zip_longest - iteritems = operator.methodcaller('iteritems') - iterkeys = operator.methodcaller('iterkeys') - itervalues = operator.methodcaller('itervalues') - from collections import Sequence, Mapping + +map = map +filter = filter +range = range +zip = zip +from functools import reduce +from itertools import zip_longest +from itertools import filterfalse +iteritems = operator.methodcaller('items') +iterkeys = operator.methodcaller('keys') +itervalues = operator.methodcaller('values') +from collections.abc import Sequence diff --git a/toolz/dicttoolz.py b/toolz/dicttoolz.py index 17567547..35048d32 100644 --- a/toolz/dicttoolz.py +++ b/toolz/dicttoolz.py @@ -1,6 +1,6 @@ import operator -from toolz.compatibility import (map, zip, iteritems, iterkeys, itervalues, - reduce, Mapping) +from functools import reduce +from collections.abc import Mapping __all__ = ('merge', 'merge_with', 'valmap', 'keymap', 'itemmap', 'valfilter', 'keyfilter', 'itemfilter', @@ -60,7 +60,7 @@ def merge_with(func, *dicts, **kwargs): result = factory() for d in dicts: - for k, v in iteritems(d): + for k, v in d.items(): if k not in result: result[k] = [v] else: @@ -80,7 +80,7 @@ def valmap(func, d, factory=dict): itemmap """ rv = factory() - rv.update(zip(iterkeys(d), map(func, itervalues(d)))) + rv.update(zip(d.keys(), map(func, d.values()))) return rv @@ -96,7 +96,7 @@ def keymap(func, d, factory=dict): itemmap """ rv = factory() - rv.update(zip(map(func, iterkeys(d)), itervalues(d))) + rv.update(zip(map(func, d.keys()), d.values())) return rv @@ -112,7 +112,7 @@ def itemmap(func, d, factory=dict): valmap """ rv = factory() - rv.update(map(func, iteritems(d))) + rv.update(map(func, d.items())) return rv @@ -130,7 +130,7 @@ def valfilter(predicate, d, factory=dict): valmap """ rv = factory() - for k, v in iteritems(d): + for k, v in d.items(): if predicate(v): rv[k] = v return rv @@ -150,7 +150,7 @@ def keyfilter(predicate, d, factory=dict): keymap """ rv = factory() - for k, v in iteritems(d): + for k, v in d.items(): if predicate(k): rv[k] = v return rv @@ -173,7 +173,7 @@ def itemfilter(predicate, d, factory=dict): itemmap """ rv = factory() - for item in iteritems(d): + for item in d.items(): if predicate(item): k, v = item rv[k] = v diff --git a/toolz/functoolz.py b/toolz/functoolz.py index 01d3857a..21a7d3be 100644 --- a/toolz/functoolz.py +++ b/toolz/functoolz.py @@ -6,7 +6,7 @@ from textwrap import dedent from types import MethodType -from .compatibility import PY3, PY34, PYPY +from .compatibility import PYPY from .utils import no_default @@ -229,44 +229,43 @@ def __init__(self, *args, **kwargs): def func(self): return self._partial.func - if PY3: # pragma: py2 no cover - @instanceproperty - def __signature__(self): - sig = inspect.signature(self.func) - args = self.args or () - keywords = self.keywords or {} - if is_partial_args(self.func, args, keywords, sig) is False: - raise TypeError('curry object has incorrect arguments') - - params = list(sig.parameters.values()) - skip = 0 - for param in params[:len(args)]: - if param.kind == param.VAR_POSITIONAL: - break - skip += 1 - - kwonly = False - newparams = [] - for param in params[skip:]: - kind = param.kind - default = param.default - if kind == param.VAR_KEYWORD: - pass - elif kind == param.VAR_POSITIONAL: - if kwonly: - continue - elif param.name in keywords: - default = keywords[param.name] + @instanceproperty + def __signature__(self): + sig = inspect.signature(self.func) + args = self.args or () + keywords = self.keywords or {} + if is_partial_args(self.func, args, keywords, sig) is False: + raise TypeError('curry object has incorrect arguments') + + params = list(sig.parameters.values()) + skip = 0 + for param in params[:len(args)]: + if param.kind == param.VAR_POSITIONAL: + break + skip += 1 + + kwonly = False + newparams = [] + for param in params[skip:]: + kind = param.kind + default = param.default + if kind == param.VAR_KEYWORD: + pass + elif kind == param.VAR_POSITIONAL: + if kwonly: + continue + elif param.name in keywords: + default = keywords[param.name] + kind = param.KEYWORD_ONLY + kwonly = True + else: + if kwonly: kind = param.KEYWORD_ONLY - kwonly = True - else: - if kwonly: - kind = param.KEYWORD_ONLY - if default is param.empty: - default = no_default - newparams.append(param.replace(default=default, kind=kind)) + if default is param.empty: + default = no_default + newparams.append(param.replace(default=default, kind=kind)) - return sig.replace(parameters=newparams) + return sig.replace(parameters=newparams) @instanceproperty def args(self): @@ -347,14 +346,14 @@ def __reduce__(self): func = self.func modname = getattr(func, '__module__', None) qualname = getattr(func, '__qualname__', None) - if qualname is None: # pragma: py3 no cover + if qualname is None: # pragma: no cover qualname = getattr(func, '__name__', None) is_decorated = None if modname and qualname: attrs = [] obj = import_module(modname) for attr in qualname.split('.'): - if isinstance(obj, curry): # pragma: py2 no cover + if isinstance(obj, curry): attrs.append('func') obj = obj.func obj = getattr(obj, attr, None) @@ -541,24 +540,17 @@ def __hash__(self): # Mimic the descriptor behavior of python functions. # i.e. let Compose be called as a method when bound to a class. - if PY3: # pragma: py2 no cover - # adapted from - # docs.python.org/3/howto/descriptor.html#functions-and-methods - def __get__(self, obj, objtype=None): - return self if obj is None else MethodType(self, obj) - else: # pragma: py3 no cover - # adapted from - # docs.python.org/2/howto/descriptor.html#functions-and-methods - def __get__(self, obj, objtype=None): - return self if obj is None else MethodType(self, obj, objtype) + # adapted from + # docs.python.org/3/howto/descriptor.html#functions-and-methods + def __get__(self, obj, objtype=None): + return self if obj is None else MethodType(self, obj) # introspection with Signature is only possible from py3.3+ - if PY3: # pragma: py2 no cover - @instanceproperty - def __signature__(self): - base = inspect.signature(self.first) - last = inspect.signature(self.funcs[-1]) - return base.replace(return_annotation=last.return_annotation) + @instanceproperty + def __signature__(self): + base = inspect.signature(self.first) + last = inspect.signature(self.funcs[-1]) + return base.replace(return_annotation=last.return_annotation) __wrapped__ = instanceproperty(attrgetter('first')) @@ -825,52 +817,39 @@ def __name__(self): return 'excepting' -if PY3: # pragma: py2 no cover - def _check_sigspec(sigspec, func, builtin_func, *builtin_args): - if sigspec is None: - try: - sigspec = inspect.signature(func) - except (ValueError, TypeError) as e: - sigspec = e - if isinstance(sigspec, ValueError): - return None, builtin_func(*builtin_args) - elif not isinstance(sigspec, inspect.Signature): - if ( - func in _sigs.signatures - and (( - hasattr(func, '__signature__') - and hasattr(func.__signature__, '__get__') - )) - ): # pragma: no cover (not covered in Python 3.4) - val = builtin_func(*builtin_args) - return None, val - return None, False - return sigspec, None - -else: # pragma: py3 no cover - def _check_sigspec(sigspec, func, builtin_func, *builtin_args): - if sigspec is None: - try: - sigspec = inspect.getargspec(func) - except TypeError as e: - sigspec = e - if isinstance(sigspec, TypeError): - if not callable(func): - return None, False - return None, builtin_func(*builtin_args) - return sigspec, None - - -if PY34 or PYPY: # pragma: no cover +def _check_sigspec(sigspec, func, builtin_func, *builtin_args): + if sigspec is None: + try: + sigspec = inspect.signature(func) + except (ValueError, TypeError) as e: + sigspec = e + if isinstance(sigspec, ValueError): + return None, builtin_func(*builtin_args) + elif not isinstance(sigspec, inspect.Signature): + if ( + func in _sigs.signatures + and (( + hasattr(func, '__signature__') + and hasattr(func.__signature__, '__get__') + )) + ): + val = builtin_func(*builtin_args) + return None, val + return None, False + return sigspec, None + + +if PYPY: # pragma: no cover _check_sigspec_orig = _check_sigspec def _check_sigspec(sigspec, func, builtin_func, *builtin_args): - # Python 3.4 and PyPy may lie, so use our registry for builtins instead + # PyPy may lie, so use our registry for builtins instead if func in _sigs.signatures: val = builtin_func(*builtin_args) return None, val return _check_sigspec_orig(sigspec, func, builtin_func, *builtin_args) + _check_sigspec.__doc__ = """ \ Private function to aid in introspection compatibly across Python versions. @@ -878,143 +857,56 @@ def _check_sigspec(sigspec, func, builtin_func, *builtin_args): the signature registry in toolz._signatures is used. """ -if PY3: # pragma: py2 no cover - def num_required_args(func, sigspec=None): - sigspec, rv = _check_sigspec(sigspec, func, _sigs._num_required_args, - func) - if sigspec is None: - return rv - return sum(1 for p in sigspec.parameters.values() - if p.default is p.empty - and p.kind in (p.POSITIONAL_OR_KEYWORD, p.POSITIONAL_ONLY)) - - def has_varargs(func, sigspec=None): - sigspec, rv = _check_sigspec(sigspec, func, _sigs._has_varargs, func) - if sigspec is None: - return rv - return any(p.kind == p.VAR_POSITIONAL - for p in sigspec.parameters.values()) - - def has_keywords(func, sigspec=None): - sigspec, rv = _check_sigspec(sigspec, func, _sigs._has_keywords, func) - if sigspec is None: - return rv - return any(p.default is not p.empty - or p.kind in (p.KEYWORD_ONLY, p.VAR_KEYWORD) - for p in sigspec.parameters.values()) - - def is_valid_args(func, args, kwargs, sigspec=None): - sigspec, rv = _check_sigspec(sigspec, func, _sigs._is_valid_args, - func, args, kwargs) - if sigspec is None: - return rv - try: - sigspec.bind(*args, **kwargs) - except TypeError: - return False - return True - def is_partial_args(func, args, kwargs, sigspec=None): - sigspec, rv = _check_sigspec(sigspec, func, _sigs._is_partial_args, - func, args, kwargs) - if sigspec is None: - return rv - try: - sigspec.bind_partial(*args, **kwargs) - except TypeError: - return False - return True - -else: # pragma: py3 no cover - def num_required_args(func, sigspec=None): - sigspec, rv = _check_sigspec(sigspec, func, _sigs._num_required_args, - func) - if sigspec is None: - return rv - num_defaults = len(sigspec.defaults) if sigspec.defaults else 0 - return len(sigspec.args) - num_defaults - - def has_varargs(func, sigspec=None): - sigspec, rv = _check_sigspec(sigspec, func, _sigs._has_varargs, func) - if sigspec is None: - return rv - return sigspec.varargs is not None - - def has_keywords(func, sigspec=None): - sigspec, rv = _check_sigspec(sigspec, func, _sigs._has_keywords, func) - if sigspec is None: - return rv - return sigspec.defaults is not None or sigspec.keywords is not None - - def is_valid_args(func, args, kwargs, sigspec=None): - sigspec, rv = _check_sigspec(sigspec, func, _sigs._is_valid_args, - func, args, kwargs) - if sigspec is None: - return rv - spec = sigspec - defaults = spec.defaults or () - num_pos = len(spec.args) - len(defaults) - missing_pos = spec.args[len(args):num_pos] - if any(arg not in kwargs for arg in missing_pos): - return False - - if spec.varargs is None: - num_extra_pos = max(0, len(args) - num_pos) - else: - num_extra_pos = 0 - - kwargs = dict(kwargs) - - # Add missing keyword arguments (unless already included in `args`) - missing_kwargs = spec.args[num_pos + num_extra_pos:] - kwargs.update(zip(missing_kwargs, defaults[num_extra_pos:])) +def num_required_args(func, sigspec=None): + sigspec, rv = _check_sigspec(sigspec, func, _sigs._num_required_args, + func) + if sigspec is None: + return rv + return sum(1 for p in sigspec.parameters.values() + if p.default is p.empty + and p.kind in (p.POSITIONAL_OR_KEYWORD, p.POSITIONAL_ONLY)) - # Convert call to use positional arguments - args = args + tuple(kwargs.pop(key) for key in spec.args[len(args):]) - if ( - not spec.keywords and kwargs - or not spec.varargs and len(args) > len(spec.args) - or set(spec.args[:len(args)]) & set(kwargs) - ): - return False - else: - return True +def has_varargs(func, sigspec=None): + sigspec, rv = _check_sigspec(sigspec, func, _sigs._has_varargs, func) + if sigspec is None: + return rv + return any(p.kind == p.VAR_POSITIONAL + for p in sigspec.parameters.values()) - def is_partial_args(func, args, kwargs, sigspec=None): - sigspec, rv = _check_sigspec(sigspec, func, _sigs._is_partial_args, - func, args, kwargs) - if sigspec is None: - return rv - spec = sigspec - defaults = spec.defaults or () - num_pos = len(spec.args) - len(defaults) - if spec.varargs is None: - num_extra_pos = max(0, len(args) - num_pos) - else: - num_extra_pos = 0 - kwargs = dict(kwargs) +def has_keywords(func, sigspec=None): + sigspec, rv = _check_sigspec(sigspec, func, _sigs._has_keywords, func) + if sigspec is None: + return rv + return any(p.default is not p.empty + or p.kind in (p.KEYWORD_ONLY, p.VAR_KEYWORD) + for p in sigspec.parameters.values()) - # Add missing keyword arguments (unless already included in `args`) - missing_kwargs = spec.args[num_pos + num_extra_pos:] - kwargs.update(zip(missing_kwargs, defaults[num_extra_pos:])) - # Add missing position arguments as keywords (may already be in kwargs) - missing_args = spec.args[len(args):num_pos + num_extra_pos] - kwargs.update((x, None) for x in missing_args) +def is_valid_args(func, args, kwargs, sigspec=None): + sigspec, rv = _check_sigspec(sigspec, func, _sigs._is_valid_args, + func, args, kwargs) + if sigspec is None: + return rv + try: + sigspec.bind(*args, **kwargs) + except TypeError: + return False + return True - # Convert call to use positional arguments - args = args + tuple(kwargs.pop(key) for key in spec.args[len(args):]) - if ( - not spec.keywords and kwargs - or not spec.varargs and len(args) > len(spec.args) - or set(spec.args[:len(args)]) & set(kwargs) - ): - return False - else: - return True +def is_partial_args(func, args, kwargs, sigspec=None): + sigspec, rv = _check_sigspec(sigspec, func, _sigs._is_partial_args, + func, args, kwargs) + if sigspec is None: + return rv + try: + sigspec.bind_partial(*args, **kwargs) + except TypeError: + return False + return True def is_arity(n, func, sigspec=None): diff --git a/toolz/itertoolz.py b/toolz/itertoolz.py index e71f1eee..72d41896 100644 --- a/toolz/itertoolz.py +++ b/toolz/itertoolz.py @@ -3,9 +3,9 @@ import collections import operator from functools import partial +from itertools import filterfalse, zip_longest from random import Random -from toolz.compatibility import (map, filterfalse, zip, zip_longest, iteritems, - filter, Sequence) +from collections.abc import Sequence from toolz.utils import no_default @@ -100,7 +100,7 @@ def groupby(key, seq): for item in seq: d[key(item)](item) rv = {} - for k, v in iteritems(d): + for k, v in d.items(): rv[k] = v.__self__ return rv @@ -916,7 +916,7 @@ def join(leftkey, leftseq, rightkey, rightseq, else: yield (left_default, item) - for key, matches in iteritems(d): + for key, matches in d.items(): if key not in seen_keys: for match in matches: yield (match, right_default) diff --git a/toolz/recipes.py b/toolz/recipes.py index 08c6c8c1..89de88db 100644 --- a/toolz/recipes.py +++ b/toolz/recipes.py @@ -1,6 +1,5 @@ import itertools from .itertoolz import frequencies, pluck, getter -from .compatibility import map __all__ = ('countby', 'partitionby') diff --git a/toolz/sandbox/parallel.py b/toolz/sandbox/parallel.py index ef8ed39d..114077d2 100644 --- a/toolz/sandbox/parallel.py +++ b/toolz/sandbox/parallel.py @@ -1,6 +1,5 @@ import functools from toolz.itertoolz import partition_all -from toolz.compatibility import reduce, map from toolz.utils import no_default diff --git a/toolz/sandbox/tests/test_core.py b/toolz/sandbox/tests/test_core.py index 14e3847f..ca7cc68b 100644 --- a/toolz/sandbox/tests/test_core.py +++ b/toolz/sandbox/tests/test_core.py @@ -1,8 +1,6 @@ from toolz import curry, unique, first, take from toolz.sandbox.core import EqualityHashKey, unzip from itertools import count, repeat -from toolz.compatibility import map, zip - def test_EqualityHashKey_default_key(): EqualityHashDefault = curry(EqualityHashKey, None) diff --git a/toolz/tests/test_compatibility.py b/toolz/tests/test_compatibility.py index 6071606d..db304027 100644 --- a/toolz/tests/test_compatibility.py +++ b/toolz/tests/test_compatibility.py @@ -1,18 +1 @@ -from toolz.compatibility import map, filter, iteritems, iterkeys, itervalues - - -def test_map_filter_are_lazy(): - def bad(x): - raise Exception() - map(bad, [1, 2, 3]) - filter(bad, [1, 2, 3]) - - -def test_dict_iteration(): - d = {'a': 1, 'b': 2, 'c': 3} - assert not isinstance(iteritems(d), list) - assert not isinstance(iterkeys(d), list) - assert not isinstance(itervalues(d), list) - assert set(iteritems(d)) == set(d.items()) - assert set(iterkeys(d)) == set(d.keys()) - assert set(itervalues(d)) == set(d.values()) +import toolz.compatibility \ No newline at end of file diff --git a/toolz/tests/test_dicttoolz.py b/toolz/tests/test_dicttoolz.py index 6ad1d82d..d45cd6cf 100644 --- a/toolz/tests/test_dicttoolz.py +++ b/toolz/tests/test_dicttoolz.py @@ -1,11 +1,11 @@ from collections import defaultdict as _defaultdict +from collections.abc import Mapping import os from toolz.dicttoolz import (merge, merge_with, valmap, keymap, update_in, assoc, dissoc, keyfilter, valfilter, itemmap, itemfilter, assoc_in) from toolz.functoolz import identity from toolz.utils import raises -from toolz.compatibility import PY3, Mapping def inc(x): @@ -215,17 +215,6 @@ def items(self): def update(self, *args, **kwargs): self._d.update(*args, **kwargs) - # Should we require these to be defined for Python 2? - if not PY3: - def iterkeys(self): - return self._d.iterkeys() - - def itervalues(self): - return self._d.itervalues() - - def iteritems(self): - return self._d.iteritems() - # Unused methods that are part of the MutableMapping protocol #def get(self, key, *args): # return self._d.get(key, *args) diff --git a/toolz/tests/test_functoolz.py b/toolz/tests/test_functoolz.py index 4938df8a..555cf48d 100644 --- a/toolz/tests/test_functoolz.py +++ b/toolz/tests/test_functoolz.py @@ -3,7 +3,6 @@ from toolz.functoolz import (thread_first, thread_last, memoize, curry, compose, compose_left, pipe, complement, do, juxt, flip, excepts, apply) -from toolz.compatibility import PY3 from operator import add, mul, itemgetter from toolz.utils import raises from functools import partial @@ -632,34 +631,33 @@ def __int__(self): assert compose(f, h).__class__.__wrapped__ is None # __signature__ is python3 only - if PY3: - - def myfunc(a, b, c, *d, **e): - return 4 - - def otherfunc(f): - return 'result: {}'.format(f) - - # set annotations compatibly with python2 syntax - myfunc.__annotations__ = { - 'a': int, - 'b': str, - 'c': float, - 'd': int, - 'e': bool, - 'return': int, - } - otherfunc.__annotations__ = {'f': int, 'return': str} - - composed = compose(otherfunc, myfunc) - sig = inspect.signature(composed) - assert sig.parameters == inspect.signature(myfunc).parameters - assert sig.return_annotation == str - - class MyClass: - method = composed - - assert len(inspect.signature(MyClass().method).parameters) == 4 + + def myfunc(a, b, c, *d, **e): + return 4 + + def otherfunc(f): + return 'result: {}'.format(f) + + # set annotations compatibly with python2 syntax + myfunc.__annotations__ = { + 'a': int, + 'b': str, + 'c': float, + 'd': int, + 'e': bool, + 'return': int, + } + otherfunc.__annotations__ = {'f': int, 'return': str} + + composed = compose(otherfunc, myfunc) + sig = inspect.signature(composed) + assert sig.parameters == inspect.signature(myfunc).parameters + assert sig.return_annotation == str + + class MyClass: + method = composed + + assert len(inspect.signature(MyClass().method).parameters) == 4 def generate_compose_left_test_cases(): diff --git a/toolz/tests/test_inspect_args.py b/toolz/tests/test_inspect_args.py index 7381612f..da985044 100644 --- a/toolz/tests/test_inspect_args.py +++ b/toolz/tests/test_inspect_args.py @@ -7,7 +7,6 @@ num_required_args, has_varargs, has_keywords) from toolz._signatures import builtins import toolz._signatures as _sigs -from toolz.compatibility import PY3 from toolz.utils import raises @@ -115,8 +114,6 @@ def test_is_valid(check_valid=is_valid_args, incomplete=False): def test_is_valid_py3(check_valid=is_valid_args, incomplete=False): - if not PY3: - return orig_check_valid = check_valid check_valid = lambda func, *args, **kwargs: orig_check_valid(func, args, kwargs) @@ -255,20 +252,19 @@ def test_has_unknown_args(): assert has_varargs(make_func('x, y, z=1')) is False assert has_varargs(make_func('x, y, z=1, **kwargs')) is False - if PY3: - f = make_func('*args') - f.__signature__ = 34 - assert has_varargs(f) is False + f = make_func('*args') + f.__signature__ = 34 + assert has_varargs(f) is False - class RaisesValueError(object): - def __call__(self): - pass - @property - def __signature__(self): - raise ValueError('Testing Python 3.4') + class RaisesValueError(object): + def __call__(self): + pass + @property + def __signature__(self): + raise ValueError('Testing Python 3.4') - f = RaisesValueError() - assert has_varargs(f) is None + f = RaisesValueError() + assert has_varargs(f) is None def test_num_required_args(): @@ -298,8 +294,7 @@ def test_has_varargs(): assert has_varargs(lambda *args: None) assert has_varargs(lambda **kwargs: None) is False assert has_varargs(map) - if PY3: - assert has_varargs(max) is None + assert has_varargs(max) is None def test_is_arity(): @@ -315,8 +310,6 @@ def test_is_arity(): def test_introspect_curry_valid_py3(check_valid=is_valid_args, incomplete=False): - if not PY3: - return orig_check_valid = check_valid check_valid = lambda _func, *args, **kwargs: orig_check_valid(_func, args, kwargs) @@ -361,8 +354,6 @@ def test_introspect_curry_partial_py3(): def test_introspect_curry_py3(): - if not PY3: - return f = toolz.curry(make_func('')) assert num_required_args(f) == 0 assert is_arity(0, f) @@ -443,8 +434,6 @@ def is_missing(modname, name, func): def test_inspect_signature_property(): - if not PY3: - return # By adding AddX to our signature registry, we can inspect the class # itself and objects of the class. `inspect.signature` doesn't like @@ -491,8 +480,7 @@ def __wrapped__(self): func = lambda x: x wrapped = Wrapped(func) - if PY3: - assert inspect.signature(func) == inspect.signature(wrapped) + assert inspect.signature(func) == inspect.signature(wrapped) assert num_required_args(Wrapped) is None _sigs.signatures[Wrapped] = (_sigs.expand_sig((0, lambda func: None)),) diff --git a/toolz/tests/test_itertoolz.py b/toolz/tests/test_itertoolz.py index 3444e4b1..61618725 100644 --- a/toolz/tests/test_itertoolz.py +++ b/toolz/tests/test_itertoolz.py @@ -14,7 +14,6 @@ sliding_window, count, partition, partition_all, take_nth, pluck, join, diff, topk, peek, peekn, random_sample) -from toolz.compatibility import range, filter from operator import add, mul diff --git a/toolz/tests/test_serialization.py b/toolz/tests/test_serialization.py index 92b07f05..5f432ae5 100644 --- a/toolz/tests/test_serialization.py +++ b/toolz/tests/test_serialization.py @@ -2,7 +2,6 @@ import toolz import toolz.curried import pickle -from toolz.compatibility import PY3 from toolz.utils import raises @@ -114,8 +113,6 @@ def g3(self): def test_curried_qualname(): - if not PY3: - return def preserves_identity(obj): return pickle.loads(pickle.dumps(obj)) is obj diff --git a/toolz/tests/test_signatures.py b/toolz/tests/test_signatures.py index b5bb12e9..03b9293c 100644 --- a/toolz/tests/test_signatures.py +++ b/toolz/tests/test_signatures.py @@ -1,7 +1,6 @@ import functools import toolz._signatures as _sigs from toolz._signatures import builtins, _is_valid_args, _is_partial_args -from toolz.compatibility import PY3 def test_is_valid(check_valid=_is_valid_args, incomplete=False): @@ -53,8 +52,8 @@ def test_is_valid(check_valid=_is_valid_args, incomplete=False): assert check_valid(f, 1, key=None) assert check_valid(f, 1, 2, key=None) assert check_valid(f, 1, 2, 3, key=None) - assert check_valid(f, key=None, default=None) is (PY3 and incomplete) - assert check_valid(f, 1, key=None, default=None) is PY3 + assert check_valid(f, key=None, default=None) is incomplete + assert check_valid(f, 1, key=None, default=None) assert check_valid(f, 1, 2, key=None, default=None) is False assert check_valid(f, 1, 2, 3, key=None, default=None) is False @@ -84,4 +83,3 @@ def test_for_coverage(): # :) assert _sigs._has_varargs(None) is None assert _sigs._has_keywords(None) is None assert _sigs._num_required_args(None) is None -