From 483de5a5b4c8bf03949b95cf6f77ded784d9673e Mon Sep 17 00:00:00 2001 From: Robert Ma Date: Tue, 15 Dec 2020 09:30:55 -0500 Subject: [PATCH] Squashed 'tools/third_party/funcsigs/' changes from db7f0afe3e..29e7a6ebb2 1b88d78a38 Release 1.0.2. a2602c812c Fix #21: setup_requires setuptools 17.1 3b0a393c27 Release 1.0.1. 6848ca897c Fixes #18: depend on ordereddict on old Pythons d967e51973 Version bump to 1.0.0. 9610297407 Merge pull request #17 from rbtcollins/mfield-varargs_class_method f2c0368f2e Deal with unbound methods like foo(*args). 3306968f41 Cleanup tests: e70929ceb2 Update README.rst fa0f64c06a Use the ordereddict pypi package when needed 7693e97193 Add .gitignore e7fb456d52 Merge pull request #6 from rbtcollins/github 43ee6b7b4e Fixup tox patch. 4e80d81554 Add tox.ini for tox 95ce8ebc49 README.rst: A few more tweaks 6cea81cced README.rst: Add detail to example d131c4af91 README.rst: compatability => compatibility 82d9c949d6 Avoid easy-install in travis. aa7288e532 Updates to fit into the new home. 32a9d3e37b Closes #14: Fix binding with self as a kwarg. git-subtree-dir: tools/third_party/funcsigs git-subtree-split: 29e7a6ebb21bf1c4dc9aca710d9fbfea08875849 --- .gitignore | 19 ++ .travis.yml | 11 +- CHANGELOG | 5 + README.rst | 338 +++++++++++++++++++++++++++++---- docs/index.rst | 316 +----------------------------- funcsigs/__init__.py | 43 +++-- funcsigs/odict.py | 261 ------------------------- funcsigs/version.py | 2 +- requirements/development.txt | 3 +- requirements/production.txt | 0 setup.py | 23 +-- tests/test_formatannotation.py | 12 +- tests/test_funcsigs.py | 36 ++-- tests/test_inspect.py | 39 ++-- tox.ini | 8 + 15 files changed, 411 insertions(+), 705 deletions(-) create mode 100644 .gitignore mode change 100644 => 120000 docs/index.rst delete mode 100644 funcsigs/odict.py delete mode 100644 requirements/production.txt create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000000..c8d2af85d3c617 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +*~ +*.egg +*.egg-info +*.pyc +*.pyo +*.swp +.DS_Store +.coverage +.tox/ +MANIFEST +build/ +docs/.build/ +dist/ +env*/ +htmlcov/ +tmp/ +coverage.xml +junit.xml +.eggs/ diff --git a/.travis.yml b/.travis.yml index d2a7ab3051a0ac..c1e7abe0b44a89 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,16 +2,17 @@ language: python python: - 2.6 - 2.7 - - 3.2 - 3.3 + - 3.4 + - 3.5 + - nightly - pypy +# - pypy3 install: - - pip install -r requirements/development.txt -r requirements/production.txt - - python setup.py install + - pip install -U pip setuptools wheel + - pip install -r requirements/development.txt . script: - coverage run setup.py test - coverage report --show-missing after_success: - coveralls -notifications: - email: aaron.iles+travis-ci@gmail.com diff --git a/CHANGELOG b/CHANGELOG index 602eec5e7c2ab1..e1366d2668d03d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,11 @@ Changelog --------- +0.5 +``` + +* Fix binding with self as a kwarg. (Robert Collins #14) + 0.4 (2013-12-20) ```````````````` * Fix unbound methods getting their first parameter curried diff --git a/README.rst b/README.rst index f04b7b422cf51b..5fbca27e6e6b01 100644 --- a/README.rst +++ b/README.rst @@ -1,74 +1,342 @@ -funcsigs -======== +.. funcsigs documentation master file, created by + sphinx-quickstart on Fri Apr 20 20:27:52 2012. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Introducing funcsigs +==================== + +The Funcsigs Package +-------------------- ``funcsigs`` is a backport of the `PEP 362`_ function signature features from Python 3.3's `inspect`_ module. The backport is compatible with Python 2.6, 2.7 -as well as 3.2 and up. +as well as 3.3 and up. 3.2 was supported by version 0.4, but with setuptools and +pip no longer supporting 3.2, we cannot make any statement about 3.2 +compatibility. -|pypi_version| +Compatibility +````````````` + +The ``funcsigs`` backport has been tested against: + +* CPython 2.6 +* CPython 2.7 +* CPython 3.3 +* CPython 3.4 +* CPython 3.5 +* CPython nightlies +* PyPy and PyPy3(currently failing CI) + +Continuous integration testing is provided by `Travis CI`_. + +Under Python 2.x there is a compatibility issue when a function is assigned to +the ``__wrapped__`` property of a class after it has been constructed. +Similiarily there under PyPy directly passing the ``__call__`` method of a +builtin is also a compatibility issues. Otherwise the functionality is +believed to be uniform between both Python2 and Python3. -Documentation -------------- +Issues +`````` -The reference documentation is standard library documentation for the -`inspect`_ module in Python3. This documentation has been included in the -``funcsigs`` package documentation hosted on `Read The Docs`_. +Source code for ``funcsigs`` is hosted on `GitHub`_. Any bug reports or feature +requests can be made using GitHub's `issues system`_. |build_status| |coverage| Example ------- -To obtain a signature object, pass the target function to the -``funcsigs.signature`` function. :: +To obtain a `Signature` object, pass the target function to the +``funcsigs.signature`` function. + +.. code-block:: python >>> from funcsigs import signature >>> def foo(a, b=None, *args, **kwargs): ... pass - + ... >>> sig = signature(foo) + >>> sig + + >>> sig.parameters + OrderedDict([('a', ), ('b', ), ('args', ), ('kwargs', )]) + >>> sig.return_annotation + -For the details of the signature object, refer to the either the package of -standard library documentation. +Introspecting callables with the Signature object +------------------------------------------------- -Compatability -------------- +.. note:: -The ``funcsigs`` backport has been tested against: + This section of documentation is a direct reproduction of the Python + standard library documentation for the inspect module. -* CPython 2.6 -* CPython 2.7 -* CPython 3.2 -* PyPy 1.9 +The Signature object represents the call signature of a callable object and its +return annotation. To retrieve a Signature object, use the :func:`signature` +function. -Continuous integration testing is provided by `Travis CI`_. +.. function:: signature(callable) -Under Python 2.x there is a compatability issue when a function is assigned to -the ``__wrapped__`` property of a class after it has been constructed. -Similiarily there under PyPy directly passing the ``__call__`` method of a -builtin is also a compatability issues. Otherwise the functionality is -believed to be uniform between both Python2 and Python3. + Return a :class:`Signature` object for the given ``callable``:: -Issues ------- + >>> from funcsigs import signature + >>> def foo(a, *, b:int, **kwargs): + ... pass -Source code for ``funcsigs`` is hosted on `GitHub`_. Any bug reports or feature -requests can be made using GitHub's `issues system`_. |build_status| |coverage| + >>> sig = signature(foo) + + >>> str(sig) + '(a, *, b:int, **kwargs)' + + >>> str(sig.parameters['b']) + 'b:int' + + >>> sig.parameters['b'].annotation + + + Accepts a wide range of python callables, from plain functions and classes to + :func:`functools.partial` objects. + + .. note:: + + Some callables may not be introspectable in certain implementations of + Python. For example, in CPython, built-in functions defined in C provide + no metadata about their arguments. + + +.. class:: Signature + + A Signature object represents the call signature of a function and its return + annotation. For each parameter accepted by the function it stores a + :class:`Parameter` object in its :attr:`parameters` collection. + + Signature objects are *immutable*. Use :meth:`Signature.replace` to make a + modified copy. + + .. attribute:: Signature.empty + + A special class-level marker to specify absence of a return annotation. + + .. attribute:: Signature.parameters + + An ordered mapping of parameters' names to the corresponding + :class:`Parameter` objects. + + .. attribute:: Signature.return_annotation + + The "return" annotation for the callable. If the callable has no "return" + annotation, this attribute is set to :attr:`Signature.empty`. + + .. method:: Signature.bind(*args, **kwargs) + + Create a mapping from positional and keyword arguments to parameters. + Returns :class:`BoundArguments` if ``*args`` and ``**kwargs`` match the + signature, or raises a :exc:`TypeError`. + + .. method:: Signature.bind_partial(*args, **kwargs) + + Works the same way as :meth:`Signature.bind`, but allows the omission of + some required arguments (mimics :func:`functools.partial` behavior.) + Returns :class:`BoundArguments`, or raises a :exc:`TypeError` if the + passed arguments do not match the signature. + + .. method:: Signature.replace(*[, parameters][, return_annotation]) + + Create a new Signature instance based on the instance replace was invoked + on. It is possible to pass different ``parameters`` and/or + ``return_annotation`` to override the corresponding properties of the base + signature. To remove return_annotation from the copied Signature, pass in + :attr:`Signature.empty`. + + :: + + >>> def test(a, b): + ... pass + >>> sig = signature(test) + >>> new_sig = sig.replace(return_annotation="new return anno") + >>> str(new_sig) + "(a, b) -> 'new return anno'" + + +.. class:: Parameter + + Parameter objects are *immutable*. Instead of modifying a Parameter object, + you can use :meth:`Parameter.replace` to create a modified copy. + + .. attribute:: Parameter.empty + + A special class-level marker to specify absence of default values and + annotations. + + .. attribute:: Parameter.name + + The name of the parameter as a string. Must be a valid python identifier + name (with the exception of ``POSITIONAL_ONLY`` parameters, which can have + it set to ``None``). + + .. attribute:: Parameter.default + + The default value for the parameter. If the parameter has no default + value, this attribute is set to :attr:`Parameter.empty`. + + .. attribute:: Parameter.annotation + + The annotation for the parameter. If the parameter has no annotation, + this attribute is set to :attr:`Parameter.empty`. + + .. attribute:: Parameter.kind + + Describes how argument values are bound to the parameter. Possible values + (accessible via :class:`Parameter`, like ``Parameter.KEYWORD_ONLY``): + + +------------------------+----------------------------------------------+ + | Name | Meaning | + +========================+==============================================+ + | *POSITIONAL_ONLY* | Value must be supplied as a positional | + | | argument. | + | | | + | | Python has no explicit syntax for defining | + | | positional-only parameters, but many built-in| + | | and extension module functions (especially | + | | those that accept only one or two parameters)| + | | accept them. | + +------------------------+----------------------------------------------+ + | *POSITIONAL_OR_KEYWORD*| Value may be supplied as either a keyword or | + | | positional argument (this is the standard | + | | binding behaviour for functions implemented | + | | in Python.) | + +------------------------+----------------------------------------------+ + | *VAR_POSITIONAL* | A tuple of positional arguments that aren't | + | | bound to any other parameter. This | + | | corresponds to a ``*args`` parameter in a | + | | Python function definition. | + +------------------------+----------------------------------------------+ + | *KEYWORD_ONLY* | Value must be supplied as a keyword argument.| + | | Keyword only parameters are those which | + | | appear after a ``*`` or ``*args`` entry in a | + | | Python function definition. | + +------------------------+----------------------------------------------+ + | *VAR_KEYWORD* | A dict of keyword arguments that aren't bound| + | | to any other parameter. This corresponds to a| + | | ``**kwargs`` parameter in a Python function | + | | definition. | + +------------------------+----------------------------------------------+ + + Example: print all keyword-only arguments without default values:: + + >>> def foo(a, b, *, c, d=10): + ... pass + + >>> sig = signature(foo) + >>> for param in sig.parameters.values(): + ... if (param.kind == param.KEYWORD_ONLY and + ... param.default is param.empty): + ... print('Parameter:', param) + Parameter: c + + .. method:: Parameter.replace(*[, name][, kind][, default][, annotation]) + + Create a new Parameter instance based on the instance replaced was invoked + on. To override a :class:`Parameter` attribute, pass the corresponding + argument. To remove a default value or/and an annotation from a + Parameter, pass :attr:`Parameter.empty`. + + :: + + >>> from funcsigs import Parameter + >>> param = Parameter('foo', Parameter.KEYWORD_ONLY, default=42) + >>> str(param) + 'foo=42' + + >>> str(param.replace()) # Will create a shallow copy of 'param' + 'foo=42' + + >>> str(param.replace(default=Parameter.empty, annotation='spam')) + "foo:'spam'" + + +.. class:: BoundArguments + + Result of a :meth:`Signature.bind` or :meth:`Signature.bind_partial` call. + Holds the mapping of arguments to the function's parameters. + + .. attribute:: BoundArguments.arguments + + An ordered, mutable mapping (:class:`collections.OrderedDict`) of + parameters' names to arguments' values. Contains only explicitly bound + arguments. Changes in :attr:`arguments` will reflect in :attr:`args` and + :attr:`kwargs`. + + Should be used in conjunction with :attr:`Signature.parameters` for any + argument processing purposes. + + .. note:: + + Arguments for which :meth:`Signature.bind` or + :meth:`Signature.bind_partial` relied on a default value are skipped. + However, if needed, it is easy to include them. + + :: + + >>> def foo(a, b=10): + ... pass + + >>> sig = signature(foo) + >>> ba = sig.bind(5) + + >>> ba.args, ba.kwargs + ((5,), {}) + + >>> for param in sig.parameters.values(): + ... if param.name not in ba.arguments: + ... ba.arguments[param.name] = param.default + + >>> ba.args, ba.kwargs + ((5, 10), {}) + + + .. attribute:: BoundArguments.args + + A tuple of positional arguments values. Dynamically computed from the + :attr:`arguments` attribute. + + .. attribute:: BoundArguments.kwargs + + A dict of keyword arguments values. Dynamically computed from the + :attr:`arguments` attribute. + + The :attr:`args` and :attr:`kwargs` properties can be used to invoke + functions:: + + def test(a, *, b): + ... + + sig = signature(test) + ba = sig.bind(10, b=20) + test(*ba.args, **ba.kwargs) + + +.. seealso:: + + :pep:`362` - Function Signature Object. + The detailed specification, implementation details and examples. Copyright --------- -This is a derived work of CPython under the terms of the `PSF License +*funcsigs* is a derived work of CPython under the terms of the `PSF License Agreement`_. The original CPython inspect module, its unit tests and documentation are the copyright of the Python Software Foundation. The derived work is distributed under the `Apache License Version 2.0`_. +.. _PSF License Agreement: http://docs.python.org/3/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python .. _Apache License Version 2.0: http://opensource.org/licenses/Apache-2.0 -.. _GitHub: https://github.com/aliles/funcsigs +.. _GitHub: https://github.com/testing-cabal/funcsigs .. _PSF License Agreement: http://docs.python.org/3/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python .. _Travis CI: http://travis-ci.org/ .. _Read The Docs: http://funcsigs.readthedocs.org/ .. _PEP 362: http://www.python.org/dev/peps/pep-0362/ .. _inspect: http://docs.python.org/3/library/inspect.html#introspecting-callables-with-the-signature-object -.. _issues system: https://github.com/alies/funcsigs/issues +.. _issues system: https://github.com/testing-cabal/funcsigs/issues .. |build_status| image:: https://secure.travis-ci.org/aliles/funcsigs.png?branch=master :target: http://travis-ci.org/#!/aliles/funcsigs @@ -81,3 +349,5 @@ work is distributed under the `Apache License Version 2.0`_. .. |pypi_version| image:: https://pypip.in/v/funcsigs/badge.png :target: https://crate.io/packages/funcsigs/ :alt: Latest PyPI version + + diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 5d0f42f9368b76..00000000000000 --- a/docs/index.rst +++ /dev/null @@ -1,315 +0,0 @@ -.. funcsigs documentation master file, created by - sphinx-quickstart on Fri Apr 20 20:27:52 2012. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Introducing funcsigs -==================== - -The Funcsigs Package --------------------- - -*funcsigs* is a backport of the `PEP 362`_ function signature features from -Python 3.3's `inspect`_ module. The backport is compatible with Python 2.6, 2.7 -as well as 3.2 and up. - -.. _PEP 362: http://www.python.org/dev/peps/pep-0362/ -.. _inspect: http://docs.python.org/3/library/inspect.html#introspecting-callables-with-the-signature-object - -Compatability -````````````` - -The *funcsigs* backport has been tested against: - -* CPython 2.6 -* CPython 2.7 -* CPython 3.2 -* PyPy 1.9 - -Continuous integration testing is provided by `Travis CI`_. - -Under Python 2.x there is a compatability issue when a function is assigned to -the ``__wrapped__`` property of a class after it has been constructed. -Similiarily there under PyPy directly passing the ``__call__`` method of a -builtin is also a compatability issues. Otherwise the functionality is -believed to be uniform between both Python2 and Python3. - -.. _Travis CI: http://travis-ci.org/ - -Issues -`````` - -Source code for *funcsigs* is hosted on `GitHub`_. Any bug reports or feature -requests can be made using GitHub's `issues system`_. - -.. _GitHub: https://github.com/aliles/funcsigs -.. _issues system: https://github.com/alies/funcsigs/issues - -Introspecting callables with the Signature object -------------------------------------------------- - -.. note:: - - This section of documentation is a direct repoduction of the Python - standard library documentation for the inspect module. - -The Signature object represents the call signature of a callable object and its -return annotation. To retrieve a Signature object, use the :func:`signature` -function. - -.. function:: signature(callable) - - Return a :class:`Signature` object for the given ``callable``:: - - >>> from inspect import signature - >>> def foo(a, *, b:int, **kwargs): - ... pass - - >>> sig = signature(foo) - - >>> str(sig) - '(a, *, b:int, **kwargs)' - - >>> str(sig.parameters['b']) - 'b:int' - - >>> sig.parameters['b'].annotation - - - Accepts a wide range of python callables, from plain functions and classes to - :func:`functools.partial` objects. - - .. note:: - - Some callables may not be introspectable in certain implementations of - Python. For example, in CPython, built-in functions defined in C provide - no metadata about their arguments. - - -.. class:: Signature - - A Signature object represents the call signature of a function and its return - annotation. For each parameter accepted by the function it stores a - :class:`Parameter` object in its :attr:`parameters` collection. - - Signature objects are *immutable*. Use :meth:`Signature.replace` to make a - modified copy. - - .. attribute:: Signature.empty - - A special class-level marker to specify absence of a return annotation. - - .. attribute:: Signature.parameters - - An ordered mapping of parameters' names to the corresponding - :class:`Parameter` objects. - - .. attribute:: Signature.return_annotation - - The "return" annotation for the callable. If the callable has no "return" - annotation, this attribute is set to :attr:`Signature.empty`. - - .. method:: Signature.bind(*args, **kwargs) - - Create a mapping from positional and keyword arguments to parameters. - Returns :class:`BoundArguments` if ``*args`` and ``**kwargs`` match the - signature, or raises a :exc:`TypeError`. - - .. method:: Signature.bind_partial(*args, **kwargs) - - Works the same way as :meth:`Signature.bind`, but allows the omission of - some required arguments (mimics :func:`functools.partial` behavior.) - Returns :class:`BoundArguments`, or raises a :exc:`TypeError` if the - passed arguments do not match the signature. - - .. method:: Signature.replace(*[, parameters][, return_annotation]) - - Create a new Signature instance based on the instance replace was invoked - on. It is possible to pass different ``parameters`` and/or - ``return_annotation`` to override the corresponding properties of the base - signature. To remove return_annotation from the copied Signature, pass in - :attr:`Signature.empty`. - - :: - - >>> def test(a, b): - ... pass - >>> sig = signature(test) - >>> new_sig = sig.replace(return_annotation="new return anno") - >>> str(new_sig) - "(a, b) -> 'new return anno'" - - -.. class:: Parameter - - Parameter objects are *immutable*. Instead of modifying a Parameter object, - you can use :meth:`Parameter.replace` to create a modified copy. - - .. attribute:: Parameter.empty - - A special class-level marker to specify absence of default values and - annotations. - - .. attribute:: Parameter.name - - The name of the parameter as a string. Must be a valid python identifier - name (with the exception of ``POSITIONAL_ONLY`` parameters, which can have - it set to ``None``). - - .. attribute:: Parameter.default - - The default value for the parameter. If the parameter has no default - value, this attribute is set to :attr:`Parameter.empty`. - - .. attribute:: Parameter.annotation - - The annotation for the parameter. If the parameter has no annotation, - this attribute is set to :attr:`Parameter.empty`. - - .. attribute:: Parameter.kind - - Describes how argument values are bound to the parameter. Possible values - (accessible via :class:`Parameter`, like ``Parameter.KEYWORD_ONLY``): - - +------------------------+----------------------------------------------+ - | Name | Meaning | - +========================+==============================================+ - | *POSITIONAL_ONLY* | Value must be supplied as a positional | - | | argument. | - | | | - | | Python has no explicit syntax for defining | - | | positional-only parameters, but many built-in| - | | and extension module functions (especially | - | | those that accept only one or two parameters)| - | | accept them. | - +------------------------+----------------------------------------------+ - | *POSITIONAL_OR_KEYWORD*| Value may be supplied as either a keyword or | - | | positional argument (this is the standard | - | | binding behaviour for functions implemented | - | | in Python.) | - +------------------------+----------------------------------------------+ - | *VAR_POSITIONAL* | A tuple of positional arguments that aren't | - | | bound to any other parameter. This | - | | corresponds to a ``*args`` parameter in a | - | | Python function definition. | - +------------------------+----------------------------------------------+ - | *KEYWORD_ONLY* | Value must be supplied as a keyword argument.| - | | Keyword only parameters are those which | - | | appear after a ``*`` or ``*args`` entry in a | - | | Python function definition. | - +------------------------+----------------------------------------------+ - | *VAR_KEYWORD* | A dict of keyword arguments that aren't bound| - | | to any other parameter. This corresponds to a| - | | ``**kwargs`` parameter in a Python function | - | | definition. | - +------------------------+----------------------------------------------+ - - Example: print all keyword-only arguments without default values:: - - >>> def foo(a, b, *, c, d=10): - ... pass - - >>> sig = signature(foo) - >>> for param in sig.parameters.values(): - ... if (param.kind == param.KEYWORD_ONLY and - ... param.default is param.empty): - ... print('Parameter:', param) - Parameter: c - - .. method:: Parameter.replace(*[, name][, kind][, default][, annotation]) - - Create a new Parameter instance based on the instance replaced was invoked - on. To override a :class:`Parameter` attribute, pass the corresponding - argument. To remove a default value or/and an annotation from a - Parameter, pass :attr:`Parameter.empty`. - - :: - - >>> from inspect import Parameter - >>> param = Parameter('foo', Parameter.KEYWORD_ONLY, default=42) - >>> str(param) - 'foo=42' - - >>> str(param.replace()) # Will create a shallow copy of 'param' - 'foo=42' - - >>> str(param.replace(default=Parameter.empty, annotation='spam')) - "foo:'spam'" - - -.. class:: BoundArguments - - Result of a :meth:`Signature.bind` or :meth:`Signature.bind_partial` call. - Holds the mapping of arguments to the function's parameters. - - .. attribute:: BoundArguments.arguments - - An ordered, mutable mapping (:class:`collections.OrderedDict`) of - parameters' names to arguments' values. Contains only explicitly bound - arguments. Changes in :attr:`arguments` will reflect in :attr:`args` and - :attr:`kwargs`. - - Should be used in conjunction with :attr:`Signature.parameters` for any - argument processing purposes. - - .. note:: - - Arguments for which :meth:`Signature.bind` or - :meth:`Signature.bind_partial` relied on a default value are skipped. - However, if needed, it is easy to include them. - - :: - - >>> def foo(a, b=10): - ... pass - - >>> sig = signature(foo) - >>> ba = sig.bind(5) - - >>> ba.args, ba.kwargs - ((5,), {}) - - >>> for param in sig.parameters.values(): - ... if param.name not in ba.arguments: - ... ba.arguments[param.name] = param.default - - >>> ba.args, ba.kwargs - ((5, 10), {}) - - - .. attribute:: BoundArguments.args - - A tuple of positional arguments values. Dynamically computed from the - :attr:`arguments` attribute. - - .. attribute:: BoundArguments.kwargs - - A dict of keyword arguments values. Dynamically computed from the - :attr:`arguments` attribute. - - The :attr:`args` and :attr:`kwargs` properties can be used to invoke - functions:: - - def test(a, *, b): - ... - - sig = signature(test) - ba = sig.bind(10, b=20) - test(*ba.args, **ba.kwargs) - - -.. seealso:: - - :pep:`362` - Function Signature Object. - The detailed specification, implementation details and examples. - -Copyright ---------- - -*funcsigs* is a derived work of CPython under the terms of the `PSF License -Agreement`_. The original CPython inspect module, its unit tests and -documentation are the copyright of the Python Software Foundation. The derived -work is distributed under the `Apache License Version 2.0`_. - -.. _PSF License Agreement: http://docs.python.org/3/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python -.. _Apache License Version 2.0: http://opensource.org/licenses/Apache-2.0 diff --git a/docs/index.rst b/docs/index.rst new file mode 120000 index 00000000000000..89a0106941ff39 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1 @@ +../README.rst \ No newline at end of file diff --git a/funcsigs/__init__.py b/funcsigs/__init__.py index fd2f47b1d4df6e..5f5378b42a6d27 100644 --- a/funcsigs/__init__.py +++ b/funcsigs/__init__.py @@ -2,7 +2,7 @@ """Function signature objects for callables Back port of Python 3.3's function signature tools from the inspect module, -modified to be compatible with Python 2.6, 2.7 and 3.2+. +modified to be compatible with Python 2.6, 2.7 and 3.3+. """ from __future__ import absolute_import, division, print_function import itertools @@ -13,7 +13,7 @@ try: from collections import OrderedDict except ImportError: - from funcsigs.odict import OrderedDict + from ordereddict import OrderedDict from funcsigs.version import __version__ @@ -61,18 +61,29 @@ def signature(obj): if isinstance(obj, types.MethodType): sig = signature(obj.__func__) if obj.__self__ is None: - # Unbound method: the first parameter becomes positional-only - if sig.parameters: - first = sig.parameters.values()[0].replace( - kind=_POSITIONAL_ONLY) - return sig.replace( - parameters=(first,) + tuple(sig.parameters.values())[1:]) - else: - return sig + # Unbound method - preserve as-is. + return sig else: - # In this case we skip the first parameter of the underlying - # function (usually `self` or `cls`). - return sig.replace(parameters=tuple(sig.parameters.values())[1:]) + # Bound method. Eat self - if we can. + params = tuple(sig.parameters.values()) + + if not params or params[0].kind in (_VAR_KEYWORD, _KEYWORD_ONLY): + raise ValueError('invalid method signature') + + kind = params[0].kind + if kind in (_POSITIONAL_OR_KEYWORD, _POSITIONAL_ONLY): + # Drop first parameter: + # '(p1, p2[, ...])' -> '(p2[, ...])' + params = params[1:] + else: + if kind is not _VAR_POSITIONAL: + # Unless we add a new parameter type we never + # get here + raise ValueError('invalid argument type') + # It's a var-positional parameter. + # Do nothing. '(*args[, ...])' -> '(*args[, ...])' + + return sig.replace(parameters=params) try: sig = obj.__signature__ @@ -769,16 +780,16 @@ def _bind(self, args, kwargs, partial=False): # Process our '**kwargs'-like parameter arguments[kwargs_param.name] = kwargs else: - raise TypeError('too many keyword arguments') + raise TypeError('too many keyword arguments %r' % kwargs) return self._bound_arguments_cls(self, arguments) - def bind(self, *args, **kwargs): + def bind(*args, **kwargs): '''Get a BoundArguments object, that maps the passed `args` and `kwargs` to the function's signature. Raises `TypeError` if the passed arguments can not be bound. ''' - return self._bind(args, kwargs) + return args[0]._bind(args[1:], kwargs) def bind_partial(self, *args, **kwargs): '''Get a BoundArguments object, that partially maps the diff --git a/funcsigs/odict.py b/funcsigs/odict.py deleted file mode 100644 index 6221e971b3794d..00000000000000 --- a/funcsigs/odict.py +++ /dev/null @@ -1,261 +0,0 @@ -# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy. -# Passes Python2.7's test suite and incorporates all the latest updates. -# Copyright 2009 Raymond Hettinger -# http://code.activestate.com/recipes/576693/ -"Ordered dictionary" - -try: - from thread import get_ident as _get_ident -except ImportError: - from dummy_thread import get_ident as _get_ident - -try: - from _abcoll import KeysView, ValuesView, ItemsView -except ImportError: - pass - - -class OrderedDict(dict): - 'Dictionary that remembers insertion order' - # An inherited dict maps keys to values. - # The inherited dict provides __getitem__, __len__, __contains__, and get. - # The remaining methods are order-aware. - # Big-O running times for all methods are the same as for regular dictionaries. - - # The internal self.__map dictionary maps keys to links in a doubly linked list. - # The circular doubly linked list starts and ends with a sentinel element. - # The sentinel element never gets deleted (this simplifies the algorithm). - # Each link is stored as a list of length three: [PREV, NEXT, KEY]. - - def __init__(self, *args, **kwds): - '''Initialize an ordered dictionary. Signature is the same as for - regular dictionaries, but keyword arguments are not recommended - because their insertion order is arbitrary. - - ''' - if len(args) > 1: - raise TypeError('expected at most 1 arguments, got %d' % len(args)) - try: - self.__root - except AttributeError: - self.__root = root = [] # sentinel node - root[:] = [root, root, None] - self.__map = {} - self.__update(*args, **kwds) - - def __setitem__(self, key, value, dict_setitem=dict.__setitem__): - 'od.__setitem__(i, y) <==> od[i]=y' - # Setting a new item creates a new link which goes at the end of the linked - # list, and the inherited dictionary is updated with the new key/value pair. - if key not in self: - root = self.__root - last = root[0] - last[1] = root[0] = self.__map[key] = [last, root, key] - dict_setitem(self, key, value) - - def __delitem__(self, key, dict_delitem=dict.__delitem__): - 'od.__delitem__(y) <==> del od[y]' - # Deleting an existing item uses self.__map to find the link which is - # then removed by updating the links in the predecessor and successor nodes. - dict_delitem(self, key) - link_prev, link_next, key = self.__map.pop(key) - link_prev[1] = link_next - link_next[0] = link_prev - - def __iter__(self): - 'od.__iter__() <==> iter(od)' - root = self.__root - curr = root[1] - while curr is not root: - yield curr[2] - curr = curr[1] - - def __reversed__(self): - 'od.__reversed__() <==> reversed(od)' - root = self.__root - curr = root[0] - while curr is not root: - yield curr[2] - curr = curr[0] - - def clear(self): - 'od.clear() -> None. Remove all items from od.' - try: - for node in self.__map.itervalues(): - del node[:] - root = self.__root - root[:] = [root, root, None] - self.__map.clear() - except AttributeError: - pass - dict.clear(self) - - def popitem(self, last=True): - '''od.popitem() -> (k, v), return and remove a (key, value) pair. - Pairs are returned in LIFO order if last is true or FIFO order if false. - - ''' - if not self: - raise KeyError('dictionary is empty') - root = self.__root - if last: - link = root[0] - link_prev = link[0] - link_prev[1] = root - root[0] = link_prev - else: - link = root[1] - link_next = link[1] - root[1] = link_next - link_next[0] = root - key = link[2] - del self.__map[key] - value = dict.pop(self, key) - return key, value - - # -- the following methods do not depend on the internal structure -- - - def keys(self): - 'od.keys() -> list of keys in od' - return list(self) - - def values(self): - 'od.values() -> list of values in od' - return [self[key] for key in self] - - def items(self): - 'od.items() -> list of (key, value) pairs in od' - return [(key, self[key]) for key in self] - - def iterkeys(self): - 'od.iterkeys() -> an iterator over the keys in od' - return iter(self) - - def itervalues(self): - 'od.itervalues -> an iterator over the values in od' - for k in self: - yield self[k] - - def iteritems(self): - 'od.iteritems -> an iterator over the (key, value) items in od' - for k in self: - yield (k, self[k]) - - def update(*args, **kwds): - '''od.update(E, **F) -> None. Update od from dict/iterable E and F. - - If E is a dict instance, does: for k in E: od[k] = E[k] - If E has a .keys() method, does: for k in E.keys(): od[k] = E[k] - Or if E is an iterable of items, does: for k, v in E: od[k] = v - In either case, this is followed by: for k, v in F.items(): od[k] = v - - ''' - if len(args) > 2: - raise TypeError('update() takes at most 2 positional ' - 'arguments (%d given)' % (len(args),)) - elif not args: - raise TypeError('update() takes at least 1 argument (0 given)') - self = args[0] - # Make progressively weaker assumptions about "other" - other = () - if len(args) == 2: - other = args[1] - if isinstance(other, dict): - for key in other: - self[key] = other[key] - elif hasattr(other, 'keys'): - for key in other.keys(): - self[key] = other[key] - else: - for key, value in other: - self[key] = value - for key, value in kwds.items(): - self[key] = value - - __update = update # let subclasses override update without breaking __init__ - - __marker = object() - - def pop(self, key, default=__marker): - '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value. - If key is not found, d is returned if given, otherwise KeyError is raised. - - ''' - if key in self: - result = self[key] - del self[key] - return result - if default is self.__marker: - raise KeyError(key) - return default - - def setdefault(self, key, default=None): - 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od' - if key in self: - return self[key] - self[key] = default - return default - - def __repr__(self, _repr_running={}): - 'od.__repr__() <==> repr(od)' - call_key = id(self), _get_ident() - if call_key in _repr_running: - return '...' - _repr_running[call_key] = 1 - try: - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, self.items()) - finally: - del _repr_running[call_key] - - def __reduce__(self): - 'Return state information for pickling' - items = [[k, self[k]] for k in self] - inst_dict = vars(self).copy() - for k in vars(OrderedDict()): - inst_dict.pop(k, None) - if inst_dict: - return (self.__class__, (items,), inst_dict) - return self.__class__, (items,) - - def copy(self): - 'od.copy() -> a shallow copy of od' - return self.__class__(self) - - @classmethod - def fromkeys(cls, iterable, value=None): - '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S - and values equal to v (which defaults to None). - - ''' - d = cls() - for key in iterable: - d[key] = value - return d - - def __eq__(self, other): - '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive - while comparison to a regular mapping is order-insensitive. - - ''' - if isinstance(other, OrderedDict): - return len(self)==len(other) and self.items() == other.items() - return dict.__eq__(self, other) - - def __ne__(self, other): - return not self == other - - # -- the following methods are only used in Python 2.7 -- - - def viewkeys(self): - "od.viewkeys() -> a set-like object providing a view on od's keys" - return KeysView(self) - - def viewvalues(self): - "od.viewvalues() -> an object providing a view on od's values" - return ValuesView(self) - - def viewitems(self): - "od.viewitems() -> a set-like object providing a view on od's items" - return ItemsView(self) diff --git a/funcsigs/version.py b/funcsigs/version.py index 896a370cad16cf..7863915fa5f8f0 100644 --- a/funcsigs/version.py +++ b/funcsigs/version.py @@ -1 +1 @@ -__version__ = "0.4" +__version__ = "1.0.2" diff --git a/requirements/development.txt b/requirements/development.txt index ecafb0a527d3e7..40dedd92bf06ac 100644 --- a/requirements/development.txt +++ b/requirements/development.txt @@ -1,6 +1,5 @@ coverage coveralls -pip flake8 sphinx -wheel +unittest2 diff --git a/requirements/production.txt b/requirements/production.txt deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/setup.py b/setup.py index 98b091260453c1..f3696888f9ebea 100644 --- a/setup.py +++ b/setup.py @@ -14,26 +14,22 @@ def load_version(filename='funcsigs/version.py'): version = match.group(1) return version -def load_rst(filename='docs/source/guide_content.rst'): - "Purge refs directives from restructured text" - with open(filename) as source: - text = source.read() - doc = re.sub(r':\w+:`~?([a-zA-Z._()]+)`', r'*\1*', text) - return doc setup( name="funcsigs", version=load_version(), packages=['funcsigs'], zip_safe=False, - author="Aaron Iles", - author_email="aaron.iles@gmail.com", + author="Testing Cabal", + author_email="testing-in-python@lists.idyll.org", url="http://funcsigs.readthedocs.org", description="Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+", long_description=open('README.rst').read(), - # long_description=load_rst(), license="ASL", - install_requires = [], + extras_require = { + ':python_version<"2.7"': ['ordereddict'], + }, + setup_requires = ["setuptools>=17.1"], classifiers = [ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', @@ -44,12 +40,13 @@ def load_rst(filename='docs/source/guide_content.rst'): 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Python Modules' ], - tests_require = [] if sys.version_info[0] > 2 else ['unittest2'], - test_suite = "tests" if sys.version_info[0] > 2 else 'unittest2.collector' + tests_require = ['unittest2'], + test_suite = 'unittest2.collector', ) diff --git a/tests/test_formatannotation.py b/tests/test_formatannotation.py index fd7a8873f855ca..4b98e6037d8302 100644 --- a/tests/test_formatannotation.py +++ b/tests/test_formatannotation.py @@ -1,12 +1,6 @@ -try: - # python 2.x - import unittest2 as unittest -except ImportError: - # python 3.x - import unittest - import funcsigs +import unittest2 as unittest class TestFormatAnnotation(unittest.TestCase): def test_string (self): @@ -21,7 +15,3 @@ def test_user_type (self): class dummy (object): pass self.assertEqual(funcsigs.formatannotation(dummy), "tests.test_formatannotation.dummy") - - -if __name__ == "__main__": - unittest.begin() diff --git a/tests/test_funcsigs.py b/tests/test_funcsigs.py index eecc0a8a709198..a7b9cca7679f4b 100644 --- a/tests/test_funcsigs.py +++ b/tests/test_funcsigs.py @@ -1,9 +1,4 @@ -try: - # python 2.x - import unittest2 as unittest -except ImportError: - # python 3.x - import unittest +import unittest2 as unittest import doctest import sys @@ -69,25 +64,28 @@ def test_has_version(self): self.assertTrue(inspect.__version__) def test_readme(self): + # XXX: This fails but doesn't fail the build. + # (and the syntax isn't valid on all pythons so that seems a little + # hard to get right. doctest.testfile('../README.rst') def test_unbound_method(self): - if sys.version_info < (3,): - self_kind = "positional_only" - else: - self_kind = "positional_or_keyword" + self_kind = "positional_or_keyword" class Test(object): def method(self): pass def method_with_args(self, a): pass - self.assertEqual(self.signature(Test.method), - (((('self', Ellipsis, Ellipsis, self_kind)),), Ellipsis)) - self.assertEqual(self.signature(Test.method_with_args), (( - ('self', Ellipsis, Ellipsis, self_kind), - ('a', Ellipsis, Ellipsis, "positional_or_keyword"), + def method_with_varargs(*args): + pass + self.assertEqual( + self.signature(Test.method), + (((('self', Ellipsis, Ellipsis, self_kind)),), Ellipsis)) + self.assertEqual( + self.signature(Test.method_with_args), + ((('self', Ellipsis, Ellipsis, self_kind), + ('a', Ellipsis, Ellipsis, "positional_or_keyword"), ), Ellipsis)) - - -if __name__ == "__main__": - unittest.begin() + self.assertEqual( + self.signature(Test.method_with_varargs), + ((('args', Ellipsis, Ellipsis, "var_positional"),), Ellipsis)) diff --git a/tests/test_inspect.py b/tests/test_inspect.py index 323c323eafd986..98d6592fcc7222 100644 --- a/tests/test_inspect.py +++ b/tests/test_inspect.py @@ -1,12 +1,10 @@ # Copyright 2001-2013 Python Software Foundation; All Rights Reserved from __future__ import absolute_import, division, print_function import collections +import functools import sys -try: - import unittest2 as unittest -except ImportError: - import unittest +import unittest2 as unittest import funcsigs as inspect @@ -24,11 +22,6 @@ def signature(func): (Ellipsis if sig.return_annotation is sig.empty else sig.return_annotation)) - def __init__(self, *args, **kwargs): - unittest.TestCase.__init__(self, *args, **kwargs) - if not hasattr(self, 'assertRaisesRegex'): - self.assertRaisesRegex = self.assertRaisesRegexp - if sys.version_info[0] > 2: exec(""" def test_signature_object(self): @@ -656,11 +649,6 @@ def test() -> 42: class TestParameterObject(unittest.TestCase): - def __init__(self, *args, **kwargs): - unittest.TestCase.__init__(self, *args, **kwargs) - if not hasattr(self, 'assertRaisesRegex'): - self.assertRaisesRegex = self.assertRaisesRegexp - def test_signature_parameter_kinds(self): P = inspect.Parameter self.assertTrue(P.POSITIONAL_ONLY < P.POSITIONAL_OR_KEYWORD < \ @@ -780,11 +768,6 @@ def call(func, *args, **kwargs): ba = sig.bind(*args, **kwargs) return func(*ba.args, **ba.kwargs) - def __init__(self, *args, **kwargs): - unittest.TestCase.__init__(self, *args, **kwargs) - if not hasattr(self, 'assertRaisesRegex'): - self.assertRaisesRegex = self.assertRaisesRegexp - def test_signature_bind_empty(self): def test(): return 42 @@ -982,13 +965,17 @@ def test(a_po, b_po, c_po=3, foo=42, *, bar=50, **kwargs): self.call(test, a_po=1, b_po=2) """) + def test_bind_self(self): + class F: + def f(a, self): + return a, self + an_f = F() + partial_f = functools.partial(F.f, an_f) + ba = inspect.signature(partial_f).bind(self=10) + self.assertEqual((an_f, 10), partial_f(*ba.args, **ba.kwargs)) -class TestBoundArguments(unittest.TestCase): - def __init__(self, *args, **kwargs): - unittest.TestCase.__init__(self, *args, **kwargs) - if not hasattr(self, 'assertRaisesRegex'): - self.assertRaisesRegex = self.assertRaisesRegexp +class TestBoundArguments(unittest.TestCase): def test_signature_bound_arguments_unhashable(self): def foo(a): pass @@ -1013,7 +1000,3 @@ def foo(a): pass def bar(b): pass ba4 = inspect.signature(bar).bind(1) self.assertNotEqual(ba, ba4) - - -if __name__ == "__main__": - unittest.begin() diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000000000..1873c744a005e8 --- /dev/null +++ b/tox.ini @@ -0,0 +1,8 @@ +[tox] +envlist = py26, py27, py33, py34, py35, py36, pypy, pypy3 + +[testenv] +deps = -rrequirements/development.txt +commands = + coverage run setup.py test + coverage report --show-missing