зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1472201 - Vendor pytest 3.6.2 and dependencies; r=ahal
MozReview-Commit-ID: 5qfK6OygVMH --HG-- rename : third_party/python/pytest/_pytest/vendored_packages/pluggy-0.4.0.dist-info/LICENSE.txt => third_party/python/pluggy/LICENSE rename : third_party/python/pytest/doc/en/example/costlysetup/sub1/__init__.py => third_party/python/pytest/doc/en/example/costlysetup/sub_a/__init__.py rename : third_party/python/pytest/doc/en/example/costlysetup/sub1/__init__.py => third_party/python/pytest/doc/en/example/costlysetup/sub_b/__init__.py rename : third_party/python/pytest/_pytest/_code/__init__.py => third_party/python/pytest/src/_pytest/_code/__init__.py extra : rebase_source : d80873f2b1899decefbddddfc2f69ae045925b81
This commit is contained in:
Родитель
a4cd34df05
Коммит
a631fc714d
2
Pipfile
2
Pipfile
|
@ -11,7 +11,7 @@ blessings = "==1.7"
|
|||
jsmin = "==2.1.0"
|
||||
json-e = "==2.5.0"
|
||||
pipenv = "==2018.5.18"
|
||||
pytest = "==3.2.5"
|
||||
pytest = "==3.6.2"
|
||||
python-hglib = "==2.4"
|
||||
requests = "==2.9.1"
|
||||
six = "==1.10.0"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "7e168601e5f93e71900ebc68d8c18ff17edb6d5e224bcc83286b9bafaac41fe8"
|
||||
"sha256": "609a35f65e9a4c07e0e1473ec982c6b5028622e9a795b6cfb8555ad8574804f3"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
|
@ -14,6 +14,13 @@
|
|||
]
|
||||
},
|
||||
"default": {
|
||||
"atomicwrites": {
|
||||
"hashes": [
|
||||
"sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585",
|
||||
"sha256:a24da68318b08ac9c9c45029f4a10371ab5b20e4226738e150e6e7c571630ae6"
|
||||
],
|
||||
"version": "==1.1.5"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265",
|
||||
|
@ -38,6 +45,14 @@
|
|||
],
|
||||
"version": "==2018.4.16"
|
||||
},
|
||||
"funcsigs": {
|
||||
"hashes": [
|
||||
"sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca",
|
||||
"sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"
|
||||
],
|
||||
"markers": "python_version < '3.0'",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"jsmin": {
|
||||
"hashes": [
|
||||
"sha256:5d07bf0251a4128e5e8e8eef603849b6b5741c337bff087731a248f9cc774f56"
|
||||
|
@ -52,6 +67,14 @@
|
|||
"index": "pypi",
|
||||
"version": "==2.5.0"
|
||||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
"sha256:2b6b9893337bfd9166bee6a62c2b0c9fe7735dcf85948b387ec8cba30e85d8e8",
|
||||
"sha256:6703844a52d3588f951883005efcf555e49566a48afd4db4e965d69b883980d3",
|
||||
"sha256:a18d870ef2ffca2b8463c0070ad17b5978056f403fb64e3f15fe62a52db21cc0"
|
||||
],
|
||||
"version": "==4.2.0"
|
||||
},
|
||||
"pipenv": {
|
||||
"hashes": [
|
||||
"sha256:04b9a8b02a3ff12a5502b335850cfdb192adcfd1d6bbdb7a7c47cae9ab9ddece",
|
||||
|
@ -60,20 +83,28 @@
|
|||
"index": "pypi",
|
||||
"version": "==2018.5.18"
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
"sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff",
|
||||
"sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c",
|
||||
"sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5"
|
||||
],
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"py": {
|
||||
"hashes": [
|
||||
"sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881",
|
||||
"sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a"
|
||||
"sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7",
|
||||
"sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e"
|
||||
],
|
||||
"version": "==1.5.3"
|
||||
"version": "==1.5.4"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:241d7e7798d79192a123ceaf64c602b4d233eacf6d6e42ae27caa97f498b7dc6",
|
||||
"sha256:6d5bd4f7113b444c55a3bbb5c738a3dd80d43563d063fc42dcb0aaefbdd78b81"
|
||||
"sha256:8ea01fc4fcc8e1b1e305252b4bc80a1528019ab99fd3b88666c9dc38d754406c",
|
||||
"sha256:90898786b3d0b880b47645bae7b51aa9bbf1e9d1e4510c2cfd15dd65c70ea0cd"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.2.5"
|
||||
"version": "==3.6.2"
|
||||
},
|
||||
"python-hglib": {
|
||||
"hashes": [
|
||||
|
|
|
@ -6,6 +6,7 @@ mozilla.pth:python/mozrelease
|
|||
mozilla.pth:python/mozterm
|
||||
mozilla.pth:python/mozversioncontrol
|
||||
mozilla.pth:python/l10n
|
||||
mozilla.pth:third_party/python/atomicwrites
|
||||
mozilla.pth:third_party/python/attrs/src
|
||||
mozilla.pth:third_party/python/blessings
|
||||
mozilla.pth:third_party/python/compare-locales
|
||||
|
@ -13,8 +14,11 @@ mozilla.pth:third_party/python/configobj
|
|||
mozilla.pth:third_party/python/cram
|
||||
mozilla.pth:third_party/python/dlmanager
|
||||
mozilla.pth:third_party/python/fluent
|
||||
mozilla.pth:third_party/python/funcsigs
|
||||
mozilla.pth:third_party/python/futures
|
||||
mozilla.pth:third_party/python/more-itertools
|
||||
mozilla.pth:third_party/python/python-hglib
|
||||
mozilla.pth:third_party/python/pluggy
|
||||
mozilla.pth:third_party/python/jsmin
|
||||
optional:setup.py:third_party/python/psutil:build_ext:--inplace
|
||||
mozilla.pth:third_party/python/psutil
|
||||
|
@ -26,7 +30,7 @@ mozilla.pth:third_party/python/requests
|
|||
mozilla.pth:third_party/python/requests-unixsocket
|
||||
mozilla.pth:third_party/python/slugid
|
||||
mozilla.pth:third_party/python/py
|
||||
mozilla.pth:third_party/python/pytest
|
||||
mozilla.pth:third_party/python/pytest/src
|
||||
mozilla.pth:third_party/python/pytoml
|
||||
mozilla.pth:third_party/python/redo
|
||||
mozilla.pth:third_party/python/six
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2015-2016 Markus Unterwaditzer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,6 @@
|
|||
include LICENSE
|
||||
include README.rst
|
||||
|
||||
recursive-include docs *
|
||||
recursive-include tests *
|
||||
prune docs/_build
|
|
@ -0,0 +1,112 @@
|
|||
Metadata-Version: 1.0
|
||||
Name: atomicwrites
|
||||
Version: 1.1.5
|
||||
Summary: Atomic file writes.
|
||||
Home-page: https://github.com/untitaker/python-atomicwrites
|
||||
Author: Markus Unterwaditzer
|
||||
Author-email: markus@unterwaditzer.net
|
||||
License: MIT
|
||||
Description: ===================
|
||||
python-atomicwrites
|
||||
===================
|
||||
|
||||
.. image:: https://travis-ci.org/untitaker/python-atomicwrites.svg?branch=master
|
||||
:target: https://travis-ci.org/untitaker/python-atomicwrites
|
||||
|
||||
.. image:: https://ci.appveyor.com/api/projects/status/vadc4le3c27to59x/branch/master?svg=true
|
||||
:target: https://ci.appveyor.com/project/untitaker/python-atomicwrites/branch/master
|
||||
|
||||
Atomic file writes.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from atomicwrites import atomic_write
|
||||
|
||||
with atomic_write('foo.txt', overwrite=True) as f:
|
||||
f.write('Hello world.')
|
||||
# "foo.txt" doesn't exist yet.
|
||||
|
||||
# Now it does.
|
||||
|
||||
|
||||
Features that distinguish it from other similar libraries (see `Alternatives and Credit`_):
|
||||
|
||||
- Race-free assertion that the target file doesn't yet exist. This can be
|
||||
controlled with the ``overwrite`` parameter.
|
||||
|
||||
- Windows support, although not well-tested. The MSDN resources are not very
|
||||
explicit about which operations are atomic.
|
||||
|
||||
- Simple high-level API that wraps a very flexible class-based API.
|
||||
|
||||
- Consistent error handling across platforms.
|
||||
|
||||
|
||||
How it works
|
||||
============
|
||||
|
||||
It uses a temporary file in the same directory as the given path. This ensures
|
||||
that the temporary file resides on the same filesystem.
|
||||
|
||||
The temporary file will then be atomically moved to the target location: On
|
||||
POSIX, it will use ``rename`` if files should be overwritten, otherwise a
|
||||
combination of ``link`` and ``unlink``. On Windows, it uses MoveFileEx_ through
|
||||
stdlib's ``ctypes`` with the appropriate flags.
|
||||
|
||||
Note that with ``link`` and ``unlink``, there's a timewindow where the file
|
||||
might be available under two entries in the filesystem: The name of the
|
||||
temporary file, and the name of the target file.
|
||||
|
||||
Also note that the permissions of the target file may change this way. In some
|
||||
situations a ``chmod`` can be issued without any concurrency problems, but
|
||||
since that is not always the case, this library doesn't do it by itself.
|
||||
|
||||
.. _MoveFileEx: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365240%28v=vs.85%29.aspx
|
||||
|
||||
fsync
|
||||
-----
|
||||
|
||||
On POSIX, ``fsync`` is invoked on the temporary file after it is written (to
|
||||
flush file content and metadata), and on the parent directory after the file is
|
||||
moved (to flush filename).
|
||||
|
||||
``fsync`` does not take care of disks' internal buffers, but there don't seem
|
||||
to be any standard POSIX APIs for that. On OS X, ``fcntl`` is used with
|
||||
``F_FULLFSYNC`` instead of ``fsync`` for that reason.
|
||||
|
||||
On Windows, `_commit <https://msdn.microsoft.com/en-us/library/17618685.aspx>`_
|
||||
is used, but there are no guarantees about disk internal buffers.
|
||||
|
||||
Alternatives and Credit
|
||||
=======================
|
||||
|
||||
Atomicwrites is directly inspired by the following libraries (and shares a
|
||||
minimal amount of code):
|
||||
|
||||
- The Trac project's `utility functions
|
||||
<http://www.edgewall.org/docs/tags-trac-0.11.7/epydoc/trac.util-pysrc.html>`_,
|
||||
also used in `Werkzeug <http://werkzeug.pocoo.org/>`_ and
|
||||
`mitsuhiko/python-atomicfile
|
||||
<https://github.com/mitsuhiko/python-atomicfile>`_. The idea to use
|
||||
``ctypes`` instead of ``PyWin32`` originated there.
|
||||
|
||||
- `abarnert/fatomic <https://github.com/abarnert/fatomic>`_. Windows support
|
||||
(based on ``PyWin32``) was originally taken from there.
|
||||
|
||||
Other alternatives to atomicwrites include:
|
||||
|
||||
- `sashka/atomicfile <https://github.com/sashka/atomicfile>`_. Originally I
|
||||
considered using that, but at the time it was lacking a lot of features I
|
||||
needed (Windows support, overwrite-parameter, overriding behavior through
|
||||
subclassing).
|
||||
|
||||
- The `Boltons library collection <https://github.com/mahmoud/boltons>`_
|
||||
features a class for atomic file writes, which seems to have a very similar
|
||||
``overwrite`` parameter. It is lacking Windows support though.
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
Licensed under the MIT, see ``LICENSE``.
|
||||
|
||||
Platform: UNKNOWN
|
|
@ -0,0 +1,102 @@
|
|||
===================
|
||||
python-atomicwrites
|
||||
===================
|
||||
|
||||
.. image:: https://travis-ci.org/untitaker/python-atomicwrites.svg?branch=master
|
||||
:target: https://travis-ci.org/untitaker/python-atomicwrites
|
||||
|
||||
.. image:: https://ci.appveyor.com/api/projects/status/vadc4le3c27to59x/branch/master?svg=true
|
||||
:target: https://ci.appveyor.com/project/untitaker/python-atomicwrites/branch/master
|
||||
|
||||
Atomic file writes.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from atomicwrites import atomic_write
|
||||
|
||||
with atomic_write('foo.txt', overwrite=True) as f:
|
||||
f.write('Hello world.')
|
||||
# "foo.txt" doesn't exist yet.
|
||||
|
||||
# Now it does.
|
||||
|
||||
|
||||
Features that distinguish it from other similar libraries (see `Alternatives and Credit`_):
|
||||
|
||||
- Race-free assertion that the target file doesn't yet exist. This can be
|
||||
controlled with the ``overwrite`` parameter.
|
||||
|
||||
- Windows support, although not well-tested. The MSDN resources are not very
|
||||
explicit about which operations are atomic.
|
||||
|
||||
- Simple high-level API that wraps a very flexible class-based API.
|
||||
|
||||
- Consistent error handling across platforms.
|
||||
|
||||
|
||||
How it works
|
||||
============
|
||||
|
||||
It uses a temporary file in the same directory as the given path. This ensures
|
||||
that the temporary file resides on the same filesystem.
|
||||
|
||||
The temporary file will then be atomically moved to the target location: On
|
||||
POSIX, it will use ``rename`` if files should be overwritten, otherwise a
|
||||
combination of ``link`` and ``unlink``. On Windows, it uses MoveFileEx_ through
|
||||
stdlib's ``ctypes`` with the appropriate flags.
|
||||
|
||||
Note that with ``link`` and ``unlink``, there's a timewindow where the file
|
||||
might be available under two entries in the filesystem: The name of the
|
||||
temporary file, and the name of the target file.
|
||||
|
||||
Also note that the permissions of the target file may change this way. In some
|
||||
situations a ``chmod`` can be issued without any concurrency problems, but
|
||||
since that is not always the case, this library doesn't do it by itself.
|
||||
|
||||
.. _MoveFileEx: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365240%28v=vs.85%29.aspx
|
||||
|
||||
fsync
|
||||
-----
|
||||
|
||||
On POSIX, ``fsync`` is invoked on the temporary file after it is written (to
|
||||
flush file content and metadata), and on the parent directory after the file is
|
||||
moved (to flush filename).
|
||||
|
||||
``fsync`` does not take care of disks' internal buffers, but there don't seem
|
||||
to be any standard POSIX APIs for that. On OS X, ``fcntl`` is used with
|
||||
``F_FULLFSYNC`` instead of ``fsync`` for that reason.
|
||||
|
||||
On Windows, `_commit <https://msdn.microsoft.com/en-us/library/17618685.aspx>`_
|
||||
is used, but there are no guarantees about disk internal buffers.
|
||||
|
||||
Alternatives and Credit
|
||||
=======================
|
||||
|
||||
Atomicwrites is directly inspired by the following libraries (and shares a
|
||||
minimal amount of code):
|
||||
|
||||
- The Trac project's `utility functions
|
||||
<http://www.edgewall.org/docs/tags-trac-0.11.7/epydoc/trac.util-pysrc.html>`_,
|
||||
also used in `Werkzeug <http://werkzeug.pocoo.org/>`_ and
|
||||
`mitsuhiko/python-atomicfile
|
||||
<https://github.com/mitsuhiko/python-atomicfile>`_. The idea to use
|
||||
``ctypes`` instead of ``PyWin32`` originated there.
|
||||
|
||||
- `abarnert/fatomic <https://github.com/abarnert/fatomic>`_. Windows support
|
||||
(based on ``PyWin32``) was originally taken from there.
|
||||
|
||||
Other alternatives to atomicwrites include:
|
||||
|
||||
- `sashka/atomicfile <https://github.com/sashka/atomicfile>`_. Originally I
|
||||
considered using that, but at the time it was lacking a lot of features I
|
||||
needed (Windows support, overwrite-parameter, overriding behavior through
|
||||
subclassing).
|
||||
|
||||
- The `Boltons library collection <https://github.com/mahmoud/boltons>`_
|
||||
features a class for atomic file writes, which seems to have a very similar
|
||||
``overwrite`` parameter. It is lacking Windows support though.
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
Licensed under the MIT, see ``LICENSE``.
|
|
@ -0,0 +1,201 @@
|
|||
import contextlib
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
try:
|
||||
import fcntl
|
||||
except ImportError:
|
||||
fcntl = None
|
||||
|
||||
__version__ = '1.1.5'
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
|
||||
text_type = unicode if PY2 else str # noqa
|
||||
|
||||
|
||||
def _path_to_unicode(x):
|
||||
if not isinstance(x, text_type):
|
||||
return x.decode(sys.getfilesystemencoding())
|
||||
return x
|
||||
|
||||
|
||||
_proper_fsync = os.fsync
|
||||
|
||||
|
||||
if sys.platform != 'win32':
|
||||
if hasattr(fcntl, 'F_FULLFSYNC'):
|
||||
def _proper_fsync(fd):
|
||||
# https://lists.apple.com/archives/darwin-dev/2005/Feb/msg00072.html
|
||||
# https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/fsync.2.html
|
||||
# https://github.com/untitaker/python-atomicwrites/issues/6
|
||||
fcntl.fcntl(fd, fcntl.F_FULLFSYNC)
|
||||
|
||||
def _sync_directory(directory):
|
||||
# Ensure that filenames are written to disk
|
||||
fd = os.open(directory, 0)
|
||||
try:
|
||||
_proper_fsync(fd)
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
def _replace_atomic(src, dst):
|
||||
os.rename(src, dst)
|
||||
_sync_directory(os.path.normpath(os.path.dirname(dst)))
|
||||
|
||||
def _move_atomic(src, dst):
|
||||
os.link(src, dst)
|
||||
os.unlink(src)
|
||||
|
||||
src_dir = os.path.normpath(os.path.dirname(src))
|
||||
dst_dir = os.path.normpath(os.path.dirname(dst))
|
||||
_sync_directory(dst_dir)
|
||||
if src_dir != dst_dir:
|
||||
_sync_directory(src_dir)
|
||||
else:
|
||||
from ctypes import windll, WinError
|
||||
|
||||
_MOVEFILE_REPLACE_EXISTING = 0x1
|
||||
_MOVEFILE_WRITE_THROUGH = 0x8
|
||||
_windows_default_flags = _MOVEFILE_WRITE_THROUGH
|
||||
|
||||
def _handle_errors(rv):
|
||||
if not rv:
|
||||
raise WinError()
|
||||
|
||||
def _replace_atomic(src, dst):
|
||||
_handle_errors(windll.kernel32.MoveFileExW(
|
||||
_path_to_unicode(src), _path_to_unicode(dst),
|
||||
_windows_default_flags | _MOVEFILE_REPLACE_EXISTING
|
||||
))
|
||||
|
||||
def _move_atomic(src, dst):
|
||||
_handle_errors(windll.kernel32.MoveFileExW(
|
||||
_path_to_unicode(src), _path_to_unicode(dst),
|
||||
_windows_default_flags
|
||||
))
|
||||
|
||||
|
||||
def replace_atomic(src, dst):
|
||||
'''
|
||||
Move ``src`` to ``dst``. If ``dst`` exists, it will be silently
|
||||
overwritten.
|
||||
|
||||
Both paths must reside on the same filesystem for the operation to be
|
||||
atomic.
|
||||
'''
|
||||
return _replace_atomic(src, dst)
|
||||
|
||||
|
||||
def move_atomic(src, dst):
|
||||
'''
|
||||
Move ``src`` to ``dst``. There might a timewindow where both filesystem
|
||||
entries exist. If ``dst`` already exists, :py:exc:`FileExistsError` will be
|
||||
raised.
|
||||
|
||||
Both paths must reside on the same filesystem for the operation to be
|
||||
atomic.
|
||||
'''
|
||||
return _move_atomic(src, dst)
|
||||
|
||||
|
||||
class AtomicWriter(object):
|
||||
'''
|
||||
A helper class for performing atomic writes. Usage::
|
||||
|
||||
with AtomicWriter(path).open() as f:
|
||||
f.write(...)
|
||||
|
||||
:param path: The destination filepath. May or may not exist.
|
||||
:param mode: The filemode for the temporary file.
|
||||
:param overwrite: If set to false, an error is raised if ``path`` exists.
|
||||
Errors are only raised after the file has been written to. Either way,
|
||||
the operation is atomic.
|
||||
|
||||
If you need further control over the exact behavior, you are encouraged to
|
||||
subclass.
|
||||
'''
|
||||
|
||||
def __init__(self, path, mode='w', overwrite=False):
|
||||
if 'a' in mode:
|
||||
raise ValueError(
|
||||
'Appending to an existing file is not supported, because that '
|
||||
'would involve an expensive `copy`-operation to a temporary '
|
||||
'file. Open the file in normal `w`-mode and copy explicitly '
|
||||
'if that\'s what you\'re after.'
|
||||
)
|
||||
if 'x' in mode:
|
||||
raise ValueError('Use the `overwrite`-parameter instead.')
|
||||
if 'w' not in mode:
|
||||
raise ValueError('AtomicWriters can only be written to.')
|
||||
|
||||
self._path = path
|
||||
self._mode = mode
|
||||
self._overwrite = overwrite
|
||||
|
||||
def open(self):
|
||||
'''
|
||||
Open the temporary file.
|
||||
'''
|
||||
return self._open(self.get_fileobject)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _open(self, get_fileobject):
|
||||
f = None # make sure f exists even if get_fileobject() fails
|
||||
try:
|
||||
success = False
|
||||
with get_fileobject() as f:
|
||||
yield f
|
||||
self.sync(f)
|
||||
self.commit(f)
|
||||
success = True
|
||||
finally:
|
||||
if not success:
|
||||
try:
|
||||
self.rollback(f)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def get_fileobject(self, dir=None, **kwargs):
|
||||
'''Return the temporary file to use.'''
|
||||
if dir is None:
|
||||
dir = os.path.normpath(os.path.dirname(self._path))
|
||||
return tempfile.NamedTemporaryFile(mode=self._mode, dir=dir,
|
||||
delete=False, **kwargs)
|
||||
|
||||
def sync(self, f):
|
||||
'''responsible for clearing as many file caches as possible before
|
||||
commit'''
|
||||
f.flush()
|
||||
_proper_fsync(f.fileno())
|
||||
|
||||
def commit(self, f):
|
||||
'''Move the temporary file to the target location.'''
|
||||
if self._overwrite:
|
||||
replace_atomic(f.name, self._path)
|
||||
else:
|
||||
move_atomic(f.name, self._path)
|
||||
|
||||
def rollback(self, f):
|
||||
'''Clean up all temporary resources.'''
|
||||
os.unlink(f.name)
|
||||
|
||||
|
||||
def atomic_write(path, writer_cls=AtomicWriter, **cls_kwargs):
|
||||
'''
|
||||
Simple atomic writes. This wraps :py:class:`AtomicWriter`::
|
||||
|
||||
with atomic_write(path) as f:
|
||||
f.write(...)
|
||||
|
||||
:param path: The target path to write to.
|
||||
:param writer_cls: The writer class to use. This parameter is useful if you
|
||||
subclassed :py:class:`AtomicWriter` to change some behavior and want to
|
||||
use that new subclass.
|
||||
|
||||
Additional keyword arguments are passed to the writer class. See
|
||||
:py:class:`AtomicWriter`.
|
||||
'''
|
||||
return writer_cls(path, **cls_kwargs).open()
|
|
@ -0,0 +1,177 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# User-friendly check for sphinx-build
|
||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
||||
endif
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " xml to make Docutils-native XML files"
|
||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/atomicwrites.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/atomicwrites.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/atomicwrites"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/atomicwrites"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
latexpdfja:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
xml:
|
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||
@echo
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
|
@ -0,0 +1,107 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import sys
|
||||
import pkg_resources
|
||||
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.viewcode',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'atomicwrites'
|
||||
copyright = '2015, Markus Unterwaditzer'
|
||||
|
||||
try:
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = pkg_resources.require('atomicwrites')[0].version
|
||||
except pkg_resources.DistributionNotFound:
|
||||
print('To build the documentation, the distribution information of '
|
||||
'atomicwrites has to be available. Run "setup.py develop" to do '
|
||||
'this.')
|
||||
sys.exit(1)
|
||||
|
||||
version = '.'.join(release.split('.')[:2]) # The short X.Y version.
|
||||
|
||||
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
|
||||
|
||||
try:
|
||||
import sphinx_rtd_theme
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
except ImportError:
|
||||
html_theme = 'default'
|
||||
if not on_rtd:
|
||||
print('-' * 74)
|
||||
print('Warning: sphinx-rtd-theme not installed, building with default '
|
||||
'theme.')
|
||||
print('-' * 74)
|
||||
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'atomicwritesdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'atomicwrites.tex', 'atomicwrites Documentation',
|
||||
'Markus Unterwaditzer', 'manual'),
|
||||
]
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'atomicwrites', 'atomicwrites Documentation',
|
||||
['Markus Unterwaditzer'], 1)
|
||||
]
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'atomicwrites', 'atomicwrites Documentation',
|
||||
'Markus Unterwaditzer', 'atomicwrites', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Bibliographic Dublin Core info.
|
||||
epub_title = 'atomicwrites'
|
||||
epub_author = 'Markus Unterwaditzer'
|
||||
epub_publisher = 'Markus Unterwaditzer'
|
||||
epub_copyright = '2015, Markus Unterwaditzer'
|
||||
|
||||
# A list of files that should not be packed into the epub file.
|
||||
epub_exclude_files = ['search.html']
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {'http://docs.python.org/': None}
|
|
@ -0,0 +1,35 @@
|
|||
.. include:: ../README.rst
|
||||
|
||||
.. module:: atomicwrites
|
||||
|
||||
API
|
||||
===
|
||||
|
||||
.. autofunction:: atomic_write
|
||||
|
||||
|
||||
Errorhandling
|
||||
-------------
|
||||
|
||||
All filesystem errors are subclasses of :py:exc:`OSError`.
|
||||
|
||||
- On UNIX systems, errors from the Python stdlib calls are thrown.
|
||||
- On Windows systems, errors from Python's ``ctypes`` are thrown.
|
||||
|
||||
In either case, the ``errno`` attribute on the thrown exception maps to an
|
||||
errorcode in the ``errno`` module.
|
||||
|
||||
Low-level API
|
||||
-------------
|
||||
|
||||
.. autofunction:: replace_atomic
|
||||
|
||||
.. autofunction:: move_atomic
|
||||
|
||||
.. autoclass:: AtomicWriter
|
||||
:members:
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
.. include:: ../LICENSE
|
|
@ -0,0 +1,242 @@
|
|||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. xml to make Docutils-native XML files
|
||||
echo. pseudoxml to make pseudoxml-XML files for display purposes
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
|
||||
%SPHINXBUILD% 2> nul
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\atomicwrites.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\atomicwrites.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdf" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf
|
||||
cd %BUILDDIR%/..
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdfja" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf-ja
|
||||
cd %BUILDDIR%/..
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "xml" (
|
||||
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The XML files are in %BUILDDIR%/xml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pseudoxml" (
|
||||
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
|
@ -0,0 +1,8 @@
|
|||
[wheel]
|
||||
universal = 1
|
||||
|
||||
[egg_info]
|
||||
tag_date = 0
|
||||
tag_svn_revision = 0
|
||||
tag_build =
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import ast
|
||||
import re
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
|
||||
_version_re = re.compile(r'__version__\s+=\s+(.*)')
|
||||
|
||||
|
||||
with open('atomicwrites/__init__.py', 'rb') as f:
|
||||
version = str(ast.literal_eval(_version_re.search(
|
||||
f.read().decode('utf-8')).group(1)))
|
||||
|
||||
setup(
|
||||
name='atomicwrites',
|
||||
version=version,
|
||||
author='Markus Unterwaditzer',
|
||||
author_email='markus@unterwaditzer.net',
|
||||
url='https://github.com/untitaker/python-atomicwrites',
|
||||
description='Atomic file writes.',
|
||||
license='MIT',
|
||||
long_description=open('README.rst').read(),
|
||||
packages=find_packages(exclude=['tests.*', 'tests']),
|
||||
include_package_data=True,
|
||||
)
|
|
@ -0,0 +1,89 @@
|
|||
import errno
|
||||
import os
|
||||
|
||||
from atomicwrites import atomic_write
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_atomic_write(tmpdir):
|
||||
fname = tmpdir.join('ha')
|
||||
for i in range(2):
|
||||
with atomic_write(str(fname), overwrite=True) as f:
|
||||
f.write('hoho')
|
||||
|
||||
with pytest.raises(OSError) as excinfo:
|
||||
with atomic_write(str(fname), overwrite=False) as f:
|
||||
f.write('haha')
|
||||
|
||||
assert excinfo.value.errno == errno.EEXIST
|
||||
|
||||
assert fname.read() == 'hoho'
|
||||
assert len(tmpdir.listdir()) == 1
|
||||
|
||||
|
||||
def test_teardown(tmpdir):
|
||||
fname = tmpdir.join('ha')
|
||||
with pytest.raises(AssertionError):
|
||||
with atomic_write(str(fname), overwrite=True):
|
||||
assert False
|
||||
|
||||
assert not tmpdir.listdir()
|
||||
|
||||
|
||||
def test_replace_simultaneously_created_file(tmpdir):
|
||||
fname = tmpdir.join('ha')
|
||||
with atomic_write(str(fname), overwrite=True) as f:
|
||||
f.write('hoho')
|
||||
fname.write('harhar')
|
||||
assert fname.read() == 'harhar'
|
||||
assert fname.read() == 'hoho'
|
||||
assert len(tmpdir.listdir()) == 1
|
||||
|
||||
|
||||
def test_dont_remove_simultaneously_created_file(tmpdir):
|
||||
fname = tmpdir.join('ha')
|
||||
with pytest.raises(OSError) as excinfo:
|
||||
with atomic_write(str(fname), overwrite=False) as f:
|
||||
f.write('hoho')
|
||||
fname.write('harhar')
|
||||
assert fname.read() == 'harhar'
|
||||
|
||||
assert excinfo.value.errno == errno.EEXIST
|
||||
assert fname.read() == 'harhar'
|
||||
assert len(tmpdir.listdir()) == 1
|
||||
|
||||
|
||||
# Verify that nested exceptions during rollback do not overwrite the initial
|
||||
# exception that triggered a rollback.
|
||||
def test_open_reraise(tmpdir):
|
||||
fname = tmpdir.join('ha')
|
||||
with pytest.raises(AssertionError):
|
||||
with atomic_write(str(fname), overwrite=False) as f:
|
||||
# Mess with f, so rollback will trigger an OSError. We're testing
|
||||
# that the initial AssertionError triggered below is propagated up
|
||||
# the stack, not the second exception triggered during rollback.
|
||||
f.name = "asdf"
|
||||
# Now trigger our own exception.
|
||||
assert False, "Intentional failure for testing purposes"
|
||||
|
||||
|
||||
def test_atomic_write_in_pwd(tmpdir):
|
||||
orig_curdir = os.getcwd()
|
||||
try:
|
||||
os.chdir(str(tmpdir))
|
||||
fname = 'ha'
|
||||
for i in range(2):
|
||||
with atomic_write(str(fname), overwrite=True) as f:
|
||||
f.write('hoho')
|
||||
|
||||
with pytest.raises(OSError) as excinfo:
|
||||
with atomic_write(str(fname), overwrite=False) as f:
|
||||
f.write('haha')
|
||||
|
||||
assert excinfo.value.errno == errno.EEXIST
|
||||
|
||||
assert open(fname).read() == 'hoho'
|
||||
assert len(tmpdir.listdir()) == 1
|
||||
finally:
|
||||
os.chdir(orig_curdir)
|
|
@ -0,0 +1,24 @@
|
|||
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
|
||||
* Publish Python wheel packages
|
||||
|
||||
0.3 (2013-05-29)
|
||||
````````````````
|
||||
* Fix annotation formatting of builtin types on Python 2.x
|
||||
|
||||
0.2 (2012-01-07)
|
||||
````````````````
|
||||
* PyPy compatability
|
||||
|
||||
0.1 (2012-01-06)
|
||||
````````````````
|
||||
* Initial release
|
|
@ -0,0 +1,13 @@
|
|||
Copyright 2013 Aaron Iles
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,7 @@
|
|||
recursive-include docs *
|
||||
recursive-include tests *.py
|
||||
include *.py
|
||||
include CHANGELOG
|
||||
include LICENSE
|
||||
include MANIFEST.in
|
||||
include README.rst
|
|
@ -0,0 +1,378 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: funcsigs
|
||||
Version: 1.0.2
|
||||
Summary: Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+
|
||||
Home-page: http://funcsigs.readthedocs.org
|
||||
Author: Testing Cabal
|
||||
Author-email: testing-in-python@lists.idyll.org
|
||||
License: ASL
|
||||
Description: .. 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.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.
|
||||
|
||||
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.
|
||||
|
||||
Issues
|
||||
``````
|
||||
|
||||
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.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from funcsigs import signature
|
||||
>>> def foo(a, b=None, *args, **kwargs):
|
||||
... pass
|
||||
...
|
||||
>>> sig = signature(foo)
|
||||
>>> sig
|
||||
<funcsigs.Signature object at 0x...>
|
||||
>>> sig.parameters
|
||||
OrderedDict([('a', <Parameter at 0x... 'a'>), ('b', <Parameter at 0x... 'b'>), ('args', <Parameter at 0x... 'args'>), ('kwargs', <Parameter at 0x... 'kwargs'>)])
|
||||
>>> sig.return_annotation
|
||||
<class 'funcsigs._empty'>
|
||||
|
||||
Introspecting callables with the Signature object
|
||||
-------------------------------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
This section of documentation is a direct reproduction 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 funcsigs 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
|
||||
<class 'int'>
|
||||
|
||||
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
|
||||
---------
|
||||
|
||||
*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/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/testing-cabal/funcsigs/issues
|
||||
|
||||
.. |build_status| image:: https://secure.travis-ci.org/aliles/funcsigs.png?branch=master
|
||||
:target: http://travis-ci.org/#!/aliles/funcsigs
|
||||
:alt: Current build status
|
||||
|
||||
.. |coverage| image:: https://coveralls.io/repos/aliles/funcsigs/badge.png?branch=master
|
||||
:target: https://coveralls.io/r/aliles/funcsigs?branch=master
|
||||
:alt: Coverage status
|
||||
|
||||
.. |pypi_version| image:: https://pypip.in/v/funcsigs/badge.png
|
||||
:target: https://crate.io/packages/funcsigs/
|
||||
:alt: Latest PyPI version
|
||||
|
||||
|
||||
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
@ -0,0 +1,353 @@
|
|||
.. 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.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.
|
||||
|
||||
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.
|
||||
|
||||
Issues
|
||||
``````
|
||||
|
||||
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.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from funcsigs import signature
|
||||
>>> def foo(a, b=None, *args, **kwargs):
|
||||
... pass
|
||||
...
|
||||
>>> sig = signature(foo)
|
||||
>>> sig
|
||||
<funcsigs.Signature object at 0x...>
|
||||
>>> sig.parameters
|
||||
OrderedDict([('a', <Parameter at 0x... 'a'>), ('b', <Parameter at 0x... 'b'>), ('args', <Parameter at 0x... 'args'>), ('kwargs', <Parameter at 0x... 'kwargs'>)])
|
||||
>>> sig.return_annotation
|
||||
<class 'funcsigs._empty'>
|
||||
|
||||
Introspecting callables with the Signature object
|
||||
-------------------------------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
This section of documentation is a direct reproduction 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 funcsigs 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
|
||||
<class 'int'>
|
||||
|
||||
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
|
||||
---------
|
||||
|
||||
*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/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/testing-cabal/funcsigs/issues
|
||||
|
||||
.. |build_status| image:: https://secure.travis-ci.org/aliles/funcsigs.png?branch=master
|
||||
:target: http://travis-ci.org/#!/aliles/funcsigs
|
||||
:alt: Current build status
|
||||
|
||||
.. |coverage| image:: https://coveralls.io/repos/aliles/funcsigs/badge.png?branch=master
|
||||
:target: https://coveralls.io/r/aliles/funcsigs?branch=master
|
||||
:alt: Coverage status
|
||||
|
||||
.. |pypi_version| image:: https://pypip.in/v/funcsigs/badge.png
|
||||
:target: https://crate.io/packages/funcsigs/
|
||||
:alt: Latest PyPI version
|
||||
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/funcsigs.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/funcsigs.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/funcsigs"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/funcsigs"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
|
@ -0,0 +1,9 @@
|
|||
{% extends "!page.html" %}
|
||||
{% block extrahead %}
|
||||
<a href="https://github.com/aliles/funcsigs">
|
||||
<img style="position: absolute; top: 0; right: 0; border: 0;"
|
||||
src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"
|
||||
alt="Fork me on GitHub">
|
||||
</a>
|
||||
{{ super() }}
|
||||
{% endblock %}
|
|
@ -0,0 +1,251 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# funcsigs documentation build configuration file, created by
|
||||
# sphinx-quickstart on Fri Apr 20 20:27:52 2012.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'funcsigs'
|
||||
copyright = '2013, Aaron Iles'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
from funcsigs import __version__
|
||||
version = '.'.join(__version__.split('.')[:2])
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = __version__
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'agogo'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'funcsigsdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'funcsigs.tex', 'funcsigs Documentation',
|
||||
'Aaron Iles', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'funcsigs', 'funcsigs Documentation',
|
||||
['Aaron Iles'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'funcsigs', 'funcsigs Documentation',
|
||||
'Aaron Iles', 'funcsigs', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python3': ('http://docs.python.org/py3k', None),
|
||||
'python': ('http://docs.python.org/', None)
|
||||
}
|
|
@ -0,0 +1,353 @@
|
|||
.. 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.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.
|
||||
|
||||
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.
|
||||
|
||||
Issues
|
||||
``````
|
||||
|
||||
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.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from funcsigs import signature
|
||||
>>> def foo(a, b=None, *args, **kwargs):
|
||||
... pass
|
||||
...
|
||||
>>> sig = signature(foo)
|
||||
>>> sig
|
||||
<funcsigs.Signature object at 0x...>
|
||||
>>> sig.parameters
|
||||
OrderedDict([('a', <Parameter at 0x... 'a'>), ('b', <Parameter at 0x... 'b'>), ('args', <Parameter at 0x... 'args'>), ('kwargs', <Parameter at 0x... 'kwargs'>)])
|
||||
>>> sig.return_annotation
|
||||
<class 'funcsigs._empty'>
|
||||
|
||||
Introspecting callables with the Signature object
|
||||
-------------------------------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
This section of documentation is a direct reproduction 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 funcsigs 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
|
||||
<class 'int'>
|
||||
|
||||
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
|
||||
---------
|
||||
|
||||
*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/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/testing-cabal/funcsigs/issues
|
||||
|
||||
.. |build_status| image:: https://secure.travis-ci.org/aliles/funcsigs.png?branch=master
|
||||
:target: http://travis-ci.org/#!/aliles/funcsigs
|
||||
:alt: Current build status
|
||||
|
||||
.. |coverage| image:: https://coveralls.io/repos/aliles/funcsigs/badge.png?branch=master
|
||||
:target: https://coveralls.io/r/aliles/funcsigs?branch=master
|
||||
:alt: Coverage status
|
||||
|
||||
.. |pypi_version| image:: https://pypip.in/v/funcsigs/badge.png
|
||||
:target: https://crate.io/packages/funcsigs/
|
||||
:alt: Latest PyPI version
|
||||
|
||||
|
|
@ -0,0 +1,829 @@
|
|||
# Copyright 2001-2013 Python Software Foundation; All Rights Reserved
|
||||
"""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.3+.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
import itertools
|
||||
import functools
|
||||
import re
|
||||
import types
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from ordereddict import OrderedDict
|
||||
|
||||
from funcsigs.version import __version__
|
||||
|
||||
__all__ = ['BoundArguments', 'Parameter', 'Signature', 'signature']
|
||||
|
||||
|
||||
_WrapperDescriptor = type(type.__call__)
|
||||
_MethodWrapper = type(all.__call__)
|
||||
|
||||
_NonUserDefinedCallables = (_WrapperDescriptor,
|
||||
_MethodWrapper,
|
||||
types.BuiltinFunctionType)
|
||||
|
||||
|
||||
def formatannotation(annotation, base_module=None):
|
||||
if isinstance(annotation, type):
|
||||
if annotation.__module__ in ('builtins', '__builtin__', base_module):
|
||||
return annotation.__name__
|
||||
return annotation.__module__+'.'+annotation.__name__
|
||||
return repr(annotation)
|
||||
|
||||
|
||||
def _get_user_defined_method(cls, method_name, *nested):
|
||||
try:
|
||||
if cls is type:
|
||||
return
|
||||
meth = getattr(cls, method_name)
|
||||
for name in nested:
|
||||
meth = getattr(meth, name, meth)
|
||||
except AttributeError:
|
||||
return
|
||||
else:
|
||||
if not isinstance(meth, _NonUserDefinedCallables):
|
||||
# Once '__signature__' will be added to 'C'-level
|
||||
# callables, this check won't be necessary
|
||||
return meth
|
||||
|
||||
|
||||
def signature(obj):
|
||||
'''Get a signature object for the passed callable.'''
|
||||
|
||||
if not callable(obj):
|
||||
raise TypeError('{0!r} is not a callable object'.format(obj))
|
||||
|
||||
if isinstance(obj, types.MethodType):
|
||||
sig = signature(obj.__func__)
|
||||
if obj.__self__ is None:
|
||||
# Unbound method - preserve as-is.
|
||||
return sig
|
||||
else:
|
||||
# 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__
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if sig is not None:
|
||||
return sig
|
||||
|
||||
try:
|
||||
# Was this function wrapped by a decorator?
|
||||
wrapped = obj.__wrapped__
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
return signature(wrapped)
|
||||
|
||||
if isinstance(obj, types.FunctionType):
|
||||
return Signature.from_function(obj)
|
||||
|
||||
if isinstance(obj, functools.partial):
|
||||
sig = signature(obj.func)
|
||||
|
||||
new_params = OrderedDict(sig.parameters.items())
|
||||
|
||||
partial_args = obj.args or ()
|
||||
partial_keywords = obj.keywords or {}
|
||||
try:
|
||||
ba = sig.bind_partial(*partial_args, **partial_keywords)
|
||||
except TypeError as ex:
|
||||
msg = 'partial object {0!r} has incorrect arguments'.format(obj)
|
||||
raise ValueError(msg)
|
||||
|
||||
for arg_name, arg_value in ba.arguments.items():
|
||||
param = new_params[arg_name]
|
||||
if arg_name in partial_keywords:
|
||||
# We set a new default value, because the following code
|
||||
# is correct:
|
||||
#
|
||||
# >>> def foo(a): print(a)
|
||||
# >>> print(partial(partial(foo, a=10), a=20)())
|
||||
# 20
|
||||
# >>> print(partial(partial(foo, a=10), a=20)(a=30))
|
||||
# 30
|
||||
#
|
||||
# So, with 'partial' objects, passing a keyword argument is
|
||||
# like setting a new default value for the corresponding
|
||||
# parameter
|
||||
#
|
||||
# We also mark this parameter with '_partial_kwarg'
|
||||
# flag. Later, in '_bind', the 'default' value of this
|
||||
# parameter will be added to 'kwargs', to simulate
|
||||
# the 'functools.partial' real call.
|
||||
new_params[arg_name] = param.replace(default=arg_value,
|
||||
_partial_kwarg=True)
|
||||
|
||||
elif (param.kind not in (_VAR_KEYWORD, _VAR_POSITIONAL) and
|
||||
not param._partial_kwarg):
|
||||
new_params.pop(arg_name)
|
||||
|
||||
return sig.replace(parameters=new_params.values())
|
||||
|
||||
sig = None
|
||||
if isinstance(obj, type):
|
||||
# obj is a class or a metaclass
|
||||
|
||||
# First, let's see if it has an overloaded __call__ defined
|
||||
# in its metaclass
|
||||
call = _get_user_defined_method(type(obj), '__call__')
|
||||
if call is not None:
|
||||
sig = signature(call)
|
||||
else:
|
||||
# Now we check if the 'obj' class has a '__new__' method
|
||||
new = _get_user_defined_method(obj, '__new__')
|
||||
if new is not None:
|
||||
sig = signature(new)
|
||||
else:
|
||||
# Finally, we should have at least __init__ implemented
|
||||
init = _get_user_defined_method(obj, '__init__')
|
||||
if init is not None:
|
||||
sig = signature(init)
|
||||
elif not isinstance(obj, _NonUserDefinedCallables):
|
||||
# An object with __call__
|
||||
# We also check that the 'obj' is not an instance of
|
||||
# _WrapperDescriptor or _MethodWrapper to avoid
|
||||
# infinite recursion (and even potential segfault)
|
||||
call = _get_user_defined_method(type(obj), '__call__', 'im_func')
|
||||
if call is not None:
|
||||
sig = signature(call)
|
||||
|
||||
if sig is not None:
|
||||
# For classes and objects we skip the first parameter of their
|
||||
# __call__, __new__, or __init__ methods
|
||||
return sig.replace(parameters=tuple(sig.parameters.values())[1:])
|
||||
|
||||
if isinstance(obj, types.BuiltinFunctionType):
|
||||
# Raise a nicer error message for builtins
|
||||
msg = 'no signature found for builtin function {0!r}'.format(obj)
|
||||
raise ValueError(msg)
|
||||
|
||||
raise ValueError('callable {0!r} is not supported by signature'.format(obj))
|
||||
|
||||
|
||||
class _void(object):
|
||||
'''A private marker - used in Parameter & Signature'''
|
||||
|
||||
|
||||
class _empty(object):
|
||||
pass
|
||||
|
||||
|
||||
class _ParameterKind(int):
|
||||
def __new__(self, *args, **kwargs):
|
||||
obj = int.__new__(self, *args)
|
||||
obj._name = kwargs['name']
|
||||
return obj
|
||||
|
||||
def __str__(self):
|
||||
return self._name
|
||||
|
||||
def __repr__(self):
|
||||
return '<_ParameterKind: {0!r}>'.format(self._name)
|
||||
|
||||
|
||||
_POSITIONAL_ONLY = _ParameterKind(0, name='POSITIONAL_ONLY')
|
||||
_POSITIONAL_OR_KEYWORD = _ParameterKind(1, name='POSITIONAL_OR_KEYWORD')
|
||||
_VAR_POSITIONAL = _ParameterKind(2, name='VAR_POSITIONAL')
|
||||
_KEYWORD_ONLY = _ParameterKind(3, name='KEYWORD_ONLY')
|
||||
_VAR_KEYWORD = _ParameterKind(4, name='VAR_KEYWORD')
|
||||
|
||||
|
||||
class Parameter(object):
|
||||
'''Represents a parameter in a function signature.
|
||||
|
||||
Has the following public attributes:
|
||||
|
||||
* name : str
|
||||
The name of the parameter as a string.
|
||||
* default : object
|
||||
The default value for the parameter if specified. If the
|
||||
parameter has no default value, this attribute is not set.
|
||||
* annotation
|
||||
The annotation for the parameter if specified. If the
|
||||
parameter has no annotation, this attribute is not set.
|
||||
* kind : str
|
||||
Describes how argument values are bound to the parameter.
|
||||
Possible values: `Parameter.POSITIONAL_ONLY`,
|
||||
`Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`,
|
||||
`Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`.
|
||||
'''
|
||||
|
||||
__slots__ = ('_name', '_kind', '_default', '_annotation', '_partial_kwarg')
|
||||
|
||||
POSITIONAL_ONLY = _POSITIONAL_ONLY
|
||||
POSITIONAL_OR_KEYWORD = _POSITIONAL_OR_KEYWORD
|
||||
VAR_POSITIONAL = _VAR_POSITIONAL
|
||||
KEYWORD_ONLY = _KEYWORD_ONLY
|
||||
VAR_KEYWORD = _VAR_KEYWORD
|
||||
|
||||
empty = _empty
|
||||
|
||||
def __init__(self, name, kind, default=_empty, annotation=_empty,
|
||||
_partial_kwarg=False):
|
||||
|
||||
if kind not in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD,
|
||||
_VAR_POSITIONAL, _KEYWORD_ONLY, _VAR_KEYWORD):
|
||||
raise ValueError("invalid value for 'Parameter.kind' attribute")
|
||||
self._kind = kind
|
||||
|
||||
if default is not _empty:
|
||||
if kind in (_VAR_POSITIONAL, _VAR_KEYWORD):
|
||||
msg = '{0} parameters cannot have default values'.format(kind)
|
||||
raise ValueError(msg)
|
||||
self._default = default
|
||||
self._annotation = annotation
|
||||
|
||||
if name is None:
|
||||
if kind != _POSITIONAL_ONLY:
|
||||
raise ValueError("None is not a valid name for a "
|
||||
"non-positional-only parameter")
|
||||
self._name = name
|
||||
else:
|
||||
name = str(name)
|
||||
if kind != _POSITIONAL_ONLY and not re.match(r'[a-z_]\w*$', name, re.I):
|
||||
msg = '{0!r} is not a valid parameter name'.format(name)
|
||||
raise ValueError(msg)
|
||||
self._name = name
|
||||
|
||||
self._partial_kwarg = _partial_kwarg
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
return self._default
|
||||
|
||||
@property
|
||||
def annotation(self):
|
||||
return self._annotation
|
||||
|
||||
@property
|
||||
def kind(self):
|
||||
return self._kind
|
||||
|
||||
def replace(self, name=_void, kind=_void, annotation=_void,
|
||||
default=_void, _partial_kwarg=_void):
|
||||
'''Creates a customized copy of the Parameter.'''
|
||||
|
||||
if name is _void:
|
||||
name = self._name
|
||||
|
||||
if kind is _void:
|
||||
kind = self._kind
|
||||
|
||||
if annotation is _void:
|
||||
annotation = self._annotation
|
||||
|
||||
if default is _void:
|
||||
default = self._default
|
||||
|
||||
if _partial_kwarg is _void:
|
||||
_partial_kwarg = self._partial_kwarg
|
||||
|
||||
return type(self)(name, kind, default=default, annotation=annotation,
|
||||
_partial_kwarg=_partial_kwarg)
|
||||
|
||||
def __str__(self):
|
||||
kind = self.kind
|
||||
|
||||
formatted = self._name
|
||||
if kind == _POSITIONAL_ONLY:
|
||||
if formatted is None:
|
||||
formatted = ''
|
||||
formatted = '<{0}>'.format(formatted)
|
||||
|
||||
# Add annotation and default value
|
||||
if self._annotation is not _empty:
|
||||
formatted = '{0}:{1}'.format(formatted,
|
||||
formatannotation(self._annotation))
|
||||
|
||||
if self._default is not _empty:
|
||||
formatted = '{0}={1}'.format(formatted, repr(self._default))
|
||||
|
||||
if kind == _VAR_POSITIONAL:
|
||||
formatted = '*' + formatted
|
||||
elif kind == _VAR_KEYWORD:
|
||||
formatted = '**' + formatted
|
||||
|
||||
return formatted
|
||||
|
||||
def __repr__(self):
|
||||
return '<{0} at {1:#x} {2!r}>'.format(self.__class__.__name__,
|
||||
id(self), self.name)
|
||||
|
||||
def __hash__(self):
|
||||
msg = "unhashable type: '{0}'".format(self.__class__.__name__)
|
||||
raise TypeError(msg)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (issubclass(other.__class__, Parameter) and
|
||||
self._name == other._name and
|
||||
self._kind == other._kind and
|
||||
self._default == other._default and
|
||||
self._annotation == other._annotation)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
class BoundArguments(object):
|
||||
'''Result of `Signature.bind` call. Holds the mapping of arguments
|
||||
to the function's parameters.
|
||||
|
||||
Has the following public attributes:
|
||||
|
||||
* arguments : OrderedDict
|
||||
An ordered mutable mapping of parameters' names to arguments' values.
|
||||
Does not contain arguments' default values.
|
||||
* signature : Signature
|
||||
The Signature object that created this instance.
|
||||
* args : tuple
|
||||
Tuple of positional arguments values.
|
||||
* kwargs : dict
|
||||
Dict of keyword arguments values.
|
||||
'''
|
||||
|
||||
def __init__(self, signature, arguments):
|
||||
self.arguments = arguments
|
||||
self._signature = signature
|
||||
|
||||
@property
|
||||
def signature(self):
|
||||
return self._signature
|
||||
|
||||
@property
|
||||
def args(self):
|
||||
args = []
|
||||
for param_name, param in self._signature.parameters.items():
|
||||
if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
|
||||
param._partial_kwarg):
|
||||
# Keyword arguments mapped by 'functools.partial'
|
||||
# (Parameter._partial_kwarg is True) are mapped
|
||||
# in 'BoundArguments.kwargs', along with VAR_KEYWORD &
|
||||
# KEYWORD_ONLY
|
||||
break
|
||||
|
||||
try:
|
||||
arg = self.arguments[param_name]
|
||||
except KeyError:
|
||||
# We're done here. Other arguments
|
||||
# will be mapped in 'BoundArguments.kwargs'
|
||||
break
|
||||
else:
|
||||
if param.kind == _VAR_POSITIONAL:
|
||||
# *args
|
||||
args.extend(arg)
|
||||
else:
|
||||
# plain argument
|
||||
args.append(arg)
|
||||
|
||||
return tuple(args)
|
||||
|
||||
@property
|
||||
def kwargs(self):
|
||||
kwargs = {}
|
||||
kwargs_started = False
|
||||
for param_name, param in self._signature.parameters.items():
|
||||
if not kwargs_started:
|
||||
if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
|
||||
param._partial_kwarg):
|
||||
kwargs_started = True
|
||||
else:
|
||||
if param_name not in self.arguments:
|
||||
kwargs_started = True
|
||||
continue
|
||||
|
||||
if not kwargs_started:
|
||||
continue
|
||||
|
||||
try:
|
||||
arg = self.arguments[param_name]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if param.kind == _VAR_KEYWORD:
|
||||
# **kwargs
|
||||
kwargs.update(arg)
|
||||
else:
|
||||
# plain keyword argument
|
||||
kwargs[param_name] = arg
|
||||
|
||||
return kwargs
|
||||
|
||||
def __hash__(self):
|
||||
msg = "unhashable type: '{0}'".format(self.__class__.__name__)
|
||||
raise TypeError(msg)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (issubclass(other.__class__, BoundArguments) and
|
||||
self.signature == other.signature and
|
||||
self.arguments == other.arguments)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
class Signature(object):
|
||||
'''A Signature object represents the overall signature of a function.
|
||||
It stores a Parameter object for each parameter accepted by the
|
||||
function, as well as information specific to the function itself.
|
||||
|
||||
A Signature object has the following public attributes and methods:
|
||||
|
||||
* parameters : OrderedDict
|
||||
An ordered mapping of parameters' names to the corresponding
|
||||
Parameter objects (keyword-only arguments are in the same order
|
||||
as listed in `code.co_varnames`).
|
||||
* return_annotation : object
|
||||
The annotation for the return type of the function if specified.
|
||||
If the function has no annotation for its return type, this
|
||||
attribute is not set.
|
||||
* bind(*args, **kwargs) -> BoundArguments
|
||||
Creates a mapping from positional and keyword arguments to
|
||||
parameters.
|
||||
* bind_partial(*args, **kwargs) -> BoundArguments
|
||||
Creates a partial mapping from positional and keyword arguments
|
||||
to parameters (simulating 'functools.partial' behavior.)
|
||||
'''
|
||||
|
||||
__slots__ = ('_return_annotation', '_parameters')
|
||||
|
||||
_parameter_cls = Parameter
|
||||
_bound_arguments_cls = BoundArguments
|
||||
|
||||
empty = _empty
|
||||
|
||||
def __init__(self, parameters=None, return_annotation=_empty,
|
||||
__validate_parameters__=True):
|
||||
'''Constructs Signature from the given list of Parameter
|
||||
objects and 'return_annotation'. All arguments are optional.
|
||||
'''
|
||||
|
||||
if parameters is None:
|
||||
params = OrderedDict()
|
||||
else:
|
||||
if __validate_parameters__:
|
||||
params = OrderedDict()
|
||||
top_kind = _POSITIONAL_ONLY
|
||||
|
||||
for idx, param in enumerate(parameters):
|
||||
kind = param.kind
|
||||
if kind < top_kind:
|
||||
msg = 'wrong parameter order: {0} before {1}'
|
||||
msg = msg.format(top_kind, param.kind)
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
top_kind = kind
|
||||
|
||||
name = param.name
|
||||
if name is None:
|
||||
name = str(idx)
|
||||
param = param.replace(name=name)
|
||||
|
||||
if name in params:
|
||||
msg = 'duplicate parameter name: {0!r}'.format(name)
|
||||
raise ValueError(msg)
|
||||
params[name] = param
|
||||
else:
|
||||
params = OrderedDict(((param.name, param)
|
||||
for param in parameters))
|
||||
|
||||
self._parameters = params
|
||||
self._return_annotation = return_annotation
|
||||
|
||||
@classmethod
|
||||
def from_function(cls, func):
|
||||
'''Constructs Signature for the given python function'''
|
||||
|
||||
if not isinstance(func, types.FunctionType):
|
||||
raise TypeError('{0!r} is not a Python function'.format(func))
|
||||
|
||||
Parameter = cls._parameter_cls
|
||||
|
||||
# Parameter information.
|
||||
func_code = func.__code__
|
||||
pos_count = func_code.co_argcount
|
||||
arg_names = func_code.co_varnames
|
||||
positional = tuple(arg_names[:pos_count])
|
||||
keyword_only_count = getattr(func_code, 'co_kwonlyargcount', 0)
|
||||
keyword_only = arg_names[pos_count:(pos_count + keyword_only_count)]
|
||||
annotations = getattr(func, '__annotations__', {})
|
||||
defaults = func.__defaults__
|
||||
kwdefaults = getattr(func, '__kwdefaults__', None)
|
||||
|
||||
if defaults:
|
||||
pos_default_count = len(defaults)
|
||||
else:
|
||||
pos_default_count = 0
|
||||
|
||||
parameters = []
|
||||
|
||||
# Non-keyword-only parameters w/o defaults.
|
||||
non_default_count = pos_count - pos_default_count
|
||||
for name in positional[:non_default_count]:
|
||||
annotation = annotations.get(name, _empty)
|
||||
parameters.append(Parameter(name, annotation=annotation,
|
||||
kind=_POSITIONAL_OR_KEYWORD))
|
||||
|
||||
# ... w/ defaults.
|
||||
for offset, name in enumerate(positional[non_default_count:]):
|
||||
annotation = annotations.get(name, _empty)
|
||||
parameters.append(Parameter(name, annotation=annotation,
|
||||
kind=_POSITIONAL_OR_KEYWORD,
|
||||
default=defaults[offset]))
|
||||
|
||||
# *args
|
||||
if func_code.co_flags & 0x04:
|
||||
name = arg_names[pos_count + keyword_only_count]
|
||||
annotation = annotations.get(name, _empty)
|
||||
parameters.append(Parameter(name, annotation=annotation,
|
||||
kind=_VAR_POSITIONAL))
|
||||
|
||||
# Keyword-only parameters.
|
||||
for name in keyword_only:
|
||||
default = _empty
|
||||
if kwdefaults is not None:
|
||||
default = kwdefaults.get(name, _empty)
|
||||
|
||||
annotation = annotations.get(name, _empty)
|
||||
parameters.append(Parameter(name, annotation=annotation,
|
||||
kind=_KEYWORD_ONLY,
|
||||
default=default))
|
||||
# **kwargs
|
||||
if func_code.co_flags & 0x08:
|
||||
index = pos_count + keyword_only_count
|
||||
if func_code.co_flags & 0x04:
|
||||
index += 1
|
||||
|
||||
name = arg_names[index]
|
||||
annotation = annotations.get(name, _empty)
|
||||
parameters.append(Parameter(name, annotation=annotation,
|
||||
kind=_VAR_KEYWORD))
|
||||
|
||||
return cls(parameters,
|
||||
return_annotation=annotations.get('return', _empty),
|
||||
__validate_parameters__=False)
|
||||
|
||||
@property
|
||||
def parameters(self):
|
||||
try:
|
||||
return types.MappingProxyType(self._parameters)
|
||||
except AttributeError:
|
||||
return OrderedDict(self._parameters.items())
|
||||
|
||||
@property
|
||||
def return_annotation(self):
|
||||
return self._return_annotation
|
||||
|
||||
def replace(self, parameters=_void, return_annotation=_void):
|
||||
'''Creates a customized copy of the Signature.
|
||||
Pass 'parameters' and/or 'return_annotation' arguments
|
||||
to override them in the new copy.
|
||||
'''
|
||||
|
||||
if parameters is _void:
|
||||
parameters = self.parameters.values()
|
||||
|
||||
if return_annotation is _void:
|
||||
return_annotation = self._return_annotation
|
||||
|
||||
return type(self)(parameters,
|
||||
return_annotation=return_annotation)
|
||||
|
||||
def __hash__(self):
|
||||
msg = "unhashable type: '{0}'".format(self.__class__.__name__)
|
||||
raise TypeError(msg)
|
||||
|
||||
def __eq__(self, other):
|
||||
if (not issubclass(type(other), Signature) or
|
||||
self.return_annotation != other.return_annotation or
|
||||
len(self.parameters) != len(other.parameters)):
|
||||
return False
|
||||
|
||||
other_positions = dict((param, idx)
|
||||
for idx, param in enumerate(other.parameters.keys()))
|
||||
|
||||
for idx, (param_name, param) in enumerate(self.parameters.items()):
|
||||
if param.kind == _KEYWORD_ONLY:
|
||||
try:
|
||||
other_param = other.parameters[param_name]
|
||||
except KeyError:
|
||||
return False
|
||||
else:
|
||||
if param != other_param:
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
other_idx = other_positions[param_name]
|
||||
except KeyError:
|
||||
return False
|
||||
else:
|
||||
if (idx != other_idx or
|
||||
param != other.parameters[param_name]):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def _bind(self, args, kwargs, partial=False):
|
||||
'''Private method. Don't use directly.'''
|
||||
|
||||
arguments = OrderedDict()
|
||||
|
||||
parameters = iter(self.parameters.values())
|
||||
parameters_ex = ()
|
||||
arg_vals = iter(args)
|
||||
|
||||
if partial:
|
||||
# Support for binding arguments to 'functools.partial' objects.
|
||||
# See 'functools.partial' case in 'signature()' implementation
|
||||
# for details.
|
||||
for param_name, param in self.parameters.items():
|
||||
if (param._partial_kwarg and param_name not in kwargs):
|
||||
# Simulating 'functools.partial' behavior
|
||||
kwargs[param_name] = param.default
|
||||
|
||||
while True:
|
||||
# Let's iterate through the positional arguments and corresponding
|
||||
# parameters
|
||||
try:
|
||||
arg_val = next(arg_vals)
|
||||
except StopIteration:
|
||||
# No more positional arguments
|
||||
try:
|
||||
param = next(parameters)
|
||||
except StopIteration:
|
||||
# No more parameters. That's it. Just need to check that
|
||||
# we have no `kwargs` after this while loop
|
||||
break
|
||||
else:
|
||||
if param.kind == _VAR_POSITIONAL:
|
||||
# That's OK, just empty *args. Let's start parsing
|
||||
# kwargs
|
||||
break
|
||||
elif param.name in kwargs:
|
||||
if param.kind == _POSITIONAL_ONLY:
|
||||
msg = '{arg!r} parameter is positional only, ' \
|
||||
'but was passed as a keyword'
|
||||
msg = msg.format(arg=param.name)
|
||||
raise TypeError(msg)
|
||||
parameters_ex = (param,)
|
||||
break
|
||||
elif (param.kind == _VAR_KEYWORD or
|
||||
param.default is not _empty):
|
||||
# That's fine too - we have a default value for this
|
||||
# parameter. So, lets start parsing `kwargs`, starting
|
||||
# with the current parameter
|
||||
parameters_ex = (param,)
|
||||
break
|
||||
else:
|
||||
if partial:
|
||||
parameters_ex = (param,)
|
||||
break
|
||||
else:
|
||||
msg = '{arg!r} parameter lacking default value'
|
||||
msg = msg.format(arg=param.name)
|
||||
raise TypeError(msg)
|
||||
else:
|
||||
# We have a positional argument to process
|
||||
try:
|
||||
param = next(parameters)
|
||||
except StopIteration:
|
||||
raise TypeError('too many positional arguments')
|
||||
else:
|
||||
if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
|
||||
# Looks like we have no parameter for this positional
|
||||
# argument
|
||||
raise TypeError('too many positional arguments')
|
||||
|
||||
if param.kind == _VAR_POSITIONAL:
|
||||
# We have an '*args'-like argument, let's fill it with
|
||||
# all positional arguments we have left and move on to
|
||||
# the next phase
|
||||
values = [arg_val]
|
||||
values.extend(arg_vals)
|
||||
arguments[param.name] = tuple(values)
|
||||
break
|
||||
|
||||
if param.name in kwargs:
|
||||
raise TypeError('multiple values for argument '
|
||||
'{arg!r}'.format(arg=param.name))
|
||||
|
||||
arguments[param.name] = arg_val
|
||||
|
||||
# Now, we iterate through the remaining parameters to process
|
||||
# keyword arguments
|
||||
kwargs_param = None
|
||||
for param in itertools.chain(parameters_ex, parameters):
|
||||
if param.kind == _POSITIONAL_ONLY:
|
||||
# This should never happen in case of a properly built
|
||||
# Signature object (but let's have this check here
|
||||
# to ensure correct behaviour just in case)
|
||||
raise TypeError('{arg!r} parameter is positional only, '
|
||||
'but was passed as a keyword'. \
|
||||
format(arg=param.name))
|
||||
|
||||
if param.kind == _VAR_KEYWORD:
|
||||
# Memorize that we have a '**kwargs'-like parameter
|
||||
kwargs_param = param
|
||||
continue
|
||||
|
||||
param_name = param.name
|
||||
try:
|
||||
arg_val = kwargs.pop(param_name)
|
||||
except KeyError:
|
||||
# We have no value for this parameter. It's fine though,
|
||||
# if it has a default value, or it is an '*args'-like
|
||||
# parameter, left alone by the processing of positional
|
||||
# arguments.
|
||||
if (not partial and param.kind != _VAR_POSITIONAL and
|
||||
param.default is _empty):
|
||||
raise TypeError('{arg!r} parameter lacking default value'. \
|
||||
format(arg=param_name))
|
||||
|
||||
else:
|
||||
arguments[param_name] = arg_val
|
||||
|
||||
if kwargs:
|
||||
if kwargs_param is not None:
|
||||
# Process our '**kwargs'-like parameter
|
||||
arguments[kwargs_param.name] = kwargs
|
||||
else:
|
||||
raise TypeError('too many keyword arguments %r' % kwargs)
|
||||
|
||||
return self._bound_arguments_cls(self, arguments)
|
||||
|
||||
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 args[0]._bind(args[1:], kwargs)
|
||||
|
||||
def bind_partial(self, *args, **kwargs):
|
||||
'''Get a BoundArguments object, that partially 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, partial=True)
|
||||
|
||||
def __str__(self):
|
||||
result = []
|
||||
render_kw_only_separator = True
|
||||
for idx, param in enumerate(self.parameters.values()):
|
||||
formatted = str(param)
|
||||
|
||||
kind = param.kind
|
||||
if kind == _VAR_POSITIONAL:
|
||||
# OK, we have an '*args'-like parameter, so we won't need
|
||||
# a '*' to separate keyword-only arguments
|
||||
render_kw_only_separator = False
|
||||
elif kind == _KEYWORD_ONLY and render_kw_only_separator:
|
||||
# We have a keyword-only parameter to render and we haven't
|
||||
# rendered an '*args'-like parameter before, so add a '*'
|
||||
# separator to the parameters list ("foo(arg1, *, arg2)" case)
|
||||
result.append('*')
|
||||
# This condition should be only triggered once, so
|
||||
# reset the flag
|
||||
render_kw_only_separator = False
|
||||
|
||||
result.append(formatted)
|
||||
|
||||
rendered = '({0})'.format(', '.join(result))
|
||||
|
||||
if self.return_annotation is not _empty:
|
||||
anno = formatannotation(self.return_annotation)
|
||||
rendered += ' -> {0}'.format(anno)
|
||||
|
||||
return rendered
|
|
@ -0,0 +1 @@
|
|||
__version__ = "1.0.2"
|
|
@ -0,0 +1,8 @@
|
|||
[wheel]
|
||||
universal = 1
|
||||
|
||||
[egg_info]
|
||||
tag_build =
|
||||
tag_date = 0
|
||||
tag_svn_revision = 0
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/env python
|
||||
from setuptools import setup
|
||||
import re
|
||||
import sys
|
||||
|
||||
def load_version(filename='funcsigs/version.py'):
|
||||
"Parse a __version__ number from a source file"
|
||||
with open(filename) as source:
|
||||
text = source.read()
|
||||
match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", text)
|
||||
if not match:
|
||||
msg = "Unable to find version number in {}".format(filename)
|
||||
raise RuntimeError(msg)
|
||||
version = match.group(1)
|
||||
return version
|
||||
|
||||
|
||||
setup(
|
||||
name="funcsigs",
|
||||
version=load_version(),
|
||||
packages=['funcsigs'],
|
||||
zip_safe=False,
|
||||
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(),
|
||||
license="ASL",
|
||||
extras_require = {
|
||||
':python_version<"2.7"': ['ordereddict'],
|
||||
},
|
||||
setup_requires = ["setuptools>=17.1"],
|
||||
classifiers = [
|
||||
'Development Status :: 4 - Beta',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'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 = ['unittest2'],
|
||||
test_suite = 'unittest2.collector',
|
||||
)
|
|
@ -0,0 +1,17 @@
|
|||
import funcsigs
|
||||
|
||||
import unittest2 as unittest
|
||||
|
||||
class TestFormatAnnotation(unittest.TestCase):
|
||||
def test_string (self):
|
||||
self.assertEqual(funcsigs.formatannotation("annotation"),
|
||||
"'annotation'")
|
||||
|
||||
def test_builtin_type (self):
|
||||
self.assertEqual(funcsigs.formatannotation(int),
|
||||
"int")
|
||||
|
||||
def test_user_type (self):
|
||||
class dummy (object): pass
|
||||
self.assertEqual(funcsigs.formatannotation(dummy),
|
||||
"tests.test_formatannotation.dummy")
|
|
@ -0,0 +1,91 @@
|
|||
import unittest2 as unittest
|
||||
|
||||
import doctest
|
||||
import sys
|
||||
|
||||
import funcsigs as inspect
|
||||
|
||||
|
||||
class TestFunctionSignatures(unittest.TestCase):
|
||||
|
||||
@staticmethod
|
||||
def signature(func):
|
||||
sig = inspect.signature(func)
|
||||
return (tuple((param.name,
|
||||
(Ellipsis if param.default is param.empty else param.default),
|
||||
(Ellipsis if param.annotation is param.empty
|
||||
else param.annotation),
|
||||
str(param.kind).lower())
|
||||
for param in sig.parameters.values()),
|
||||
(Ellipsis if sig.return_annotation is sig.empty
|
||||
else sig.return_annotation))
|
||||
|
||||
def test_zero_arguments(self):
|
||||
def test():
|
||||
pass
|
||||
self.assertEqual(self.signature(test),
|
||||
((), Ellipsis))
|
||||
|
||||
def test_single_positional_argument(self):
|
||||
def test(a):
|
||||
pass
|
||||
self.assertEqual(self.signature(test),
|
||||
(((('a', Ellipsis, Ellipsis, "positional_or_keyword")),), Ellipsis))
|
||||
|
||||
def test_single_keyword_argument(self):
|
||||
def test(a=None):
|
||||
pass
|
||||
self.assertEqual(self.signature(test),
|
||||
(((('a', None, Ellipsis, "positional_or_keyword")),), Ellipsis))
|
||||
|
||||
def test_var_args(self):
|
||||
def test(*args):
|
||||
pass
|
||||
self.assertEqual(self.signature(test),
|
||||
(((('args', Ellipsis, Ellipsis, "var_positional")),), Ellipsis))
|
||||
|
||||
def test_keywords_args(self):
|
||||
def test(**kwargs):
|
||||
pass
|
||||
self.assertEqual(self.signature(test),
|
||||
(((('kwargs', Ellipsis, Ellipsis, "var_keyword")),), Ellipsis))
|
||||
|
||||
def test_multiple_arguments(self):
|
||||
def test(a, b=None, *args, **kwargs):
|
||||
pass
|
||||
self.assertEqual(self.signature(test), ((
|
||||
('a', Ellipsis, Ellipsis, "positional_or_keyword"),
|
||||
('b', None, Ellipsis, "positional_or_keyword"),
|
||||
('args', Ellipsis, Ellipsis, "var_positional"),
|
||||
('kwargs', Ellipsis, Ellipsis, "var_keyword"),
|
||||
), Ellipsis))
|
||||
|
||||
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):
|
||||
self_kind = "positional_or_keyword"
|
||||
class Test(object):
|
||||
def method(self):
|
||||
pass
|
||||
def method_with_args(self, a):
|
||||
pass
|
||||
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))
|
||||
self.assertEqual(
|
||||
self.signature(Test.method_with_varargs),
|
||||
((('args', Ellipsis, Ellipsis, "var_positional"),), Ellipsis))
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2012 Erik Rose
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,8 @@
|
|||
include README.rst
|
||||
include LICENSE
|
||||
include docs/*.rst
|
||||
include docs/Makefile
|
||||
include docs/make.bat
|
||||
include docs/conf.py
|
||||
include fabfile.py
|
||||
include tox.ini
|
|
@ -0,0 +1,321 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: more-itertools
|
||||
Version: 4.2.0
|
||||
Summary: More routines for operating on iterables, beyond itertools
|
||||
Home-page: https://github.com/erikrose/more-itertools
|
||||
Author: Erik Rose
|
||||
Author-email: erikrose@grinchcentral.com
|
||||
License: MIT
|
||||
Description: ==============
|
||||
More Itertools
|
||||
==============
|
||||
|
||||
.. image:: https://coveralls.io/repos/github/erikrose/more-itertools/badge.svg?branch=master
|
||||
:target: https://coveralls.io/github/erikrose/more-itertools?branch=master
|
||||
|
||||
Python's ``itertools`` library is a gem - you can compose elegant solutions
|
||||
for a variety of problems with the functions it provides. In ``more-itertools``
|
||||
we collect additional building blocks, recipes, and routines for working with
|
||||
Python iterables.
|
||||
|
||||
Getting started
|
||||
===============
|
||||
|
||||
To get started, install the library with `pip <https://pip.pypa.io/en/stable/>`_:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
pip install more-itertools
|
||||
|
||||
The recipes from the `itertools docs <https://docs.python.org/3/library/itertools.html#itertools-recipes>`_
|
||||
are included in the top-level package:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from more_itertools import flatten
|
||||
>>> iterable = [(0, 1), (2, 3)]
|
||||
>>> list(flatten(iterable))
|
||||
[0, 1, 2, 3]
|
||||
|
||||
Several new recipes are available as well:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from more_itertools import chunked
|
||||
>>> iterable = [0, 1, 2, 3, 4, 5, 6, 7, 8]
|
||||
>>> list(chunked(iterable, 3))
|
||||
[[0, 1, 2], [3, 4, 5], [6, 7, 8]]
|
||||
|
||||
>>> from more_itertools import spy
|
||||
>>> iterable = (x * x for x in range(1, 6))
|
||||
>>> head, iterable = spy(iterable, n=3)
|
||||
>>> list(head)
|
||||
[1, 4, 9]
|
||||
>>> list(iterable)
|
||||
[1, 4, 9, 16, 25]
|
||||
|
||||
|
||||
|
||||
For the full listing of functions, see the `API documentation <https://more-itertools.readthedocs.io/en/latest/api.html>`_.
|
||||
|
||||
Development
|
||||
===========
|
||||
|
||||
``more-itertools`` is maintained by `@erikrose <https://github.com/erikrose>`_
|
||||
and `@bbayles <https://github.com/bbayles>`_, with help from `many others <https://github.com/erikrose/more-itertools/graphs/contributors>`_.
|
||||
If you have a problem or suggestion, please file a bug or pull request in this
|
||||
repository. Thanks for contributing!
|
||||
|
||||
|
||||
Version History
|
||||
===============
|
||||
|
||||
|
||||
|
||||
4.2.0
|
||||
-----
|
||||
|
||||
* New itertools:
|
||||
* map_reduce (thanks to pylang)
|
||||
* prepend (from the `Python 3.7 docs <https://docs.python.org/3.7/library/itertools.html#itertools-recipes>`_)
|
||||
|
||||
* Improvements to existing itertools:
|
||||
* bucket now complies with PEP 479 (thanks to irmen)
|
||||
|
||||
* Other changes:
|
||||
* Python 3.7 is now supported (thanks to irmen)
|
||||
* Python 3.3 is no longer supported
|
||||
* The test suite no longer requires third-party modules to run
|
||||
* The API docs now include links to source code
|
||||
|
||||
4.1.0
|
||||
-----
|
||||
|
||||
* New itertools:
|
||||
* split_at (thanks to michael-celani)
|
||||
* circular_shifts (thanks to hiqua)
|
||||
* make_decorator - see the blog post `Yo, I heard you like decorators <https://sites.google.com/site/bbayles/index/decorator_factory>`_
|
||||
for a tour (thanks to pylang)
|
||||
* always_reversible (thanks to michael-celani)
|
||||
* nth_combination (from the `Python 3.7 docs <https://docs.python.org/3.7/library/itertools.html#itertools-recipes>`_)
|
||||
|
||||
* Improvements to existing itertools:
|
||||
* seekable now has an ``elements`` method to return cached items.
|
||||
* The performance tradeoffs between roundrobin and
|
||||
interleave_longest are now documented (thanks michael-celani,
|
||||
pylang, and MSeifert04)
|
||||
|
||||
4.0.1
|
||||
-----
|
||||
|
||||
* No code changes - this release fixes how the docs display on PyPI.
|
||||
|
||||
4.0.0
|
||||
-----
|
||||
|
||||
* New itertools:
|
||||
* consecutive_groups (Based on the example in the `Python 2.4 docs <https://docs.python.org/release/2.4.4/lib/itertools-example.html>`_)
|
||||
* seekable (If you're looking for how to "reset" an iterator,
|
||||
you're in luck!)
|
||||
* exactly_n (thanks to michael-celani)
|
||||
* run_length.encode and run_length.decode
|
||||
* difference
|
||||
|
||||
* Improvements to existing itertools:
|
||||
* The number of items between filler elements in intersperse can
|
||||
now be specified (thanks to pylang)
|
||||
* distinct_permutations and peekable got some minor
|
||||
adjustments (thanks to MSeifert04)
|
||||
* always_iterable now returns an iterator object. It also now
|
||||
allows different types to be considered iterable (thanks to jaraco)
|
||||
* bucket can now limit the keys it stores in memory
|
||||
* one now allows for custom exceptions (thanks to kalekundert)
|
||||
|
||||
* Other changes:
|
||||
* A few typos were fixed (thanks to EdwardBetts)
|
||||
* All tests can now be run with ``python setup.py test``
|
||||
|
||||
The major version update is due to the change in the return value of always_iterable.
|
||||
It now always returns iterator objects:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from more_itertools import always_iterable
|
||||
# Non-iterable objects are wrapped with iter(tuple(obj))
|
||||
>>> always_iterable(12345)
|
||||
<tuple_iterator object at 0x7fb24c9488d0>
|
||||
>>> list(always_iterable(12345))
|
||||
[12345]
|
||||
# Iterable objects are wrapped with iter()
|
||||
>>> always_iterable([1, 2, 3, 4, 5])
|
||||
<list_iterator object at 0x7fb24c948c50>
|
||||
|
||||
3.2.0
|
||||
-----
|
||||
|
||||
* New itertools:
|
||||
* lstrip, rstrip, and strip
|
||||
(thanks to MSeifert04 and pylang)
|
||||
* islice_extended
|
||||
* Improvements to existing itertools:
|
||||
* Some bugs with slicing peekable-wrapped iterables were fixed
|
||||
|
||||
3.1.0
|
||||
-----
|
||||
|
||||
* New itertools:
|
||||
* numeric_range (Thanks to BebeSparkelSparkel and MSeifert04)
|
||||
* count_cycle (Thanks to BebeSparkelSparkel)
|
||||
* locate (Thanks to pylang and MSeifert04)
|
||||
* Improvements to existing itertools:
|
||||
* A few itertools are now slightly faster due to some function
|
||||
optimizations. (Thanks to MSeifert04)
|
||||
* The docs have been substantially revised with installation notes,
|
||||
categories for library functions, links, and more. (Thanks to pylang)
|
||||
|
||||
|
||||
3.0.0
|
||||
-----
|
||||
|
||||
* Removed itertools:
|
||||
* ``context`` has been removed due to a design flaw - see below for
|
||||
replacement options. (thanks to NeilGirdhar)
|
||||
* Improvements to existing itertools:
|
||||
* ``side_effect`` now supports ``before`` and ``after`` keyword
|
||||
arguments. (Thanks to yardsale8)
|
||||
* PyPy and PyPy3 are now supported.
|
||||
|
||||
The major version change is due to the removal of the ``context`` function.
|
||||
Replace it with standard ``with`` statement context management:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Don't use context() anymore
|
||||
file_obj = StringIO()
|
||||
consume(print(x, file=f) for f in context(file_obj) for x in u'123')
|
||||
|
||||
# Use a with statement instead
|
||||
file_obj = StringIO()
|
||||
with file_obj as f:
|
||||
consume(print(x, file=f) for x in u'123')
|
||||
|
||||
2.6.0
|
||||
-----
|
||||
|
||||
* New itertools:
|
||||
* ``adjacent`` and ``groupby_transform`` (Thanks to diazona)
|
||||
* ``always_iterable`` (Thanks to jaraco)
|
||||
* (Removed in 3.0.0) ``context`` (Thanks to yardsale8)
|
||||
* ``divide`` (Thanks to mozbhearsum)
|
||||
* Improvements to existing itertools:
|
||||
* ``ilen`` is now slightly faster. (Thanks to wbolster)
|
||||
* ``peekable`` can now prepend items to an iterable. (Thanks to diazona)
|
||||
|
||||
2.5.0
|
||||
-----
|
||||
|
||||
* New itertools:
|
||||
* ``distribute`` (Thanks to mozbhearsum and coady)
|
||||
* ``sort_together`` (Thanks to clintval)
|
||||
* ``stagger`` and ``zip_offset`` (Thanks to joshbode)
|
||||
* ``padded``
|
||||
* Improvements to existing itertools:
|
||||
* ``peekable`` now handles negative indexes and slices with negative
|
||||
components properly.
|
||||
* ``intersperse`` is now slightly faster. (Thanks to pylang)
|
||||
* ``windowed`` now accepts a ``step`` keyword argument.
|
||||
(Thanks to pylang)
|
||||
* Python 3.6 is now supported.
|
||||
|
||||
2.4.1
|
||||
-----
|
||||
|
||||
* Move docs 100% to readthedocs.io.
|
||||
|
||||
2.4
|
||||
-----
|
||||
|
||||
* New itertools:
|
||||
* ``accumulate``, ``all_equal``, ``first_true``, ``partition``, and
|
||||
``tail`` from the itertools documentation.
|
||||
* ``bucket`` (Thanks to Rosuav and cvrebert)
|
||||
* ``collapse`` (Thanks to abarnet)
|
||||
* ``interleave`` and ``interleave_longest`` (Thanks to abarnet)
|
||||
* ``side_effect`` (Thanks to nvie)
|
||||
* ``sliced`` (Thanks to j4mie and coady)
|
||||
* ``split_before`` and ``split_after`` (Thanks to astronouth7303)
|
||||
* ``spy`` (Thanks to themiurgo and mathieulongtin)
|
||||
* Improvements to existing itertools:
|
||||
* ``chunked`` is now simpler and more friendly to garbage collection.
|
||||
(Contributed by coady, with thanks to piskvorky)
|
||||
* ``collate`` now delegates to ``heapq.merge`` when possible.
|
||||
(Thanks to kmike and julianpistorius)
|
||||
* ``peekable``-wrapped iterables are now indexable and sliceable.
|
||||
Iterating through ``peekable``-wrapped iterables is also faster.
|
||||
* ``one`` and ``unique_to_each`` have been simplified.
|
||||
(Thanks to coady)
|
||||
|
||||
|
||||
2.3
|
||||
-----
|
||||
|
||||
* Added ``one`` from ``jaraco.util.itertools``. (Thanks, jaraco!)
|
||||
* Added ``distinct_permutations`` and ``unique_to_each``. (Contributed by
|
||||
bbayles)
|
||||
* Added ``windowed``. (Contributed by bbayles, with thanks to buchanae,
|
||||
jaraco, and abarnert)
|
||||
* Simplified the implementation of ``chunked``. (Thanks, nvie!)
|
||||
* Python 3.5 is now supported. Python 2.6 is no longer supported.
|
||||
* Python 3 is now supported directly; there is no 2to3 step.
|
||||
|
||||
2.2
|
||||
-----
|
||||
|
||||
* Added ``iterate`` and ``with_iter``. (Thanks, abarnert!)
|
||||
|
||||
2.1
|
||||
-----
|
||||
|
||||
* Added (tested!) implementations of the recipes from the itertools
|
||||
documentation. (Thanks, Chris Lonnen!)
|
||||
* Added ``ilen``. (Thanks for the inspiration, Matt Basta!)
|
||||
|
||||
2.0
|
||||
-----
|
||||
|
||||
* ``chunked`` now returns lists rather than tuples. After all, they're
|
||||
homogeneous. This slightly backward-incompatible change is the reason for
|
||||
the major version bump.
|
||||
* Added ``@consumer``.
|
||||
* Improved test machinery.
|
||||
|
||||
1.1
|
||||
-----
|
||||
|
||||
* Added ``first`` function.
|
||||
* Added Python 3 support.
|
||||
* Added a default arg to ``peekable.peek()``.
|
||||
* Noted how to easily test whether a peekable iterator is exhausted.
|
||||
* Rewrote documentation.
|
||||
|
||||
1.0
|
||||
-----
|
||||
|
||||
* Initial release, with ``collate``, ``peekable``, and ``chunked``. Could
|
||||
really use better docs.
|
||||
Keywords: itertools,iterator,iteration,filter,peek,peekable,collate,chunk,chunked
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Natural Language :: English
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.2
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Topic :: Software Development :: Libraries
|
|
@ -0,0 +1,59 @@
|
|||
==============
|
||||
More Itertools
|
||||
==============
|
||||
|
||||
.. image:: https://coveralls.io/repos/github/erikrose/more-itertools/badge.svg?branch=master
|
||||
:target: https://coveralls.io/github/erikrose/more-itertools?branch=master
|
||||
|
||||
Python's ``itertools`` library is a gem - you can compose elegant solutions
|
||||
for a variety of problems with the functions it provides. In ``more-itertools``
|
||||
we collect additional building blocks, recipes, and routines for working with
|
||||
Python iterables.
|
||||
|
||||
Getting started
|
||||
===============
|
||||
|
||||
To get started, install the library with `pip <https://pip.pypa.io/en/stable/>`_:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
pip install more-itertools
|
||||
|
||||
The recipes from the `itertools docs <https://docs.python.org/3/library/itertools.html#itertools-recipes>`_
|
||||
are included in the top-level package:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from more_itertools import flatten
|
||||
>>> iterable = [(0, 1), (2, 3)]
|
||||
>>> list(flatten(iterable))
|
||||
[0, 1, 2, 3]
|
||||
|
||||
Several new recipes are available as well:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from more_itertools import chunked
|
||||
>>> iterable = [0, 1, 2, 3, 4, 5, 6, 7, 8]
|
||||
>>> list(chunked(iterable, 3))
|
||||
[[0, 1, 2], [3, 4, 5], [6, 7, 8]]
|
||||
|
||||
>>> from more_itertools import spy
|
||||
>>> iterable = (x * x for x in range(1, 6))
|
||||
>>> head, iterable = spy(iterable, n=3)
|
||||
>>> list(head)
|
||||
[1, 4, 9]
|
||||
>>> list(iterable)
|
||||
[1, 4, 9, 16, 25]
|
||||
|
||||
|
||||
|
||||
For the full listing of functions, see the `API documentation <https://more-itertools.readthedocs.io/en/latest/api.html>`_.
|
||||
|
||||
Development
|
||||
===========
|
||||
|
||||
``more-itertools`` is maintained by `@erikrose <https://github.com/erikrose>`_
|
||||
and `@bbayles <https://github.com/bbayles>`_, with help from `many others <https://github.com/erikrose/more-itertools/graphs/contributors>`_.
|
||||
If you have a problem or suggestion, please file a bug or pull request in this
|
||||
repository. Thanks for contributing!
|
|
@ -0,0 +1,153 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/more-itertools.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/more-itertools.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/more-itertools"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/more-itertools"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
|
@ -0,0 +1,234 @@
|
|||
=============
|
||||
API Reference
|
||||
=============
|
||||
|
||||
.. automodule:: more_itertools
|
||||
|
||||
Grouping
|
||||
========
|
||||
|
||||
These tools yield groups of items from a source iterable.
|
||||
|
||||
----
|
||||
|
||||
**New itertools**
|
||||
|
||||
.. autofunction:: chunked
|
||||
.. autofunction:: sliced
|
||||
.. autofunction:: distribute
|
||||
.. autofunction:: divide
|
||||
.. autofunction:: split_at
|
||||
.. autofunction:: split_before
|
||||
.. autofunction:: split_after
|
||||
.. autofunction:: bucket
|
||||
|
||||
----
|
||||
|
||||
**Itertools recipes**
|
||||
|
||||
.. autofunction:: grouper
|
||||
.. autofunction:: partition
|
||||
|
||||
|
||||
Lookahead and lookback
|
||||
======================
|
||||
|
||||
These tools peek at an iterable's values without advancing it.
|
||||
|
||||
----
|
||||
|
||||
**New itertools**
|
||||
|
||||
|
||||
.. autofunction:: spy
|
||||
.. autoclass:: peekable
|
||||
.. autoclass:: seekable
|
||||
|
||||
|
||||
Windowing
|
||||
=========
|
||||
|
||||
These tools yield windows of items from an iterable.
|
||||
|
||||
----
|
||||
|
||||
**New itertools**
|
||||
|
||||
.. autofunction:: windowed
|
||||
.. autofunction:: stagger
|
||||
|
||||
----
|
||||
|
||||
**Itertools recipes**
|
||||
|
||||
.. autofunction:: pairwise
|
||||
|
||||
|
||||
Augmenting
|
||||
==========
|
||||
|
||||
These tools yield items from an iterable, plus additional data.
|
||||
|
||||
----
|
||||
|
||||
**New itertools**
|
||||
|
||||
.. autofunction:: count_cycle
|
||||
.. autofunction:: intersperse
|
||||
.. autofunction:: padded
|
||||
.. autofunction:: adjacent
|
||||
.. autofunction:: groupby_transform
|
||||
|
||||
----
|
||||
|
||||
**Itertools recipes**
|
||||
|
||||
.. autofunction:: padnone
|
||||
.. autofunction:: ncycles
|
||||
|
||||
|
||||
Combining
|
||||
=========
|
||||
|
||||
These tools combine multiple iterables.
|
||||
|
||||
----
|
||||
|
||||
**New itertools**
|
||||
|
||||
.. autofunction:: collapse
|
||||
.. autofunction:: sort_together
|
||||
.. autofunction:: interleave
|
||||
.. autofunction:: interleave_longest
|
||||
.. autofunction:: collate(*iterables, key=lambda a: a, reverse=False)
|
||||
.. autofunction:: zip_offset(*iterables, offsets, longest=False, fillvalue=None)
|
||||
|
||||
----
|
||||
|
||||
**Itertools recipes**
|
||||
|
||||
.. autofunction:: dotproduct
|
||||
.. autofunction:: flatten
|
||||
.. autofunction:: roundrobin
|
||||
.. autofunction:: prepend
|
||||
|
||||
|
||||
Summarizing
|
||||
===========
|
||||
|
||||
These tools return summarized or aggregated data from an iterable.
|
||||
|
||||
----
|
||||
|
||||
**New itertools**
|
||||
|
||||
.. autofunction:: ilen
|
||||
.. autofunction:: first(iterable[, default])
|
||||
.. autofunction:: one
|
||||
.. autofunction:: unique_to_each
|
||||
.. autofunction:: locate(iterable, pred=bool)
|
||||
.. autofunction:: consecutive_groups(iterable, ordering=lambda x: x)
|
||||
.. autofunction:: exactly_n(iterable, n, predicate=bool)
|
||||
.. autoclass:: run_length
|
||||
.. autofunction:: map_reduce
|
||||
|
||||
----
|
||||
|
||||
**Itertools recipes**
|
||||
|
||||
.. autofunction:: all_equal
|
||||
.. autofunction:: first_true
|
||||
.. autofunction:: nth
|
||||
.. autofunction:: quantify(iterable, pred=bool)
|
||||
|
||||
|
||||
Selecting
|
||||
=========
|
||||
|
||||
These tools yield certain items from an iterable.
|
||||
|
||||
----
|
||||
|
||||
**New itertools**
|
||||
|
||||
.. autofunction:: islice_extended(start, stop, step)
|
||||
.. autofunction:: strip
|
||||
.. autofunction:: lstrip
|
||||
.. autofunction:: rstrip
|
||||
|
||||
----
|
||||
|
||||
**Itertools recipes**
|
||||
|
||||
.. autofunction:: take
|
||||
.. autofunction:: tail
|
||||
.. autofunction:: unique_everseen
|
||||
.. autofunction:: unique_justseen
|
||||
|
||||
|
||||
Combinatorics
|
||||
=============
|
||||
|
||||
These tools yield combinatorial arrangements of items from iterables.
|
||||
|
||||
----
|
||||
|
||||
**New itertools**
|
||||
|
||||
.. autofunction:: distinct_permutations
|
||||
.. autofunction:: circular_shifts
|
||||
|
||||
----
|
||||
|
||||
**Itertools recipes**
|
||||
|
||||
.. autofunction:: powerset
|
||||
.. autofunction:: random_product
|
||||
.. autofunction:: random_permutation
|
||||
.. autofunction:: random_combination
|
||||
.. autofunction:: random_combination_with_replacement
|
||||
.. autofunction:: nth_combination
|
||||
|
||||
|
||||
Wrapping
|
||||
========
|
||||
|
||||
These tools provide wrappers to smooth working with objects that produce or
|
||||
consume iterables.
|
||||
|
||||
----
|
||||
|
||||
**New itertools**
|
||||
|
||||
.. autofunction:: always_iterable
|
||||
.. autofunction:: consumer
|
||||
.. autofunction:: with_iter
|
||||
|
||||
----
|
||||
|
||||
**Itertools recipes**
|
||||
|
||||
.. autofunction:: iter_except
|
||||
|
||||
|
||||
Others
|
||||
======
|
||||
|
||||
**New itertools**
|
||||
|
||||
.. autofunction:: numeric_range(start, stop, step)
|
||||
.. autofunction:: always_reversible
|
||||
.. autofunction:: side_effect
|
||||
.. autofunction:: iterate
|
||||
.. autofunction:: difference(iterable, func=operator.sub)
|
||||
.. autofunction:: make_decorator
|
||||
.. autoclass:: SequenceView
|
||||
|
||||
----
|
||||
|
||||
**Itertools recipes**
|
||||
|
||||
.. autofunction:: consume
|
||||
.. autofunction:: accumulate(iterable, func=operator.add)
|
||||
.. autofunction:: tabulate
|
||||
.. autofunction:: repeatfunc
|
|
@ -0,0 +1,244 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# more-itertools documentation build configuration file, created by
|
||||
# sphinx-quickstart on Mon Jun 25 20:42:39 2012.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
import sphinx_rtd_theme
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'more-itertools'
|
||||
copyright = u'2012, Erik Rose'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '4.2.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'more-itertoolsdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'more-itertools.tex', u'more-itertools Documentation',
|
||||
u'Erik Rose', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'more-itertools', u'more-itertools Documentation',
|
||||
[u'Erik Rose'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'more-itertools', u'more-itertools Documentation',
|
||||
u'Erik Rose', 'more-itertools', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
|
@ -0,0 +1,16 @@
|
|||
.. include:: ../README.rst
|
||||
|
||||
Contents
|
||||
========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
license
|
||||
testing
|
||||
versions
|
|
@ -0,0 +1,16 @@
|
|||
=======
|
||||
License
|
||||
=======
|
||||
|
||||
more-itertools is under the MIT License. See the LICENSE file.
|
||||
|
||||
Conditions for Contributors
|
||||
===========================
|
||||
|
||||
By contributing to this software project, you are agreeing to the following
|
||||
terms and conditions for your contributions: First, you agree your
|
||||
contributions are submitted under the MIT license. Second, you represent you
|
||||
are authorized to make the contributions and grant the license. If your
|
||||
employer has rights to intellectual property that includes your contributions,
|
||||
you represent that you have received permission to make contributions and grant
|
||||
the required license on behalf of that employer.
|
|
@ -0,0 +1,190 @@
|
|||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\more-itertools.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\more-itertools.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
|
@ -0,0 +1,19 @@
|
|||
=======
|
||||
Testing
|
||||
=======
|
||||
|
||||
To run install dependencies and run tests, use this command::
|
||||
|
||||
python setup.py test
|
||||
|
||||
Multiple Python Versions
|
||||
========================
|
||||
|
||||
To run the tests on all the versions of Python more-itertools supports, install
|
||||
tox::
|
||||
|
||||
pip install tox
|
||||
|
||||
Then, run the tests::
|
||||
|
||||
tox
|
|
@ -0,0 +1,237 @@
|
|||
===============
|
||||
Version History
|
||||
===============
|
||||
|
||||
.. automodule:: more_itertools
|
||||
|
||||
4.2.0
|
||||
-----
|
||||
|
||||
* New itertools:
|
||||
* :func:`map_reduce` (thanks to pylang)
|
||||
* :func:`prepend` (from the `Python 3.7 docs <https://docs.python.org/3.7/library/itertools.html#itertools-recipes>`_)
|
||||
|
||||
* Improvements to existing itertools:
|
||||
* :func:`bucket` now complies with PEP 479 (thanks to irmen)
|
||||
|
||||
* Other changes:
|
||||
* Python 3.7 is now supported (thanks to irmen)
|
||||
* Python 3.3 is no longer supported
|
||||
* The test suite no longer requires third-party modules to run
|
||||
* The API docs now include links to source code
|
||||
|
||||
4.1.0
|
||||
-----
|
||||
|
||||
* New itertools:
|
||||
* :func:`split_at` (thanks to michael-celani)
|
||||
* :func:`circular_shifts` (thanks to hiqua)
|
||||
* :func:`make_decorator` - see the blog post `Yo, I heard you like decorators <https://sites.google.com/site/bbayles/index/decorator_factory>`_
|
||||
for a tour (thanks to pylang)
|
||||
* :func:`always_reversible` (thanks to michael-celani)
|
||||
* :func:`nth_combination` (from the `Python 3.7 docs <https://docs.python.org/3.7/library/itertools.html#itertools-recipes>`_)
|
||||
|
||||
* Improvements to existing itertools:
|
||||
* :func:`seekable` now has an ``elements`` method to return cached items.
|
||||
* The performance tradeoffs between :func:`roundrobin` and
|
||||
:func:`interleave_longest` are now documented (thanks michael-celani,
|
||||
pylang, and MSeifert04)
|
||||
|
||||
4.0.1
|
||||
-----
|
||||
|
||||
* No code changes - this release fixes how the docs display on PyPI.
|
||||
|
||||
4.0.0
|
||||
-----
|
||||
|
||||
* New itertools:
|
||||
* :func:`consecutive_groups` (Based on the example in the `Python 2.4 docs <https://docs.python.org/release/2.4.4/lib/itertools-example.html>`_)
|
||||
* :func:`seekable` (If you're looking for how to "reset" an iterator,
|
||||
you're in luck!)
|
||||
* :func:`exactly_n` (thanks to michael-celani)
|
||||
* :func:`run_length.encode` and :func:`run_length.decode`
|
||||
* :func:`difference`
|
||||
|
||||
* Improvements to existing itertools:
|
||||
* The number of items between filler elements in :func:`intersperse` can
|
||||
now be specified (thanks to pylang)
|
||||
* :func:`distinct_permutations` and :func:`peekable` got some minor
|
||||
adjustments (thanks to MSeifert04)
|
||||
* :func:`always_iterable` now returns an iterator object. It also now
|
||||
allows different types to be considered iterable (thanks to jaraco)
|
||||
* :func:`bucket` can now limit the keys it stores in memory
|
||||
* :func:`one` now allows for custom exceptions (thanks to kalekundert)
|
||||
|
||||
* Other changes:
|
||||
* A few typos were fixed (thanks to EdwardBetts)
|
||||
* All tests can now be run with ``python setup.py test``
|
||||
|
||||
The major version update is due to the change in the return value of :func:`always_iterable`.
|
||||
It now always returns iterator objects:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from more_itertools import always_iterable
|
||||
# Non-iterable objects are wrapped with iter(tuple(obj))
|
||||
>>> always_iterable(12345)
|
||||
<tuple_iterator object at 0x7fb24c9488d0>
|
||||
>>> list(always_iterable(12345))
|
||||
[12345]
|
||||
# Iterable objects are wrapped with iter()
|
||||
>>> always_iterable([1, 2, 3, 4, 5])
|
||||
<list_iterator object at 0x7fb24c948c50>
|
||||
|
||||
3.2.0
|
||||
-----
|
||||
|
||||
* New itertools:
|
||||
* :func:`lstrip`, :func:`rstrip`, and :func:`strip`
|
||||
(thanks to MSeifert04 and pylang)
|
||||
* :func:`islice_extended`
|
||||
* Improvements to existing itertools:
|
||||
* Some bugs with slicing :func:`peekable`-wrapped iterables were fixed
|
||||
|
||||
3.1.0
|
||||
-----
|
||||
|
||||
* New itertools:
|
||||
* :func:`numeric_range` (Thanks to BebeSparkelSparkel and MSeifert04)
|
||||
* :func:`count_cycle` (Thanks to BebeSparkelSparkel)
|
||||
* :func:`locate` (Thanks to pylang and MSeifert04)
|
||||
* Improvements to existing itertools:
|
||||
* A few itertools are now slightly faster due to some function
|
||||
optimizations. (Thanks to MSeifert04)
|
||||
* The docs have been substantially revised with installation notes,
|
||||
categories for library functions, links, and more. (Thanks to pylang)
|
||||
|
||||
|
||||
3.0.0
|
||||
-----
|
||||
|
||||
* Removed itertools:
|
||||
* ``context`` has been removed due to a design flaw - see below for
|
||||
replacement options. (thanks to NeilGirdhar)
|
||||
* Improvements to existing itertools:
|
||||
* ``side_effect`` now supports ``before`` and ``after`` keyword
|
||||
arguments. (Thanks to yardsale8)
|
||||
* PyPy and PyPy3 are now supported.
|
||||
|
||||
The major version change is due to the removal of the ``context`` function.
|
||||
Replace it with standard ``with`` statement context management:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Don't use context() anymore
|
||||
file_obj = StringIO()
|
||||
consume(print(x, file=f) for f in context(file_obj) for x in u'123')
|
||||
|
||||
# Use a with statement instead
|
||||
file_obj = StringIO()
|
||||
with file_obj as f:
|
||||
consume(print(x, file=f) for x in u'123')
|
||||
|
||||
2.6.0
|
||||
-----
|
||||
|
||||
* New itertools:
|
||||
* ``adjacent`` and ``groupby_transform`` (Thanks to diazona)
|
||||
* ``always_iterable`` (Thanks to jaraco)
|
||||
* (Removed in 3.0.0) ``context`` (Thanks to yardsale8)
|
||||
* ``divide`` (Thanks to mozbhearsum)
|
||||
* Improvements to existing itertools:
|
||||
* ``ilen`` is now slightly faster. (Thanks to wbolster)
|
||||
* ``peekable`` can now prepend items to an iterable. (Thanks to diazona)
|
||||
|
||||
2.5.0
|
||||
-----
|
||||
|
||||
* New itertools:
|
||||
* ``distribute`` (Thanks to mozbhearsum and coady)
|
||||
* ``sort_together`` (Thanks to clintval)
|
||||
* ``stagger`` and ``zip_offset`` (Thanks to joshbode)
|
||||
* ``padded``
|
||||
* Improvements to existing itertools:
|
||||
* ``peekable`` now handles negative indexes and slices with negative
|
||||
components properly.
|
||||
* ``intersperse`` is now slightly faster. (Thanks to pylang)
|
||||
* ``windowed`` now accepts a ``step`` keyword argument.
|
||||
(Thanks to pylang)
|
||||
* Python 3.6 is now supported.
|
||||
|
||||
2.4.1
|
||||
-----
|
||||
|
||||
* Move docs 100% to readthedocs.io.
|
||||
|
||||
2.4
|
||||
-----
|
||||
|
||||
* New itertools:
|
||||
* ``accumulate``, ``all_equal``, ``first_true``, ``partition``, and
|
||||
``tail`` from the itertools documentation.
|
||||
* ``bucket`` (Thanks to Rosuav and cvrebert)
|
||||
* ``collapse`` (Thanks to abarnet)
|
||||
* ``interleave`` and ``interleave_longest`` (Thanks to abarnet)
|
||||
* ``side_effect`` (Thanks to nvie)
|
||||
* ``sliced`` (Thanks to j4mie and coady)
|
||||
* ``split_before`` and ``split_after`` (Thanks to astronouth7303)
|
||||
* ``spy`` (Thanks to themiurgo and mathieulongtin)
|
||||
* Improvements to existing itertools:
|
||||
* ``chunked`` is now simpler and more friendly to garbage collection.
|
||||
(Contributed by coady, with thanks to piskvorky)
|
||||
* ``collate`` now delegates to ``heapq.merge`` when possible.
|
||||
(Thanks to kmike and julianpistorius)
|
||||
* ``peekable``-wrapped iterables are now indexable and sliceable.
|
||||
Iterating through ``peekable``-wrapped iterables is also faster.
|
||||
* ``one`` and ``unique_to_each`` have been simplified.
|
||||
(Thanks to coady)
|
||||
|
||||
|
||||
2.3
|
||||
-----
|
||||
|
||||
* Added ``one`` from ``jaraco.util.itertools``. (Thanks, jaraco!)
|
||||
* Added ``distinct_permutations`` and ``unique_to_each``. (Contributed by
|
||||
bbayles)
|
||||
* Added ``windowed``. (Contributed by bbayles, with thanks to buchanae,
|
||||
jaraco, and abarnert)
|
||||
* Simplified the implementation of ``chunked``. (Thanks, nvie!)
|
||||
* Python 3.5 is now supported. Python 2.6 is no longer supported.
|
||||
* Python 3 is now supported directly; there is no 2to3 step.
|
||||
|
||||
2.2
|
||||
-----
|
||||
|
||||
* Added ``iterate`` and ``with_iter``. (Thanks, abarnert!)
|
||||
|
||||
2.1
|
||||
-----
|
||||
|
||||
* Added (tested!) implementations of the recipes from the itertools
|
||||
documentation. (Thanks, Chris Lonnen!)
|
||||
* Added ``ilen``. (Thanks for the inspiration, Matt Basta!)
|
||||
|
||||
2.0
|
||||
-----
|
||||
|
||||
* ``chunked`` now returns lists rather than tuples. After all, they're
|
||||
homogeneous. This slightly backward-incompatible change is the reason for
|
||||
the major version bump.
|
||||
* Added ``@consumer``.
|
||||
* Improved test machinery.
|
||||
|
||||
1.1
|
||||
-----
|
||||
|
||||
* Added ``first`` function.
|
||||
* Added Python 3 support.
|
||||
* Added a default arg to ``peekable.peek()``.
|
||||
* Noted how to easily test whether a peekable iterator is exhausted.
|
||||
* Rewrote documentation.
|
||||
|
||||
1.0
|
||||
-----
|
||||
|
||||
* Initial release, with ``collate``, ``peekable``, and ``chunked``. Could
|
||||
really use better docs.
|
|
@ -0,0 +1,2 @@
|
|||
from more_itertools.more import * # noqa
|
||||
from more_itertools.recipes import * # noqa
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,565 @@
|
|||
"""Imported from the recipes section of the itertools documentation.
|
||||
|
||||
All functions taken from the recipes section of the itertools library docs
|
||||
[1]_.
|
||||
Some backward-compatible usability improvements have been made.
|
||||
|
||||
.. [1] http://docs.python.org/library/itertools.html#recipes
|
||||
|
||||
"""
|
||||
from collections import deque
|
||||
from itertools import (
|
||||
chain, combinations, count, cycle, groupby, islice, repeat, starmap, tee
|
||||
)
|
||||
import operator
|
||||
from random import randrange, sample, choice
|
||||
|
||||
from six import PY2
|
||||
from six.moves import filter, filterfalse, map, range, zip, zip_longest
|
||||
|
||||
__all__ = [
|
||||
'accumulate',
|
||||
'all_equal',
|
||||
'consume',
|
||||
'dotproduct',
|
||||
'first_true',
|
||||
'flatten',
|
||||
'grouper',
|
||||
'iter_except',
|
||||
'ncycles',
|
||||
'nth',
|
||||
'nth_combination',
|
||||
'padnone',
|
||||
'pairwise',
|
||||
'partition',
|
||||
'powerset',
|
||||
'prepend',
|
||||
'quantify',
|
||||
'random_combination_with_replacement',
|
||||
'random_combination',
|
||||
'random_permutation',
|
||||
'random_product',
|
||||
'repeatfunc',
|
||||
'roundrobin',
|
||||
'tabulate',
|
||||
'tail',
|
||||
'take',
|
||||
'unique_everseen',
|
||||
'unique_justseen',
|
||||
]
|
||||
|
||||
|
||||
def accumulate(iterable, func=operator.add):
|
||||
"""
|
||||
Return an iterator whose items are the accumulated results of a function
|
||||
(specified by the optional *func* argument) that takes two arguments.
|
||||
By default, returns accumulated sums with :func:`operator.add`.
|
||||
|
||||
>>> list(accumulate([1, 2, 3, 4, 5])) # Running sum
|
||||
[1, 3, 6, 10, 15]
|
||||
>>> list(accumulate([1, 2, 3], func=operator.mul)) # Running product
|
||||
[1, 2, 6]
|
||||
>>> list(accumulate([0, 1, -1, 2, 3, 2], func=max)) # Running maximum
|
||||
[0, 1, 1, 2, 3, 3]
|
||||
|
||||
This function is available in the ``itertools`` module for Python 3.2 and
|
||||
greater.
|
||||
|
||||
"""
|
||||
it = iter(iterable)
|
||||
try:
|
||||
total = next(it)
|
||||
except StopIteration:
|
||||
return
|
||||
else:
|
||||
yield total
|
||||
|
||||
for element in it:
|
||||
total = func(total, element)
|
||||
yield total
|
||||
|
||||
|
||||
def take(n, iterable):
|
||||
"""Return first *n* items of the iterable as a list.
|
||||
|
||||
>>> take(3, range(10))
|
||||
[0, 1, 2]
|
||||
>>> take(5, range(3))
|
||||
[0, 1, 2]
|
||||
|
||||
Effectively a short replacement for ``next`` based iterator consumption
|
||||
when you want more than one item, but less than the whole iterator.
|
||||
|
||||
"""
|
||||
return list(islice(iterable, n))
|
||||
|
||||
|
||||
def tabulate(function, start=0):
|
||||
"""Return an iterator over the results of ``func(start)``,
|
||||
``func(start + 1)``, ``func(start + 2)``...
|
||||
|
||||
*func* should be a function that accepts one integer argument.
|
||||
|
||||
If *start* is not specified it defaults to 0. It will be incremented each
|
||||
time the iterator is advanced.
|
||||
|
||||
>>> square = lambda x: x ** 2
|
||||
>>> iterator = tabulate(square, -3)
|
||||
>>> take(4, iterator)
|
||||
[9, 4, 1, 0]
|
||||
|
||||
"""
|
||||
return map(function, count(start))
|
||||
|
||||
|
||||
def tail(n, iterable):
|
||||
"""Return an iterator over the last *n* items of *iterable*.
|
||||
|
||||
>>> t = tail(3, 'ABCDEFG')
|
||||
>>> list(t)
|
||||
['E', 'F', 'G']
|
||||
|
||||
"""
|
||||
return iter(deque(iterable, maxlen=n))
|
||||
|
||||
|
||||
def consume(iterator, n=None):
|
||||
"""Advance *iterable* by *n* steps. If *n* is ``None``, consume it
|
||||
entirely.
|
||||
|
||||
Efficiently exhausts an iterator without returning values. Defaults to
|
||||
consuming the whole iterator, but an optional second argument may be
|
||||
provided to limit consumption.
|
||||
|
||||
>>> i = (x for x in range(10))
|
||||
>>> next(i)
|
||||
0
|
||||
>>> consume(i, 3)
|
||||
>>> next(i)
|
||||
4
|
||||
>>> consume(i)
|
||||
>>> next(i)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
StopIteration
|
||||
|
||||
If the iterator has fewer items remaining than the provided limit, the
|
||||
whole iterator will be consumed.
|
||||
|
||||
>>> i = (x for x in range(3))
|
||||
>>> consume(i, 5)
|
||||
>>> next(i)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
StopIteration
|
||||
|
||||
"""
|
||||
# Use functions that consume iterators at C speed.
|
||||
if n is None:
|
||||
# feed the entire iterator into a zero-length deque
|
||||
deque(iterator, maxlen=0)
|
||||
else:
|
||||
# advance to the empty slice starting at position n
|
||||
next(islice(iterator, n, n), None)
|
||||
|
||||
|
||||
def nth(iterable, n, default=None):
|
||||
"""Returns the nth item or a default value.
|
||||
|
||||
>>> l = range(10)
|
||||
>>> nth(l, 3)
|
||||
3
|
||||
>>> nth(l, 20, "zebra")
|
||||
'zebra'
|
||||
|
||||
"""
|
||||
return next(islice(iterable, n, None), default)
|
||||
|
||||
|
||||
def all_equal(iterable):
|
||||
"""
|
||||
Returns ``True`` if all the elements are equal to each other.
|
||||
|
||||
>>> all_equal('aaaa')
|
||||
True
|
||||
>>> all_equal('aaab')
|
||||
False
|
||||
|
||||
"""
|
||||
g = groupby(iterable)
|
||||
return next(g, True) and not next(g, False)
|
||||
|
||||
|
||||
def quantify(iterable, pred=bool):
|
||||
"""Return the how many times the predicate is true.
|
||||
|
||||
>>> quantify([True, False, True])
|
||||
2
|
||||
|
||||
"""
|
||||
return sum(map(pred, iterable))
|
||||
|
||||
|
||||
def padnone(iterable):
|
||||
"""Returns the sequence of elements and then returns ``None`` indefinitely.
|
||||
|
||||
>>> take(5, padnone(range(3)))
|
||||
[0, 1, 2, None, None]
|
||||
|
||||
Useful for emulating the behavior of the built-in :func:`map` function.
|
||||
|
||||
See also :func:`padded`.
|
||||
|
||||
"""
|
||||
return chain(iterable, repeat(None))
|
||||
|
||||
|
||||
def ncycles(iterable, n):
|
||||
"""Returns the sequence elements *n* times
|
||||
|
||||
>>> list(ncycles(["a", "b"], 3))
|
||||
['a', 'b', 'a', 'b', 'a', 'b']
|
||||
|
||||
"""
|
||||
return chain.from_iterable(repeat(tuple(iterable), n))
|
||||
|
||||
|
||||
def dotproduct(vec1, vec2):
|
||||
"""Returns the dot product of the two iterables.
|
||||
|
||||
>>> dotproduct([10, 10], [20, 20])
|
||||
400
|
||||
|
||||
"""
|
||||
return sum(map(operator.mul, vec1, vec2))
|
||||
|
||||
|
||||
def flatten(listOfLists):
|
||||
"""Return an iterator flattening one level of nesting in a list of lists.
|
||||
|
||||
>>> list(flatten([[0, 1], [2, 3]]))
|
||||
[0, 1, 2, 3]
|
||||
|
||||
See also :func:`collapse`, which can flatten multiple levels of nesting.
|
||||
|
||||
"""
|
||||
return chain.from_iterable(listOfLists)
|
||||
|
||||
|
||||
def repeatfunc(func, times=None, *args):
|
||||
"""Call *func* with *args* repeatedly, returning an iterable over the
|
||||
results.
|
||||
|
||||
If *times* is specified, the iterable will terminate after that many
|
||||
repetitions:
|
||||
|
||||
>>> from operator import add
|
||||
>>> times = 4
|
||||
>>> args = 3, 5
|
||||
>>> list(repeatfunc(add, times, *args))
|
||||
[8, 8, 8, 8]
|
||||
|
||||
If *times* is ``None`` the iterable will not terminate:
|
||||
|
||||
>>> from random import randrange
|
||||
>>> times = None
|
||||
>>> args = 1, 11
|
||||
>>> take(6, repeatfunc(randrange, times, *args)) # doctest:+SKIP
|
||||
[2, 4, 8, 1, 8, 4]
|
||||
|
||||
"""
|
||||
if times is None:
|
||||
return starmap(func, repeat(args))
|
||||
return starmap(func, repeat(args, times))
|
||||
|
||||
|
||||
def pairwise(iterable):
|
||||
"""Returns an iterator of paired items, overlapping, from the original
|
||||
|
||||
>>> take(4, pairwise(count()))
|
||||
[(0, 1), (1, 2), (2, 3), (3, 4)]
|
||||
|
||||
"""
|
||||
a, b = tee(iterable)
|
||||
next(b, None)
|
||||
return zip(a, b)
|
||||
|
||||
|
||||
def grouper(n, iterable, fillvalue=None):
|
||||
"""Collect data into fixed-length chunks or blocks.
|
||||
|
||||
>>> list(grouper(3, 'ABCDEFG', 'x'))
|
||||
[('A', 'B', 'C'), ('D', 'E', 'F'), ('G', 'x', 'x')]
|
||||
|
||||
"""
|
||||
args = [iter(iterable)] * n
|
||||
return zip_longest(fillvalue=fillvalue, *args)
|
||||
|
||||
|
||||
def roundrobin(*iterables):
|
||||
"""Yields an item from each iterable, alternating between them.
|
||||
|
||||
>>> list(roundrobin('ABC', 'D', 'EF'))
|
||||
['A', 'D', 'E', 'B', 'F', 'C']
|
||||
|
||||
This function produces the same output as :func:`interleave_longest`, but
|
||||
may perform better for some inputs (in particular when the number of
|
||||
iterables is small).
|
||||
|
||||
"""
|
||||
# Recipe credited to George Sakkis
|
||||
pending = len(iterables)
|
||||
if PY2:
|
||||
nexts = cycle(iter(it).next for it in iterables)
|
||||
else:
|
||||
nexts = cycle(iter(it).__next__ for it in iterables)
|
||||
while pending:
|
||||
try:
|
||||
for next in nexts:
|
||||
yield next()
|
||||
except StopIteration:
|
||||
pending -= 1
|
||||
nexts = cycle(islice(nexts, pending))
|
||||
|
||||
|
||||
def partition(pred, iterable):
|
||||
"""
|
||||
Returns a 2-tuple of iterables derived from the input iterable.
|
||||
The first yields the items that have ``pred(item) == False``.
|
||||
The second yields the items that have ``pred(item) == True``.
|
||||
|
||||
>>> is_odd = lambda x: x % 2 != 0
|
||||
>>> iterable = range(10)
|
||||
>>> even_items, odd_items = partition(is_odd, iterable)
|
||||
>>> list(even_items), list(odd_items)
|
||||
([0, 2, 4, 6, 8], [1, 3, 5, 7, 9])
|
||||
|
||||
"""
|
||||
# partition(is_odd, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9
|
||||
t1, t2 = tee(iterable)
|
||||
return filterfalse(pred, t1), filter(pred, t2)
|
||||
|
||||
|
||||
def powerset(iterable):
|
||||
"""Yields all possible subsets of the iterable.
|
||||
|
||||
>>> list(powerset([1,2,3]))
|
||||
[(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)]
|
||||
|
||||
"""
|
||||
s = list(iterable)
|
||||
return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1))
|
||||
|
||||
|
||||
def unique_everseen(iterable, key=None):
|
||||
"""
|
||||
Yield unique elements, preserving order.
|
||||
|
||||
>>> list(unique_everseen('AAAABBBCCDAABBB'))
|
||||
['A', 'B', 'C', 'D']
|
||||
>>> list(unique_everseen('ABBCcAD', str.lower))
|
||||
['A', 'B', 'C', 'D']
|
||||
|
||||
Sequences with a mix of hashable and unhashable items can be used.
|
||||
The function will be slower (i.e., `O(n^2)`) for unhashable items.
|
||||
|
||||
"""
|
||||
seenset = set()
|
||||
seenset_add = seenset.add
|
||||
seenlist = []
|
||||
seenlist_add = seenlist.append
|
||||
if key is None:
|
||||
for element in iterable:
|
||||
try:
|
||||
if element not in seenset:
|
||||
seenset_add(element)
|
||||
yield element
|
||||
except TypeError:
|
||||
if element not in seenlist:
|
||||
seenlist_add(element)
|
||||
yield element
|
||||
else:
|
||||
for element in iterable:
|
||||
k = key(element)
|
||||
try:
|
||||
if k not in seenset:
|
||||
seenset_add(k)
|
||||
yield element
|
||||
except TypeError:
|
||||
if k not in seenlist:
|
||||
seenlist_add(k)
|
||||
yield element
|
||||
|
||||
|
||||
def unique_justseen(iterable, key=None):
|
||||
"""Yields elements in order, ignoring serial duplicates
|
||||
|
||||
>>> list(unique_justseen('AAAABBBCCDAABBB'))
|
||||
['A', 'B', 'C', 'D', 'A', 'B']
|
||||
>>> list(unique_justseen('ABBCcAD', str.lower))
|
||||
['A', 'B', 'C', 'A', 'D']
|
||||
|
||||
"""
|
||||
return map(next, map(operator.itemgetter(1), groupby(iterable, key)))
|
||||
|
||||
|
||||
def iter_except(func, exception, first=None):
|
||||
"""Yields results from a function repeatedly until an exception is raised.
|
||||
|
||||
Converts a call-until-exception interface to an iterator interface.
|
||||
Like ``iter(func, sentinel)``, but uses an exception instead of a sentinel
|
||||
to end the loop.
|
||||
|
||||
>>> l = [0, 1, 2]
|
||||
>>> list(iter_except(l.pop, IndexError))
|
||||
[2, 1, 0]
|
||||
|
||||
"""
|
||||
try:
|
||||
if first is not None:
|
||||
yield first()
|
||||
while 1:
|
||||
yield func()
|
||||
except exception:
|
||||
pass
|
||||
|
||||
|
||||
def first_true(iterable, default=False, pred=None):
|
||||
"""
|
||||
Returns the first true value in the iterable.
|
||||
|
||||
If no true value is found, returns *default*
|
||||
|
||||
If *pred* is not None, returns the first item for which
|
||||
``pred(item) == True`` .
|
||||
|
||||
>>> first_true(range(10))
|
||||
1
|
||||
>>> first_true(range(10), pred=lambda x: x > 5)
|
||||
6
|
||||
>>> first_true(range(10), default='missing', pred=lambda x: x > 9)
|
||||
'missing'
|
||||
|
||||
"""
|
||||
return next(filter(pred, iterable), default)
|
||||
|
||||
|
||||
def random_product(*args, **kwds):
|
||||
"""Draw an item at random from each of the input iterables.
|
||||
|
||||
>>> random_product('abc', range(4), 'XYZ') # doctest:+SKIP
|
||||
('c', 3, 'Z')
|
||||
|
||||
If *repeat* is provided as a keyword argument, that many items will be
|
||||
drawn from each iterable.
|
||||
|
||||
>>> random_product('abcd', range(4), repeat=2) # doctest:+SKIP
|
||||
('a', 2, 'd', 3)
|
||||
|
||||
This equivalent to taking a random selection from
|
||||
``itertools.product(*args, **kwarg)``.
|
||||
|
||||
"""
|
||||
pools = [tuple(pool) for pool in args] * kwds.get('repeat', 1)
|
||||
return tuple(choice(pool) for pool in pools)
|
||||
|
||||
|
||||
def random_permutation(iterable, r=None):
|
||||
"""Return a random *r* length permutation of the elements in *iterable*.
|
||||
|
||||
If *r* is not specified or is ``None``, then *r* defaults to the length of
|
||||
*iterable*.
|
||||
|
||||
>>> random_permutation(range(5)) # doctest:+SKIP
|
||||
(3, 4, 0, 1, 2)
|
||||
|
||||
This equivalent to taking a random selection from
|
||||
``itertools.permutations(iterable, r)``.
|
||||
|
||||
"""
|
||||
pool = tuple(iterable)
|
||||
r = len(pool) if r is None else r
|
||||
return tuple(sample(pool, r))
|
||||
|
||||
|
||||
def random_combination(iterable, r):
|
||||
"""Return a random *r* length subsequence of the elements in *iterable*.
|
||||
|
||||
>>> random_combination(range(5), 3) # doctest:+SKIP
|
||||
(2, 3, 4)
|
||||
|
||||
This equivalent to taking a random selection from
|
||||
``itertools.combinations(iterable, r)``.
|
||||
|
||||
"""
|
||||
pool = tuple(iterable)
|
||||
n = len(pool)
|
||||
indices = sorted(sample(range(n), r))
|
||||
return tuple(pool[i] for i in indices)
|
||||
|
||||
|
||||
def random_combination_with_replacement(iterable, r):
|
||||
"""Return a random *r* length subsequence of elements in *iterable*,
|
||||
allowing individual elements to be repeated.
|
||||
|
||||
>>> random_combination_with_replacement(range(3), 5) # doctest:+SKIP
|
||||
(0, 0, 1, 2, 2)
|
||||
|
||||
This equivalent to taking a random selection from
|
||||
``itertools.combinations_with_replacement(iterable, r)``.
|
||||
|
||||
"""
|
||||
pool = tuple(iterable)
|
||||
n = len(pool)
|
||||
indices = sorted(randrange(n) for i in range(r))
|
||||
return tuple(pool[i] for i in indices)
|
||||
|
||||
|
||||
def nth_combination(iterable, r, index):
|
||||
"""Equivalent to ``list(combinations(iterable, r))[index]``.
|
||||
|
||||
The subsequences of *iterable* that are of length *r* can be ordered
|
||||
lexicographically. :func:`nth_combination` computes the subsequence at
|
||||
sort position *index* directly, without computing the previous
|
||||
subsequences.
|
||||
|
||||
"""
|
||||
pool = tuple(iterable)
|
||||
n = len(pool)
|
||||
if (r < 0) or (r > n):
|
||||
raise ValueError
|
||||
|
||||
c = 1
|
||||
k = min(r, n - r)
|
||||
for i in range(1, k + 1):
|
||||
c = c * (n - k + i) // i
|
||||
|
||||
if index < 0:
|
||||
index += c
|
||||
|
||||
if (index < 0) or (index >= c):
|
||||
raise IndexError
|
||||
|
||||
result = []
|
||||
while r:
|
||||
c, n, r = c * r // n, n - 1, r - 1
|
||||
while index >= c:
|
||||
index -= c
|
||||
c, n = c * (n - r) // n, n - 1
|
||||
result.append(pool[-1 - n])
|
||||
|
||||
return tuple(result)
|
||||
|
||||
|
||||
def prepend(value, iterator):
|
||||
"""Yield *value*, followed by the elements in *iterator*.
|
||||
|
||||
>>> value = '0'
|
||||
>>> iterator = ['1', '2', '3']
|
||||
>>> list(prepend(value, iterator))
|
||||
['0', '1', '2', '3']
|
||||
|
||||
To prepend multiple values, see :func:`itertools.chain`.
|
||||
|
||||
"""
|
||||
return chain([value], iterator)
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,607 @@
|
|||
from doctest import DocTestSuite
|
||||
from unittest import TestCase
|
||||
|
||||
from itertools import combinations
|
||||
from six.moves import range
|
||||
|
||||
import more_itertools as mi
|
||||
|
||||
|
||||
def load_tests(loader, tests, ignore):
|
||||
# Add the doctests
|
||||
tests.addTests(DocTestSuite('more_itertools.recipes'))
|
||||
return tests
|
||||
|
||||
|
||||
class AccumulateTests(TestCase):
|
||||
"""Tests for ``accumulate()``"""
|
||||
|
||||
def test_empty(self):
|
||||
"""Test that an empty input returns an empty output"""
|
||||
self.assertEqual(list(mi.accumulate([])), [])
|
||||
|
||||
def test_default(self):
|
||||
"""Test accumulate with the default function (addition)"""
|
||||
self.assertEqual(list(mi.accumulate([1, 2, 3])), [1, 3, 6])
|
||||
|
||||
def test_bogus_function(self):
|
||||
"""Test accumulate with an invalid function"""
|
||||
with self.assertRaises(TypeError):
|
||||
list(mi.accumulate([1, 2, 3], func=lambda x: x))
|
||||
|
||||
def test_custom_function(self):
|
||||
"""Test accumulate with a custom function"""
|
||||
self.assertEqual(
|
||||
list(mi.accumulate((1, 2, 3, 2, 1), func=max)), [1, 2, 3, 3, 3]
|
||||
)
|
||||
|
||||
|
||||
class TakeTests(TestCase):
|
||||
"""Tests for ``take()``"""
|
||||
|
||||
def test_simple_take(self):
|
||||
"""Test basic usage"""
|
||||
t = mi.take(5, range(10))
|
||||
self.assertEqual(t, [0, 1, 2, 3, 4])
|
||||
|
||||
def test_null_take(self):
|
||||
"""Check the null case"""
|
||||
t = mi.take(0, range(10))
|
||||
self.assertEqual(t, [])
|
||||
|
||||
def test_negative_take(self):
|
||||
"""Make sure taking negative items results in a ValueError"""
|
||||
self.assertRaises(ValueError, lambda: mi.take(-3, range(10)))
|
||||
|
||||
def test_take_too_much(self):
|
||||
"""Taking more than an iterator has remaining should return what the
|
||||
iterator has remaining.
|
||||
|
||||
"""
|
||||
t = mi.take(10, range(5))
|
||||
self.assertEqual(t, [0, 1, 2, 3, 4])
|
||||
|
||||
|
||||
class TabulateTests(TestCase):
|
||||
"""Tests for ``tabulate()``"""
|
||||
|
||||
def test_simple_tabulate(self):
|
||||
"""Test the happy path"""
|
||||
t = mi.tabulate(lambda x: x)
|
||||
f = tuple([next(t) for _ in range(3)])
|
||||
self.assertEqual(f, (0, 1, 2))
|
||||
|
||||
def test_count(self):
|
||||
"""Ensure tabulate accepts specific count"""
|
||||
t = mi.tabulate(lambda x: 2 * x, -1)
|
||||
f = (next(t), next(t), next(t))
|
||||
self.assertEqual(f, (-2, 0, 2))
|
||||
|
||||
|
||||
class TailTests(TestCase):
|
||||
"""Tests for ``tail()``"""
|
||||
|
||||
def test_greater(self):
|
||||
"""Length of iterable is greather than requested tail"""
|
||||
self.assertEqual(list(mi.tail(3, 'ABCDEFG')), ['E', 'F', 'G'])
|
||||
|
||||
def test_equal(self):
|
||||
"""Length of iterable is equal to the requested tail"""
|
||||
self.assertEqual(
|
||||
list(mi.tail(7, 'ABCDEFG')), ['A', 'B', 'C', 'D', 'E', 'F', 'G']
|
||||
)
|
||||
|
||||
def test_less(self):
|
||||
"""Length of iterable is less than requested tail"""
|
||||
self.assertEqual(
|
||||
list(mi.tail(8, 'ABCDEFG')), ['A', 'B', 'C', 'D', 'E', 'F', 'G']
|
||||
)
|
||||
|
||||
|
||||
class ConsumeTests(TestCase):
|
||||
"""Tests for ``consume()``"""
|
||||
|
||||
def test_sanity(self):
|
||||
"""Test basic functionality"""
|
||||
r = (x for x in range(10))
|
||||
mi.consume(r, 3)
|
||||
self.assertEqual(3, next(r))
|
||||
|
||||
def test_null_consume(self):
|
||||
"""Check the null case"""
|
||||
r = (x for x in range(10))
|
||||
mi.consume(r, 0)
|
||||
self.assertEqual(0, next(r))
|
||||
|
||||
def test_negative_consume(self):
|
||||
"""Check that negative consumsion throws an error"""
|
||||
r = (x for x in range(10))
|
||||
self.assertRaises(ValueError, lambda: mi.consume(r, -1))
|
||||
|
||||
def test_total_consume(self):
|
||||
"""Check that iterator is totally consumed by default"""
|
||||
r = (x for x in range(10))
|
||||
mi.consume(r)
|
||||
self.assertRaises(StopIteration, lambda: next(r))
|
||||
|
||||
|
||||
class NthTests(TestCase):
|
||||
"""Tests for ``nth()``"""
|
||||
|
||||
def test_basic(self):
|
||||
"""Make sure the nth item is returned"""
|
||||
l = range(10)
|
||||
for i, v in enumerate(l):
|
||||
self.assertEqual(mi.nth(l, i), v)
|
||||
|
||||
def test_default(self):
|
||||
"""Ensure a default value is returned when nth item not found"""
|
||||
l = range(3)
|
||||
self.assertEqual(mi.nth(l, 100, "zebra"), "zebra")
|
||||
|
||||
def test_negative_item_raises(self):
|
||||
"""Ensure asking for a negative item raises an exception"""
|
||||
self.assertRaises(ValueError, lambda: mi.nth(range(10), -3))
|
||||
|
||||
|
||||
class AllEqualTests(TestCase):
|
||||
"""Tests for ``all_equal()``"""
|
||||
|
||||
def test_true(self):
|
||||
"""Everything is equal"""
|
||||
self.assertTrue(mi.all_equal('aaaaaa'))
|
||||
self.assertTrue(mi.all_equal([0, 0, 0, 0]))
|
||||
|
||||
def test_false(self):
|
||||
"""Not everything is equal"""
|
||||
self.assertFalse(mi.all_equal('aaaaab'))
|
||||
self.assertFalse(mi.all_equal([0, 0, 0, 1]))
|
||||
|
||||
def test_tricky(self):
|
||||
"""Not everything is identical, but everything is equal"""
|
||||
items = [1, complex(1, 0), 1.0]
|
||||
self.assertTrue(mi.all_equal(items))
|
||||
|
||||
def test_empty(self):
|
||||
"""Return True if the iterable is empty"""
|
||||
self.assertTrue(mi.all_equal(''))
|
||||
self.assertTrue(mi.all_equal([]))
|
||||
|
||||
def test_one(self):
|
||||
"""Return True if the iterable is singular"""
|
||||
self.assertTrue(mi.all_equal('0'))
|
||||
self.assertTrue(mi.all_equal([0]))
|
||||
|
||||
|
||||
class QuantifyTests(TestCase):
|
||||
"""Tests for ``quantify()``"""
|
||||
|
||||
def test_happy_path(self):
|
||||
"""Make sure True count is returned"""
|
||||
q = [True, False, True]
|
||||
self.assertEqual(mi.quantify(q), 2)
|
||||
|
||||
def test_custom_predicate(self):
|
||||
"""Ensure non-default predicates return as expected"""
|
||||
q = range(10)
|
||||
self.assertEqual(mi.quantify(q, lambda x: x % 2 == 0), 5)
|
||||
|
||||
|
||||
class PadnoneTests(TestCase):
|
||||
"""Tests for ``padnone()``"""
|
||||
|
||||
def test_happy_path(self):
|
||||
"""wrapper iterator should return None indefinitely"""
|
||||
r = range(2)
|
||||
p = mi.padnone(r)
|
||||
self.assertEqual([0, 1, None, None], [next(p) for _ in range(4)])
|
||||
|
||||
|
||||
class NcyclesTests(TestCase):
|
||||
"""Tests for ``nyclces()``"""
|
||||
|
||||
def test_happy_path(self):
|
||||
"""cycle a sequence three times"""
|
||||
r = ["a", "b", "c"]
|
||||
n = mi.ncycles(r, 3)
|
||||
self.assertEqual(
|
||||
["a", "b", "c", "a", "b", "c", "a", "b", "c"],
|
||||
list(n)
|
||||
)
|
||||
|
||||
def test_null_case(self):
|
||||
"""asking for 0 cycles should return an empty iterator"""
|
||||
n = mi.ncycles(range(100), 0)
|
||||
self.assertRaises(StopIteration, lambda: next(n))
|
||||
|
||||
def test_pathalogical_case(self):
|
||||
"""asking for negative cycles should return an empty iterator"""
|
||||
n = mi.ncycles(range(100), -10)
|
||||
self.assertRaises(StopIteration, lambda: next(n))
|
||||
|
||||
|
||||
class DotproductTests(TestCase):
|
||||
"""Tests for ``dotproduct()``'"""
|
||||
|
||||
def test_happy_path(self):
|
||||
"""simple dotproduct example"""
|
||||
self.assertEqual(400, mi.dotproduct([10, 10], [20, 20]))
|
||||
|
||||
|
||||
class FlattenTests(TestCase):
|
||||
"""Tests for ``flatten()``"""
|
||||
|
||||
def test_basic_usage(self):
|
||||
"""ensure list of lists is flattened one level"""
|
||||
f = [[0, 1, 2], [3, 4, 5]]
|
||||
self.assertEqual(list(range(6)), list(mi.flatten(f)))
|
||||
|
||||
def test_single_level(self):
|
||||
"""ensure list of lists is flattened only one level"""
|
||||
f = [[0, [1, 2]], [[3, 4], 5]]
|
||||
self.assertEqual([0, [1, 2], [3, 4], 5], list(mi.flatten(f)))
|
||||
|
||||
|
||||
class RepeatfuncTests(TestCase):
|
||||
"""Tests for ``repeatfunc()``"""
|
||||
|
||||
def test_simple_repeat(self):
|
||||
"""test simple repeated functions"""
|
||||
r = mi.repeatfunc(lambda: 5)
|
||||
self.assertEqual([5, 5, 5, 5, 5], [next(r) for _ in range(5)])
|
||||
|
||||
def test_finite_repeat(self):
|
||||
"""ensure limited repeat when times is provided"""
|
||||
r = mi.repeatfunc(lambda: 5, times=5)
|
||||
self.assertEqual([5, 5, 5, 5, 5], list(r))
|
||||
|
||||
def test_added_arguments(self):
|
||||
"""ensure arguments are applied to the function"""
|
||||
r = mi.repeatfunc(lambda x: x, 2, 3)
|
||||
self.assertEqual([3, 3], list(r))
|
||||
|
||||
def test_null_times(self):
|
||||
"""repeat 0 should return an empty iterator"""
|
||||
r = mi.repeatfunc(range, 0, 3)
|
||||
self.assertRaises(StopIteration, lambda: next(r))
|
||||
|
||||
|
||||
class PairwiseTests(TestCase):
|
||||
"""Tests for ``pairwise()``"""
|
||||
|
||||
def test_base_case(self):
|
||||
"""ensure an iterable will return pairwise"""
|
||||
p = mi.pairwise([1, 2, 3])
|
||||
self.assertEqual([(1, 2), (2, 3)], list(p))
|
||||
|
||||
def test_short_case(self):
|
||||
"""ensure an empty iterator if there's not enough values to pair"""
|
||||
p = mi.pairwise("a")
|
||||
self.assertRaises(StopIteration, lambda: next(p))
|
||||
|
||||
|
||||
class GrouperTests(TestCase):
|
||||
"""Tests for ``grouper()``"""
|
||||
|
||||
def test_even(self):
|
||||
"""Test when group size divides evenly into the length of
|
||||
the iterable.
|
||||
|
||||
"""
|
||||
self.assertEqual(
|
||||
list(mi.grouper(3, 'ABCDEF')), [('A', 'B', 'C'), ('D', 'E', 'F')]
|
||||
)
|
||||
|
||||
def test_odd(self):
|
||||
"""Test when group size does not divide evenly into the length of the
|
||||
iterable.
|
||||
|
||||
"""
|
||||
self.assertEqual(
|
||||
list(mi.grouper(3, 'ABCDE')), [('A', 'B', 'C'), ('D', 'E', None)]
|
||||
)
|
||||
|
||||
def test_fill_value(self):
|
||||
"""Test that the fill value is used to pad the final group"""
|
||||
self.assertEqual(
|
||||
list(mi.grouper(3, 'ABCDE', 'x')),
|
||||
[('A', 'B', 'C'), ('D', 'E', 'x')]
|
||||
)
|
||||
|
||||
|
||||
class RoundrobinTests(TestCase):
|
||||
"""Tests for ``roundrobin()``"""
|
||||
|
||||
def test_even_groups(self):
|
||||
"""Ensure ordered output from evenly populated iterables"""
|
||||
self.assertEqual(
|
||||
list(mi.roundrobin('ABC', [1, 2, 3], range(3))),
|
||||
['A', 1, 0, 'B', 2, 1, 'C', 3, 2]
|
||||
)
|
||||
|
||||
def test_uneven_groups(self):
|
||||
"""Ensure ordered output from unevenly populated iterables"""
|
||||
self.assertEqual(
|
||||
list(mi.roundrobin('ABCD', [1, 2], range(0))),
|
||||
['A', 1, 'B', 2, 'C', 'D']
|
||||
)
|
||||
|
||||
|
||||
class PartitionTests(TestCase):
|
||||
"""Tests for ``partition()``"""
|
||||
|
||||
def test_bool(self):
|
||||
"""Test when pred() returns a boolean"""
|
||||
lesser, greater = mi.partition(lambda x: x > 5, range(10))
|
||||
self.assertEqual(list(lesser), [0, 1, 2, 3, 4, 5])
|
||||
self.assertEqual(list(greater), [6, 7, 8, 9])
|
||||
|
||||
def test_arbitrary(self):
|
||||
"""Test when pred() returns an integer"""
|
||||
divisibles, remainders = mi.partition(lambda x: x % 3, range(10))
|
||||
self.assertEqual(list(divisibles), [0, 3, 6, 9])
|
||||
self.assertEqual(list(remainders), [1, 2, 4, 5, 7, 8])
|
||||
|
||||
|
||||
class PowersetTests(TestCase):
|
||||
"""Tests for ``powerset()``"""
|
||||
|
||||
def test_combinatorics(self):
|
||||
"""Ensure a proper enumeration"""
|
||||
p = mi.powerset([1, 2, 3])
|
||||
self.assertEqual(
|
||||
list(p),
|
||||
[(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)]
|
||||
)
|
||||
|
||||
|
||||
class UniqueEverseenTests(TestCase):
|
||||
"""Tests for ``unique_everseen()``"""
|
||||
|
||||
def test_everseen(self):
|
||||
"""ensure duplicate elements are ignored"""
|
||||
u = mi.unique_everseen('AAAABBBBCCDAABBB')
|
||||
self.assertEqual(
|
||||
['A', 'B', 'C', 'D'],
|
||||
list(u)
|
||||
)
|
||||
|
||||
def test_custom_key(self):
|
||||
"""ensure the custom key comparison works"""
|
||||
u = mi.unique_everseen('aAbACCc', key=str.lower)
|
||||
self.assertEqual(list('abC'), list(u))
|
||||
|
||||
def test_unhashable(self):
|
||||
"""ensure things work for unhashable items"""
|
||||
iterable = ['a', [1, 2, 3], [1, 2, 3], 'a']
|
||||
u = mi.unique_everseen(iterable)
|
||||
self.assertEqual(list(u), ['a', [1, 2, 3]])
|
||||
|
||||
def test_unhashable_key(self):
|
||||
"""ensure things work for unhashable items with a custom key"""
|
||||
iterable = ['a', [1, 2, 3], [1, 2, 3], 'a']
|
||||
u = mi.unique_everseen(iterable, key=lambda x: x)
|
||||
self.assertEqual(list(u), ['a', [1, 2, 3]])
|
||||
|
||||
|
||||
class UniqueJustseenTests(TestCase):
|
||||
"""Tests for ``unique_justseen()``"""
|
||||
|
||||
def test_justseen(self):
|
||||
"""ensure only last item is remembered"""
|
||||
u = mi.unique_justseen('AAAABBBCCDABB')
|
||||
self.assertEqual(list('ABCDAB'), list(u))
|
||||
|
||||
def test_custom_key(self):
|
||||
"""ensure the custom key comparison works"""
|
||||
u = mi.unique_justseen('AABCcAD', str.lower)
|
||||
self.assertEqual(list('ABCAD'), list(u))
|
||||
|
||||
|
||||
class IterExceptTests(TestCase):
|
||||
"""Tests for ``iter_except()``"""
|
||||
|
||||
def test_exact_exception(self):
|
||||
"""ensure the exact specified exception is caught"""
|
||||
l = [1, 2, 3]
|
||||
i = mi.iter_except(l.pop, IndexError)
|
||||
self.assertEqual(list(i), [3, 2, 1])
|
||||
|
||||
def test_generic_exception(self):
|
||||
"""ensure the generic exception can be caught"""
|
||||
l = [1, 2]
|
||||
i = mi.iter_except(l.pop, Exception)
|
||||
self.assertEqual(list(i), [2, 1])
|
||||
|
||||
def test_uncaught_exception_is_raised(self):
|
||||
"""ensure a non-specified exception is raised"""
|
||||
l = [1, 2, 3]
|
||||
i = mi.iter_except(l.pop, KeyError)
|
||||
self.assertRaises(IndexError, lambda: list(i))
|
||||
|
||||
def test_first(self):
|
||||
"""ensure first is run before the function"""
|
||||
l = [1, 2, 3]
|
||||
f = lambda: 25
|
||||
i = mi.iter_except(l.pop, IndexError, f)
|
||||
self.assertEqual(list(i), [25, 3, 2, 1])
|
||||
|
||||
|
||||
class FirstTrueTests(TestCase):
|
||||
"""Tests for ``first_true()``"""
|
||||
|
||||
def test_something_true(self):
|
||||
"""Test with no keywords"""
|
||||
self.assertEqual(mi.first_true(range(10)), 1)
|
||||
|
||||
def test_nothing_true(self):
|
||||
"""Test default return value."""
|
||||
self.assertEqual(mi.first_true([0, 0, 0]), False)
|
||||
|
||||
def test_default(self):
|
||||
"""Test with a default keyword"""
|
||||
self.assertEqual(mi.first_true([0, 0, 0], default='!'), '!')
|
||||
|
||||
def test_pred(self):
|
||||
"""Test with a custom predicate"""
|
||||
self.assertEqual(
|
||||
mi.first_true([2, 4, 6], pred=lambda x: x % 3 == 0), 6
|
||||
)
|
||||
|
||||
|
||||
class RandomProductTests(TestCase):
|
||||
"""Tests for ``random_product()``
|
||||
|
||||
Since random.choice() has different results with the same seed across
|
||||
python versions 2.x and 3.x, these tests use highly probably events to
|
||||
create predictable outcomes across platforms.
|
||||
"""
|
||||
|
||||
def test_simple_lists(self):
|
||||
"""Ensure that one item is chosen from each list in each pair.
|
||||
Also ensure that each item from each list eventually appears in
|
||||
the chosen combinations.
|
||||
|
||||
Odds are roughly 1 in 7.1 * 10e16 that one item from either list will
|
||||
not be chosen after 100 samplings of one item from each list. Just to
|
||||
be safe, better use a known random seed, too.
|
||||
|
||||
"""
|
||||
nums = [1, 2, 3]
|
||||
lets = ['a', 'b', 'c']
|
||||
n, m = zip(*[mi.random_product(nums, lets) for _ in range(100)])
|
||||
n, m = set(n), set(m)
|
||||
self.assertEqual(n, set(nums))
|
||||
self.assertEqual(m, set(lets))
|
||||
self.assertEqual(len(n), len(nums))
|
||||
self.assertEqual(len(m), len(lets))
|
||||
|
||||
def test_list_with_repeat(self):
|
||||
"""ensure multiple items are chosen, and that they appear to be chosen
|
||||
from one list then the next, in proper order.
|
||||
|
||||
"""
|
||||
nums = [1, 2, 3]
|
||||
lets = ['a', 'b', 'c']
|
||||
r = list(mi.random_product(nums, lets, repeat=100))
|
||||
self.assertEqual(2 * 100, len(r))
|
||||
n, m = set(r[::2]), set(r[1::2])
|
||||
self.assertEqual(n, set(nums))
|
||||
self.assertEqual(m, set(lets))
|
||||
self.assertEqual(len(n), len(nums))
|
||||
self.assertEqual(len(m), len(lets))
|
||||
|
||||
|
||||
class RandomPermutationTests(TestCase):
|
||||
"""Tests for ``random_permutation()``"""
|
||||
|
||||
def test_full_permutation(self):
|
||||
"""ensure every item from the iterable is returned in a new ordering
|
||||
|
||||
15 elements have a 1 in 1.3 * 10e12 of appearing in sorted order, so
|
||||
we fix a seed value just to be sure.
|
||||
|
||||
"""
|
||||
i = range(15)
|
||||
r = mi.random_permutation(i)
|
||||
self.assertEqual(set(i), set(r))
|
||||
if i == r:
|
||||
raise AssertionError("Values were not permuted")
|
||||
|
||||
def test_partial_permutation(self):
|
||||
"""ensure all returned items are from the iterable, that the returned
|
||||
permutation is of the desired length, and that all items eventually
|
||||
get returned.
|
||||
|
||||
Sampling 100 permutations of length 5 from a set of 15 leaves a
|
||||
(2/3)^100 chance that an item will not be chosen. Multiplied by 15
|
||||
items, there is a 1 in 2.6e16 chance that at least 1 item will not
|
||||
show up in the resulting output. Using a random seed will fix that.
|
||||
|
||||
"""
|
||||
items = range(15)
|
||||
item_set = set(items)
|
||||
all_items = set()
|
||||
for _ in range(100):
|
||||
permutation = mi.random_permutation(items, 5)
|
||||
self.assertEqual(len(permutation), 5)
|
||||
permutation_set = set(permutation)
|
||||
self.assertLessEqual(permutation_set, item_set)
|
||||
all_items |= permutation_set
|
||||
self.assertEqual(all_items, item_set)
|
||||
|
||||
|
||||
class RandomCombinationTests(TestCase):
|
||||
"""Tests for ``random_combination()``"""
|
||||
|
||||
def test_psuedorandomness(self):
|
||||
"""ensure different subsets of the iterable get returned over many
|
||||
samplings of random combinations"""
|
||||
items = range(15)
|
||||
all_items = set()
|
||||
for _ in range(50):
|
||||
combination = mi.random_combination(items, 5)
|
||||
all_items |= set(combination)
|
||||
self.assertEqual(all_items, set(items))
|
||||
|
||||
def test_no_replacement(self):
|
||||
"""ensure that elements are sampled without replacement"""
|
||||
items = range(15)
|
||||
for _ in range(50):
|
||||
combination = mi.random_combination(items, len(items))
|
||||
self.assertEqual(len(combination), len(set(combination)))
|
||||
self.assertRaises(
|
||||
ValueError, lambda: mi.random_combination(items, len(items) + 1)
|
||||
)
|
||||
|
||||
|
||||
class RandomCombinationWithReplacementTests(TestCase):
|
||||
"""Tests for ``random_combination_with_replacement()``"""
|
||||
|
||||
def test_replacement(self):
|
||||
"""ensure that elements are sampled with replacement"""
|
||||
items = range(5)
|
||||
combo = mi.random_combination_with_replacement(items, len(items) * 2)
|
||||
self.assertEqual(2 * len(items), len(combo))
|
||||
if len(set(combo)) == len(combo):
|
||||
raise AssertionError("Combination contained no duplicates")
|
||||
|
||||
def test_pseudorandomness(self):
|
||||
"""ensure different subsets of the iterable get returned over many
|
||||
samplings of random combinations"""
|
||||
items = range(15)
|
||||
all_items = set()
|
||||
for _ in range(50):
|
||||
combination = mi.random_combination_with_replacement(items, 5)
|
||||
all_items |= set(combination)
|
||||
self.assertEqual(all_items, set(items))
|
||||
|
||||
|
||||
class NthCombinationTests(TestCase):
|
||||
def test_basic(self):
|
||||
iterable = 'abcdefg'
|
||||
r = 4
|
||||
for index, expected in enumerate(combinations(iterable, r)):
|
||||
actual = mi.nth_combination(iterable, r, index)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_long(self):
|
||||
actual = mi.nth_combination(range(180), 4, 2000000)
|
||||
expected = (2, 12, 35, 126)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
|
||||
class PrependTests(TestCase):
|
||||
def test_basic(self):
|
||||
value = 'a'
|
||||
iterator = iter('bcdefg')
|
||||
actual = list(mi.prepend(value, iterator))
|
||||
expected = list('abcdefg')
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_multiple(self):
|
||||
value = 'ab'
|
||||
iterator = iter('cdefg')
|
||||
actual = tuple(mi.prepend(value, iterator))
|
||||
expected = ('ab',) + tuple('cdefg')
|
||||
self.assertEqual(actual, expected)
|
|
@ -0,0 +1,8 @@
|
|||
[flake8]
|
||||
exclude = ./docs/conf.py, .eggs/
|
||||
ignore = E731, E741, F999
|
||||
|
||||
[egg_info]
|
||||
tag_build =
|
||||
tag_date = 0
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
# Hack to prevent stupid error on exit of `python setup.py test`. (See
|
||||
# http://www.eby-sarna.com/pipermail/peak/2010-May/003357.html.)
|
||||
try:
|
||||
import multiprocessing # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
from re import sub
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
def get_long_description():
|
||||
# Fix display issues on PyPI caused by RST markup
|
||||
readme = open('README.rst').read()
|
||||
|
||||
version_lines = []
|
||||
with open('docs/versions.rst') as infile:
|
||||
next(infile)
|
||||
for line in infile:
|
||||
line = line.rstrip().replace('.. automodule:: more_itertools', '')
|
||||
version_lines.append(line)
|
||||
version_history = '\n'.join(version_lines)
|
||||
version_history = sub(r':func:`([a-zA-Z0-9._]+)`', r'\1', version_history)
|
||||
|
||||
ret = readme + '\n\n' + version_history
|
||||
return ret
|
||||
|
||||
|
||||
setup(
|
||||
name='more-itertools',
|
||||
version='4.2.0',
|
||||
description='More routines for operating on iterables, beyond itertools',
|
||||
long_description=get_long_description(),
|
||||
author='Erik Rose',
|
||||
author_email='erikrose@grinchcentral.com',
|
||||
license='MIT',
|
||||
packages=find_packages(exclude=['ez_setup']),
|
||||
install_requires=['six>=1.0.0,<2.0.0'],
|
||||
test_suite='more_itertools.tests',
|
||||
url='https://github.com/erikrose/more-itertools',
|
||||
include_package_data=True,
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'Natural Language :: English',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Programming Language :: Python :: 2',
|
||||
'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 :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Topic :: Software Development :: Libraries'],
|
||||
keywords=['itertools', 'iterator', 'iteration', 'filter', 'peek',
|
||||
'peekable', 'collate', 'chunk', 'chunked'],
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
[tox]
|
||||
envlist = py27, py34, py35, py36, py37
|
||||
|
||||
[testenv]
|
||||
commands = {envbindir}/python -m unittest discover -v
|
|
@ -0,0 +1,7 @@
|
|||
include CHANGELOG
|
||||
include README.rst
|
||||
include setup.py
|
||||
include tox.ini
|
||||
include LICENSE
|
||||
graft testing
|
||||
recursive-exclude * *.pyc *.pyo
|
|
@ -0,0 +1,112 @@
|
|||
Metadata-Version: 1.2
|
||||
Name: pluggy
|
||||
Version: 0.6.0
|
||||
Summary: plugin and hook calling mechanisms for python
|
||||
Home-page: https://github.com/pytest-dev/pluggy
|
||||
Author: Holger Krekel
|
||||
Author-email: holger@merlinux.eu
|
||||
License: MIT license
|
||||
Description-Content-Type: UNKNOWN
|
||||
Description: pluggy - A minimalist production ready plugin system
|
||||
====================================================
|
||||
|pypi| |anaconda| |versions| |travis| |appveyor|
|
||||
|
||||
|
||||
This is the core framework used by the `pytest`_, `tox`_, and `devpi`_ projects.
|
||||
|
||||
Please `read the docs`_ to learn more!
|
||||
|
||||
A definitive example
|
||||
********************
|
||||
.. code-block:: python
|
||||
|
||||
import pluggy
|
||||
|
||||
hookspec = pluggy.HookspecMarker("myproject")
|
||||
hookimpl = pluggy.HookimplMarker("myproject")
|
||||
|
||||
|
||||
class MySpec(object):
|
||||
"""A hook specification namespace.
|
||||
"""
|
||||
@hookspec
|
||||
def myhook(self, arg1, arg2):
|
||||
"""My special little hook that you can customize.
|
||||
"""
|
||||
|
||||
|
||||
class Plugin_1(object):
|
||||
"""A hook implementation namespace.
|
||||
"""
|
||||
@hookimpl
|
||||
def myhook(self, arg1, arg2):
|
||||
print("inside Plugin_1.myhook()")
|
||||
return arg1 + arg2
|
||||
|
||||
|
||||
class Plugin_2(object):
|
||||
"""A 2nd hook implementation namespace.
|
||||
"""
|
||||
@hookimpl
|
||||
def myhook(self, arg1, arg2):
|
||||
print("inside Plugin_2.myhook()")
|
||||
return arg1 - arg2
|
||||
|
||||
|
||||
# create a manager and add the spec
|
||||
pm = pluggy.PluginManager("myproject")
|
||||
pm.add_hookspecs(MySpec)
|
||||
|
||||
# register plugins
|
||||
pm.register(Plugin_1())
|
||||
pm.register(Plugin_2())
|
||||
|
||||
# call our `myhook` hook
|
||||
results = pm.hook.myhook(arg1=1, arg2=2)
|
||||
print(results)
|
||||
|
||||
|
||||
.. badges
|
||||
.. |pypi| image:: https://img.shields.io/pypi/v/pluggy.svg
|
||||
:target: https://pypi.python.org/pypi/pluggy
|
||||
.. |versions| image:: https://img.shields.io/pypi/pyversions/pluggy.svg
|
||||
:target: https://pypi.python.org/pypi/pluggy
|
||||
.. |travis| image:: https://img.shields.io/travis/pytest-dev/pluggy/master.svg
|
||||
:target: https://travis-ci.org/pytest-dev/pluggy
|
||||
.. |appveyor| image:: https://img.shields.io/appveyor/ci/pytestbot/pluggy/master.svg
|
||||
:target: https://ci.appveyor.com/project/pytestbot/pluggy
|
||||
.. |anaconda| image:: https://anaconda.org/conda-forge/pluggy/badges/version.svg
|
||||
:target: https://anaconda.org/conda-forge/pluggy
|
||||
|
||||
.. links
|
||||
.. _pytest:
|
||||
http://pytest.org
|
||||
.. _tox:
|
||||
https://tox.readthedocs.org
|
||||
.. _devpi:
|
||||
http://doc.devpi.net
|
||||
.. _read the docs:
|
||||
https://pluggy.readthedocs.io/en/latest/
|
||||
|
||||
Platform: unix
|
||||
Platform: linux
|
||||
Platform: osx
|
||||
Platform: win32
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: POSIX
|
||||
Classifier: Operating System :: Microsoft :: Windows
|
||||
Classifier: Operating System :: MacOS :: MacOS X
|
||||
Classifier: Topic :: Software Development :: Testing
|
||||
Classifier: Topic :: Software Development :: Libraries
|
||||
Classifier: Topic :: Utilities
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
|
@ -0,0 +1,80 @@
|
|||
pluggy - A minimalist production ready plugin system
|
||||
====================================================
|
||||
|pypi| |anaconda| |versions| |travis| |appveyor|
|
||||
|
||||
|
||||
This is the core framework used by the `pytest`_, `tox`_, and `devpi`_ projects.
|
||||
|
||||
Please `read the docs`_ to learn more!
|
||||
|
||||
A definitive example
|
||||
********************
|
||||
.. code-block:: python
|
||||
|
||||
import pluggy
|
||||
|
||||
hookspec = pluggy.HookspecMarker("myproject")
|
||||
hookimpl = pluggy.HookimplMarker("myproject")
|
||||
|
||||
|
||||
class MySpec(object):
|
||||
"""A hook specification namespace.
|
||||
"""
|
||||
@hookspec
|
||||
def myhook(self, arg1, arg2):
|
||||
"""My special little hook that you can customize.
|
||||
"""
|
||||
|
||||
|
||||
class Plugin_1(object):
|
||||
"""A hook implementation namespace.
|
||||
"""
|
||||
@hookimpl
|
||||
def myhook(self, arg1, arg2):
|
||||
print("inside Plugin_1.myhook()")
|
||||
return arg1 + arg2
|
||||
|
||||
|
||||
class Plugin_2(object):
|
||||
"""A 2nd hook implementation namespace.
|
||||
"""
|
||||
@hookimpl
|
||||
def myhook(self, arg1, arg2):
|
||||
print("inside Plugin_2.myhook()")
|
||||
return arg1 - arg2
|
||||
|
||||
|
||||
# create a manager and add the spec
|
||||
pm = pluggy.PluginManager("myproject")
|
||||
pm.add_hookspecs(MySpec)
|
||||
|
||||
# register plugins
|
||||
pm.register(Plugin_1())
|
||||
pm.register(Plugin_2())
|
||||
|
||||
# call our `myhook` hook
|
||||
results = pm.hook.myhook(arg1=1, arg2=2)
|
||||
print(results)
|
||||
|
||||
|
||||
.. badges
|
||||
.. |pypi| image:: https://img.shields.io/pypi/v/pluggy.svg
|
||||
:target: https://pypi.python.org/pypi/pluggy
|
||||
.. |versions| image:: https://img.shields.io/pypi/pyversions/pluggy.svg
|
||||
:target: https://pypi.python.org/pypi/pluggy
|
||||
.. |travis| image:: https://img.shields.io/travis/pytest-dev/pluggy/master.svg
|
||||
:target: https://travis-ci.org/pytest-dev/pluggy
|
||||
.. |appveyor| image:: https://img.shields.io/appveyor/ci/pytestbot/pluggy/master.svg
|
||||
:target: https://ci.appveyor.com/project/pytestbot/pluggy
|
||||
.. |anaconda| image:: https://anaconda.org/conda-forge/pluggy/badges/version.svg
|
||||
:target: https://anaconda.org/conda-forge/pluggy
|
||||
|
||||
.. links
|
||||
.. _pytest:
|
||||
http://pytest.org
|
||||
.. _tox:
|
||||
https://tox.readthedocs.org
|
||||
.. _devpi:
|
||||
http://doc.devpi.net
|
||||
.. _read the docs:
|
||||
https://pluggy.readthedocs.io/en/latest/
|
|
@ -1,81 +1,18 @@
|
|||
"""
|
||||
PluginManager, basic initialization and tracing.
|
||||
|
||||
pluggy is the cristallized core of plugin management as used
|
||||
by some 150 plugins for pytest.
|
||||
|
||||
Pluggy uses semantic versioning. Breaking changes are only foreseen for
|
||||
Major releases (incremented X in "X.Y.Z"). If you want to use pluggy in
|
||||
your project you should thus use a dependency restriction like
|
||||
"pluggy>=0.1.0,<1.0" to avoid surprises.
|
||||
|
||||
pluggy is concerned with hook specification, hook implementations and hook
|
||||
calling. For any given hook specification a hook call invokes up to N implementations.
|
||||
A hook implementation can influence its position and type of execution:
|
||||
if attributed "tryfirst" or "trylast" it will be tried to execute
|
||||
first or last. However, if attributed "hookwrapper" an implementation
|
||||
can wrap all calls to non-hookwrapper implementations. A hookwrapper
|
||||
can thus execute some code ahead and after the execution of other hooks.
|
||||
|
||||
Hook specification is done by way of a regular python function where
|
||||
both the function name and the names of all its arguments are significant.
|
||||
Each hook implementation function is verified against the original specification
|
||||
function, including the names of all its arguments. To allow for hook specifications
|
||||
to evolve over the livetime of a project, hook implementations can
|
||||
accept less arguments. One can thus add new arguments and semantics to
|
||||
a hook specification by adding another argument typically without breaking
|
||||
existing hook implementations.
|
||||
|
||||
The chosen approach is meant to let a hook designer think carefuly about
|
||||
which objects are needed by an extension writer. By contrast, subclass-based
|
||||
extension mechanisms often expose a lot more state and behaviour than needed,
|
||||
thus restricting future developments.
|
||||
|
||||
Pluggy currently consists of functionality for:
|
||||
|
||||
- a way to register new hook specifications. Without a hook
|
||||
specification no hook calling can be performed.
|
||||
|
||||
- a registry of plugins which contain hook implementation functions. It
|
||||
is possible to register plugins for which a hook specification is not yet
|
||||
known and validate all hooks when the system is in a more referentially
|
||||
consistent state. Setting an "optionalhook" attribution to a hook
|
||||
implementation will avoid PluginValidationError's if a specification
|
||||
is missing. This allows to have optional integration between plugins.
|
||||
|
||||
- a "hook" relay object from which you can launch 1:N calls to
|
||||
registered hook implementation functions
|
||||
|
||||
- a mechanism for ordering hook implementation functions
|
||||
|
||||
- mechanisms for two different type of 1:N calls: "firstresult" for when
|
||||
the call should stop when the first implementation returns a non-None result.
|
||||
And the other (default) way of guaranteeing that all hook implementations
|
||||
will be called and their non-None result collected.
|
||||
|
||||
- mechanisms for "historic" extension points such that all newly
|
||||
registered functions will receive all hook calls that happened
|
||||
before their registration.
|
||||
|
||||
- a mechanism for discovering plugin objects which are based on
|
||||
setuptools based entry points.
|
||||
|
||||
- a simple tracing mechanism, including tracing of plugin calls and
|
||||
their arguments.
|
||||
|
||||
"""
|
||||
import sys
|
||||
import inspect
|
||||
import warnings
|
||||
from .callers import _multicall, HookCallError, _Result, _legacymulticall
|
||||
|
||||
__version__ = '0.4.0'
|
||||
__version__ = '0.6.0'
|
||||
|
||||
__all__ = ["PluginManager", "PluginValidationError", "HookCallError",
|
||||
"HookspecMarker", "HookimplMarker"]
|
||||
|
||||
_py3 = sys.version_info > (3, 0)
|
||||
|
||||
class PluginValidationError(Exception):
|
||||
""" plugin failed validation. """
|
||||
|
||||
|
||||
class HookspecMarker:
|
||||
class HookspecMarker(object):
|
||||
""" Decorator helper class for marking functions as hook specifications.
|
||||
|
||||
You can instantiate it with a project_name to get a decorator.
|
||||
|
@ -104,7 +41,7 @@ class HookspecMarker:
|
|||
if historic and firstresult:
|
||||
raise ValueError("cannot have a historic firstresult hook")
|
||||
setattr(func, self.project_name + "_spec",
|
||||
dict(firstresult=firstresult, historic=historic))
|
||||
dict(firstresult=firstresult, historic=historic))
|
||||
return func
|
||||
|
||||
if function is not None:
|
||||
|
@ -113,7 +50,7 @@ class HookspecMarker:
|
|||
return setattr_hookspec_opts
|
||||
|
||||
|
||||
class HookimplMarker:
|
||||
class HookimplMarker(object):
|
||||
""" Decorator helper class for marking functions as hook implementations.
|
||||
|
||||
You can instantiate with a project_name to get a decorator.
|
||||
|
@ -143,15 +80,15 @@ class HookimplMarker:
|
|||
If hookwrapper is True the hook implementations needs to execute exactly
|
||||
one "yield". The code before the yield is run early before any non-hookwrapper
|
||||
function is run. The code after the yield is run after all non-hookwrapper
|
||||
function have run. The yield receives an ``_CallOutcome`` object representing
|
||||
function have run. The yield receives a ``_Result`` object representing
|
||||
the exception or result outcome of the inner calls (including other hookwrapper
|
||||
calls).
|
||||
|
||||
"""
|
||||
def setattr_hookimpl_opts(func):
|
||||
setattr(func, self.project_name + "_impl",
|
||||
dict(hookwrapper=hookwrapper, optionalhook=optionalhook,
|
||||
tryfirst=tryfirst, trylast=trylast))
|
||||
dict(hookwrapper=hookwrapper, optionalhook=optionalhook,
|
||||
tryfirst=tryfirst, trylast=trylast))
|
||||
return func
|
||||
|
||||
if function is None:
|
||||
|
@ -167,7 +104,7 @@ def normalize_hookimpl_opts(opts):
|
|||
opts.setdefault("optionalhook", False)
|
||||
|
||||
|
||||
class _TagTracer:
|
||||
class _TagTracer(object):
|
||||
def __init__(self):
|
||||
self._tag2proc = {}
|
||||
self.writer = None
|
||||
|
@ -214,7 +151,7 @@ class _TagTracer:
|
|||
self._tag2proc[tags] = processor
|
||||
|
||||
|
||||
class _TagTracerSub:
|
||||
class _TagTracerSub(object):
|
||||
def __init__(self, root, tags):
|
||||
self.root = root
|
||||
self.tags = tags
|
||||
|
@ -229,64 +166,7 @@ class _TagTracerSub:
|
|||
return self.__class__(self.root, self.tags + (name,))
|
||||
|
||||
|
||||
def _raise_wrapfail(wrap_controller, msg):
|
||||
co = wrap_controller.gi_code
|
||||
raise RuntimeError("wrap_controller at %r %s:%d %s" %
|
||||
(co.co_name, co.co_filename, co.co_firstlineno, msg))
|
||||
|
||||
|
||||
def _wrapped_call(wrap_controller, func):
|
||||
""" Wrap calling to a function with a generator which needs to yield
|
||||
exactly once. The yield point will trigger calling the wrapped function
|
||||
and return its _CallOutcome to the yield point. The generator then needs
|
||||
to finish (raise StopIteration) in order for the wrapped call to complete.
|
||||
"""
|
||||
try:
|
||||
next(wrap_controller) # first yield
|
||||
except StopIteration:
|
||||
_raise_wrapfail(wrap_controller, "did not yield")
|
||||
call_outcome = _CallOutcome(func)
|
||||
try:
|
||||
wrap_controller.send(call_outcome)
|
||||
_raise_wrapfail(wrap_controller, "has second yield")
|
||||
except StopIteration:
|
||||
pass
|
||||
return call_outcome.get_result()
|
||||
|
||||
|
||||
class _CallOutcome:
|
||||
""" Outcome of a function call, either an exception or a proper result.
|
||||
Calling the ``get_result`` method will return the result or reraise
|
||||
the exception raised when the function was called. """
|
||||
excinfo = None
|
||||
|
||||
def __init__(self, func):
|
||||
try:
|
||||
self.result = func()
|
||||
except BaseException:
|
||||
self.excinfo = sys.exc_info()
|
||||
|
||||
def force_result(self, result):
|
||||
self.result = result
|
||||
self.excinfo = None
|
||||
|
||||
def get_result(self):
|
||||
if self.excinfo is None:
|
||||
return self.result
|
||||
else:
|
||||
ex = self.excinfo
|
||||
if _py3:
|
||||
raise ex[1].with_traceback(ex[2])
|
||||
_reraise(*ex) # noqa
|
||||
|
||||
if not _py3:
|
||||
exec("""
|
||||
def _reraise(cls, val, tb):
|
||||
raise cls, val, tb
|
||||
""")
|
||||
|
||||
|
||||
class _TracedHookExecution:
|
||||
class _TracedHookExecution(object):
|
||||
def __init__(self, pluginmanager, before, after):
|
||||
self.pluginmanager = pluginmanager
|
||||
self.before = before
|
||||
|
@ -297,7 +177,7 @@ class _TracedHookExecution:
|
|||
|
||||
def __call__(self, hook, hook_impls, kwargs):
|
||||
self.before(hook.name, hook_impls, kwargs)
|
||||
outcome = _CallOutcome(lambda: self.oldcall(hook, hook_impls, kwargs))
|
||||
outcome = _Result.from_call(lambda: self.oldcall(hook, hook_impls, kwargs))
|
||||
self.after(outcome, hook.name, hook_impls, kwargs)
|
||||
return outcome.get_result()
|
||||
|
||||
|
@ -331,7 +211,10 @@ class PluginManager(object):
|
|||
self.hook = _HookRelay(self.trace.root.get("hook"))
|
||||
self._implprefix = implprefix
|
||||
self._inner_hookexec = lambda hook, methods, kwargs: \
|
||||
_MultiCall(methods, kwargs, hook.spec_opts).execute()
|
||||
hook.multicall(
|
||||
methods, kwargs,
|
||||
firstresult=hook.spec_opts.get('firstresult'),
|
||||
)
|
||||
|
||||
def _hookexec(self, hook, methods, kwargs):
|
||||
# called from all hookcaller instances.
|
||||
|
@ -348,7 +231,7 @@ class PluginManager(object):
|
|||
if self._name2plugin.get(plugin_name, -1) is None:
|
||||
return # blocked plugin, return None to indicate no registration
|
||||
raise ValueError("Plugin already registered: %s=%s\n%s" %
|
||||
(plugin_name, plugin, self._name2plugin))
|
||||
(plugin_name, plugin, self._name2plugin))
|
||||
|
||||
# XXX if an error happens we should make sure no state has been
|
||||
# changed at point of return
|
||||
|
@ -375,6 +258,8 @@ class PluginManager(object):
|
|||
|
||||
def parse_hookimpl_opts(self, plugin, name):
|
||||
method = getattr(plugin, name)
|
||||
if not inspect.isroutine(method):
|
||||
return
|
||||
try:
|
||||
res = getattr(method, self.project_name + "_impl", None)
|
||||
except Exception:
|
||||
|
@ -475,14 +360,16 @@ class PluginManager(object):
|
|||
"Plugin %r\nhook %r\nhistoric incompatible to hookwrapper" %
|
||||
(hookimpl.plugin_name, hook.name))
|
||||
|
||||
for arg in hookimpl.argnames:
|
||||
if arg not in hook.argnames:
|
||||
raise PluginValidationError(
|
||||
"Plugin %r\nhook %r\nargument %r not available\n"
|
||||
"plugin definition: %s\n"
|
||||
"available hookargs: %s" %
|
||||
(hookimpl.plugin_name, hook.name, arg,
|
||||
_formatdef(hookimpl.function), ", ".join(hook.argnames)))
|
||||
# positional arg checking
|
||||
notinspec = set(hookimpl.argnames) - set(hook.argnames)
|
||||
if notinspec:
|
||||
raise PluginValidationError(
|
||||
"Plugin %r for hook %r\nhookimpl definition: %s\n"
|
||||
"Argument(s) %s are declared in the hookimpl but "
|
||||
"can not be found in the hookspec" %
|
||||
(hookimpl.plugin_name, hook.name,
|
||||
_formatdef(hookimpl.function), notinspec)
|
||||
)
|
||||
|
||||
def check_pending(self):
|
||||
""" Verify that all hooks which have not been verified against
|
||||
|
@ -540,7 +427,7 @@ class PluginManager(object):
|
|||
of HookImpl instances and the keyword arguments for the hook call.
|
||||
|
||||
``after(outcome, hook_name, hook_impls, kwargs)`` receives the
|
||||
same arguments as ``before`` but also a :py:class:`_CallOutcome <_pytest.vendored_packages.pluggy._CallOutcome>` object
|
||||
same arguments as ``before`` but also a :py:class:`_Result`` object
|
||||
which represents the result of the overall hook call.
|
||||
"""
|
||||
return _TracedHookExecution(self, before, after).undo
|
||||
|
@ -555,7 +442,7 @@ class PluginManager(object):
|
|||
|
||||
def after(outcome, hook_name, methods, kwargs):
|
||||
if outcome.excinfo is None:
|
||||
hooktrace("finish", hook_name, "-->", outcome.result)
|
||||
hooktrace("finish", hook_name, "-->", outcome.get_result())
|
||||
hooktrace.root.indent -= 1
|
||||
|
||||
return self.add_hookcall_monitoring(before, after)
|
||||
|
@ -580,100 +467,58 @@ class PluginManager(object):
|
|||
return orig
|
||||
|
||||
|
||||
class _MultiCall:
|
||||
""" execute a call into multiple python functions/methods. """
|
||||
def varnames(func):
|
||||
"""Return tuple of positional and keywrord argument names for a function,
|
||||
method, class or callable.
|
||||
|
||||
# XXX note that the __multicall__ argument is supported only
|
||||
# for pytest compatibility reasons. It was never officially
|
||||
# supported there and is explicitely deprecated since 2.8
|
||||
# so we can remove it soon, allowing to avoid the below recursion
|
||||
# in execute() and simplify/speed up the execute loop.
|
||||
|
||||
def __init__(self, hook_impls, kwargs, specopts={}):
|
||||
self.hook_impls = hook_impls
|
||||
self.kwargs = kwargs
|
||||
self.kwargs["__multicall__"] = self
|
||||
self.specopts = specopts
|
||||
|
||||
def execute(self):
|
||||
all_kwargs = self.kwargs
|
||||
self.results = results = []
|
||||
firstresult = self.specopts.get("firstresult")
|
||||
|
||||
while self.hook_impls:
|
||||
hook_impl = self.hook_impls.pop()
|
||||
try:
|
||||
args = [all_kwargs[argname] for argname in hook_impl.argnames]
|
||||
except KeyError:
|
||||
for argname in hook_impl.argnames:
|
||||
if argname not in all_kwargs:
|
||||
raise HookCallError(
|
||||
"hook call must provide argument %r" % (argname,))
|
||||
if hook_impl.hookwrapper:
|
||||
return _wrapped_call(hook_impl.function(*args), self.execute)
|
||||
res = hook_impl.function(*args)
|
||||
if res is not None:
|
||||
if firstresult:
|
||||
return res
|
||||
results.append(res)
|
||||
|
||||
if not firstresult:
|
||||
return results
|
||||
|
||||
def __repr__(self):
|
||||
status = "%d meths" % (len(self.hook_impls),)
|
||||
if hasattr(self, "results"):
|
||||
status = ("%d results, " % len(self.results)) + status
|
||||
return "<_MultiCall %s, kwargs=%r>" % (status, self.kwargs)
|
||||
|
||||
|
||||
def varnames(func, startindex=None):
|
||||
""" return argument name tuple for a function, method, class or callable.
|
||||
|
||||
In case of a class, its "__init__" method is considered.
|
||||
For methods the "self" parameter is not included unless you are passing
|
||||
an unbound method with Python3 (which has no supports for unbound methods)
|
||||
In case of a class, its ``__init__`` method is considered.
|
||||
For methods the ``self`` parameter is not included.
|
||||
"""
|
||||
cache = getattr(func, "__dict__", {})
|
||||
try:
|
||||
return cache["_varnames"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if inspect.isclass(func):
|
||||
try:
|
||||
func = func.__init__
|
||||
except AttributeError:
|
||||
return (), ()
|
||||
elif not inspect.isroutine(func): # callable object?
|
||||
try:
|
||||
func = getattr(func, '__call__', func)
|
||||
except Exception:
|
||||
return ()
|
||||
startindex = 1
|
||||
else:
|
||||
if not inspect.isfunction(func) and not inspect.ismethod(func):
|
||||
try:
|
||||
func = getattr(func, '__call__', func)
|
||||
except Exception:
|
||||
return ()
|
||||
if startindex is None:
|
||||
startindex = int(inspect.ismethod(func))
|
||||
|
||||
try:
|
||||
rawcode = func.__code__
|
||||
except AttributeError:
|
||||
return ()
|
||||
try:
|
||||
x = rawcode.co_varnames[startindex:rawcode.co_argcount]
|
||||
except AttributeError:
|
||||
x = ()
|
||||
try: # func MUST be a function or method here or we won't parse any args
|
||||
spec = _getargspec(func)
|
||||
except TypeError:
|
||||
return (), ()
|
||||
|
||||
args, defaults = tuple(spec.args), spec.defaults
|
||||
if defaults:
|
||||
index = -len(defaults)
|
||||
args, defaults = args[:index], tuple(args[index:])
|
||||
else:
|
||||
defaults = func.__defaults__
|
||||
if defaults:
|
||||
x = x[:-len(defaults)]
|
||||
defaults = ()
|
||||
|
||||
# strip any implicit instance arg
|
||||
if args:
|
||||
if inspect.ismethod(func) or (
|
||||
'.' in getattr(func, '__qualname__', ()) and args[0] == 'self'
|
||||
):
|
||||
args = args[1:]
|
||||
|
||||
assert "self" not in args # best naming practises check?
|
||||
try:
|
||||
cache["_varnames"] = x
|
||||
cache["_varnames"] = args, defaults
|
||||
except TypeError:
|
||||
pass
|
||||
return x
|
||||
return args, defaults
|
||||
|
||||
|
||||
class _HookRelay:
|
||||
class _HookRelay(object):
|
||||
""" hook holder object for performing 1:N hook calls where N is the number
|
||||
of registered plugins.
|
||||
|
||||
|
@ -684,26 +529,31 @@ class _HookRelay:
|
|||
|
||||
|
||||
class _HookCaller(object):
|
||||
def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None):
|
||||
def __init__(self, name, hook_execute, specmodule_or_class=None,
|
||||
spec_opts=None):
|
||||
self.name = name
|
||||
self._wrappers = []
|
||||
self._nonwrappers = []
|
||||
self._hookexec = hook_execute
|
||||
self._specmodule_or_class = None
|
||||
self.argnames = None
|
||||
self.kwargnames = None
|
||||
self.multicall = _multicall
|
||||
self.spec_opts = spec_opts or {}
|
||||
if specmodule_or_class is not None:
|
||||
assert spec_opts is not None
|
||||
self.set_specification(specmodule_or_class, spec_opts)
|
||||
|
||||
def has_spec(self):
|
||||
return hasattr(self, "_specmodule_or_class")
|
||||
return self._specmodule_or_class is not None
|
||||
|
||||
def set_specification(self, specmodule_or_class, spec_opts):
|
||||
assert not self.has_spec()
|
||||
self._specmodule_or_class = specmodule_or_class
|
||||
specfunc = getattr(specmodule_or_class, self.name)
|
||||
argnames = varnames(specfunc, startindex=inspect.isclass(specmodule_or_class))
|
||||
assert "self" not in argnames # sanity check
|
||||
# get spec arg signature
|
||||
argnames, self.kwargnames = varnames(specfunc)
|
||||
self.argnames = ["__multicall__"] + list(argnames)
|
||||
self.spec_opts = spec_opts
|
||||
self.spec_opts.update(spec_opts)
|
||||
if spec_opts.get("historic"):
|
||||
self._call_history = []
|
||||
|
||||
|
@ -721,6 +571,8 @@ class _HookCaller(object):
|
|||
raise ValueError("plugin %r not found" % (plugin,))
|
||||
|
||||
def _add_hookimpl(self, hookimpl):
|
||||
"""A an implementation to the callback chain.
|
||||
"""
|
||||
if hookimpl.hookwrapper:
|
||||
methods = self._wrappers
|
||||
else:
|
||||
|
@ -737,17 +589,45 @@ class _HookCaller(object):
|
|||
i -= 1
|
||||
methods.insert(i + 1, hookimpl)
|
||||
|
||||
if '__multicall__' in hookimpl.argnames:
|
||||
warnings.warn(
|
||||
"Support for __multicall__ is now deprecated and will be"
|
||||
"removed in an upcoming release.",
|
||||
DeprecationWarning
|
||||
)
|
||||
self.multicall = _legacymulticall
|
||||
|
||||
def __repr__(self):
|
||||
return "<_HookCaller %r>" % (self.name,)
|
||||
|
||||
def __call__(self, **kwargs):
|
||||
def __call__(self, *args, **kwargs):
|
||||
if args:
|
||||
raise TypeError("hook calling supports only keyword arguments")
|
||||
assert not self.is_historic()
|
||||
if self.argnames:
|
||||
notincall = set(self.argnames) - set(['__multicall__']) - set(
|
||||
kwargs.keys())
|
||||
if notincall:
|
||||
warnings.warn(
|
||||
"Argument(s) {} which are declared in the hookspec "
|
||||
"can not be found in this hook call"
|
||||
.format(tuple(notincall)),
|
||||
stacklevel=2,
|
||||
)
|
||||
return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
|
||||
|
||||
def call_historic(self, proc=None, kwargs=None):
|
||||
""" call the hook with given ``kwargs`` for all registered plugins and
|
||||
for all plugins which will be registered afterwards.
|
||||
|
||||
If ``proc`` is not None it will be called for for each non-None result
|
||||
obtained from a hook implementation.
|
||||
"""
|
||||
self._call_history.append((kwargs or {}, proc))
|
||||
# historizing hooks don't return results
|
||||
self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
|
||||
res = self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
|
||||
for x in res or []:
|
||||
proc(x)
|
||||
|
||||
def call_extra(self, methods, kwargs):
|
||||
""" Call the hook with some additional temporarily participating
|
||||
|
@ -763,6 +643,8 @@ class _HookCaller(object):
|
|||
self._nonwrappers, self._wrappers = old
|
||||
|
||||
def _maybe_apply_history(self, method):
|
||||
"""Apply call history to a new hookimpl if it is marked as historic.
|
||||
"""
|
||||
if self.is_historic():
|
||||
for kwargs, proc in self._call_history:
|
||||
res = self._hookexec(self, [method], kwargs)
|
||||
|
@ -770,22 +652,22 @@ class _HookCaller(object):
|
|||
proc(res[0])
|
||||
|
||||
|
||||
class HookImpl:
|
||||
class HookImpl(object):
|
||||
def __init__(self, plugin, plugin_name, function, hook_impl_opts):
|
||||
self.function = function
|
||||
self.argnames = varnames(self.function)
|
||||
self.argnames, self.kwargnames = varnames(self.function)
|
||||
self.plugin = plugin
|
||||
self.opts = hook_impl_opts
|
||||
self.plugin_name = plugin_name
|
||||
self.__dict__.update(hook_impl_opts)
|
||||
|
||||
|
||||
class PluginValidationError(Exception):
|
||||
""" plugin failed validation. """
|
||||
|
||||
|
||||
class HookCallError(Exception):
|
||||
""" Hook was called wrongly. """
|
||||
if hasattr(inspect, 'getfullargspec'):
|
||||
def _getargspec(func):
|
||||
return inspect.getfullargspec(func)
|
||||
else:
|
||||
def _getargspec(func):
|
||||
return inspect.getargspec(func)
|
||||
|
||||
|
||||
if hasattr(inspect, 'signature'):
|
|
@ -0,0 +1,201 @@
|
|||
'''
|
||||
Call loop machinery
|
||||
'''
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
_py3 = sys.version_info > (3, 0)
|
||||
|
||||
|
||||
if not _py3:
|
||||
exec("""
|
||||
def _reraise(cls, val, tb):
|
||||
raise cls, val, tb
|
||||
""")
|
||||
|
||||
|
||||
def _raise_wrapfail(wrap_controller, msg):
|
||||
co = wrap_controller.gi_code
|
||||
raise RuntimeError("wrap_controller at %r %s:%d %s" %
|
||||
(co.co_name, co.co_filename, co.co_firstlineno, msg))
|
||||
|
||||
|
||||
class HookCallError(Exception):
|
||||
""" Hook was called wrongly. """
|
||||
|
||||
|
||||
class _Result(object):
|
||||
def __init__(self, result, excinfo):
|
||||
self._result = result
|
||||
self._excinfo = excinfo
|
||||
|
||||
@property
|
||||
def excinfo(self):
|
||||
return self._excinfo
|
||||
|
||||
@property
|
||||
def result(self):
|
||||
"""Get the result(s) for this hook call (DEPRECATED in favor of ``get_result()``)."""
|
||||
msg = 'Use get_result() which forces correct exception handling'
|
||||
warnings.warn(DeprecationWarning(msg), stacklevel=2)
|
||||
return self._result
|
||||
|
||||
@classmethod
|
||||
def from_call(cls, func):
|
||||
__tracebackhide__ = True
|
||||
result = excinfo = None
|
||||
try:
|
||||
result = func()
|
||||
except BaseException:
|
||||
excinfo = sys.exc_info()
|
||||
|
||||
return cls(result, excinfo)
|
||||
|
||||
def force_result(self, result):
|
||||
"""Force the result(s) to ``result``.
|
||||
|
||||
If the hook was marked as a ``firstresult`` a single value should
|
||||
be set otherwise set a (modified) list of results. Any exceptions
|
||||
found during invocation will be deleted.
|
||||
"""
|
||||
self._result = result
|
||||
self._excinfo = None
|
||||
|
||||
def get_result(self):
|
||||
"""Get the result(s) for this hook call.
|
||||
|
||||
If the hook was marked as a ``firstresult`` only a single value
|
||||
will be returned otherwise a list of results.
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
if self._excinfo is None:
|
||||
return self._result
|
||||
else:
|
||||
ex = self._excinfo
|
||||
if _py3:
|
||||
raise ex[1].with_traceback(ex[2])
|
||||
_reraise(*ex) # noqa
|
||||
|
||||
|
||||
def _wrapped_call(wrap_controller, func):
|
||||
""" Wrap calling to a function with a generator which needs to yield
|
||||
exactly once. The yield point will trigger calling the wrapped function
|
||||
and return its ``_Result`` to the yield point. The generator then needs
|
||||
to finish (raise StopIteration) in order for the wrapped call to complete.
|
||||
"""
|
||||
try:
|
||||
next(wrap_controller) # first yield
|
||||
except StopIteration:
|
||||
_raise_wrapfail(wrap_controller, "did not yield")
|
||||
call_outcome = _Result.from_call(func)
|
||||
try:
|
||||
wrap_controller.send(call_outcome)
|
||||
_raise_wrapfail(wrap_controller, "has second yield")
|
||||
except StopIteration:
|
||||
pass
|
||||
return call_outcome.get_result()
|
||||
|
||||
|
||||
class _LegacyMultiCall(object):
|
||||
""" execute a call into multiple python functions/methods. """
|
||||
|
||||
# XXX note that the __multicall__ argument is supported only
|
||||
# for pytest compatibility reasons. It was never officially
|
||||
# supported there and is explicitely deprecated since 2.8
|
||||
# so we can remove it soon, allowing to avoid the below recursion
|
||||
# in execute() and simplify/speed up the execute loop.
|
||||
|
||||
def __init__(self, hook_impls, kwargs, firstresult=False):
|
||||
self.hook_impls = hook_impls
|
||||
self.caller_kwargs = kwargs # come from _HookCaller.__call__()
|
||||
self.caller_kwargs["__multicall__"] = self
|
||||
self.firstresult = firstresult
|
||||
|
||||
def execute(self):
|
||||
caller_kwargs = self.caller_kwargs
|
||||
self.results = results = []
|
||||
firstresult = self.firstresult
|
||||
|
||||
while self.hook_impls:
|
||||
hook_impl = self.hook_impls.pop()
|
||||
try:
|
||||
args = [caller_kwargs[argname] for argname in hook_impl.argnames]
|
||||
except KeyError:
|
||||
for argname in hook_impl.argnames:
|
||||
if argname not in caller_kwargs:
|
||||
raise HookCallError(
|
||||
"hook call must provide argument %r" % (argname,))
|
||||
if hook_impl.hookwrapper:
|
||||
return _wrapped_call(hook_impl.function(*args), self.execute)
|
||||
res = hook_impl.function(*args)
|
||||
if res is not None:
|
||||
if firstresult:
|
||||
return res
|
||||
results.append(res)
|
||||
|
||||
if not firstresult:
|
||||
return results
|
||||
|
||||
def __repr__(self):
|
||||
status = "%d meths" % (len(self.hook_impls),)
|
||||
if hasattr(self, "results"):
|
||||
status = ("%d results, " % len(self.results)) + status
|
||||
return "<_MultiCall %s, kwargs=%r>" % (status, self.caller_kwargs)
|
||||
|
||||
|
||||
def _legacymulticall(hook_impls, caller_kwargs, firstresult=False):
|
||||
return _LegacyMultiCall(
|
||||
hook_impls, caller_kwargs, firstresult=firstresult).execute()
|
||||
|
||||
|
||||
def _multicall(hook_impls, caller_kwargs, firstresult=False):
|
||||
"""Execute a call into multiple python functions/methods and return the
|
||||
result(s).
|
||||
|
||||
``caller_kwargs`` comes from _HookCaller.__call__().
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
results = []
|
||||
excinfo = None
|
||||
try: # run impl and wrapper setup functions in a loop
|
||||
teardowns = []
|
||||
try:
|
||||
for hook_impl in reversed(hook_impls):
|
||||
try:
|
||||
args = [caller_kwargs[argname] for argname in hook_impl.argnames]
|
||||
except KeyError:
|
||||
for argname in hook_impl.argnames:
|
||||
if argname not in caller_kwargs:
|
||||
raise HookCallError(
|
||||
"hook call must provide argument %r" % (argname,))
|
||||
|
||||
if hook_impl.hookwrapper:
|
||||
try:
|
||||
gen = hook_impl.function(*args)
|
||||
next(gen) # first yield
|
||||
teardowns.append(gen)
|
||||
except StopIteration:
|
||||
_raise_wrapfail(gen, "did not yield")
|
||||
else:
|
||||
res = hook_impl.function(*args)
|
||||
if res is not None:
|
||||
results.append(res)
|
||||
if firstresult: # halt further impl calls
|
||||
break
|
||||
except BaseException:
|
||||
excinfo = sys.exc_info()
|
||||
finally:
|
||||
if firstresult: # first result hooks return a single value
|
||||
outcome = _Result(results[0] if results else None, excinfo)
|
||||
else:
|
||||
outcome = _Result(results, excinfo)
|
||||
|
||||
# run all wrapper post-yield blocks
|
||||
for gen in reversed(teardowns):
|
||||
try:
|
||||
gen.send(outcome)
|
||||
_raise_wrapfail(gen, "has second yield")
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
return outcome.get_result()
|
|
@ -0,0 +1,13 @@
|
|||
[bdist_wheel]
|
||||
universal = 1
|
||||
|
||||
[metadata]
|
||||
license_file = LICENSE
|
||||
|
||||
[devpi:upload]
|
||||
formats = sdist.tgz,bdist_wheel
|
||||
|
||||
[egg_info]
|
||||
tag_build =
|
||||
tag_date = 0
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import os
|
||||
from setuptools import setup
|
||||
|
||||
classifiers = [
|
||||
'Development Status :: 4 - Beta',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: POSIX',
|
||||
'Operating System :: Microsoft :: Windows',
|
||||
'Operating System :: MacOS :: MacOS X',
|
||||
'Topic :: Software Development :: Testing',
|
||||
'Topic :: Software Development :: Libraries',
|
||||
'Topic :: Utilities',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Programming Language :: Python :: Implementation :: PyPy'] + [
|
||||
('Programming Language :: Python :: %s' % x) for x in
|
||||
'2 2.7 3 3.4 3.5 3.6'.split()]
|
||||
|
||||
with open('README.rst') as fd:
|
||||
long_description = fd.read()
|
||||
|
||||
|
||||
def get_version():
|
||||
p = os.path.join(os.path.dirname(
|
||||
os.path.abspath(__file__)), "pluggy/__init__.py")
|
||||
with open(p) as f:
|
||||
for line in f.readlines():
|
||||
if "__version__" in line:
|
||||
return line.strip().split("=")[-1].strip(" '")
|
||||
raise ValueError("could not read version")
|
||||
|
||||
|
||||
def main():
|
||||
setup(
|
||||
name='pluggy',
|
||||
description='plugin and hook calling mechanisms for python',
|
||||
long_description=long_description,
|
||||
version=get_version(),
|
||||
license='MIT license',
|
||||
platforms=['unix', 'linux', 'osx', 'win32'],
|
||||
author='Holger Krekel',
|
||||
author_email='holger@merlinux.eu',
|
||||
url='https://github.com/pytest-dev/pluggy',
|
||||
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
|
||||
classifiers=classifiers,
|
||||
packages=['pluggy'],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,59 @@
|
|||
"""
|
||||
Benchmarking and performance tests.
|
||||
"""
|
||||
import pytest
|
||||
from pluggy import (_multicall, _legacymulticall, HookImpl, HookspecMarker,
|
||||
HookimplMarker)
|
||||
|
||||
hookspec = HookspecMarker("example")
|
||||
hookimpl = HookimplMarker("example")
|
||||
|
||||
|
||||
def MC(methods, kwargs, callertype, firstresult=False):
|
||||
hookfuncs = []
|
||||
for method in methods:
|
||||
f = HookImpl(None, "<temp>", method, method.example_impl)
|
||||
hookfuncs.append(f)
|
||||
return callertype(hookfuncs, kwargs, {"firstresult": firstresult})
|
||||
|
||||
|
||||
@hookimpl
|
||||
def hook(arg1, arg2, arg3):
|
||||
return arg1, arg2, arg3
|
||||
|
||||
|
||||
@hookimpl(hookwrapper=True)
|
||||
def wrapper(arg1, arg2, arg3):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
params=[10, 100],
|
||||
ids="hooks={}".format,
|
||||
)
|
||||
def hooks(request):
|
||||
return [hook for i in range(request.param)]
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
params=[10, 100],
|
||||
ids="wrappers={}".format,
|
||||
)
|
||||
def wrappers(request):
|
||||
return [wrapper for i in range(request.param)]
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
params=[_multicall, _legacymulticall],
|
||||
ids=lambda item: item.__name__
|
||||
)
|
||||
def callertype(request):
|
||||
return request.param
|
||||
|
||||
|
||||
def inner_exec(methods, callertype):
|
||||
return MC(methods, {'arg1': 1, 'arg2': 2, 'arg3': 3}, callertype)
|
||||
|
||||
|
||||
def test_hook_and_wrappers_speed(benchmark, hooks, wrappers, callertype):
|
||||
benchmark(inner_exec, hooks + wrappers, callertype)
|
|
@ -0,0 +1,30 @@
|
|||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
params=[
|
||||
lambda spec: spec,
|
||||
lambda spec: spec()
|
||||
],
|
||||
ids=[
|
||||
"spec-is-class",
|
||||
"spec-is-instance"
|
||||
],
|
||||
)
|
||||
def he_pm(request, pm):
|
||||
from pluggy import HookspecMarker
|
||||
hookspec = HookspecMarker("example")
|
||||
|
||||
class Hooks(object):
|
||||
@hookspec
|
||||
def he_method1(self, arg):
|
||||
return arg + 1
|
||||
|
||||
pm.add_hookspecs(request.param(Hooks))
|
||||
return pm
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pm():
|
||||
from pluggy import PluginManager
|
||||
return PluginManager("example")
|
|
@ -0,0 +1,103 @@
|
|||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from pluggy import PluginManager, HookimplMarker, HookspecMarker, _Result
|
||||
|
||||
hookspec = HookspecMarker("example")
|
||||
hookimpl = HookimplMarker("example")
|
||||
|
||||
|
||||
def test_parse_hookimpl_override():
|
||||
class MyPluginManager(PluginManager):
|
||||
def parse_hookimpl_opts(self, module_or_class, name):
|
||||
opts = PluginManager.parse_hookimpl_opts(
|
||||
self, module_or_class, name)
|
||||
if opts is None:
|
||||
if name.startswith("x1"):
|
||||
opts = {}
|
||||
return opts
|
||||
|
||||
class Plugin(object):
|
||||
def x1meth(self):
|
||||
pass
|
||||
|
||||
@hookimpl(hookwrapper=True, tryfirst=True)
|
||||
def x1meth2(self):
|
||||
pass
|
||||
|
||||
class Spec(object):
|
||||
@hookspec
|
||||
def x1meth(self):
|
||||
pass
|
||||
|
||||
@hookspec
|
||||
def x1meth2(self):
|
||||
pass
|
||||
|
||||
pm = MyPluginManager(hookspec.project_name)
|
||||
pm.register(Plugin())
|
||||
pm.add_hookspecs(Spec)
|
||||
assert not pm.hook.x1meth._nonwrappers[0].hookwrapper
|
||||
assert not pm.hook.x1meth._nonwrappers[0].tryfirst
|
||||
assert not pm.hook.x1meth._nonwrappers[0].trylast
|
||||
assert not pm.hook.x1meth._nonwrappers[0].optionalhook
|
||||
|
||||
assert pm.hook.x1meth2._wrappers[0].tryfirst
|
||||
assert pm.hook.x1meth2._wrappers[0].hookwrapper
|
||||
|
||||
|
||||
def test_plugin_getattr_raises_errors():
|
||||
"""Pluggy must be able to handle plugins which raise weird exceptions
|
||||
when getattr() gets called (#11).
|
||||
"""
|
||||
class DontTouchMe(object):
|
||||
def __getattr__(self, x):
|
||||
raise Exception('cant touch me')
|
||||
|
||||
class Module(object):
|
||||
pass
|
||||
|
||||
module = Module()
|
||||
module.x = DontTouchMe()
|
||||
|
||||
pm = PluginManager(hookspec.project_name)
|
||||
# register() would raise an error
|
||||
pm.register(module, 'donttouch')
|
||||
assert pm.get_plugin('donttouch') is module
|
||||
|
||||
|
||||
def test_warning_on_call_vs_hookspec_arg_mismatch():
|
||||
"""Verify that is a hook is called with less arguments then defined in the
|
||||
spec that a warning is emitted.
|
||||
"""
|
||||
class Spec:
|
||||
@hookspec
|
||||
def myhook(self, arg1, arg2):
|
||||
pass
|
||||
|
||||
class Plugin:
|
||||
@hookimpl
|
||||
def myhook(self, arg1):
|
||||
pass
|
||||
|
||||
pm = PluginManager(hookspec.project_name)
|
||||
pm.register(Plugin())
|
||||
pm.add_hookspecs(Spec())
|
||||
|
||||
with warnings.catch_warnings(record=True) as warns:
|
||||
warnings.simplefilter('always')
|
||||
|
||||
# calling should trigger a warning
|
||||
pm.hook.myhook(arg1=1)
|
||||
|
||||
assert len(warns) == 1
|
||||
warning = warns[-1]
|
||||
assert issubclass(warning.category, Warning)
|
||||
assert "Argument(s) ('arg2',)" in str(warning.message)
|
||||
|
||||
|
||||
def test_result_deprecated():
|
||||
r = _Result(10, None)
|
||||
with pytest.deprecated_call():
|
||||
assert r.result == 10
|
|
@ -0,0 +1,68 @@
|
|||
from pluggy import _formatdef, varnames
|
||||
|
||||
|
||||
def test_varnames():
|
||||
def f(x):
|
||||
i = 3 # noqa
|
||||
|
||||
class A(object):
|
||||
def f(self, y):
|
||||
pass
|
||||
|
||||
class B(object):
|
||||
def __call__(self, z):
|
||||
pass
|
||||
|
||||
assert varnames(f) == (("x",), ())
|
||||
assert varnames(A().f) == (('y',), ())
|
||||
assert varnames(B()) == (('z',), ())
|
||||
|
||||
|
||||
def test_varnames_default():
|
||||
def f(x, y=3):
|
||||
pass
|
||||
|
||||
assert varnames(f) == (("x",), ("y",))
|
||||
|
||||
|
||||
def test_varnames_class():
|
||||
class C(object):
|
||||
def __init__(self, x):
|
||||
pass
|
||||
|
||||
class D(object):
|
||||
pass
|
||||
|
||||
class E(object):
|
||||
def __init__(self, x):
|
||||
pass
|
||||
|
||||
class F(object):
|
||||
pass
|
||||
|
||||
assert varnames(C) == (("x",), ())
|
||||
assert varnames(D) == ((), ())
|
||||
assert varnames(E) == (("x",), ())
|
||||
assert varnames(F) == ((), ())
|
||||
|
||||
|
||||
def test_formatdef():
|
||||
def function1():
|
||||
pass
|
||||
|
||||
assert _formatdef(function1) == 'function1()'
|
||||
|
||||
def function2(arg1):
|
||||
pass
|
||||
|
||||
assert _formatdef(function2) == "function2(arg1)"
|
||||
|
||||
def function3(arg1, arg2="qwe"):
|
||||
pass
|
||||
|
||||
assert _formatdef(function3) == "function3(arg1, arg2='qwe')"
|
||||
|
||||
def function4(arg1, *args, **kwargs):
|
||||
pass
|
||||
|
||||
assert _formatdef(function4) == "function4(arg1, *args, **kwargs)"
|
|
@ -0,0 +1,210 @@
|
|||
import pytest
|
||||
from pluggy import PluginValidationError, HookimplMarker, HookspecMarker
|
||||
|
||||
|
||||
hookspec = HookspecMarker("example")
|
||||
hookimpl = HookimplMarker("example")
|
||||
|
||||
|
||||
def test_happypath(pm):
|
||||
class Api(object):
|
||||
@hookspec
|
||||
def hello(self, arg):
|
||||
"api hook 1"
|
||||
|
||||
pm.add_hookspecs(Api)
|
||||
hook = pm.hook
|
||||
assert hasattr(hook, 'hello')
|
||||
assert repr(hook.hello).find("hello") != -1
|
||||
|
||||
class Plugin(object):
|
||||
@hookimpl
|
||||
def hello(self, arg):
|
||||
return arg + 1
|
||||
|
||||
plugin = Plugin()
|
||||
pm.register(plugin)
|
||||
out = hook.hello(arg=3)
|
||||
assert out == [4]
|
||||
assert not hasattr(hook, 'world')
|
||||
pm.unregister(plugin)
|
||||
assert hook.hello(arg=3) == []
|
||||
|
||||
|
||||
def test_argmismatch(pm):
|
||||
class Api(object):
|
||||
@hookspec
|
||||
def hello(self, arg):
|
||||
"api hook 1"
|
||||
|
||||
pm.add_hookspecs(Api)
|
||||
|
||||
class Plugin(object):
|
||||
@hookimpl
|
||||
def hello(self, argwrong):
|
||||
pass
|
||||
|
||||
with pytest.raises(PluginValidationError) as exc:
|
||||
pm.register(Plugin())
|
||||
|
||||
assert "argwrong" in str(exc.value)
|
||||
|
||||
|
||||
def test_only_kwargs(pm):
|
||||
class Api(object):
|
||||
@hookspec
|
||||
def hello(self, arg):
|
||||
"api hook 1"
|
||||
|
||||
pm.add_hookspecs(Api)
|
||||
with pytest.raises(TypeError) as exc:
|
||||
pm.hook.hello(3)
|
||||
|
||||
comprehensible = "hook calling supports only keyword arguments"
|
||||
assert comprehensible in str(exc.value)
|
||||
|
||||
|
||||
def test_call_order(pm):
|
||||
class Api(object):
|
||||
@hookspec
|
||||
def hello(self, arg):
|
||||
"api hook 1"
|
||||
|
||||
pm.add_hookspecs(Api)
|
||||
|
||||
class Plugin1(object):
|
||||
@hookimpl
|
||||
def hello(self, arg):
|
||||
return 1
|
||||
|
||||
class Plugin2(object):
|
||||
@hookimpl
|
||||
def hello(self, arg):
|
||||
return 2
|
||||
|
||||
class Plugin3(object):
|
||||
@hookimpl
|
||||
def hello(self, arg):
|
||||
return 3
|
||||
|
||||
class Plugin4(object):
|
||||
@hookimpl(hookwrapper=True)
|
||||
def hello(self, arg):
|
||||
assert arg == 0
|
||||
outcome = yield
|
||||
assert outcome.get_result() == [3, 2, 1]
|
||||
|
||||
pm.register(Plugin1())
|
||||
pm.register(Plugin2())
|
||||
pm.register(Plugin3())
|
||||
pm.register(Plugin4()) # hookwrapper should get same list result
|
||||
res = pm.hook.hello(arg=0)
|
||||
assert res == [3, 2, 1]
|
||||
|
||||
|
||||
def test_firstresult_definition(pm):
|
||||
class Api(object):
|
||||
@hookspec(firstresult=True)
|
||||
def hello(self, arg):
|
||||
"api hook 1"
|
||||
|
||||
pm.add_hookspecs(Api)
|
||||
|
||||
class Plugin1(object):
|
||||
@hookimpl
|
||||
def hello(self, arg):
|
||||
return arg + 1
|
||||
|
||||
class Plugin2(object):
|
||||
@hookimpl
|
||||
def hello(self, arg):
|
||||
return arg - 1
|
||||
|
||||
class Plugin3(object):
|
||||
@hookimpl
|
||||
def hello(self, arg):
|
||||
return None
|
||||
|
||||
class Plugin4(object):
|
||||
@hookimpl(hookwrapper=True)
|
||||
def hello(self, arg):
|
||||
assert arg == 3
|
||||
outcome = yield
|
||||
assert outcome.get_result() == 2
|
||||
|
||||
pm.register(Plugin1()) # discarded - not the last registered plugin
|
||||
pm.register(Plugin2()) # used as result
|
||||
pm.register(Plugin3()) # None result is ignored
|
||||
pm.register(Plugin4()) # hookwrapper should get same non-list result
|
||||
res = pm.hook.hello(arg=3)
|
||||
assert res == 2
|
||||
|
||||
|
||||
def test_firstresult_force_result(pm):
|
||||
"""Verify forcing a result in a wrapper.
|
||||
"""
|
||||
class Api(object):
|
||||
@hookspec(firstresult=True)
|
||||
def hello(self, arg):
|
||||
"api hook 1"
|
||||
|
||||
pm.add_hookspecs(Api)
|
||||
|
||||
class Plugin1(object):
|
||||
@hookimpl
|
||||
def hello(self, arg):
|
||||
return arg + 1
|
||||
|
||||
class Plugin2(object):
|
||||
@hookimpl(hookwrapper=True)
|
||||
def hello(self, arg):
|
||||
assert arg == 3
|
||||
outcome = yield
|
||||
assert outcome.get_result() == 4
|
||||
outcome.force_result(0)
|
||||
|
||||
class Plugin3(object):
|
||||
@hookimpl
|
||||
def hello(self, arg):
|
||||
return None
|
||||
|
||||
pm.register(Plugin1())
|
||||
pm.register(Plugin2()) # wrapper
|
||||
pm.register(Plugin3()) # ignored since returns None
|
||||
res = pm.hook.hello(arg=3)
|
||||
assert res == 0 # this result is forced and not a list
|
||||
|
||||
|
||||
def test_firstresult_returns_none(pm):
|
||||
"""If None results are returned by underlying implementations ensure
|
||||
the multi-call loop returns a None value.
|
||||
"""
|
||||
class Api(object):
|
||||
@hookspec(firstresult=True)
|
||||
def hello(self, arg):
|
||||
"api hook 1"
|
||||
|
||||
pm.add_hookspecs(Api)
|
||||
|
||||
class Plugin1(object):
|
||||
@hookimpl
|
||||
def hello(self, arg):
|
||||
return None
|
||||
|
||||
pm.register(Plugin1())
|
||||
res = pm.hook.hello(arg=3)
|
||||
assert res is None
|
||||
|
||||
|
||||
def test_firstresult_no_plugin(pm):
|
||||
"""If no implementations/plugins have been registered for a firstresult
|
||||
hook the multi-call loop should return a None value.
|
||||
"""
|
||||
class Api(object):
|
||||
@hookspec(firstresult=True)
|
||||
def hello(self, arg):
|
||||
"api hook 1"
|
||||
|
||||
pm.add_hookspecs(Api)
|
||||
res = pm.hook.hello(arg=3)
|
||||
assert res is None
|
|
@ -0,0 +1,322 @@
|
|||
import pytest
|
||||
|
||||
|
||||
import sys
|
||||
import types
|
||||
|
||||
from pluggy import PluginManager, HookImpl, HookimplMarker, HookspecMarker
|
||||
|
||||
hookspec = HookspecMarker("example")
|
||||
hookimpl = HookimplMarker("example")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def hc(pm):
|
||||
class Hooks(object):
|
||||
@hookspec
|
||||
def he_method1(self, arg):
|
||||
pass
|
||||
pm.add_hookspecs(Hooks)
|
||||
return pm.hook.he_method1
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def addmeth(hc):
|
||||
def addmeth(tryfirst=False, trylast=False, hookwrapper=False):
|
||||
def wrap(func):
|
||||
hookimpl(tryfirst=tryfirst, trylast=trylast,
|
||||
hookwrapper=hookwrapper)(func)
|
||||
hc._add_hookimpl(HookImpl(None, "<temp>", func, func.example_impl))
|
||||
return func
|
||||
return wrap
|
||||
return addmeth
|
||||
|
||||
|
||||
def funcs(hookmethods):
|
||||
return [hookmethod.function for hookmethod in hookmethods]
|
||||
|
||||
|
||||
def test_adding_nonwrappers(hc, addmeth):
|
||||
@addmeth()
|
||||
def he_method1():
|
||||
pass
|
||||
|
||||
@addmeth()
|
||||
def he_method2():
|
||||
pass
|
||||
|
||||
@addmeth()
|
||||
def he_method3():
|
||||
pass
|
||||
assert funcs(hc._nonwrappers) == [he_method1, he_method2, he_method3]
|
||||
|
||||
|
||||
def test_adding_nonwrappers_trylast(hc, addmeth):
|
||||
@addmeth()
|
||||
def he_method1_middle():
|
||||
pass
|
||||
|
||||
@addmeth(trylast=True)
|
||||
def he_method1():
|
||||
pass
|
||||
|
||||
@addmeth()
|
||||
def he_method1_b():
|
||||
pass
|
||||
assert funcs(hc._nonwrappers) == [he_method1, he_method1_middle, he_method1_b]
|
||||
|
||||
|
||||
def test_adding_nonwrappers_trylast3(hc, addmeth):
|
||||
@addmeth()
|
||||
def he_method1_a():
|
||||
pass
|
||||
|
||||
@addmeth(trylast=True)
|
||||
def he_method1_b():
|
||||
pass
|
||||
|
||||
@addmeth()
|
||||
def he_method1_c():
|
||||
pass
|
||||
|
||||
@addmeth(trylast=True)
|
||||
def he_method1_d():
|
||||
pass
|
||||
assert funcs(hc._nonwrappers) == \
|
||||
[he_method1_d, he_method1_b, he_method1_a, he_method1_c]
|
||||
|
||||
|
||||
def test_adding_nonwrappers_trylast2(hc, addmeth):
|
||||
@addmeth()
|
||||
def he_method1_middle():
|
||||
pass
|
||||
|
||||
@addmeth()
|
||||
def he_method1_b():
|
||||
pass
|
||||
|
||||
@addmeth(trylast=True)
|
||||
def he_method1():
|
||||
pass
|
||||
assert funcs(hc._nonwrappers) == \
|
||||
[he_method1, he_method1_middle, he_method1_b]
|
||||
|
||||
|
||||
def test_adding_nonwrappers_tryfirst(hc, addmeth):
|
||||
@addmeth(tryfirst=True)
|
||||
def he_method1():
|
||||
pass
|
||||
|
||||
@addmeth()
|
||||
def he_method1_middle():
|
||||
pass
|
||||
|
||||
@addmeth()
|
||||
def he_method1_b():
|
||||
pass
|
||||
assert funcs(hc._nonwrappers) == [
|
||||
he_method1_middle, he_method1_b, he_method1]
|
||||
|
||||
|
||||
def test_adding_wrappers_ordering(hc, addmeth):
|
||||
@addmeth(hookwrapper=True)
|
||||
def he_method1():
|
||||
pass
|
||||
|
||||
@addmeth()
|
||||
def he_method1_middle():
|
||||
pass
|
||||
|
||||
@addmeth(hookwrapper=True)
|
||||
def he_method3():
|
||||
pass
|
||||
|
||||
assert funcs(hc._nonwrappers) == [he_method1_middle]
|
||||
assert funcs(hc._wrappers) == [he_method1, he_method3]
|
||||
|
||||
|
||||
def test_adding_wrappers_ordering_tryfirst(hc, addmeth):
|
||||
@addmeth(hookwrapper=True, tryfirst=True)
|
||||
def he_method1():
|
||||
pass
|
||||
|
||||
@addmeth(hookwrapper=True)
|
||||
def he_method2():
|
||||
pass
|
||||
|
||||
assert hc._nonwrappers == []
|
||||
assert funcs(hc._wrappers) == [he_method2, he_method1]
|
||||
|
||||
|
||||
def test_hookspec(pm):
|
||||
class HookSpec(object):
|
||||
@hookspec()
|
||||
def he_myhook1(arg1):
|
||||
pass
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def he_myhook2(arg1):
|
||||
pass
|
||||
|
||||
@hookspec(firstresult=False)
|
||||
def he_myhook3(arg1):
|
||||
pass
|
||||
|
||||
pm.add_hookspecs(HookSpec)
|
||||
assert not pm.hook.he_myhook1.spec_opts["firstresult"]
|
||||
assert pm.hook.he_myhook2.spec_opts["firstresult"]
|
||||
assert not pm.hook.he_myhook3.spec_opts["firstresult"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name', ["hookwrapper", "optionalhook", "tryfirst", "trylast"])
|
||||
@pytest.mark.parametrize('val', [True, False])
|
||||
def test_hookimpl(name, val):
|
||||
@hookimpl(**{name: val})
|
||||
def he_myhook1(arg1):
|
||||
pass
|
||||
if val:
|
||||
assert he_myhook1.example_impl.get(name)
|
||||
else:
|
||||
assert not hasattr(he_myhook1, name)
|
||||
|
||||
|
||||
def test_load_setuptools_instantiation(monkeypatch, pm):
|
||||
pkg_resources = pytest.importorskip("pkg_resources")
|
||||
|
||||
def my_iter(name):
|
||||
assert name == "hello"
|
||||
|
||||
class EntryPoint(object):
|
||||
name = "myname"
|
||||
dist = None
|
||||
|
||||
def load(self):
|
||||
class PseudoPlugin(object):
|
||||
x = 42
|
||||
return PseudoPlugin()
|
||||
|
||||
return iter([EntryPoint()])
|
||||
|
||||
monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
|
||||
num = pm.load_setuptools_entrypoints("hello")
|
||||
assert num == 1
|
||||
plugin = pm.get_plugin("myname")
|
||||
assert plugin.x == 42
|
||||
assert pm.list_plugin_distinfo() == [(plugin, None)]
|
||||
|
||||
|
||||
def test_load_setuptools_not_installed(monkeypatch, pm):
|
||||
monkeypatch.setitem(
|
||||
sys.modules, 'pkg_resources',
|
||||
types.ModuleType("pkg_resources"))
|
||||
|
||||
with pytest.raises(ImportError):
|
||||
pm.load_setuptools_entrypoints("qwe")
|
||||
|
||||
|
||||
def test_add_tracefuncs(he_pm):
|
||||
out = []
|
||||
|
||||
class api1(object):
|
||||
@hookimpl
|
||||
def he_method1(self):
|
||||
out.append("he_method1-api1")
|
||||
|
||||
class api2(object):
|
||||
@hookimpl
|
||||
def he_method1(self):
|
||||
out.append("he_method1-api2")
|
||||
|
||||
he_pm.register(api1())
|
||||
he_pm.register(api2())
|
||||
|
||||
def before(hook_name, hook_impls, kwargs):
|
||||
out.append((hook_name, list(hook_impls), kwargs))
|
||||
|
||||
def after(outcome, hook_name, hook_impls, kwargs):
|
||||
out.append((outcome, hook_name, list(hook_impls), kwargs))
|
||||
|
||||
undo = he_pm.add_hookcall_monitoring(before, after)
|
||||
|
||||
he_pm.hook.he_method1(arg=1)
|
||||
assert len(out) == 4
|
||||
assert out[0][0] == "he_method1"
|
||||
assert len(out[0][1]) == 2
|
||||
assert isinstance(out[0][2], dict)
|
||||
assert out[1] == "he_method1-api2"
|
||||
assert out[2] == "he_method1-api1"
|
||||
assert len(out[3]) == 4
|
||||
assert out[3][1] == out[0][0]
|
||||
|
||||
undo()
|
||||
he_pm.hook.he_method1(arg=1)
|
||||
assert len(out) == 4 + 2
|
||||
|
||||
|
||||
def test_hook_tracing(he_pm):
|
||||
saveindent = []
|
||||
|
||||
class api1(object):
|
||||
@hookimpl
|
||||
def he_method1(self):
|
||||
saveindent.append(he_pm.trace.root.indent)
|
||||
|
||||
class api2(object):
|
||||
@hookimpl
|
||||
def he_method1(self):
|
||||
saveindent.append(he_pm.trace.root.indent)
|
||||
raise ValueError()
|
||||
|
||||
he_pm.register(api1())
|
||||
out = []
|
||||
he_pm.trace.root.setwriter(out.append)
|
||||
undo = he_pm.enable_tracing()
|
||||
try:
|
||||
indent = he_pm.trace.root.indent
|
||||
he_pm.hook.he_method1(arg=1)
|
||||
assert indent == he_pm.trace.root.indent
|
||||
assert len(out) == 2
|
||||
assert 'he_method1' in out[0]
|
||||
assert 'finish' in out[1]
|
||||
|
||||
out[:] = []
|
||||
he_pm.register(api2())
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
he_pm.hook.he_method1(arg=1)
|
||||
assert he_pm.trace.root.indent == indent
|
||||
assert saveindent[0] > indent
|
||||
finally:
|
||||
undo()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('include_hookspec', [True, False])
|
||||
def test_prefix_hookimpl(include_hookspec):
|
||||
pm = PluginManager(hookspec.project_name, "hello_")
|
||||
|
||||
if include_hookspec:
|
||||
class HookSpec(object):
|
||||
@hookspec
|
||||
def hello_myhook(self, arg1):
|
||||
""" add to arg1 """
|
||||
|
||||
pm.add_hookspecs(HookSpec)
|
||||
|
||||
class Plugin(object):
|
||||
def hello_myhook(self, arg1):
|
||||
return arg1 + 1
|
||||
|
||||
pm.register(Plugin())
|
||||
pm.register(Plugin())
|
||||
results = pm.hook.hello_myhook(arg1=17)
|
||||
assert results == [18, 18]
|
||||
|
||||
|
||||
def test_prefix_hookimpl_dontmatch_module():
|
||||
pm = PluginManager(hookspec.project_name, "hello_")
|
||||
|
||||
class BadPlugin(object):
|
||||
hello_module = __import__('email')
|
||||
|
||||
pm.register(BadPlugin())
|
||||
pm.check_pending()
|
|
@ -0,0 +1,194 @@
|
|||
import pytest
|
||||
|
||||
from pluggy import _multicall, _legacymulticall, HookImpl, HookCallError
|
||||
from pluggy.callers import _LegacyMultiCall
|
||||
from pluggy import HookspecMarker, HookimplMarker
|
||||
|
||||
|
||||
hookspec = HookspecMarker("example")
|
||||
hookimpl = HookimplMarker("example")
|
||||
|
||||
|
||||
def test_uses_copy_of_methods():
|
||||
out = [lambda: 42]
|
||||
mc = _LegacyMultiCall(out, {})
|
||||
repr(mc)
|
||||
out[:] = []
|
||||
res = mc.execute()
|
||||
return res == 42
|
||||
|
||||
|
||||
def MC(methods, kwargs, firstresult=False):
|
||||
caller = _multicall
|
||||
hookfuncs = []
|
||||
for method in methods:
|
||||
f = HookImpl(None, "<temp>", method, method.example_impl)
|
||||
hookfuncs.append(f)
|
||||
if '__multicall__' in f.argnames:
|
||||
caller = _legacymulticall
|
||||
return caller(hookfuncs, kwargs, firstresult=firstresult)
|
||||
|
||||
|
||||
def test_call_passing():
|
||||
class P1(object):
|
||||
@hookimpl
|
||||
def m(self, __multicall__, x):
|
||||
assert len(__multicall__.results) == 1
|
||||
assert not __multicall__.hook_impls
|
||||
return 17
|
||||
|
||||
class P2(object):
|
||||
@hookimpl
|
||||
def m(self, __multicall__, x):
|
||||
assert __multicall__.results == []
|
||||
assert __multicall__.hook_impls
|
||||
return 23
|
||||
|
||||
p1 = P1()
|
||||
p2 = P2()
|
||||
reslist = MC([p1.m, p2.m], {"x": 23})
|
||||
assert len(reslist) == 2
|
||||
# ensure reversed order
|
||||
assert reslist == [23, 17]
|
||||
|
||||
|
||||
def test_keyword_args():
|
||||
@hookimpl
|
||||
def f(x):
|
||||
return x + 1
|
||||
|
||||
class A(object):
|
||||
@hookimpl
|
||||
def f(self, x, y):
|
||||
return x + y
|
||||
|
||||
reslist = MC([f, A().f], dict(x=23, y=24))
|
||||
assert reslist == [24 + 23, 24]
|
||||
|
||||
|
||||
def test_keyword_args_with_defaultargs():
|
||||
@hookimpl
|
||||
def f(x, z=1):
|
||||
return x + z
|
||||
reslist = MC([f], dict(x=23, y=24))
|
||||
assert reslist == [24]
|
||||
|
||||
|
||||
def test_tags_call_error():
|
||||
@hookimpl
|
||||
def f(x):
|
||||
return x
|
||||
with pytest.raises(HookCallError):
|
||||
MC([f], {})
|
||||
|
||||
|
||||
def test_call_subexecute():
|
||||
@hookimpl
|
||||
def m(__multicall__):
|
||||
subresult = __multicall__.execute()
|
||||
return subresult + 1
|
||||
|
||||
@hookimpl
|
||||
def n():
|
||||
return 1
|
||||
|
||||
res = MC([n, m], {}, firstresult=True)
|
||||
assert res == 2
|
||||
|
||||
|
||||
def test_call_none_is_no_result():
|
||||
@hookimpl
|
||||
def m1():
|
||||
return 1
|
||||
|
||||
@hookimpl
|
||||
def m2():
|
||||
return None
|
||||
|
||||
res = MC([m1, m2], {}, firstresult=True)
|
||||
assert res == 1
|
||||
res = MC([m1, m2], {}, {})
|
||||
assert res == [1]
|
||||
|
||||
|
||||
def test_hookwrapper():
|
||||
out = []
|
||||
|
||||
@hookimpl(hookwrapper=True)
|
||||
def m1():
|
||||
out.append("m1 init")
|
||||
yield None
|
||||
out.append("m1 finish")
|
||||
|
||||
@hookimpl
|
||||
def m2():
|
||||
out.append("m2")
|
||||
return 2
|
||||
|
||||
res = MC([m2, m1], {})
|
||||
assert res == [2]
|
||||
assert out == ["m1 init", "m2", "m1 finish"]
|
||||
out[:] = []
|
||||
res = MC([m2, m1], {}, firstresult=True)
|
||||
assert res == 2
|
||||
assert out == ["m1 init", "m2", "m1 finish"]
|
||||
|
||||
|
||||
def test_hookwrapper_order():
|
||||
out = []
|
||||
|
||||
@hookimpl(hookwrapper=True)
|
||||
def m1():
|
||||
out.append("m1 init")
|
||||
yield 1
|
||||
out.append("m1 finish")
|
||||
|
||||
@hookimpl(hookwrapper=True)
|
||||
def m2():
|
||||
out.append("m2 init")
|
||||
yield 2
|
||||
out.append("m2 finish")
|
||||
|
||||
res = MC([m2, m1], {})
|
||||
assert res == []
|
||||
assert out == ["m1 init", "m2 init", "m2 finish", "m1 finish"]
|
||||
|
||||
|
||||
def test_hookwrapper_not_yield():
|
||||
@hookimpl(hookwrapper=True)
|
||||
def m1():
|
||||
pass
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
MC([m1], {})
|
||||
|
||||
|
||||
def test_hookwrapper_too_many_yield():
|
||||
@hookimpl(hookwrapper=True)
|
||||
def m1():
|
||||
yield 1
|
||||
yield 2
|
||||
|
||||
with pytest.raises(RuntimeError) as ex:
|
||||
MC([m1], {})
|
||||
assert "m1" in str(ex.value)
|
||||
assert (__file__ + ':') in str(ex.value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exc", [ValueError, SystemExit])
|
||||
def test_hookwrapper_exception(exc):
|
||||
out = []
|
||||
|
||||
@hookimpl(hookwrapper=True)
|
||||
def m1():
|
||||
out.append("m1 init")
|
||||
yield None
|
||||
out.append("m1 finish")
|
||||
|
||||
@hookimpl
|
||||
def m2():
|
||||
raise exc
|
||||
|
||||
with pytest.raises(exc):
|
||||
MC([m2, m1], {})
|
||||
assert out == ["m1 init", "m1 finish"]
|
|
@ -0,0 +1,374 @@
|
|||
import pytest
|
||||
import types
|
||||
|
||||
from pluggy import (PluginValidationError,
|
||||
HookCallError, HookimplMarker, HookspecMarker)
|
||||
|
||||
|
||||
hookspec = HookspecMarker("example")
|
||||
hookimpl = HookimplMarker("example")
|
||||
|
||||
|
||||
def test_plugin_double_register(pm):
|
||||
pm.register(42, name="abc")
|
||||
with pytest.raises(ValueError):
|
||||
pm.register(42, name="abc")
|
||||
with pytest.raises(ValueError):
|
||||
pm.register(42, name="def")
|
||||
|
||||
|
||||
def test_pm(pm):
|
||||
class A(object):
|
||||
pass
|
||||
|
||||
a1, a2 = A(), A()
|
||||
pm.register(a1)
|
||||
assert pm.is_registered(a1)
|
||||
pm.register(a2, "hello")
|
||||
assert pm.is_registered(a2)
|
||||
out = pm.get_plugins()
|
||||
assert a1 in out
|
||||
assert a2 in out
|
||||
assert pm.get_plugin('hello') == a2
|
||||
assert pm.unregister(a1) == a1
|
||||
assert not pm.is_registered(a1)
|
||||
|
||||
out = pm.list_name_plugin()
|
||||
assert len(out) == 1
|
||||
assert out == [("hello", a2)]
|
||||
|
||||
|
||||
def test_has_plugin(pm):
|
||||
class A(object):
|
||||
pass
|
||||
|
||||
a1 = A()
|
||||
pm.register(a1, 'hello')
|
||||
assert pm.is_registered(a1)
|
||||
assert pm.has_plugin('hello')
|
||||
|
||||
|
||||
def test_register_dynamic_attr(he_pm):
|
||||
class A(object):
|
||||
def __getattr__(self, name):
|
||||
if name[0] != "_":
|
||||
return 42
|
||||
raise AttributeError()
|
||||
|
||||
a = A()
|
||||
he_pm.register(a)
|
||||
assert not he_pm.get_hookcallers(a)
|
||||
|
||||
|
||||
def test_pm_name(pm):
|
||||
class A(object):
|
||||
pass
|
||||
|
||||
a1 = A()
|
||||
name = pm.register(a1, name="hello")
|
||||
assert name == "hello"
|
||||
pm.unregister(a1)
|
||||
assert pm.get_plugin(a1) is None
|
||||
assert not pm.is_registered(a1)
|
||||
assert not pm.get_plugins()
|
||||
name2 = pm.register(a1, name="hello")
|
||||
assert name2 == name
|
||||
pm.unregister(name="hello")
|
||||
assert pm.get_plugin(a1) is None
|
||||
assert not pm.is_registered(a1)
|
||||
assert not pm.get_plugins()
|
||||
|
||||
|
||||
def test_set_blocked(pm):
|
||||
class A(object):
|
||||
pass
|
||||
|
||||
a1 = A()
|
||||
name = pm.register(a1)
|
||||
assert pm.is_registered(a1)
|
||||
assert not pm.is_blocked(name)
|
||||
pm.set_blocked(name)
|
||||
assert pm.is_blocked(name)
|
||||
assert not pm.is_registered(a1)
|
||||
|
||||
pm.set_blocked("somename")
|
||||
assert pm.is_blocked("somename")
|
||||
assert not pm.register(A(), "somename")
|
||||
pm.unregister(name="somename")
|
||||
assert pm.is_blocked("somename")
|
||||
|
||||
|
||||
def test_register_mismatch_method(he_pm):
|
||||
class hello(object):
|
||||
@hookimpl
|
||||
def he_method_notexists(self):
|
||||
pass
|
||||
|
||||
he_pm.register(hello())
|
||||
with pytest.raises(PluginValidationError):
|
||||
he_pm.check_pending()
|
||||
|
||||
|
||||
def test_register_mismatch_arg(he_pm):
|
||||
class hello(object):
|
||||
@hookimpl
|
||||
def he_method1(self, qlwkje):
|
||||
pass
|
||||
|
||||
with pytest.raises(PluginValidationError):
|
||||
he_pm.register(hello())
|
||||
|
||||
|
||||
def test_register(pm):
|
||||
class MyPlugin(object):
|
||||
pass
|
||||
my = MyPlugin()
|
||||
pm.register(my)
|
||||
assert my in pm.get_plugins()
|
||||
my2 = MyPlugin()
|
||||
pm.register(my2)
|
||||
assert set([my, my2]).issubset(pm.get_plugins())
|
||||
|
||||
assert pm.is_registered(my)
|
||||
assert pm.is_registered(my2)
|
||||
pm.unregister(my)
|
||||
assert not pm.is_registered(my)
|
||||
assert my not in pm.get_plugins()
|
||||
|
||||
|
||||
def test_register_unknown_hooks(pm):
|
||||
class Plugin1(object):
|
||||
@hookimpl
|
||||
def he_method1(self, arg):
|
||||
return arg + 1
|
||||
|
||||
pname = pm.register(Plugin1())
|
||||
|
||||
class Hooks(object):
|
||||
@hookspec
|
||||
def he_method1(self, arg):
|
||||
pass
|
||||
|
||||
pm.add_hookspecs(Hooks)
|
||||
# assert not pm._unverified_hooks
|
||||
assert pm.hook.he_method1(arg=1) == [2]
|
||||
assert len(pm.get_hookcallers(pm.get_plugin(pname))) == 1
|
||||
|
||||
|
||||
def test_register_historic(pm):
|
||||
class Hooks(object):
|
||||
@hookspec(historic=True)
|
||||
def he_method1(self, arg):
|
||||
pass
|
||||
pm.add_hookspecs(Hooks)
|
||||
|
||||
pm.hook.he_method1.call_historic(kwargs=dict(arg=1))
|
||||
out = []
|
||||
|
||||
class Plugin(object):
|
||||
@hookimpl
|
||||
def he_method1(self, arg):
|
||||
out.append(arg)
|
||||
|
||||
pm.register(Plugin())
|
||||
assert out == [1]
|
||||
|
||||
class Plugin2(object):
|
||||
@hookimpl
|
||||
def he_method1(self, arg):
|
||||
out.append(arg * 10)
|
||||
|
||||
pm.register(Plugin2())
|
||||
assert out == [1, 10]
|
||||
pm.hook.he_method1.call_historic(kwargs=dict(arg=12))
|
||||
assert out == [1, 10, 120, 12]
|
||||
|
||||
|
||||
def test_with_result_memorized(pm):
|
||||
class Hooks(object):
|
||||
@hookspec(historic=True)
|
||||
def he_method1(self, arg):
|
||||
pass
|
||||
pm.add_hookspecs(Hooks)
|
||||
|
||||
he_method1 = pm.hook.he_method1
|
||||
he_method1.call_historic(lambda res: out.append(res), dict(arg=1))
|
||||
out = []
|
||||
|
||||
class Plugin(object):
|
||||
@hookimpl
|
||||
def he_method1(self, arg):
|
||||
return arg * 10
|
||||
|
||||
pm.register(Plugin())
|
||||
assert out == [10]
|
||||
|
||||
|
||||
def test_with_callbacks_immediately_executed(pm):
|
||||
class Hooks(object):
|
||||
@hookspec(historic=True)
|
||||
def he_method1(self, arg):
|
||||
pass
|
||||
pm.add_hookspecs(Hooks)
|
||||
|
||||
class Plugin1(object):
|
||||
@hookimpl
|
||||
def he_method1(self, arg):
|
||||
return arg * 10
|
||||
|
||||
class Plugin2(object):
|
||||
@hookimpl
|
||||
def he_method1(self, arg):
|
||||
return arg * 20
|
||||
|
||||
class Plugin3(object):
|
||||
@hookimpl
|
||||
def he_method1(self, arg):
|
||||
return arg * 30
|
||||
|
||||
out = []
|
||||
pm.register(Plugin1())
|
||||
pm.register(Plugin2())
|
||||
|
||||
he_method1 = pm.hook.he_method1
|
||||
he_method1.call_historic(lambda res: out.append(res), dict(arg=1))
|
||||
assert out == [20, 10]
|
||||
pm.register(Plugin3())
|
||||
assert out == [20, 10, 30]
|
||||
|
||||
|
||||
def test_register_historic_incompat_hookwrapper(pm):
|
||||
class Hooks(object):
|
||||
@hookspec(historic=True)
|
||||
def he_method1(self, arg):
|
||||
pass
|
||||
|
||||
pm.add_hookspecs(Hooks)
|
||||
|
||||
out = []
|
||||
|
||||
class Plugin(object):
|
||||
@hookimpl(hookwrapper=True)
|
||||
def he_method1(self, arg):
|
||||
out.append(arg)
|
||||
|
||||
with pytest.raises(PluginValidationError):
|
||||
pm.register(Plugin())
|
||||
|
||||
|
||||
def test_call_extra(pm):
|
||||
class Hooks(object):
|
||||
@hookspec
|
||||
def he_method1(self, arg):
|
||||
pass
|
||||
|
||||
pm.add_hookspecs(Hooks)
|
||||
|
||||
def he_method1(arg):
|
||||
return arg * 10
|
||||
|
||||
out = pm.hook.he_method1.call_extra([he_method1], dict(arg=1))
|
||||
assert out == [10]
|
||||
|
||||
|
||||
def test_call_with_too_few_args(pm):
|
||||
class Hooks(object):
|
||||
@hookspec
|
||||
def he_method1(self, arg):
|
||||
pass
|
||||
|
||||
pm.add_hookspecs(Hooks)
|
||||
|
||||
class Plugin1(object):
|
||||
@hookimpl
|
||||
def he_method1(self, arg):
|
||||
0 / 0
|
||||
pm.register(Plugin1())
|
||||
with pytest.raises(HookCallError):
|
||||
with pytest.warns(UserWarning):
|
||||
pm.hook.he_method1()
|
||||
|
||||
|
||||
def test_subset_hook_caller(pm):
|
||||
class Hooks(object):
|
||||
@hookspec
|
||||
def he_method1(self, arg):
|
||||
pass
|
||||
|
||||
pm.add_hookspecs(Hooks)
|
||||
|
||||
out = []
|
||||
|
||||
class Plugin1(object):
|
||||
@hookimpl
|
||||
def he_method1(self, arg):
|
||||
out.append(arg)
|
||||
|
||||
class Plugin2(object):
|
||||
@hookimpl
|
||||
def he_method1(self, arg):
|
||||
out.append(arg * 10)
|
||||
|
||||
class PluginNo(object):
|
||||
pass
|
||||
|
||||
plugin1, plugin2, plugin3 = Plugin1(), Plugin2(), PluginNo()
|
||||
pm.register(plugin1)
|
||||
pm.register(plugin2)
|
||||
pm.register(plugin3)
|
||||
pm.hook.he_method1(arg=1)
|
||||
assert out == [10, 1]
|
||||
out[:] = []
|
||||
|
||||
hc = pm.subset_hook_caller("he_method1", [plugin1])
|
||||
hc(arg=2)
|
||||
assert out == [20]
|
||||
out[:] = []
|
||||
|
||||
hc = pm.subset_hook_caller("he_method1", [plugin2])
|
||||
hc(arg=2)
|
||||
assert out == [2]
|
||||
out[:] = []
|
||||
|
||||
pm.unregister(plugin1)
|
||||
hc(arg=2)
|
||||
assert out == []
|
||||
out[:] = []
|
||||
|
||||
pm.hook.he_method1(arg=1)
|
||||
assert out == [10]
|
||||
|
||||
|
||||
def test_multicall_deprecated(pm):
|
||||
class P1(object):
|
||||
@hookimpl
|
||||
def m(self, __multicall__, x):
|
||||
pass
|
||||
|
||||
pytest.deprecated_call(pm.register, P1())
|
||||
|
||||
|
||||
def test_add_hookspecs_nohooks(pm):
|
||||
with pytest.raises(ValueError):
|
||||
pm.add_hookspecs(10)
|
||||
|
||||
|
||||
def test_reject_prefixed_module(pm):
|
||||
"""Verify that a module type attribute that contains the project
|
||||
prefix in its name (in this case `'example_*'` isn't collected
|
||||
when registering a module which imports it.
|
||||
"""
|
||||
pm._implprefix = 'example'
|
||||
conftest = types.ModuleType("conftest")
|
||||
src = ("""
|
||||
def example_hook():
|
||||
pass
|
||||
""")
|
||||
exec(src, conftest.__dict__)
|
||||
conftest.example_blah = types.ModuleType("example_blah")
|
||||
name = pm.register(conftest)
|
||||
assert name == 'conftest'
|
||||
assert getattr(pm.hook, 'example_blah', None) is None
|
||||
assert getattr(pm.hook, 'example_hook', None) # conftest.example_hook should be collected
|
||||
assert pm.parse_hookimpl_opts(conftest, 'example_blah') is None
|
||||
assert pm.parse_hookimpl_opts(conftest, 'example_hook') == {}
|
|
@ -0,0 +1,89 @@
|
|||
|
||||
from pluggy import _TagTracer
|
||||
|
||||
|
||||
def test_simple():
|
||||
rootlogger = _TagTracer()
|
||||
log = rootlogger.get("pytest")
|
||||
log("hello")
|
||||
out = []
|
||||
rootlogger.setwriter(out.append)
|
||||
log("world")
|
||||
assert len(out) == 1
|
||||
assert out[0] == "world [pytest]\n"
|
||||
sublog = log.get("collection")
|
||||
sublog("hello")
|
||||
assert out[1] == "hello [pytest:collection]\n"
|
||||
|
||||
|
||||
def test_indent():
|
||||
rootlogger = _TagTracer()
|
||||
log = rootlogger.get("1")
|
||||
out = []
|
||||
log.root.setwriter(lambda arg: out.append(arg))
|
||||
log("hello")
|
||||
log.root.indent += 1
|
||||
log("line1")
|
||||
log("line2")
|
||||
log.root.indent += 1
|
||||
log("line3")
|
||||
log("line4")
|
||||
log.root.indent -= 1
|
||||
log("line5")
|
||||
log.root.indent -= 1
|
||||
log("last")
|
||||
assert len(out) == 7
|
||||
names = [x[:x.rfind(' [')] for x in out]
|
||||
assert names == [
|
||||
'hello', ' line1', ' line2',
|
||||
' line3', ' line4', ' line5', 'last']
|
||||
|
||||
|
||||
def test_readable_output_dictargs():
|
||||
rootlogger = _TagTracer()
|
||||
|
||||
out = rootlogger.format_message(['test'], [1])
|
||||
assert out == ['1 [test]\n']
|
||||
|
||||
out2 = rootlogger.format_message(['test'], ['test', {'a': 1}])
|
||||
assert out2 == [
|
||||
'test [test]\n',
|
||||
' a: 1\n'
|
||||
]
|
||||
|
||||
|
||||
def test_setprocessor():
|
||||
rootlogger = _TagTracer()
|
||||
log = rootlogger.get("1")
|
||||
log2 = log.get("2")
|
||||
assert log2.tags == tuple("12")
|
||||
out = []
|
||||
rootlogger.setprocessor(tuple("12"), lambda *args: out.append(args))
|
||||
log("not seen")
|
||||
log2("seen")
|
||||
assert len(out) == 1
|
||||
tags, args = out[0]
|
||||
assert "1" in tags
|
||||
assert "2" in tags
|
||||
assert args == ("seen",)
|
||||
l2 = []
|
||||
rootlogger.setprocessor("1:2", lambda *args: l2.append(args))
|
||||
log2("seen")
|
||||
tags, args = l2[0]
|
||||
assert args == ("seen",)
|
||||
|
||||
|
||||
def test_setmyprocessor():
|
||||
rootlogger = _TagTracer()
|
||||
log = rootlogger.get("1")
|
||||
log2 = log.get("2")
|
||||
out = []
|
||||
log2.setmyprocessor(lambda *args: out.append(args))
|
||||
log("not seen")
|
||||
assert not out
|
||||
log2(42)
|
||||
assert len(out) == 1
|
||||
tags, args = out[0]
|
||||
assert "1" in tags
|
||||
assert "2" in tags
|
||||
assert args == (42,)
|
|
@ -0,0 +1,44 @@
|
|||
[tox]
|
||||
envlist=check,docs,py{27,34,35,36,py}-pytestrelease,py{27,36}-pytest{master,features}
|
||||
|
||||
[testenv]
|
||||
commands=py.test {posargs:testing/}
|
||||
setenv=
|
||||
_PYTEST_SETUP_SKIP_PLUGGY_DEP=1
|
||||
deps=
|
||||
pytestrelease: pytest
|
||||
pytestmaster: git+https://github.com/pytest-dev/pytest.git@master
|
||||
pytestfeatures: git+https://github.com/pytest-dev/pytest.git@features
|
||||
|
||||
[testenv:benchmark]
|
||||
commands=py.test {posargs:testing/benchmark.py}
|
||||
deps=
|
||||
pytest
|
||||
pytest-benchmark
|
||||
|
||||
[testenv:check]
|
||||
deps =
|
||||
flake8
|
||||
restructuredtext_lint
|
||||
pygments
|
||||
commands =
|
||||
flake8 pluggy.py setup.py testing
|
||||
rst-lint CHANGELOG.rst README.rst
|
||||
|
||||
[testenv:docs]
|
||||
deps =
|
||||
sphinx
|
||||
pygments
|
||||
commands =
|
||||
sphinx-build -b html {toxinidir}/docs {toxinidir}/build/html-docs
|
||||
|
||||
[pytest]
|
||||
minversion=2.0
|
||||
#--pyargs --doctest-modules --ignore=.tox
|
||||
addopts=-rxsX
|
||||
norecursedirs=.tox ja .hg .env*
|
||||
filterwarnings =
|
||||
error
|
||||
|
||||
[flake8]
|
||||
max-line-length=99
|
|
@ -10,3 +10,5 @@ __pycache__/
|
|||
.eggs/
|
||||
|
||||
dist/*
|
||||
/py/_version.py
|
||||
.pytest_cache/
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
|
||||
# Automatically generated by `hgimportsvn`
|
||||
syntax:glob
|
||||
.svn
|
||||
.hgsvn
|
||||
|
||||
# These lines are suggested according to the svn:ignore property
|
||||
# Feel free to enable them by uncommenting them
|
||||
syntax:glob
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.swp
|
||||
*.html
|
||||
*.class
|
||||
*.orig
|
||||
*~
|
||||
|
||||
doc/_build
|
||||
build/
|
||||
dist/
|
||||
*.egg-info
|
||||
issue/
|
||||
env/
|
||||
3rdparty/
|
||||
.tox
|
||||
lib/
|
||||
bin/
|
||||
include/
|
||||
src/
|
|
@ -1,68 +0,0 @@
|
|||
52c6d9e78777a5a34e813123997dfc614a1a4767 1.0.0b3
|
||||
1c7aaa8c61f3b0945921a9acc7beb184201aed4b 1.0.0b4
|
||||
1c7aaa8c61f3b0945921a9acc7beb184201aed4b 1.0.0b4
|
||||
0000000000000000000000000000000000000000 1.0.0b4
|
||||
0000000000000000000000000000000000000000 1.0.0b4
|
||||
8cd6eb91eba313b012d6e568f37d844dc0751f2e 1.0.0b4
|
||||
8cd6eb91eba313b012d6e568f37d844dc0751f2e 1.0.0b4
|
||||
0000000000000000000000000000000000000000 1.0.0b4
|
||||
2cc0507f117ffe721dff7ee026648cfce00ec92f 1.0.0b6
|
||||
86f1e1b6e49bf5882a809f11edd1dbb08162cdad 1.0.0b8
|
||||
86f1e1b6e49bf5882a809f11edd1dbb08162cdad 1.0.0b8
|
||||
c63f35c266cbb26dad6b87b5e115d65685adf448 1.0.0b8
|
||||
c63f35c266cbb26dad6b87b5e115d65685adf448 1.0.0b8
|
||||
0eaa0fdf2ba0163cf534dc2eff4ba2e5fc66c261 1.0.0b8
|
||||
e2a60653cb490aeed81bbbd83c070b99401c211c 1.0.0b9
|
||||
5ea0cdf7854c3d4278d36eda94a2b68483a0e211 1.0.0
|
||||
5ea0cdf7854c3d4278d36eda94a2b68483a0e211 1.0.0
|
||||
7acde360d94b6a2690ce3d03ff39301da84c0a2b 1.0.0
|
||||
6bd221981ac99103002c1cb94fede400d23a96a1 1.0.1
|
||||
4816e8b80602a3fd3a0a120333ad85fbe7d8bab4 1.0.2
|
||||
60c44bdbf093285dc69d5462d4dbb4acad325ca6 1.1.0
|
||||
319187fcda66714c5eb1353492babeec3d3c826f 1.1.1
|
||||
4fc5212f7626a56b9eb6437b5c673f56dd7eb942 1.2.0
|
||||
c143a8c8840a1c68570890c8ac6165bbf92fd3c6 1.2.1
|
||||
eafd3c256e8732dfb0a4d49d051b5b4339858926 1.3.0
|
||||
d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1
|
||||
d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1
|
||||
8b8e7c25a13cf863f01b2dd955978285ae9daf6a 1.3.1
|
||||
3bff44b188a7ec1af328d977b9d39b6757bb38df 1.3.2
|
||||
c59d3fa8681a5b5966b8375b16fccd64a3a8dbeb 1.3.3
|
||||
79ef6377705184c55633d456832eea318fedcf61 1.3.4
|
||||
79ef6377705184c55633d456832eea318fedcf61 1.3.4
|
||||
90fffd35373e9f125af233f78b19416f0938d841 1.3.4
|
||||
5346ab41b059c95a48cbe1e8a7bae96ce6e0da27 1.4.0
|
||||
1f3125cba7976538952be268f107c1d0c36c5ce8 1.4.1
|
||||
04ab22db4ff737cf31e91d75a0f5d7077f324167 1.4.2
|
||||
9950bf9d684a984d511795013421c89c5cf88bef 1.4.3
|
||||
d9951e3bdbc765e73835ae13012f6a074d13d8bf 1.4.4
|
||||
b827dd156a36753e32c7f3f15ce82d6fe9e356c8 1.4.6
|
||||
f15726f9e5a67cc6221c499affa4840e9d591763 1.4.7
|
||||
abfabd07a1d328f13c730e8a50d80d2e470afd3b 1.4.9
|
||||
7f37ee0aff9be4b839d6759cfee336f60e8393a4 1.4.10
|
||||
fe4593263efa10ea7ba014db6e3379e0b82368a2 1.4.11
|
||||
f07af25a26786e4825b5170e17ad693245cb3426 1.4.12
|
||||
d3730d84ba7eda92fd3469a3f63fd6d8cb22c975 1.4.13
|
||||
12c1ae8e7c5345721e9ec9f8e27b1e36c07f74dc 1.4.14
|
||||
12c1ae8e7c5345721e9ec9f8e27b1e36c07f74dc 1.4.14
|
||||
0000000000000000000000000000000000000000 1.4.14
|
||||
0000000000000000000000000000000000000000 1.4.14
|
||||
1497e2efd0f8c73a0e3d529debf0c489e4cd6cab 1.4.14
|
||||
e065014c1ce8ad110a381e9baaaa5d647ba7ac6b 1.4.15
|
||||
e9e5b38f53dc35b35aa1f9ee9a9be9bbd2d2c3b1 1.4.16
|
||||
c603503945f52b78522d96a423605cbc953236d3 1.4.17
|
||||
c59201105a29801cc858eb9160b7a19791b91a35 1.4.18
|
||||
284cc172e294d48edc840012e1451c32c3963d92 1.4.19
|
||||
a3e0626aa0c5aecf271367dc77e476ab216ea3c8 1.4.20
|
||||
5e48016c4a3af8e7358a1267d33d021e71765bed 1.4.21
|
||||
01ae2cfcc61c4fcb3aa5031349adb5b467c31018 1.4.23
|
||||
5ffd982f4dff60b588f309cd9bdc61036547282a 1.4.24
|
||||
dc9ffbcaf1f7d72e96be3f68c11deebb7e7193c5 1.4.25
|
||||
6de1a44bf75de7af4fcae947c235e9072bbdbb9a 1.4.26
|
||||
7d650ba2657890a2253c8c4a83f170febebd90fa 1.4.27
|
||||
7d650ba2657890a2253c8c4a83f170febebd90fa 1.4.27
|
||||
1810003dec63dd1b506a23849861fffa5bc3ba13 1.4.27
|
||||
ba08706f08ddea1b77a426f00dfe2bdc244345e8 1.4.28
|
||||
4e8054ada63f3327bcf759ae7cd36c7c8652bc9b 1.4.29
|
||||
366ab346610c6de8aaa7617e24011794b40236c6 1.4.30
|
||||
657380e439f9b7e04918cb162cb2e46388244b42 1.4.31
|
|
@ -17,6 +17,23 @@ matrix:
|
|||
- python: '2.7'
|
||||
# using a different option due to pytest-addopts pytester issues
|
||||
env: PYTEST_XADDOPTS="-n 3 --runslowtests" DEPS="pytest~=3.0.0 pytest-xdist"
|
||||
|
||||
- stage: deploy
|
||||
python: '3.6'
|
||||
env:
|
||||
install: pip install -U setuptools setuptools_scm
|
||||
script: skip
|
||||
deploy:
|
||||
provider: pypi
|
||||
user: nicoddemus
|
||||
distributions: sdist bdist_wheel
|
||||
skip_upload_docs: true
|
||||
password:
|
||||
secure: VNYW/sZoD+9DzKCe6vANNXXJR7jP7rwySafQ33N1jAnCrdylQjEN/p6tSfUe8jDi3wDpLPL9h8pwfxuUT7CRxglHov3Qe7zSeywixvHan5aFahQiQ8+gucYIM7wITHH3oQs7jN35pnhdnF+QlW2+eDCL6qOLU5XwuRhsDKXjQ/hUWR5hlX5EniD1gzyKEf6j1YCpST87tKpeLwVEYEmsucdkUZuXhxDtyaWQHWiPsLWwh/slQtUJEHeLF26r8UxFy0RiGne9jR+CzRfH5ktcA9/pArvp4VuwOii+1TDxVSYP7+I8Z+eUKN9JBg12QLaHwoIN/8J+MvHCkuf+OGSLM3sEyNRJGDev372xg3K7ylIkeeK4WXirKEp2ojgN8tniloDjnwdu/gPWBnrXuooA60tNoByHFa8KbMZAr2B2sQeMxD4VZGr1N8l0rX4gRTrwvdk3i3ulLKVSwkXaGn+GrfZTTboa7dEnpuma8tv1niNCSpStYIy7atS8129+5ijV3OC8DzOMh/rVbO9WsDb/RPG3yjFiDvEJPIPeE0l/m5u42QBqtdZSS2ia7UWTJBiEY09uFMTRmH5hhE/1aiYBbvAztf5CReUbeKdSQz3L8TTSZqewtFZmXTkX97/xQnrEpsnGezIM2DNuMEuQG3MxGkNCxwbQKpx/bkHdrD75yMk=
|
||||
on:
|
||||
tags: true
|
||||
repo: pytest-dev/py
|
||||
|
||||
allow_failures:
|
||||
- python: 'pypy-5.4'
|
||||
install:
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
1.5.3 (unreleased)
|
||||
1.5.4 (2018-06-27)
|
||||
==================
|
||||
|
||||
- fix pytest-dev/pytest#3451: don't make assumptions about fs case sensitivity
|
||||
in ``make_numbered_dir``.
|
||||
|
||||
1.5.3
|
||||
=====
|
||||
|
||||
- fix #179: ensure we can support 'from py.error import ...'
|
||||
|
||||
1.5.2
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
Release Procedure
|
||||
-----------------
|
||||
|
||||
#. Create a branch ``release-X.Y.Z`` from the latest ``master``.
|
||||
|
||||
#. Manually update the ``CHANGELOG`` and commit.
|
||||
|
||||
#. Open a PR for this branch targeting ``master``.
|
||||
|
||||
#. After all tests pass and the PR has been approved by at least another maintainer, publish to PyPI by creating and pushing a tag::
|
||||
|
||||
git tag X.Y.Z
|
||||
git push git@github.com:pytest-dev/py X.Y.Z
|
||||
|
||||
Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/py>`_.
|
||||
|
||||
#. Merge your PR to ``master``.
|
|
@ -1,6 +1,6 @@
|
|||
Metadata-Version: 1.2
|
||||
Name: py
|
||||
Version: 1.5.3
|
||||
Version: 1.5.4
|
||||
Summary: library with cross-python path, ini-parsing, io, code, log facilities
|
||||
Home-page: http://py.readthedocs.io/
|
||||
Author: holger krekel, Ronny Pfannschmidt, Benjamin Peterson and others
|
||||
|
@ -27,10 +27,10 @@ Description: .. image:: https://img.shields.io/pypi/v/py.svg
|
|||
The py lib is a Python development support library featuring
|
||||
the following tools and modules:
|
||||
|
||||
* ``py.path``: uniform local and svn path objects
|
||||
* ``py.apipkg``: explicit API control and lazy-importing
|
||||
* ``py.iniconfig``: easy parsing of .ini files
|
||||
* ``py.code``: dynamic code generation and introspection (deprecated, moved to ``pytest``).
|
||||
* ``py.path``: uniform local and svn path objects -> please use pathlib/pathlib2 instead
|
||||
* ``py.apipkg``: explicit API control and lazy-importing -> please use the standalone package instead
|
||||
* ``py.iniconfig``: easy parsing of .ini files -> please use the standalone package instead
|
||||
* ``py.code``: dynamic code generation and introspection (deprecated, moved to ``pytest`` as a implementation detail).
|
||||
|
||||
**NOTE**: prior to the 1.4 release this distribution used to
|
||||
contain py.test which is now its own package, see http://pytest.org
|
||||
|
|
|
@ -19,10 +19,10 @@
|
|||
The py lib is a Python development support library featuring
|
||||
the following tools and modules:
|
||||
|
||||
* ``py.path``: uniform local and svn path objects
|
||||
* ``py.apipkg``: explicit API control and lazy-importing
|
||||
* ``py.iniconfig``: easy parsing of .ini files
|
||||
* ``py.code``: dynamic code generation and introspection (deprecated, moved to ``pytest``).
|
||||
* ``py.path``: uniform local and svn path objects -> please use pathlib/pathlib2 instead
|
||||
* ``py.apipkg``: explicit API control and lazy-importing -> please use the standalone package instead
|
||||
* ``py.iniconfig``: easy parsing of .ini files -> please use the standalone package instead
|
||||
* ``py.code``: dynamic code generation and introspection (deprecated, moved to ``pytest`` as a implementation detail).
|
||||
|
||||
**NOTE**: prior to the 1.4 release this distribution used to
|
||||
contain py.test which is now its own package, see http://pytest.org
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
environment:
|
||||
matrix:
|
||||
# note: please use "tox --listenvs" to populate the build matrix below
|
||||
- TOXENV: "py27-pytest29"
|
||||
- TOXENV: "py27-pytest30"
|
||||
- TOXENV: "py27-pytest31"
|
||||
- TOXENV: "py34-pytest29"
|
||||
- TOXENV: "py34-pytest30"
|
||||
- TOXENV: "py34-pytest31"
|
||||
- TOXENV: "py35-pytest29"
|
||||
- TOXENV: "py35-pytest30"
|
||||
- TOXENV: "py35-pytest31"
|
||||
- TOXENV: "py36-pytest29"
|
||||
- TOXENV: "py36-pytest30"
|
||||
- TOXENV: "py36-pytest31"
|
||||
|
||||
install:
|
||||
- echo Installed Pythons
|
||||
- dir c:\Python*
|
||||
|
||||
- C:\Python36\python -m pip install --upgrade --pre tox
|
||||
|
||||
build: false # Not a C# project, build stuff at the test step instead.
|
||||
|
||||
test_script:
|
||||
- C:\Python36\python -m tox
|
|
@ -2,6 +2,12 @@
|
|||
py.path
|
||||
=======
|
||||
|
||||
**Note**: The 'py' library is in "maintenance mode" and so is not
|
||||
recommended for new projects. Please check out
|
||||
`pathlib <https://docs.python.org/3/library/pathlib.html>`_ or
|
||||
`pathlib2 <https://pypi.python.org/pypi/pathlib2/>`_ for path
|
||||
operations.
|
||||
|
||||
The 'py' lib provides a uniform high-level api to deal with filesystems
|
||||
and filesystem-like interfaces: ``py.path``. It aims to offer a central
|
||||
object to fs-like object trees (reading from and writing to files, adding
|
||||
|
|
|
@ -18,7 +18,12 @@ except ImportError:
|
|||
import apipkg
|
||||
lib_not_mangled_by_packagers = False
|
||||
vendor_prefix = ''
|
||||
__version__ = '1.5.3'
|
||||
|
||||
try:
|
||||
from ._version import version as __version__
|
||||
except ImportError:
|
||||
# broken installation, we don't even try
|
||||
__version__ = "unknown"
|
||||
|
||||
|
||||
apipkg.initpkg(__name__, attr={'_apipkg': apipkg, 'error': error}, exportdefs={
|
||||
|
|
|
@ -4,13 +4,13 @@ local path implementation.
|
|||
from __future__ import with_statement
|
||||
|
||||
from contextlib import contextmanager
|
||||
import sys, os, re, atexit, io, uuid
|
||||
import sys, os, atexit, io, uuid
|
||||
import py
|
||||
from py._path import common
|
||||
from py._path.common import iswin32, fspath
|
||||
from stat import S_ISLNK, S_ISDIR, S_ISREG
|
||||
|
||||
from os.path import abspath, normcase, normpath, isabs, exists, isdir, isfile, islink, dirname
|
||||
from os.path import abspath, normpath, isabs, exists, isdir, isfile, islink, dirname
|
||||
|
||||
if sys.version_info > (3,0):
|
||||
def map_as_list(func, iter):
|
||||
|
@ -800,7 +800,7 @@ class LocalPath(FSBase):
|
|||
return cls(py.error.checked_call(tempfile.mkdtemp, dir=str(rootdir)))
|
||||
|
||||
def make_numbered_dir(cls, prefix='session-', rootdir=None, keep=3,
|
||||
lock_timeout = 172800): # two days
|
||||
lock_timeout=172800): # two days
|
||||
""" return unique directory with a number greater than the current
|
||||
maximum one. The number is assumed to start directly after prefix.
|
||||
if keep is true directories with a number less than (maxnum-keep)
|
||||
|
@ -810,10 +810,10 @@ class LocalPath(FSBase):
|
|||
if rootdir is None:
|
||||
rootdir = cls.get_temproot()
|
||||
|
||||
nprefix = normcase(prefix)
|
||||
nprefix = prefix.lower()
|
||||
def parse_num(path):
|
||||
""" parse the number out of a path (if it matches the prefix) """
|
||||
nbasename = normcase(path.basename)
|
||||
nbasename = path.basename.lower()
|
||||
if nbasename.startswith(nprefix):
|
||||
try:
|
||||
return int(nbasename[len(nprefix):])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# coding: utf-8
|
||||
# file generated by setuptools_scm
|
||||
# don't change, don't track in version control
|
||||
version = '3.2.5'
|
||||
version = '1.5.4'
|
|
@ -1,25 +1,13 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
def get_version():
|
||||
p = os.path.join(os.path.dirname(
|
||||
os.path.abspath(__file__)), "py", "__init__.py")
|
||||
with open(p) as f:
|
||||
for line in f.readlines():
|
||||
if "__version__" in line:
|
||||
return line.strip().split("=")[-1].strip(" '")
|
||||
raise ValueError("could not read version")
|
||||
|
||||
|
||||
def main():
|
||||
setup(
|
||||
name='py',
|
||||
description='library with cross-python path, ini-parsing, io, code, log facilities',
|
||||
long_description=open('README.rst').read(),
|
||||
version=get_version(),
|
||||
use_scm_version={"write_to": "py/_version.py"},
|
||||
setup_requires=["setuptools-scm"],
|
||||
url='http://py.readthedocs.io/',
|
||||
license='MIT license',
|
||||
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
||||
|
|
|
@ -425,24 +425,23 @@ class TestExecution:
|
|||
if i >= 3:
|
||||
assert not numdir.new(ext=str(i-3)).check()
|
||||
|
||||
def test_make_numbered_dir_case_insensitive(self, tmpdir, monkeypatch):
|
||||
# https://github.com/pytest-dev/pytest/issues/708
|
||||
monkeypatch.setattr(py._path.local, 'normcase',
|
||||
lambda path: path.lower())
|
||||
monkeypatch.setattr(tmpdir, 'listdir',
|
||||
lambda: [tmpdir._fastjoin('case.0')])
|
||||
numdir = local.make_numbered_dir(prefix='CAse.', rootdir=tmpdir,
|
||||
keep=2, lock_timeout=0)
|
||||
assert numdir.basename.endswith('.1')
|
||||
def test_make_numbered_dir_case(self, tmpdir):
|
||||
"""make_numbered_dir does not make assumptions on the underlying
|
||||
filesystem based on the platform and will assume it _could_ be case
|
||||
insensitive.
|
||||
|
||||
def test_make_numbered_dir_case_sensitive(self, tmpdir, monkeypatch):
|
||||
# https://github.com/pytest-dev/pytest/issues/708
|
||||
monkeypatch.setattr(py._path.local, 'normcase', lambda path: path)
|
||||
monkeypatch.setattr(tmpdir, 'listdir',
|
||||
lambda: [tmpdir._fastjoin('case.0')])
|
||||
numdir = local.make_numbered_dir(prefix='CAse.', rootdir=tmpdir,
|
||||
keep=2, lock_timeout=0)
|
||||
assert numdir.basename.endswith('.0')
|
||||
See issues:
|
||||
- https://github.com/pytest-dev/pytest/issues/708
|
||||
- https://github.com/pytest-dev/pytest/issues/3451
|
||||
"""
|
||||
d1 = local.make_numbered_dir(
|
||||
prefix='CAse.', rootdir=tmpdir, keep=2, lock_timeout=0,
|
||||
)
|
||||
d2 = local.make_numbered_dir(
|
||||
prefix='caSE.', rootdir=tmpdir, keep=2, lock_timeout=0,
|
||||
)
|
||||
assert str(d1).lower() != str(d2).lower()
|
||||
assert str(d2).endswith('.1')
|
||||
|
||||
def test_make_numbered_dir_NotImplemented_Error(self, tmpdir, monkeypatch):
|
||||
def notimpl(x, y):
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
[run]
|
||||
omit =
|
||||
omit =
|
||||
# standlonetemplate is read dynamically and tested by test_genscript
|
||||
*standalonetemplate.py
|
||||
# oldinterpret could be removed, as it is no longer used in py26+
|
||||
*oldinterpret.py
|
||||
vendored_packages
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
Thanks for submitting a PR, your contribution is really appreciated!
|
||||
|
||||
Here's a quick checklist that should be present in PRs:
|
||||
Here's a quick checklist that should be present in PRs (you can delete this text from the final description, this is
|
||||
just a guideline):
|
||||
|
||||
- [ ] Add a new news fragment into the changelog folder
|
||||
* name it `$issue_id.$type` for example (588.bug)
|
||||
* if you don't have an issue_id change it to the pr id after creating the pr
|
||||
* ensure type is one of `removal`, `feature`, `bugfix`, `vendor`, `doc` or `trivial`
|
||||
* Make sure to use full sentences with correct case and punctuation, for example: "Fix issue with non-ascii contents in doctest text files."
|
||||
- [ ] Target: for `bugfix`, `vendor`, `doc` or `trivial` fixes, target `master`; for removals or features target `features`;
|
||||
- [ ] Make sure to include reasonable tests for your change if necessary
|
||||
- [ ] Create a new changelog file in the `changelog` folder, with a name like `<ISSUE NUMBER>.<TYPE>.rst`. See [changelog/README.rst](/changelog/README.rst) for details.
|
||||
- [ ] Target the `master` branch for bug fixes, documentation updates and trivial changes.
|
||||
- [ ] Target the `features` branch for new features and removals/deprecations.
|
||||
- [ ] Include documentation when adding new features.
|
||||
- [ ] Include new tests or update existing tests when applicable.
|
||||
|
||||
Unless your change is a trivial or a documentation fix (e.g., a typo or reword of a small section) please:
|
||||
Unless your change is trivial or a small documentation fix (e.g., a typo or reword of a small section) please:
|
||||
|
||||
- [ ] Add yourself to `AUTHORS`, in alphabetical order;
|
||||
- [ ] Add yourself to `AUTHORS` in alphabetical order;
|
||||
|
|
|
@ -19,7 +19,7 @@ include/
|
|||
.hypothesis/
|
||||
|
||||
# autogenerated
|
||||
_pytest/_version.py
|
||||
src/_pytest/_version.py
|
||||
# setuptools
|
||||
.eggs/
|
||||
|
||||
|
@ -33,6 +33,7 @@ env/
|
|||
3rdparty/
|
||||
.tox
|
||||
.cache
|
||||
.pytest_cache
|
||||
.coverage
|
||||
.ropeproject
|
||||
.idea
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
exclude: doc/en/example/py2py3/test_py2.py
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 18.4a4
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--safe, --quiet]
|
||||
language_version: python3.6
|
||||
- repo: https://github.com/asottile/blacken-docs
|
||||
rev: v0.1.1
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
additional_dependencies: [black==18.5b1]
|
||||
language_version: python3.6
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v1.2.3
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: debug-statements
|
||||
exclude: _pytest/debugging.py
|
||||
- id: flake8
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v1.2.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: rst
|
||||
name: rst
|
||||
entry: rst-lint --encoding utf-8
|
||||
files: ^(CHANGELOG.rst|HOWTORELEASE.rst|README.rst|changelog/.*)$
|
||||
language: python
|
||||
additional_dependencies: [pygments, restructuredtext_lint]
|
||||
python_version: python3.6
|
|
@ -1,17 +1,19 @@
|
|||
sudo: false
|
||||
language: python
|
||||
stages:
|
||||
- linting
|
||||
- test
|
||||
- deploy
|
||||
python:
|
||||
- '3.6'
|
||||
# command to install dependencies
|
||||
install:
|
||||
- pip install --upgrade --pre tox
|
||||
# # command to run tests
|
||||
env:
|
||||
matrix:
|
||||
# coveralls is not listed in tox's envlist, but should run in travis
|
||||
- TOXENV=coveralls
|
||||
# note: please use "tox --listenvs" to populate the build matrix below
|
||||
- TOXENV=linting
|
||||
# please remove the linting env in all cases
|
||||
- TOXENV=py27
|
||||
- TOXENV=py34
|
||||
- TOXENV=py36
|
||||
|
@ -19,20 +21,18 @@ env:
|
|||
- TOXENV=py27-xdist
|
||||
- TOXENV=py27-trial
|
||||
- TOXENV=py27-numpy
|
||||
- TOXENV=py27-pluggymaster
|
||||
- TOXENV=py36-pexpect
|
||||
- TOXENV=py36-xdist
|
||||
- TOXENV=py36-trial
|
||||
- TOXENV=py36-numpy
|
||||
- TOXENV=py36-pluggymaster
|
||||
- TOXENV=py27-nobyte
|
||||
- TOXENV=doctesting
|
||||
- TOXENV=docs
|
||||
|
||||
matrix:
|
||||
jobs:
|
||||
include:
|
||||
- env: TOXENV=py26
|
||||
python: '2.6'
|
||||
- env: TOXENV=py33
|
||||
python: '3.3'
|
||||
- env: TOXENV=pypy
|
||||
python: 'pypy-5.4'
|
||||
- env: TOXENV=py35
|
||||
|
@ -41,9 +41,30 @@ matrix:
|
|||
python: '3.5'
|
||||
- env: TOXENV=py37
|
||||
python: 'nightly'
|
||||
allow_failures:
|
||||
- env: TOXENV=py37
|
||||
python: 'nightly'
|
||||
|
||||
- stage: deploy
|
||||
python: '3.6'
|
||||
env:
|
||||
install: pip install -U setuptools setuptools_scm
|
||||
script: skip
|
||||
deploy:
|
||||
provider: pypi
|
||||
user: nicoddemus
|
||||
distributions: sdist bdist_wheel
|
||||
skip_upload_docs: true
|
||||
password:
|
||||
secure: xanTgTUu6XDQVqB/0bwJQXoDMnU5tkwZc5koz6mBkkqZhKdNOi2CLoC1XhiSZ+ah24l4V1E0GAqY5kBBcy9d7NVe4WNg4tD095LsHw+CRU6/HCVIFfyk2IZ+FPAlguesCcUiJSXOrlBF+Wj68wEvLoK7EoRFbJeiZ/f91Ww1sbtDlqXABWGHrmhPJL5Wva7o7+wG7JwJowqdZg1pbQExsCc7b53w4v2RBu3D6TJaTAzHiVsW+nUSI67vKI/uf+cR/OixsTfy37wlHgSwihYmrYLFls3V0bSpahCim3bCgMaFZx8S8xrdgJ++PzBCof2HeflFKvW+VCkoYzGEG4NrTWJoNz6ni4red9GdvfjGH3YCjAKS56h9x58zp2E5rpsb/kVq5/45xzV+dq6JRuhQ1nJWjBC6fSKAc/bfwnuFK3EBxNLkvBssLHvsNjj5XG++cB8DdS9wVGUqjpoK4puaXUWFqy4q3S9F86HEsKNgExtieA9qNx+pCIZVs6JCXZNjr0I5eVNzqJIyggNgJG6RyravsU35t9Zd9doL5g4Y7UKmAGTn1Sz24HQ4sMQgXdm2SyD8gEK5je4tlhUvfGtDvMSlstq71kIn9nRpFnqB6MFlbYSEAZmo8dGbCquoUc++6Rum208wcVbrzzVtGlXB/Ow9AbFMYeAGA0+N/K1e59c=
|
||||
on:
|
||||
tags: true
|
||||
repo: pytest-dev/pytest
|
||||
- stage: linting
|
||||
python: '3.6'
|
||||
env:
|
||||
install:
|
||||
- pip install pre-commit
|
||||
- pre-commit install-hooks
|
||||
script:
|
||||
- pre-commit run --all-files
|
||||
|
||||
script: tox --recreate
|
||||
|
||||
|
@ -56,3 +77,7 @@ notifications:
|
|||
skip_join: true
|
||||
email:
|
||||
- pytest-commit@python.org
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/pip
|
||||
- $HOME/.cache/pre-commit
|
||||
|
|
|
@ -3,21 +3,27 @@ merlinux GmbH, Germany, office at merlinux eu
|
|||
|
||||
Contributors include::
|
||||
|
||||
Aaron Coleman
|
||||
Abdeali JK
|
||||
Abhijeet Kasurde
|
||||
Ahn Ki-Wook
|
||||
Alan Velasco
|
||||
Alexander Johnson
|
||||
Alexei Kozlenok
|
||||
Anatoly Bubenkoff
|
||||
Anders Hovmöller
|
||||
Andras Tim
|
||||
Andreas Zeidler
|
||||
Andrzej Ostrowski
|
||||
Andy Freeland
|
||||
Anthon van der Neut
|
||||
Anthony Shaw
|
||||
Anthony Sottile
|
||||
Antony Lee
|
||||
Armin Rigo
|
||||
Aron Coyle
|
||||
Aron Curzon
|
||||
Aviral Verma
|
||||
Aviv Palivoda
|
||||
Barney Gale
|
||||
Ben Webb
|
||||
|
@ -25,11 +31,14 @@ Benjamin Peterson
|
|||
Bernard Pratz
|
||||
Bob Ippolito
|
||||
Brian Dorsey
|
||||
Brian Maissy
|
||||
Brian Okken
|
||||
Brianna Laugher
|
||||
Bruno Oliveira
|
||||
Cal Leeming
|
||||
Carl Friedrich Bolz
|
||||
Carlos Jenkins
|
||||
Ceridwen
|
||||
Charles Cloud
|
||||
Charnjit SiNGH (CCSJ)
|
||||
Chris Lamb
|
||||
|
@ -37,6 +46,7 @@ Christian Boelsen
|
|||
Christian Theunert
|
||||
Christian Tismer
|
||||
Christopher Gilling
|
||||
Cyrus Maden
|
||||
Daniel Grana
|
||||
Daniel Hahler
|
||||
Daniel Nuri
|
||||
|
@ -65,34 +75,44 @@ Feng Ma
|
|||
Florian Bruhin
|
||||
Floris Bruynooghe
|
||||
Gabriel Reis
|
||||
George Kussumoto
|
||||
Georgy Dyuldin
|
||||
Graham Horler
|
||||
Greg Price
|
||||
Grig Gheorghiu
|
||||
Grigorii Eremeev (budulianin)
|
||||
Guido Wesdorp
|
||||
Guoqiang Zhang
|
||||
Harald Armin Massa
|
||||
Henk-Jaap Wagenaar
|
||||
Hugo van Kemenade
|
||||
Hui Wang (coldnight)
|
||||
Ian Bicking
|
||||
Ian Lesperance
|
||||
Jaap Broekhuizen
|
||||
Jan Balster
|
||||
Janne Vanhala
|
||||
Jason R. Coombs
|
||||
Javier Domingo Cansino
|
||||
Javier Romero
|
||||
Jeff Rackauckas
|
||||
Jeff Widman
|
||||
John Eddie Ayson
|
||||
John Towler
|
||||
Jon Sonesen
|
||||
Jonas Obrist
|
||||
Jordan Guymon
|
||||
Jordan Moldow
|
||||
Jordan Speicher
|
||||
Joshua Bronson
|
||||
Jurko Gospodnetić
|
||||
Justyna Janczyszyn
|
||||
Kale Kundert
|
||||
Katarzyna Jachim
|
||||
Katerina Koukiou
|
||||
Kevin Cox
|
||||
Kodi B. Arfer
|
||||
Kostis Anagnostopoulos
|
||||
Lawrence Mitchell
|
||||
Lee Kamentsky
|
||||
Lev Maximov
|
||||
|
@ -118,6 +138,7 @@ Matt Bachmann
|
|||
Matt Duck
|
||||
Matt Williams
|
||||
Matthias Hafner
|
||||
Maxim Filipenko
|
||||
mbyt
|
||||
Michael Aquilina
|
||||
Michael Birtwell
|
||||
|
@ -126,22 +147,26 @@ Michael Seifert
|
|||
Michal Wajszczuk
|
||||
Mihai Capotă
|
||||
Mike Lundy
|
||||
Miro Hrončok
|
||||
Nathaniel Waisbrot
|
||||
Ned Batchelder
|
||||
Neven Mundar
|
||||
Nicolas Delaby
|
||||
Oleg Pidsadnyi
|
||||
Oleg Sushchenko
|
||||
Oliver Bestwalter
|
||||
Omar Kohl
|
||||
Omer Hadari
|
||||
Patrick Hayes
|
||||
Paweł Adamczak
|
||||
Pedro Algarvio
|
||||
Pieter Mulder
|
||||
Piotr Banaszkiewicz
|
||||
Punyashloka Biswal
|
||||
Quentin Pradet
|
||||
Ralf Schmitt
|
||||
Ran Benita
|
||||
Raphael Castaneda
|
||||
Raphael Pierzina
|
||||
Raquel Alegre
|
||||
Ravi Chandra
|
||||
|
@ -152,6 +177,7 @@ Ronny Pfannschmidt
|
|||
Ross Lawley
|
||||
Russel Winder
|
||||
Ryan Wooden
|
||||
Samuel Dion-Girardeau
|
||||
Samuele Pedroni
|
||||
Segev Finer
|
||||
Simon Gomizelj
|
||||
|
@ -162,19 +188,26 @@ Stefan Zimmermann
|
|||
Stefano Taschini
|
||||
Steffen Allner
|
||||
Stephan Obermann
|
||||
Tarcisio Fischer
|
||||
Tareq Alayan
|
||||
Ted Xiao
|
||||
Thomas Grainger
|
||||
Thomas Hisch
|
||||
Tim Strazny
|
||||
Tom Dalton
|
||||
Tom Viner
|
||||
Trevor Bekolay
|
||||
Tyler Goodlet
|
||||
Tzu-ping Chung
|
||||
Vasily Kuznetsov
|
||||
Victor Uriarte
|
||||
Vidar T. Fauske
|
||||
Vitaly Lashmanov
|
||||
Vlad Dragos
|
||||
William Lee
|
||||
Wouter van Ackooy
|
||||
Xuan Luong
|
||||
Xuecong Liao
|
||||
Zoltán Máté
|
||||
Roland Puntaier
|
||||
Allan Feldman
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
..
|
||||
..
|
||||
You should *NOT* be adding new change log entries to this file, this
|
||||
file is managed by towncrier. You *may* edit previous change logs to
|
||||
fix problems like typo corrections or such.
|
||||
|
@ -8,6 +8,872 @@
|
|||
|
||||
.. towncrier release notes start
|
||||
|
||||
Pytest 3.6.2 (2018-06-20)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Fix regression in ``Node.add_marker`` by extracting the mark object of a
|
||||
``MarkDecorator``. (`#3555
|
||||
<https://github.com/pytest-dev/pytest/issues/3555>`_)
|
||||
|
||||
- Warnings without ``location`` were reported as ``None``. This is corrected to
|
||||
now report ``<undetermined location>``. (`#3563
|
||||
<https://github.com/pytest-dev/pytest/issues/3563>`_)
|
||||
|
||||
- Continue to call finalizers in the stack when a finalizer in a former scope
|
||||
raises an exception. (`#3569
|
||||
<https://github.com/pytest-dev/pytest/issues/3569>`_)
|
||||
|
||||
- Fix encoding error with `print` statements in doctests (`#3583
|
||||
<https://github.com/pytest-dev/pytest/issues/3583>`_)
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Add documentation for the ``--strict`` flag. (`#3549
|
||||
<https://github.com/pytest-dev/pytest/issues/3549>`_)
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- Update old quotation style to parens in fixture.rst documentation. (`#3525
|
||||
<https://github.com/pytest-dev/pytest/issues/3525>`_)
|
||||
|
||||
- Improve display of hint about ``--fulltrace`` with ``KeyboardInterrupt``.
|
||||
(`#3545 <https://github.com/pytest-dev/pytest/issues/3545>`_)
|
||||
|
||||
- pytest's testsuite is no longer runnable through ``python setup.py test`` --
|
||||
instead invoke ``pytest`` or ``tox`` directly. (`#3552
|
||||
<https://github.com/pytest-dev/pytest/issues/3552>`_)
|
||||
|
||||
- Fix typo in documentation (`#3567
|
||||
<https://github.com/pytest-dev/pytest/issues/3567>`_)
|
||||
|
||||
|
||||
Pytest 3.6.1 (2018-06-05)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Fixed a bug where stdout and stderr were logged twice by junitxml when a test
|
||||
was marked xfail. (`#3491
|
||||
<https://github.com/pytest-dev/pytest/issues/3491>`_)
|
||||
|
||||
- Fix ``usefixtures`` mark applyed to unittest tests by correctly instantiating
|
||||
``FixtureInfo``. (`#3498
|
||||
<https://github.com/pytest-dev/pytest/issues/3498>`_)
|
||||
|
||||
- Fix assertion rewriter compatibility with libraries that monkey patch
|
||||
``file`` objects. (`#3503
|
||||
<https://github.com/pytest-dev/pytest/issues/3503>`_)
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Added a section on how to use fixtures as factories to the fixture
|
||||
documentation. (`#3461 <https://github.com/pytest-dev/pytest/issues/3461>`_)
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- Enable caching for pip/pre-commit in order to reduce build time on
|
||||
travis/appveyor. (`#3502
|
||||
<https://github.com/pytest-dev/pytest/issues/3502>`_)
|
||||
|
||||
- Switch pytest to the src/ layout as we already suggested it for good practice
|
||||
- now we implement it as well. (`#3513
|
||||
<https://github.com/pytest-dev/pytest/issues/3513>`_)
|
||||
|
||||
- Fix if in tests to support 3.7.0b5, where a docstring handling in AST got
|
||||
reverted. (`#3530 <https://github.com/pytest-dev/pytest/issues/3530>`_)
|
||||
|
||||
- Remove some python2.5 compatibility code. (`#3529
|
||||
<https://github.com/pytest-dev/pytest/issues/3529>`_)
|
||||
|
||||
|
||||
Pytest 3.6.0 (2018-05-23)
|
||||
=========================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Revamp the internals of the ``pytest.mark`` implementation with correct per
|
||||
node handling which fixes a number of long standing bugs caused by the old
|
||||
design. This introduces new ``Node.iter_markers(name)`` and
|
||||
``Node.get_closest_mark(name)`` APIs. Users are **strongly encouraged** to
|
||||
read the `reasons for the revamp in the docs
|
||||
<https://docs.pytest.org/en/latest/mark.html#marker-revamp-and-iteration>`_,
|
||||
or jump over to details about `updating existing code to use the new APIs
|
||||
<https://docs.pytest.org/en/latest/mark.html#updating-code>`_. (`#3317
|
||||
<https://github.com/pytest-dev/pytest/issues/3317>`_)
|
||||
|
||||
- Now when ``@pytest.fixture`` is applied more than once to the same function a
|
||||
``ValueError`` is raised. This buggy behavior would cause surprising problems
|
||||
and if was working for a test suite it was mostly by accident. (`#2334
|
||||
<https://github.com/pytest-dev/pytest/issues/2334>`_)
|
||||
|
||||
- Support for Python 3.7's builtin ``breakpoint()`` method, see `Using the
|
||||
builtin breakpoint function
|
||||
<https://docs.pytest.org/en/latest/usage.html#breakpoint-builtin>`_ for
|
||||
details. (`#3180 <https://github.com/pytest-dev/pytest/issues/3180>`_)
|
||||
|
||||
- ``monkeypatch`` now supports a ``context()`` function which acts as a context
|
||||
manager which undoes all patching done within the ``with`` block. (`#3290
|
||||
<https://github.com/pytest-dev/pytest/issues/3290>`_)
|
||||
|
||||
- The ``--pdb`` option now causes KeyboardInterrupt to enter the debugger,
|
||||
instead of stopping the test session. On python 2.7, hitting CTRL+C again
|
||||
exits the debugger. On python 3.2 and higher, use CTRL+D. (`#3299
|
||||
<https://github.com/pytest-dev/pytest/issues/3299>`_)
|
||||
|
||||
- pytest not longer changes the log level of the root logger when the
|
||||
``log-level`` parameter has greater numeric value than that of the level of
|
||||
the root logger, which makes it play better with custom logging configuration
|
||||
in user code. (`#3307 <https://github.com/pytest-dev/pytest/issues/3307>`_)
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- A rare race-condition which might result in corrupted ``.pyc`` files on
|
||||
Windows has been hopefully solved. (`#3008
|
||||
<https://github.com/pytest-dev/pytest/issues/3008>`_)
|
||||
|
||||
- Also use iter_marker for discovering the marks applying for marker
|
||||
expressions from the cli to avoid the bad data from the legacy mark storage.
|
||||
(`#3441 <https://github.com/pytest-dev/pytest/issues/3441>`_)
|
||||
|
||||
- When showing diffs of failed assertions where the contents contain only
|
||||
whitespace, escape them using ``repr()`` first to make it easy to spot the
|
||||
differences. (`#3443 <https://github.com/pytest-dev/pytest/issues/3443>`_)
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Change documentation copyright year to a range which auto-updates itself each
|
||||
time it is published. (`#3303
|
||||
<https://github.com/pytest-dev/pytest/issues/3303>`_)
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- ``pytest`` now depends on the `python-atomicwrites
|
||||
<https://github.com/untitaker/python-atomicwrites>`_ library. (`#3008
|
||||
<https://github.com/pytest-dev/pytest/issues/3008>`_)
|
||||
|
||||
- Update all pypi.python.org URLs to pypi.org. (`#3431
|
||||
<https://github.com/pytest-dev/pytest/issues/3431>`_)
|
||||
|
||||
- Detect `pytest_` prefixed hooks using the internal plugin manager since
|
||||
``pluggy`` is deprecating the ``implprefix`` argument to ``PluginManager``.
|
||||
(`#3487 <https://github.com/pytest-dev/pytest/issues/3487>`_)
|
||||
|
||||
- Import ``Mapping`` and ``Sequence`` from ``_pytest.compat`` instead of
|
||||
directly from ``collections`` in ``python_api.py::approx``. Add ``Mapping``
|
||||
to ``_pytest.compat``, import it from ``collections`` on python 2, but from
|
||||
``collections.abc`` on Python 3 to avoid a ``DeprecationWarning`` on Python
|
||||
3.7 or newer. (`#3497 <https://github.com/pytest-dev/pytest/issues/3497>`_)
|
||||
|
||||
|
||||
Pytest 3.5.1 (2018-04-23)
|
||||
=========================
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Reset ``sys.last_type``, ``sys.last_value`` and ``sys.last_traceback`` before
|
||||
each test executes. Those attributes are added by pytest during the test run
|
||||
to aid debugging, but were never reset so they would create a leaking
|
||||
reference to the last failing test's frame which in turn could never be
|
||||
reclaimed by the garbage collector. (`#2798
|
||||
<https://github.com/pytest-dev/pytest/issues/2798>`_)
|
||||
|
||||
- ``pytest.raises`` now raises ``TypeError`` when receiving an unknown keyword
|
||||
argument. (`#3348 <https://github.com/pytest-dev/pytest/issues/3348>`_)
|
||||
|
||||
- ``pytest.raises`` now works with exception classes that look like iterables.
|
||||
(`#3372 <https://github.com/pytest-dev/pytest/issues/3372>`_)
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Fix typo in ``caplog`` fixture documentation, which incorrectly identified
|
||||
certain attributes as methods. (`#3406
|
||||
<https://github.com/pytest-dev/pytest/issues/3406>`_)
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- Added a more indicative error message when parametrizing a function whose
|
||||
argument takes a default value. (`#3221
|
||||
<https://github.com/pytest-dev/pytest/issues/3221>`_)
|
||||
|
||||
- Remove internal ``_pytest.terminal.flatten`` function in favor of
|
||||
``more_itertools.collapse``. (`#3330
|
||||
<https://github.com/pytest-dev/pytest/issues/3330>`_)
|
||||
|
||||
- Import some modules from ``collections.abc`` instead of ``collections`` as
|
||||
the former modules trigger ``DeprecationWarning`` in Python 3.7. (`#3339
|
||||
<https://github.com/pytest-dev/pytest/issues/3339>`_)
|
||||
|
||||
- record_property is no longer experimental, removing the warnings was
|
||||
forgotten. (`#3360 <https://github.com/pytest-dev/pytest/issues/3360>`_)
|
||||
|
||||
- Mention in documentation and CLI help that fixtures with leading ``_`` are
|
||||
printed by ``pytest --fixtures`` only if the ``-v`` option is added. (`#3398
|
||||
<https://github.com/pytest-dev/pytest/issues/3398>`_)
|
||||
|
||||
|
||||
Pytest 3.5.0 (2018-03-21)
|
||||
=========================
|
||||
|
||||
Deprecations and Removals
|
||||
-------------------------
|
||||
|
||||
- ``record_xml_property`` fixture is now deprecated in favor of the more
|
||||
generic ``record_property``. (`#2770
|
||||
<https://github.com/pytest-dev/pytest/issues/2770>`_)
|
||||
|
||||
- Defining ``pytest_plugins`` is now deprecated in non-top-level conftest.py
|
||||
files, because they "leak" to the entire directory tree. (`#3084
|
||||
<https://github.com/pytest-dev/pytest/issues/3084>`_)
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- New ``--show-capture`` command-line option that allows to specify how to
|
||||
display captured output when tests fail: ``no``, ``stdout``, ``stderr``,
|
||||
``log`` or ``all`` (the default). (`#1478
|
||||
<https://github.com/pytest-dev/pytest/issues/1478>`_)
|
||||
|
||||
- New ``--rootdir`` command-line option to override the rules for discovering
|
||||
the root directory. See `customize
|
||||
<https://docs.pytest.org/en/latest/customize.html>`_ in the documentation for
|
||||
details. (`#1642 <https://github.com/pytest-dev/pytest/issues/1642>`_)
|
||||
|
||||
- Fixtures are now instantiated based on their scopes, with higher-scoped
|
||||
fixtures (such as ``session``) being instantiated first than lower-scoped
|
||||
fixtures (such as ``function``). The relative order of fixtures of the same
|
||||
scope is kept unchanged, based in their declaration order and their
|
||||
dependencies. (`#2405 <https://github.com/pytest-dev/pytest/issues/2405>`_)
|
||||
|
||||
- ``record_xml_property`` renamed to ``record_property`` and is now compatible
|
||||
with xdist, markers and any reporter. ``record_xml_property`` name is now
|
||||
deprecated. (`#2770 <https://github.com/pytest-dev/pytest/issues/2770>`_)
|
||||
|
||||
- New ``--nf``, ``--new-first`` options: run new tests first followed by the
|
||||
rest of the tests, in both cases tests are also sorted by the file modified
|
||||
time, with more recent files coming first. (`#3034
|
||||
<https://github.com/pytest-dev/pytest/issues/3034>`_)
|
||||
|
||||
- New ``--last-failed-no-failures`` command-line option that allows to specify
|
||||
the behavior of the cache plugin's ```--last-failed`` feature when no tests
|
||||
failed in the last run (or no cache was found): ``none`` or ``all`` (the
|
||||
default). (`#3139 <https://github.com/pytest-dev/pytest/issues/3139>`_)
|
||||
|
||||
- New ``--doctest-continue-on-failure`` command-line option to enable doctests
|
||||
to show multiple failures for each snippet, instead of stopping at the first
|
||||
failure. (`#3149 <https://github.com/pytest-dev/pytest/issues/3149>`_)
|
||||
|
||||
- Captured log messages are added to the ``<system-out>`` tag in the generated
|
||||
junit xml file if the ``junit_logging`` ini option is set to ``system-out``.
|
||||
If the value of this ini option is ``system-err``, the logs are written to
|
||||
``<system-err>``. The default value for ``junit_logging`` is ``no``, meaning
|
||||
captured logs are not written to the output file. (`#3156
|
||||
<https://github.com/pytest-dev/pytest/issues/3156>`_)
|
||||
|
||||
- Allow the logging plugin to handle ``pytest_runtest_logstart`` and
|
||||
``pytest_runtest_logfinish`` hooks when live logs are enabled. (`#3189
|
||||
<https://github.com/pytest-dev/pytest/issues/3189>`_)
|
||||
|
||||
- Passing `--log-cli-level` in the command-line now automatically activates
|
||||
live logging. (`#3190 <https://github.com/pytest-dev/pytest/issues/3190>`_)
|
||||
|
||||
- Add command line option ``--deselect`` to allow deselection of individual
|
||||
tests at collection time. (`#3198
|
||||
<https://github.com/pytest-dev/pytest/issues/3198>`_)
|
||||
|
||||
- Captured logs are printed before entering pdb. (`#3204
|
||||
<https://github.com/pytest-dev/pytest/issues/3204>`_)
|
||||
|
||||
- Deselected item count is now shown before tests are run, e.g. ``collected X
|
||||
items / Y deselected``. (`#3213
|
||||
<https://github.com/pytest-dev/pytest/issues/3213>`_)
|
||||
|
||||
- The builtin module ``platform`` is now available for use in expressions in
|
||||
``pytest.mark``. (`#3236
|
||||
<https://github.com/pytest-dev/pytest/issues/3236>`_)
|
||||
|
||||
- The *short test summary info* section now is displayed after tracebacks and
|
||||
warnings in the terminal. (`#3255
|
||||
<https://github.com/pytest-dev/pytest/issues/3255>`_)
|
||||
|
||||
- New ``--verbosity`` flag to set verbosity level explicitly. (`#3296
|
||||
<https://github.com/pytest-dev/pytest/issues/3296>`_)
|
||||
|
||||
- ``pytest.approx`` now accepts comparing a numpy array with a scalar. (`#3312
|
||||
<https://github.com/pytest-dev/pytest/issues/3312>`_)
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Suppress ``IOError`` when closing the temporary file used for capturing
|
||||
streams in Python 2.7. (`#2370
|
||||
<https://github.com/pytest-dev/pytest/issues/2370>`_)
|
||||
|
||||
- Fixed ``clear()`` method on ``caplog`` fixture which cleared ``records``, but
|
||||
not the ``text`` property. (`#3297
|
||||
<https://github.com/pytest-dev/pytest/issues/3297>`_)
|
||||
|
||||
- During test collection, when stdin is not allowed to be read, the
|
||||
``DontReadFromStdin`` object still allow itself to be iterable and resolved
|
||||
to an iterator without crashing. (`#3314
|
||||
<https://github.com/pytest-dev/pytest/issues/3314>`_)
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Added a `reference <https://docs.pytest.org/en/latest/reference.html>`_ page
|
||||
to the docs. (`#1713 <https://github.com/pytest-dev/pytest/issues/1713>`_)
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- Change minimum requirement of ``attrs`` to ``17.4.0``. (`#3228
|
||||
<https://github.com/pytest-dev/pytest/issues/3228>`_)
|
||||
|
||||
- Renamed example directories so all tests pass when ran from the base
|
||||
directory. (`#3245 <https://github.com/pytest-dev/pytest/issues/3245>`_)
|
||||
|
||||
- Internal ``mark.py`` module has been turned into a package. (`#3250
|
||||
<https://github.com/pytest-dev/pytest/issues/3250>`_)
|
||||
|
||||
- ``pytest`` now depends on the `more-itertools
|
||||
<https://github.com/erikrose/more-itertools>`_ package. (`#3265
|
||||
<https://github.com/pytest-dev/pytest/issues/3265>`_)
|
||||
|
||||
- Added warning when ``[pytest]`` section is used in a ``.cfg`` file passed
|
||||
with ``-c`` (`#3268 <https://github.com/pytest-dev/pytest/issues/3268>`_)
|
||||
|
||||
- ``nodeids`` can now be passed explicitly to ``FSCollector`` and ``Node``
|
||||
constructors. (`#3291 <https://github.com/pytest-dev/pytest/issues/3291>`_)
|
||||
|
||||
- Internal refactoring of ``FormattedExcinfo`` to use ``attrs`` facilities and
|
||||
remove old support code for legacy Python versions. (`#3292
|
||||
<https://github.com/pytest-dev/pytest/issues/3292>`_)
|
||||
|
||||
- Refactoring to unify how verbosity is handled internally. (`#3296
|
||||
<https://github.com/pytest-dev/pytest/issues/3296>`_)
|
||||
|
||||
- Internal refactoring to better integrate with argparse. (`#3304
|
||||
<https://github.com/pytest-dev/pytest/issues/3304>`_)
|
||||
|
||||
- Fix a python example when calling a fixture in doc/en/usage.rst (`#3308
|
||||
<https://github.com/pytest-dev/pytest/issues/3308>`_)
|
||||
|
||||
|
||||
Pytest 3.4.2 (2018-03-04)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Removed progress information when capture option is ``no``. (`#3203
|
||||
<https://github.com/pytest-dev/pytest/issues/3203>`_)
|
||||
|
||||
- Refactor check of bindir from ``exists`` to ``isdir``. (`#3241
|
||||
<https://github.com/pytest-dev/pytest/issues/3241>`_)
|
||||
|
||||
- Fix ``TypeError`` issue when using ``approx`` with a ``Decimal`` value.
|
||||
(`#3247 <https://github.com/pytest-dev/pytest/issues/3247>`_)
|
||||
|
||||
- Fix reference cycle generated when using the ``request`` fixture. (`#3249
|
||||
<https://github.com/pytest-dev/pytest/issues/3249>`_)
|
||||
|
||||
- ``[tool:pytest]`` sections in ``*.cfg`` files passed by the ``-c`` option are
|
||||
now properly recognized. (`#3260
|
||||
<https://github.com/pytest-dev/pytest/issues/3260>`_)
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Add logging plugin to plugins list. (`#3209
|
||||
<https://github.com/pytest-dev/pytest/issues/3209>`_)
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- Fix minor typo in fixture.rst (`#3259
|
||||
<https://github.com/pytest-dev/pytest/issues/3259>`_)
|
||||
|
||||
|
||||
Pytest 3.4.1 (2018-02-20)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Move import of ``doctest.UnexpectedException`` to top-level to avoid possible
|
||||
errors when using ``--pdb``. (`#1810
|
||||
<https://github.com/pytest-dev/pytest/issues/1810>`_)
|
||||
|
||||
- Added printing of captured stdout/stderr before entering pdb, and improved a
|
||||
test which was giving false negatives about output capturing. (`#3052
|
||||
<https://github.com/pytest-dev/pytest/issues/3052>`_)
|
||||
|
||||
- Fix ordering of tests using parametrized fixtures which can lead to fixtures
|
||||
being created more than necessary. (`#3161
|
||||
<https://github.com/pytest-dev/pytest/issues/3161>`_)
|
||||
|
||||
- Fix bug where logging happening at hooks outside of "test run" hooks would
|
||||
cause an internal error. (`#3184
|
||||
<https://github.com/pytest-dev/pytest/issues/3184>`_)
|
||||
|
||||
- Detect arguments injected by ``unittest.mock.patch`` decorator correctly when
|
||||
pypi ``mock.patch`` is installed and imported. (`#3206
|
||||
<https://github.com/pytest-dev/pytest/issues/3206>`_)
|
||||
|
||||
- Errors shown when a ``pytest.raises()`` with ``match=`` fails are now cleaner
|
||||
on what happened: When no exception was raised, the "matching '...'" part got
|
||||
removed as it falsely implies that an exception was raised but it didn't
|
||||
match. When a wrong exception was raised, it's now thrown (like
|
||||
``pytest.raised()`` without ``match=`` would) instead of complaining about
|
||||
the unmatched text. (`#3222
|
||||
<https://github.com/pytest-dev/pytest/issues/3222>`_)
|
||||
|
||||
- Fixed output capture handling in doctests on macOS. (`#985
|
||||
<https://github.com/pytest-dev/pytest/issues/985>`_)
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Add Sphinx parameter docs for ``match`` and ``message`` args to
|
||||
``pytest.raises``. (`#3202
|
||||
<https://github.com/pytest-dev/pytest/issues/3202>`_)
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- pytest has changed the publication procedure and is now being published to
|
||||
PyPI directly from Travis. (`#3060
|
||||
<https://github.com/pytest-dev/pytest/issues/3060>`_)
|
||||
|
||||
- Rename ``ParameterSet._for_parameterize()`` to ``_for_parametrize()`` in
|
||||
order to comply with the naming convention. (`#3166
|
||||
<https://github.com/pytest-dev/pytest/issues/3166>`_)
|
||||
|
||||
- Skip failing pdb/doctest test on mac. (`#985
|
||||
<https://github.com/pytest-dev/pytest/issues/985>`_)
|
||||
|
||||
|
||||
Pytest 3.4.0 (2018-01-30)
|
||||
=========================
|
||||
|
||||
Deprecations and Removals
|
||||
-------------------------
|
||||
|
||||
- All pytest classes now subclass ``object`` for better Python 2/3 compatibility.
|
||||
This should not affect user code except in very rare edge cases. (`#2147
|
||||
<https://github.com/pytest-dev/pytest/issues/2147>`_)
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Introduce ``empty_parameter_set_mark`` ini option to select which mark to
|
||||
apply when ``@pytest.mark.parametrize`` is given an empty set of parameters.
|
||||
Valid options are ``skip`` (default) and ``xfail``. Note that it is planned
|
||||
to change the default to ``xfail`` in future releases as this is considered
|
||||
less error prone. (`#2527
|
||||
<https://github.com/pytest-dev/pytest/issues/2527>`_)
|
||||
|
||||
- **Incompatible change**: after community feedback the `logging
|
||||
<https://docs.pytest.org/en/latest/logging.html>`_ functionality has
|
||||
undergone some changes. Please consult the `logging documentation
|
||||
<https://docs.pytest.org/en/latest/logging.html#incompatible-changes-in-pytest-3-4>`_
|
||||
for details. (`#3013 <https://github.com/pytest-dev/pytest/issues/3013>`_)
|
||||
|
||||
- Console output falls back to "classic" mode when capturing is disabled (``-s``),
|
||||
otherwise the output gets garbled to the point of being useless. (`#3038
|
||||
<https://github.com/pytest-dev/pytest/issues/3038>`_)
|
||||
|
||||
- New `pytest_runtest_logfinish
|
||||
<https://docs.pytest.org/en/latest/writing_plugins.html#_pytest.hookspec.pytest_runtest_logfinish>`_
|
||||
hook which is called when a test item has finished executing, analogous to
|
||||
`pytest_runtest_logstart
|
||||
<https://docs.pytest.org/en/latest/writing_plugins.html#_pytest.hookspec.pytest_runtest_start>`_.
|
||||
(`#3101 <https://github.com/pytest-dev/pytest/issues/3101>`_)
|
||||
|
||||
- Improve performance when collecting tests using many fixtures. (`#3107
|
||||
<https://github.com/pytest-dev/pytest/issues/3107>`_)
|
||||
|
||||
- New ``caplog.get_records(when)`` method which provides access to the captured
|
||||
records for the ``"setup"``, ``"call"`` and ``"teardown"``
|
||||
testing stages. (`#3117 <https://github.com/pytest-dev/pytest/issues/3117>`_)
|
||||
|
||||
- New fixture ``record_xml_attribute`` that allows modifying and inserting
|
||||
attributes on the ``<testcase>`` xml node in JUnit reports. (`#3130
|
||||
<https://github.com/pytest-dev/pytest/issues/3130>`_)
|
||||
|
||||
- The default cache directory has been renamed from ``.cache`` to
|
||||
``.pytest_cache`` after community feedback that the name ``.cache`` did not
|
||||
make it clear that it was used by pytest. (`#3138
|
||||
<https://github.com/pytest-dev/pytest/issues/3138>`_)
|
||||
|
||||
- Colorize the levelname column in the live-log output. (`#3142
|
||||
<https://github.com/pytest-dev/pytest/issues/3142>`_)
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Fix hanging pexpect test on MacOS by using flush() instead of wait().
|
||||
(`#2022 <https://github.com/pytest-dev/pytest/issues/2022>`_)
|
||||
|
||||
- Fix restoring Python state after in-process pytest runs with the
|
||||
``pytester`` plugin; this may break tests using multiple inprocess
|
||||
pytest runs if later ones depend on earlier ones leaking global interpreter
|
||||
changes. (`#3016 <https://github.com/pytest-dev/pytest/issues/3016>`_)
|
||||
|
||||
- Fix skipping plugin reporting hook when test aborted before plugin setup
|
||||
hook. (`#3074 <https://github.com/pytest-dev/pytest/issues/3074>`_)
|
||||
|
||||
- Fix progress percentage reported when tests fail during teardown. (`#3088
|
||||
<https://github.com/pytest-dev/pytest/issues/3088>`_)
|
||||
|
||||
- **Incompatible change**: ``-o/--override`` option no longer eats all the
|
||||
remaining options, which can lead to surprising behavior: for example,
|
||||
``pytest -o foo=1 /path/to/test.py`` would fail because ``/path/to/test.py``
|
||||
would be considered as part of the ``-o`` command-line argument. One
|
||||
consequence of this is that now multiple configuration overrides need
|
||||
multiple ``-o`` flags: ``pytest -o foo=1 -o bar=2``. (`#3103
|
||||
<https://github.com/pytest-dev/pytest/issues/3103>`_)
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Document hooks (defined with ``historic=True``) which cannot be used with
|
||||
``hookwrapper=True``. (`#2423
|
||||
<https://github.com/pytest-dev/pytest/issues/2423>`_)
|
||||
|
||||
- Clarify that warning capturing doesn't change the warning filter by default.
|
||||
(`#2457 <https://github.com/pytest-dev/pytest/issues/2457>`_)
|
||||
|
||||
- Clarify a possible confusion when using pytest_fixture_setup with fixture
|
||||
functions that return None. (`#2698
|
||||
<https://github.com/pytest-dev/pytest/issues/2698>`_)
|
||||
|
||||
- Fix the wording of a sentence on doctest flags used in pytest. (`#3076
|
||||
<https://github.com/pytest-dev/pytest/issues/3076>`_)
|
||||
|
||||
- Prefer ``https://*.readthedocs.io`` over ``http://*.rtfd.org`` for links in
|
||||
the documentation. (`#3092
|
||||
<https://github.com/pytest-dev/pytest/issues/3092>`_)
|
||||
|
||||
- Improve readability (wording, grammar) of Getting Started guide (`#3131
|
||||
<https://github.com/pytest-dev/pytest/issues/3131>`_)
|
||||
|
||||
- Added note that calling pytest.main multiple times from the same process is
|
||||
not recommended because of import caching. (`#3143
|
||||
<https://github.com/pytest-dev/pytest/issues/3143>`_)
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- Show a simple and easy error when keyword expressions trigger a syntax error
|
||||
(for example, ``"-k foo and import"`` will show an error that you can not use
|
||||
the ``import`` keyword in expressions). (`#2953
|
||||
<https://github.com/pytest-dev/pytest/issues/2953>`_)
|
||||
|
||||
- Change parametrized automatic test id generation to use the ``__name__``
|
||||
attribute of functions instead of the fallback argument name plus counter.
|
||||
(`#2976 <https://github.com/pytest-dev/pytest/issues/2976>`_)
|
||||
|
||||
- Replace py.std with stdlib imports. (`#3067
|
||||
<https://github.com/pytest-dev/pytest/issues/3067>`_)
|
||||
|
||||
- Corrected 'you' to 'your' in logging docs. (`#3129
|
||||
<https://github.com/pytest-dev/pytest/issues/3129>`_)
|
||||
|
||||
|
||||
Pytest 3.3.2 (2017-12-25)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- pytester: ignore files used to obtain current user metadata in the fd leak
|
||||
detector. (`#2784 <https://github.com/pytest-dev/pytest/issues/2784>`_)
|
||||
|
||||
- Fix **memory leak** where objects returned by fixtures were never destructed
|
||||
by the garbage collector. (`#2981
|
||||
<https://github.com/pytest-dev/pytest/issues/2981>`_)
|
||||
|
||||
- Fix conversion of pyargs to filename to not convert symlinks on Python 2. (`#2985
|
||||
<https://github.com/pytest-dev/pytest/issues/2985>`_)
|
||||
|
||||
- ``PYTEST_DONT_REWRITE`` is now checked for plugins too rather than only for
|
||||
test modules. (`#2995 <https://github.com/pytest-dev/pytest/issues/2995>`_)
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Add clarifying note about behavior of multiple parametrized arguments (`#3001
|
||||
<https://github.com/pytest-dev/pytest/issues/3001>`_)
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- Code cleanup. (`#3015 <https://github.com/pytest-dev/pytest/issues/3015>`_,
|
||||
`#3021 <https://github.com/pytest-dev/pytest/issues/3021>`_)
|
||||
|
||||
- Clean up code by replacing imports and references of `_ast` to `ast`. (`#3018
|
||||
<https://github.com/pytest-dev/pytest/issues/3018>`_)
|
||||
|
||||
|
||||
Pytest 3.3.1 (2017-12-05)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Fix issue about ``-p no:<plugin>`` having no effect. (`#2920
|
||||
<https://github.com/pytest-dev/pytest/issues/2920>`_)
|
||||
|
||||
- Fix regression with warnings that contained non-strings in their arguments in
|
||||
Python 2. (`#2956 <https://github.com/pytest-dev/pytest/issues/2956>`_)
|
||||
|
||||
- Always escape null bytes when setting ``PYTEST_CURRENT_TEST``. (`#2957
|
||||
<https://github.com/pytest-dev/pytest/issues/2957>`_)
|
||||
|
||||
- Fix ``ZeroDivisionError`` when using the ``testmon`` plugin when no tests
|
||||
were actually collected. (`#2971
|
||||
<https://github.com/pytest-dev/pytest/issues/2971>`_)
|
||||
|
||||
- Bring back ``TerminalReporter.writer`` as an alias to
|
||||
``TerminalReporter._tw``. This alias was removed by accident in the ``3.3.0``
|
||||
release. (`#2984 <https://github.com/pytest-dev/pytest/issues/2984>`_)
|
||||
|
||||
- The ``pytest-capturelog`` plugin is now also blacklisted, avoiding errors when
|
||||
running pytest with it still installed. (`#3004
|
||||
<https://github.com/pytest-dev/pytest/issues/3004>`_)
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Fix broken link to plugin ``pytest-localserver``. (`#2963
|
||||
<https://github.com/pytest-dev/pytest/issues/2963>`_)
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- Update github "bugs" link in ``CONTRIBUTING.rst`` (`#2949
|
||||
<https://github.com/pytest-dev/pytest/issues/2949>`_)
|
||||
|
||||
|
||||
Pytest 3.3.0 (2017-11-23)
|
||||
=========================
|
||||
|
||||
Deprecations and Removals
|
||||
-------------------------
|
||||
|
||||
- Pytest no longer supports Python **2.6** and **3.3**. Those Python versions
|
||||
are EOL for some time now and incur maintenance and compatibility costs on
|
||||
the pytest core team, and following up with the rest of the community we
|
||||
decided that they will no longer be supported starting on this version. Users
|
||||
which still require those versions should pin pytest to ``<3.3``. (`#2812
|
||||
<https://github.com/pytest-dev/pytest/issues/2812>`_)
|
||||
|
||||
- Remove internal ``_preloadplugins()`` function. This removal is part of the
|
||||
``pytest_namespace()`` hook deprecation. (`#2636
|
||||
<https://github.com/pytest-dev/pytest/issues/2636>`_)
|
||||
|
||||
- Internally change ``CallSpec2`` to have a list of marks instead of a broken
|
||||
mapping of keywords. This removes the keywords attribute of the internal
|
||||
``CallSpec2`` class. (`#2672
|
||||
<https://github.com/pytest-dev/pytest/issues/2672>`_)
|
||||
|
||||
- Remove ParameterSet.deprecated_arg_dict - its not a public api and the lack
|
||||
of the underscore was a naming error. (`#2675
|
||||
<https://github.com/pytest-dev/pytest/issues/2675>`_)
|
||||
|
||||
- Remove the internal multi-typed attribute ``Node._evalskip`` and replace it
|
||||
with the boolean ``Node._skipped_by_mark``. (`#2767
|
||||
<https://github.com/pytest-dev/pytest/issues/2767>`_)
|
||||
|
||||
- The ``params`` list passed to ``pytest.fixture`` is now for
|
||||
all effects considered immutable and frozen at the moment of the ``pytest.fixture``
|
||||
call. Previously the list could be changed before the first invocation of the fixture
|
||||
allowing for a form of dynamic parametrization (for example, updated from command-line options),
|
||||
but this was an unwanted implementation detail which complicated the internals and prevented
|
||||
some internal cleanup. See issue `#2959 <https://github.com/pytest-dev/pytest/issues/2959>`_
|
||||
for details and a recommended workaround.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- ``pytest_fixture_post_finalizer`` hook can now receive a ``request``
|
||||
argument. (`#2124 <https://github.com/pytest-dev/pytest/issues/2124>`_)
|
||||
|
||||
- Replace the old introspection code in compat.py that determines the available
|
||||
arguments of fixtures with inspect.signature on Python 3 and
|
||||
funcsigs.signature on Python 2. This should respect ``__signature__``
|
||||
declarations on functions. (`#2267
|
||||
<https://github.com/pytest-dev/pytest/issues/2267>`_)
|
||||
|
||||
- Report tests with global ``pytestmark`` variable only once. (`#2549
|
||||
<https://github.com/pytest-dev/pytest/issues/2549>`_)
|
||||
|
||||
- Now pytest displays the total progress percentage while running tests. The
|
||||
previous output style can be set by configuring the ``console_output_style``
|
||||
setting to ``classic``. (`#2657 <https://github.com/pytest-dev/pytest/issues/2657>`_)
|
||||
|
||||
- Match ``warns`` signature to ``raises`` by adding ``match`` keyword. (`#2708
|
||||
<https://github.com/pytest-dev/pytest/issues/2708>`_)
|
||||
|
||||
- Pytest now captures and displays output from the standard ``logging`` module.
|
||||
The user can control the logging level to be captured by specifying options
|
||||
in ``pytest.ini``, the command line and also during individual tests using
|
||||
markers. Also, a ``caplog`` fixture is available that enables users to test
|
||||
the captured log during specific tests (similar to ``capsys`` for example).
|
||||
For more information, please see the `logging docs
|
||||
<https://docs.pytest.org/en/latest/logging.html>`_. This feature was
|
||||
introduced by merging the popular `pytest-catchlog
|
||||
<https://pypi.org/project/pytest-catchlog/>`_ plugin, thanks to `Thomas Hisch
|
||||
<https://github.com/thisch>`_. Be advised that during the merging the
|
||||
backward compatibility interface with the defunct ``pytest-capturelog`` has
|
||||
been dropped. (`#2794 <https://github.com/pytest-dev/pytest/issues/2794>`_)
|
||||
|
||||
- Add ``allow_module_level`` kwarg to ``pytest.skip()``, enabling to skip the
|
||||
whole module. (`#2808 <https://github.com/pytest-dev/pytest/issues/2808>`_)
|
||||
|
||||
- Allow setting ``file_or_dir``, ``-c``, and ``-o`` in PYTEST_ADDOPTS. (`#2824
|
||||
<https://github.com/pytest-dev/pytest/issues/2824>`_)
|
||||
|
||||
- Return stdout/stderr capture results as a ``namedtuple``, so ``out`` and
|
||||
``err`` can be accessed by attribute. (`#2879
|
||||
<https://github.com/pytest-dev/pytest/issues/2879>`_)
|
||||
|
||||
- Add ``capfdbinary``, a version of ``capfd`` which returns bytes from
|
||||
``readouterr()``. (`#2923
|
||||
<https://github.com/pytest-dev/pytest/issues/2923>`_)
|
||||
|
||||
- Add ``capsysbinary`` a version of ``capsys`` which returns bytes from
|
||||
``readouterr()``. (`#2934
|
||||
<https://github.com/pytest-dev/pytest/issues/2934>`_)
|
||||
|
||||
- Implement feature to skip ``setup.py`` files when run with
|
||||
``--doctest-modules``. (`#502
|
||||
<https://github.com/pytest-dev/pytest/issues/502>`_)
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Resume output capturing after ``capsys/capfd.disabled()`` context manager.
|
||||
(`#1993 <https://github.com/pytest-dev/pytest/issues/1993>`_)
|
||||
|
||||
- ``pytest_fixture_setup`` and ``pytest_fixture_post_finalizer`` hooks are now
|
||||
called for all ``conftest.py`` files. (`#2124
|
||||
<https://github.com/pytest-dev/pytest/issues/2124>`_)
|
||||
|
||||
- If an exception happens while loading a plugin, pytest no longer hides the
|
||||
original traceback. In Python 2 it will show the original traceback with a new
|
||||
message that explains in which plugin. In Python 3 it will show 2 canonized
|
||||
exceptions, the original exception while loading the plugin in addition to an
|
||||
exception that pytest throws about loading a plugin. (`#2491
|
||||
<https://github.com/pytest-dev/pytest/issues/2491>`_)
|
||||
|
||||
- ``capsys`` and ``capfd`` can now be used by other fixtures. (`#2709
|
||||
<https://github.com/pytest-dev/pytest/issues/2709>`_)
|
||||
|
||||
- Internal ``pytester`` plugin properly encodes ``bytes`` arguments to
|
||||
``utf-8``. (`#2738 <https://github.com/pytest-dev/pytest/issues/2738>`_)
|
||||
|
||||
- ``testdir`` now uses use the same method used by ``tmpdir`` to create its
|
||||
temporary directory. This changes the final structure of the ``testdir``
|
||||
directory slightly, but should not affect usage in normal scenarios and
|
||||
avoids a number of potential problems. (`#2751
|
||||
<https://github.com/pytest-dev/pytest/issues/2751>`_)
|
||||
|
||||
- Pytest no longer complains about warnings with unicode messages being
|
||||
non-ascii compatible even for ascii-compatible messages. As a result of this,
|
||||
warnings with unicode messages are converted first to an ascii representation
|
||||
for safety. (`#2809 <https://github.com/pytest-dev/pytest/issues/2809>`_)
|
||||
|
||||
- Change return value of pytest command when ``--maxfail`` is reached from
|
||||
``2`` (interrupted) to ``1`` (failed). (`#2845
|
||||
<https://github.com/pytest-dev/pytest/issues/2845>`_)
|
||||
|
||||
- Fix issue in assertion rewriting which could lead it to rewrite modules which
|
||||
should not be rewritten. (`#2939
|
||||
<https://github.com/pytest-dev/pytest/issues/2939>`_)
|
||||
|
||||
- Handle marks without description in ``pytest.ini``. (`#2942
|
||||
<https://github.com/pytest-dev/pytest/issues/2942>`_)
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- pytest now depends on `attrs <https://pypi.org/project/attrs/>`_ for internal
|
||||
structures to ease code maintainability. (`#2641
|
||||
<https://github.com/pytest-dev/pytest/issues/2641>`_)
|
||||
|
||||
- Refactored internal Python 2/3 compatibility code to use ``six``. (`#2642
|
||||
<https://github.com/pytest-dev/pytest/issues/2642>`_)
|
||||
|
||||
- Stop vendoring ``pluggy`` - we're missing out on its latest changes for not
|
||||
much benefit (`#2719 <https://github.com/pytest-dev/pytest/issues/2719>`_)
|
||||
|
||||
- Internal refactor: simplify ascii string escaping by using the
|
||||
backslashreplace error handler in newer Python 3 versions. (`#2734
|
||||
<https://github.com/pytest-dev/pytest/issues/2734>`_)
|
||||
|
||||
- Remove unnecessary mark evaluator in unittest plugin (`#2767
|
||||
<https://github.com/pytest-dev/pytest/issues/2767>`_)
|
||||
|
||||
- Calls to ``Metafunc.addcall`` now emit a deprecation warning. This function
|
||||
is scheduled to be removed in ``pytest-4.0``. (`#2876
|
||||
<https://github.com/pytest-dev/pytest/issues/2876>`_)
|
||||
|
||||
- Internal move of the parameterset extraction to a more maintainable place.
|
||||
(`#2877 <https://github.com/pytest-dev/pytest/issues/2877>`_)
|
||||
|
||||
- Internal refactoring to simplify scope node lookup. (`#2910
|
||||
<https://github.com/pytest-dev/pytest/issues/2910>`_)
|
||||
|
||||
- Configure ``pytest`` to prevent pip from installing pytest in unsupported
|
||||
Python versions. (`#2922
|
||||
<https://github.com/pytest-dev/pytest/issues/2922>`_)
|
||||
|
||||
|
||||
Pytest 3.2.5 (2017-11-15)
|
||||
=========================
|
||||
|
||||
|
@ -192,7 +1058,7 @@ Deprecations and Removals
|
|||
-------------------------
|
||||
|
||||
- ``pytest.approx`` no longer supports ``>``, ``>=``, ``<`` and ``<=``
|
||||
operators to avoid surprising/inconsistent behavior. See `the docs
|
||||
operators to avoid surprising/inconsistent behavior. See `the approx docs
|
||||
<https://docs.pytest.org/en/latest/builtin.html#pytest.approx>`_ for more
|
||||
information. (`#2003 <https://github.com/pytest-dev/pytest/issues/2003>`_)
|
||||
|
||||
|
@ -516,7 +1382,7 @@ Changes
|
|||
* Testcase reports with a ``url`` attribute will now properly write this to junitxml.
|
||||
Thanks `@fushi`_ for the PR (`#1874`_).
|
||||
|
||||
* Remove common items from dict comparision output when verbosity=1. Also update
|
||||
* Remove common items from dict comparison output when verbosity=1. Also update
|
||||
the truncation message to make it clearer that pytest truncates all
|
||||
assertion messages if verbosity < 2 (`#1512`_).
|
||||
Thanks `@mattduck`_ for the PR
|
||||
|
@ -528,7 +1394,7 @@ Changes
|
|||
* fix `#2013`_: turn RecordedWarning into ``namedtuple``,
|
||||
to give it a comprehensible repr while preventing unwarranted modification.
|
||||
|
||||
* fix `#2208`_: ensure a iteration limit for _pytest.compat.get_real_func.
|
||||
* fix `#2208`_: ensure an iteration limit for _pytest.compat.get_real_func.
|
||||
Thanks `@RonnyPfannschmidt`_ for the report and PR.
|
||||
|
||||
* Hooks are now verified after collection is complete, rather than right after loading installed plugins. This
|
||||
|
@ -632,7 +1498,7 @@ Bug Fixes
|
|||
Notably, importing the ``anydbm`` module is fixed. (`#2248`_).
|
||||
Thanks `@pfhayes`_ for the PR.
|
||||
|
||||
* junitxml: Fix problematic case where system-out tag occured twice per testcase
|
||||
* junitxml: Fix problematic case where system-out tag occurred twice per testcase
|
||||
element in the XML report. Thanks `@kkoukiou`_ for the PR.
|
||||
|
||||
* Fix regression, pytest now skips unittest correctly if run with ``--pdb``
|
||||
|
@ -2079,7 +2945,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
- fix issue655: work around different ways that cause python2/3
|
||||
to leak sys.exc_info into fixtures/tests causing failures in 3rd party code
|
||||
|
||||
- fix issue615: assertion re-writing did not correctly escape % signs
|
||||
- fix issue615: assertion rewriting did not correctly escape % signs
|
||||
when formatting boolean operations, which tripped over mixing
|
||||
booleans with modulo operators. Thanks to Tom Viner for the report,
|
||||
triaging and fix.
|
||||
|
@ -2228,7 +3094,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
"::" node id specifications (copy pasted from "-v" output)
|
||||
|
||||
- fix issue544 by only removing "@NUM" at the end of "::" separated parts
|
||||
and if the part has an ".py" extension
|
||||
and if the part has a ".py" extension
|
||||
|
||||
- don't use py.std import helper, rather import things directly.
|
||||
Thanks Bruno Oliveira.
|
||||
|
@ -2499,7 +3365,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
|
||||
would not work correctly because pytest assumes @pytest.mark.some
|
||||
gets a function to be decorated already. We now at least detect if this
|
||||
arg is an lambda and thus the example will work. Thanks Alex Gaynor
|
||||
arg is a lambda and thus the example will work. Thanks Alex Gaynor
|
||||
for bringing it up.
|
||||
|
||||
- xfail a test on pypy that checks wrong encoding/ascii (pypy does
|
||||
|
@ -2812,7 +3678,7 @@ Bug fixes:
|
|||
rather use the post-2.0 parametrize features instead of yield, see:
|
||||
http://pytest.org/latest/example/parametrize.html
|
||||
- fix autouse-issue where autouse-fixtures would not be discovered
|
||||
if defined in a a/conftest.py file and tests in a/tests/test_some.py
|
||||
if defined in an a/conftest.py file and tests in a/tests/test_some.py
|
||||
- fix issue226 - LIFO ordering for fixture teardowns
|
||||
- fix issue224 - invocations with >256 char arguments now work
|
||||
- fix issue91 - add/discuss package/directory level setups in example
|
||||
|
@ -3382,7 +4248,7 @@ Bug fixes:
|
|||
- make path.bestrelpath(path) return ".", note that when calling
|
||||
X.bestrelpath the assumption is that X is a directory.
|
||||
- make initial conftest discovery ignore "--" prefixed arguments
|
||||
- fix resultlog plugin when used in an multicpu/multihost xdist situation
|
||||
- fix resultlog plugin when used in a multicpu/multihost xdist situation
|
||||
(thanks Jakub Gustak)
|
||||
- perform distributed testing related reporting in the xdist-plugin
|
||||
rather than having dist-related code in the generic py.test
|
||||
|
|
|
@ -48,8 +48,7 @@ fix the bug itself.
|
|||
Fix bugs
|
||||
--------
|
||||
|
||||
Look through the GitHub issues for bugs. Here is a filter you can use:
|
||||
https://github.com/pytest-dev/pytest/labels/bug
|
||||
Look through the `GitHub issues for bugs <https://github.com/pytest-dev/pytest/labels/type:%20bug>`_.
|
||||
|
||||
:ref:`Talk <contact>` to developers to find out how you can fix specific bugs.
|
||||
|
||||
|
@ -60,8 +59,7 @@ Don't forget to check the issue trackers of your favourite plugins, too!
|
|||
Implement features
|
||||
------------------
|
||||
|
||||
Look through the GitHub issues for enhancements. Here is a filter you can use:
|
||||
https://github.com/pytest-dev/pytest/labels/enhancement
|
||||
Look through the `GitHub issues for enhancements <https://github.com/pytest-dev/pytest/labels/type:%20enhancement>`_.
|
||||
|
||||
:ref:`Talk <contact>` to developers to find out how you can implement specific
|
||||
features.
|
||||
|
@ -141,7 +139,7 @@ Here's a rundown of how a repository transfer usually proceeds
|
|||
* ``joedoe`` transfers repository ownership to ``pytest-dev`` administrator ``calvin``.
|
||||
* ``calvin`` creates ``pytest-xyz-admin`` and ``pytest-xyz-developers`` teams, inviting ``joedoe`` to both as **maintainer**.
|
||||
* ``calvin`` transfers repository to ``pytest-dev`` and configures team access:
|
||||
|
||||
|
||||
- ``pytest-xyz-admin`` **admin** access;
|
||||
- ``pytest-xyz-developers`` **write** access;
|
||||
|
||||
|
@ -164,10 +162,11 @@ Preparing Pull Requests
|
|||
Short version
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
#. Fork the repository;
|
||||
#. Target ``master`` for bugfixes and doc changes;
|
||||
#. Fork the repository.
|
||||
#. Enable and install `pre-commit <https://pre-commit.com>`_ to ensure style-guides and code checks are followed.
|
||||
#. Target ``master`` for bugfixes and doc changes.
|
||||
#. Target ``features`` for new features or functionality changes.
|
||||
#. Follow **PEP-8**. There's a ``tox`` command to help fixing it: ``tox -e fix-lint``.
|
||||
#. Follow **PEP-8** for naming and `black <https://github.com/ambv/black>`_ for formatting.
|
||||
#. Tests are run using ``tox``::
|
||||
|
||||
tox -e linting,py27,py36
|
||||
|
@ -178,7 +177,7 @@ Short version
|
|||
and one of ``bugfix``, ``removal``, ``feature``, ``vendor``, ``doc`` or
|
||||
``trivial`` for the issue type.
|
||||
#. Unless your change is a trivial or a documentation fix (e.g., a typo or reword of a small section) please
|
||||
add yourself to the ``AUTHORS`` file, in alphabetical order;
|
||||
add yourself to the ``AUTHORS`` file, in alphabetical order.
|
||||
|
||||
|
||||
Long version
|
||||
|
@ -204,20 +203,30 @@ Here is a simple overview, with pytest-specific bits:
|
|||
$ git clone git@github.com:YOUR_GITHUB_USERNAME/pytest.git
|
||||
$ cd pytest
|
||||
# now, to fix a bug create your own branch off "master":
|
||||
|
||||
|
||||
$ git checkout -b your-bugfix-branch-name master
|
||||
|
||||
# or to instead add a feature create your own branch off "features":
|
||||
|
||||
|
||||
$ git checkout -b your-feature-branch-name features
|
||||
|
||||
Given we have "major.minor.micro" version numbers, bugfixes will usually
|
||||
be released in micro releases whereas features will be released in
|
||||
Given we have "major.minor.micro" version numbers, bugfixes will usually
|
||||
be released in micro releases whereas features will be released in
|
||||
minor releases and incompatible changes in major releases.
|
||||
|
||||
If you need some help with Git, follow this quick start
|
||||
guide: https://git.wiki.kernel.org/index.php/QuickStart
|
||||
|
||||
#. Install `pre-commit <https://pre-commit.com>`_ and its hook on the pytest repo::
|
||||
|
||||
$ pip install --user pre-commit
|
||||
$ pre-commit install
|
||||
|
||||
Afterwards ``pre-commit`` will run whenever you commit.
|
||||
|
||||
https://pre-commit.com/ is a framework for managing and maintaining multi-language pre-commit hooks
|
||||
to ensure code-style and code formatting is consistent.
|
||||
|
||||
#. Install tox
|
||||
|
||||
Tox is used to run all the tests and will automatically setup virtualenvs
|
||||
|
@ -236,15 +245,7 @@ Here is a simple overview, with pytest-specific bits:
|
|||
This command will run tests via the "tox" tool against Python 2.7 and 3.6
|
||||
and also perform "lint" coding-style checks.
|
||||
|
||||
#. You can now edit your local working copy. Please follow PEP-8.
|
||||
|
||||
You can now make the changes you want and run the tests again as necessary.
|
||||
|
||||
If you have too much linting errors, try running::
|
||||
|
||||
$ tox -e fix-lint
|
||||
|
||||
To fix pep8 related errors.
|
||||
#. You can now edit your local working copy and run the tests again as necessary. Please follow PEP-8 for naming.
|
||||
|
||||
You can pass different options to ``tox``. For example, to run tests on Python 2.7 and pass options to pytest
|
||||
(e.g. enter pdb on failure) to pytest you can do::
|
||||
|
@ -255,6 +256,9 @@ Here is a simple overview, with pytest-specific bits:
|
|||
|
||||
$ tox -e py36 -- testing/test_config.py
|
||||
|
||||
|
||||
When committing, ``pre-commit`` will re-format the files if necessary.
|
||||
|
||||
#. Commit and push once your tests pass and you are happy with your change(s)::
|
||||
|
||||
$ git commit -a -m "<commit message>"
|
||||
|
@ -276,3 +280,15 @@ Here is a simple overview, with pytest-specific bits:
|
|||
base: features # if it's a feature
|
||||
|
||||
|
||||
Joining the Development Team
|
||||
----------------------------
|
||||
|
||||
Anyone who has successfully seen through a pull request which did not
|
||||
require any extra work from the development team to merge will
|
||||
themselves gain commit access if they so wish (if we forget to ask please send a friendly
|
||||
reminder). This does not mean your workflow to contribute changes,
|
||||
everyone goes through the same pull-request-and-review process and
|
||||
no-one merges their own pull requests unless already approved. It does however mean you can
|
||||
participate in the development process more fully since you can merge
|
||||
pull requests from other contributors yourself after having reviewed
|
||||
them.
|
||||
|
|
|
@ -12,7 +12,7 @@ taking a lot of time to make a new one.
|
|||
|
||||
#. Install development dependencies in a virtual environment with::
|
||||
|
||||
pip3 install -r tasks/requirements.txt
|
||||
pip3 install -U -r tasks/requirements.txt
|
||||
|
||||
#. Create a branch ``release-X.Y.Z`` with the version for the release.
|
||||
|
||||
|
@ -22,44 +22,28 @@ taking a lot of time to make a new one.
|
|||
|
||||
Ensure your are in a clean work tree.
|
||||
|
||||
#. Generate docs, changelog, announcements and upload a package to
|
||||
your ``devpi`` staging server::
|
||||
#. Generate docs, changelog, announcements and a **local** tag::
|
||||
|
||||
invoke generate.pre-release <VERSION> <DEVPI USER> --password <DEVPI PASSWORD>
|
||||
|
||||
If ``--password`` is not given, it is assumed the user is already logged in ``devpi``.
|
||||
If you don't have an account, please ask for one.
|
||||
invoke generate.pre-release <VERSION>
|
||||
|
||||
#. Open a PR for this branch targeting ``master``.
|
||||
|
||||
#. Test the package
|
||||
#. After all tests pass and the PR has been approved, publish to PyPI by pushing the tag::
|
||||
|
||||
* **Manual method**
|
||||
git push git@github.com:pytest-dev/pytest.git <VERSION>
|
||||
|
||||
Run from multiple machines::
|
||||
Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/pytest>`_.
|
||||
|
||||
devpi use https://devpi.net/USER/dev
|
||||
devpi test pytest==VERSION
|
||||
#. Send an email announcement with the contents from::
|
||||
|
||||
Check that tests pass for relevant combinations with::
|
||||
doc/en/announce/release-<VERSION>.rst
|
||||
|
||||
devpi list pytest
|
||||
To the following mailing lists:
|
||||
|
||||
* **CI servers**
|
||||
* pytest-dev@python.org (all releases)
|
||||
* python-announce-list@python.org (all releases)
|
||||
* testing-in-python@lists.idyll.org (only major/minor releases)
|
||||
|
||||
Configure a repository as per-instructions on
|
||||
devpi-cloud-test_ to test the package on Travis_ and AppVeyor_.
|
||||
All test environments should pass.
|
||||
|
||||
#. Publish to PyPI::
|
||||
|
||||
invoke generate.publish-release <VERSION> <DEVPI USER> <PYPI_NAME>
|
||||
|
||||
where PYPI_NAME is the name of pypi.python.org as configured in your ``~/.pypirc``
|
||||
file `for devpi <http://doc.devpi.net/latest/quickstart-releaseprocess.html?highlight=pypirc#devpi-push-releasing-to-an-external-index>`_.
|
||||
And announce it on `Twitter <https://twitter.com/>`_ with the ``#pytest`` hashtag.
|
||||
|
||||
#. After a minor/major release, merge ``release-X.Y.Z`` into ``master`` and push (or open a PR).
|
||||
|
||||
.. _devpi-cloud-test: https://github.com/obestwalter/devpi-cloud-test
|
||||
.. _AppVeyor: https://www.appveyor.com/
|
||||
.. _Travis: https://travis-ci.org
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
Metadata-Version: 1.1
|
||||
Metadata-Version: 1.2
|
||||
Name: pytest
|
||||
Version: 3.2.5
|
||||
Version: 3.6.2
|
||||
Summary: pytest: simple powerful testing with Python
|
||||
Home-page: http://pytest.org
|
||||
Author: Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others
|
||||
Author-email: UNKNOWN
|
||||
License: MIT license
|
||||
Project-URL: Source, https://github.com/pytest-dev/pytest
|
||||
Project-URL: Tracker, https://github.com/pytest-dev/pytest/issues
|
||||
Description: .. image:: http://docs.pytest.org/en/latest/_static/pytest1.png
|
||||
:target: http://docs.pytest.org
|
||||
:align: center
|
||||
|
@ -14,13 +15,13 @@ Description: .. image:: http://docs.pytest.org/en/latest/_static/pytest1.png
|
|||
------
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/pytest.svg
|
||||
:target: https://pypi.python.org/pypi/pytest
|
||||
:target: https://pypi.org/project/pytest/
|
||||
|
||||
.. image:: https://anaconda.org/conda-forge/pytest/badges/version.svg
|
||||
.. image:: https://img.shields.io/conda/vn/conda-forge/pytest.svg
|
||||
:target: https://anaconda.org/conda-forge/pytest
|
||||
|
||||
.. image:: https://img.shields.io/pypi/pyversions/pytest.svg
|
||||
:target: https://pypi.python.org/pypi/pytest
|
||||
:target: https://pypi.org/project/pytest/
|
||||
|
||||
.. image:: https://img.shields.io/coveralls/pytest-dev/pytest/master.svg
|
||||
:target: https://coveralls.io/r/pytest-dev/pytest
|
||||
|
@ -31,6 +32,12 @@ Description: .. image:: http://docs.pytest.org/en/latest/_static/pytest1.png
|
|||
.. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true
|
||||
:target: https://ci.appveyor.com/project/pytestbot/pytest
|
||||
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/ambv/black
|
||||
|
||||
.. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg
|
||||
:target: https://www.codetriage.com/pytest-dev/pytest
|
||||
|
||||
The ``pytest`` framework makes it easy to write small tests, yet
|
||||
scales to support complex functional testing for applications and libraries.
|
||||
|
||||
|
@ -42,6 +49,7 @@ Description: .. image:: http://docs.pytest.org/en/latest/_static/pytest1.png
|
|||
def inc(x):
|
||||
return x + 1
|
||||
|
||||
|
||||
def test_answer():
|
||||
assert inc(3) == 5
|
||||
|
||||
|
@ -84,7 +92,7 @@ Description: .. image:: http://docs.pytest.org/en/latest/_static/pytest1.png
|
|||
- Can run `unittest <http://docs.pytest.org/en/latest/unittest.html>`_ (or trial),
|
||||
`nose <http://docs.pytest.org/en/latest/nose.html>`_ test suites out of the box;
|
||||
|
||||
- Python2.6+, Python3.3+, PyPy-2.3, Jython-2.5 (untested);
|
||||
- Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (untested);
|
||||
|
||||
- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;
|
||||
|
||||
|
@ -132,10 +140,10 @@ Classifier: Topic :: Software Development :: Testing
|
|||
Classifier: Topic :: Software Development :: Libraries
|
||||
Classifier: Topic :: Utilities
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
------
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/pytest.svg
|
||||
:target: https://pypi.python.org/pypi/pytest
|
||||
:target: https://pypi.org/project/pytest/
|
||||
|
||||
.. image:: https://anaconda.org/conda-forge/pytest/badges/version.svg
|
||||
.. image:: https://img.shields.io/conda/vn/conda-forge/pytest.svg
|
||||
:target: https://anaconda.org/conda-forge/pytest
|
||||
|
||||
.. image:: https://img.shields.io/pypi/pyversions/pytest.svg
|
||||
:target: https://pypi.python.org/pypi/pytest
|
||||
:target: https://pypi.org/project/pytest/
|
||||
|
||||
.. image:: https://img.shields.io/coveralls/pytest-dev/pytest/master.svg
|
||||
:target: https://coveralls.io/r/pytest-dev/pytest
|
||||
|
@ -23,6 +23,12 @@
|
|||
.. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true
|
||||
:target: https://ci.appveyor.com/project/pytestbot/pytest
|
||||
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/ambv/black
|
||||
|
||||
.. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg
|
||||
:target: https://www.codetriage.com/pytest-dev/pytest
|
||||
|
||||
The ``pytest`` framework makes it easy to write small tests, yet
|
||||
scales to support complex functional testing for applications and libraries.
|
||||
|
||||
|
@ -34,6 +40,7 @@ An example of a simple test:
|
|||
def inc(x):
|
||||
return x + 1
|
||||
|
||||
|
||||
def test_answer():
|
||||
assert inc(3) == 5
|
||||
|
||||
|
@ -76,7 +83,7 @@ Features
|
|||
- Can run `unittest <http://docs.pytest.org/en/latest/unittest.html>`_ (or trial),
|
||||
`nose <http://docs.pytest.org/en/latest/nose.html>`_ test suites out of the box;
|
||||
|
||||
- Python2.6+, Python3.3+, PyPy-2.3, Jython-2.5 (untested);
|
||||
- Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (untested);
|
||||
|
||||
- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
"""
|
||||
imports symbols from vendored "pluggy" if available, otherwise
|
||||
falls back to importing "pluggy" from the default namespace.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
try:
|
||||
from _pytest.vendored_packages.pluggy import * # noqa
|
||||
from _pytest.vendored_packages.pluggy import __version__ # noqa
|
||||
except ImportError:
|
||||
from pluggy import * # noqa
|
||||
from pluggy import __version__ # noqa
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,42 +0,0 @@
|
|||
"""
|
||||
This module contains deprecation messages and bits of code used elsewhere in the codebase
|
||||
that is planned to be removed in the next pytest release.
|
||||
|
||||
Keeping it in a central location makes it easy to track what is deprecated and should
|
||||
be removed when the time comes.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
|
||||
class RemovedInPytest4Warning(DeprecationWarning):
|
||||
"""warning class for features removed in pytest 4.0"""
|
||||
|
||||
|
||||
MAIN_STR_ARGS = 'passing a string to pytest.main() is deprecated, ' \
|
||||
'pass a list of arguments instead.'
|
||||
|
||||
YIELD_TESTS = 'yield tests are deprecated, and scheduled to be removed in pytest 4.0'
|
||||
|
||||
FUNCARG_PREFIX = (
|
||||
'{name}: declaring fixtures using "pytest_funcarg__" prefix is deprecated '
|
||||
'and scheduled to be removed in pytest 4.0. '
|
||||
'Please remove the prefix and use the @pytest.fixture decorator instead.')
|
||||
|
||||
SETUP_CFG_PYTEST = '[pytest] section in setup.cfg files is deprecated, use [tool:pytest] instead.'
|
||||
|
||||
GETFUNCARGVALUE = "use of getfuncargvalue is deprecated, use getfixturevalue"
|
||||
|
||||
RESULT_LOG = (
|
||||
'--result-log is deprecated and scheduled for removal in pytest 4.0.\n'
|
||||
'See https://docs.pytest.org/en/latest/usage.html#creating-resultlog-format-files for more information.'
|
||||
)
|
||||
|
||||
MARK_INFO_ATTRIBUTE = RemovedInPytest4Warning(
|
||||
"MarkInfo objects are deprecated as they contain the merged marks"
|
||||
)
|
||||
|
||||
MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning(
|
||||
"Applying marks directly to parameters is deprecated,"
|
||||
" please use pytest.param(..., marks=...) instead.\n"
|
||||
"For more details, see: https://docs.pytest.org/en/latest/parametrize.html"
|
||||
)
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче