diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 00000000..f8a20eda
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,8 @@
+# Contributing
+
+## Build documentation locally
+
+Using tox:
+```shell
+$ tox -e docs
+```
diff --git a/.gitignore b/.gitignore
index 8c52a551..01c37c9c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,8 @@ develop-eggs
.installed.cfg
lib
lib64
+MANIFEST
+MANIFEST.in
# Backup files
*.bak
@@ -41,3 +43,9 @@ nosetests.xml
.mr.developer.cfg
.project
.pydevproject
+
+# PyCharm
+.idea
+
+# Generated test file
+mytempfile.py
diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml
new file mode 100644
index 00000000..dd8d0d65
--- /dev/null
+++ b/.pre-commit-hooks.yaml
@@ -0,0 +1,15 @@
+- id: futurize
+ name: futurize
+ description: Futurize your Py2 code to ensure it is runnable on Py3.
+ language: python
+ types: [python]
+ entry: futurize -w -n --no-diffs
+ args: [--stage1]
+
+- id: pasteurize
+ name: pasteurize
+ description: Pasteurize your Py3 code to ensure it is runnable on Py2.
+ language: python
+ language_version: python3
+ types: [python]
+ entry: pasteurize -w -n --no-diffs
diff --git a/.travis.yml b/.travis.yml
index eb28e459..3fe6a983 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,41 +1,11 @@
-sudo: false
-language: python
-cache: pip
+language: generic
-matrix:
- include:
- - python: 2.6
- env: TOXENV=py26
- - python: 2.7
- env: TOXENV=py27
- - python: 3.3
- env: TOXENV=py33
- - python: 3.4
- env: TOXENV=py34
- - python: 3.5
- env: TOXENV=py35
- - python: 3.6
- env: TOXENV=py36
- - python: 3.7
- env: TOXENV=py37
- dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069)
- sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069)
-
-install:
- - pip install tox==2.9.1
- - pip install virtualenv==15.2.0
- - pip install py==1.4.30
- - pip install pluggy==0.5.2
+services:
+ - docker
before_script:
- # Run flake8 tests only on Python 2.7 and 3.7...
- # 1) stop the build if there are Python syntax errors or undefined names
- # 2) exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
- - if [[ $TRAVIS_PYTHON_VERSION == *.7 ]]; then
- pip install flake8;
- flake8 . --count --exit-zero --select=E901,E999,F821,F822,F823 --show-source --statistics;
- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics;
- fi
+ - docker pull jmadler/python-future-builder:latest
script:
- - tox
+ - ./build.sh
+ - ./lint.sh
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..678de2e2
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,41 @@
+FROM debian:9
+# This docker image has a copy of a wide array of Pythons installed
+RUN apt-get update
+RUN apt-get install --yes --no-install-recommends make build-essential zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev xz-utils tk-dev libxml2-dev libffi-dev liblzma-dev libssl1.0-dev
+RUN apt-get install --yes git vim
+RUN apt-get install --yes python3-pip
+ENV PYENV_ROOT=/opt/pyenv
+RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
+RUN echo export PATH="/opt/pyenv/bin:$PATH" >> ~/.bashrc
+RUN echo 'eval "$(pyenv init -)"' >> ~/.bashrc
+RUN echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bashrc
+# venv 15.2.0 is the last to support Python 2.6.
+RUN pip3 install virtualenv==15.2.0
+# Can't get python 2.6 to build anymore
+# RUN PATH=/opt/pyenv/bin:$PATH pyenv install 2.6.9
+# RUN virtualenv /root/py26 --python /opt/pyenv/versions/2.6.9/bin/python
+RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.3.7
+RUN virtualenv /root/py33 --python /opt/pyenv/versions/3.3.7/bin/python
+RUN pip3 install virtualenv==20.0.21
+RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.4.10
+RUN virtualenv /root/py34 --python /opt/pyenv/versions/3.4.10/bin/python
+RUN apt-get install --yes libssl-dev libxmlsec1-dev
+RUN PATH=/opt/pyenv/bin:$PATH pyenv install 2.7.18
+RUN virtualenv /root/py27 --python /opt/pyenv/versions/2.7.18/bin/python
+RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.5.9
+RUN virtualenv /root/py35 --python /opt/pyenv/versions/3.5.9/bin/python
+RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.6.10
+RUN virtualenv /root/py36 --python /opt/pyenv/versions/3.6.10/bin/python
+RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.7.7
+RUN virtualenv /root/py37 --python /opt/pyenv/versions/3.7.7/bin/python
+RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.8.3
+RUN virtualenv /root/py38 --python /opt/pyenv/versions/3.8.3/bin/python
+RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.9.0
+RUN virtualenv /root/py39 --python /opt/pyenv/versions/3.9.0/bin/python
+# Lint tools
+RUN pip3 install flake8
+RUN ln -s /usr/bin/python3 /usr/bin/python
+ENV LC_ALL=C.UTF-8
+ENV LANG=C.UTF-8
+WORKDIR /root/python-future
+ADD . /root/python-future
diff --git a/LICENSE.txt b/LICENSE.txt
index d41c85d1..4c904dba 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,4 +1,4 @@
-Copyright (c) 2013-2018 Python Charmers Pty Ltd, Australia
+Copyright (c) 2013-2019 Python Charmers Pty Ltd, Australia
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.rst b/README.rst
index 807ea2a0..1ab43e53 100644
--- a/README.rst
+++ b/README.rst
@@ -3,6 +3,12 @@
Overview: Easy, clean, reliable Python 2/3 compatibility
========================================================
+.. image:: https://travis-ci.org/PythonCharmers/python-future.svg?branch=master
+ :target: https://travis-ci.org/PythonCharmers/python-future
+
+.. image:: https://readthedocs.org/projects/python-future/badge/?version=latest
+ :target: https://python-future.readthedocs.io/en/latest/?badge=latest
+
``python-future`` is the missing compatibility layer between Python 2 and
Python 3. It allows you to use a single, clean Python 3.x-compatible
codebase to support both Python 2 and Python 3 with minimal overhead.
@@ -22,9 +28,6 @@ are `Mezzanine `_ and `ObsPy
Features
--------
-.. image:: https://travis-ci.org/PythonCharmers/python-future.svg?branch=master
- :target: https://travis-ci.org/PythonCharmers/python-future
-
- ``future.builtins`` package (also available as ``builtins`` on Py2) provides
backports and remappings for 20 builtins with different semantics on Py3
versus Py2
@@ -57,6 +60,8 @@ Features
decoding the backported ``str`` and ``bytes`` objects. [This feature is
currently in alpha.]
+- support for pre-commit hooks
+
.. _code-examples:
Code examples
@@ -243,7 +248,7 @@ Example:
$ python3
- >>> from past import autotranslate
+ >>> from past.translation import autotranslate
>>> autotranslate(['plotrique'])
>>> import plotrique
@@ -261,16 +266,49 @@ development, and will likely never be perfect.
For more info, see :ref:`translation`.
+Pre-commit hooks
+----------------
+
+`Pre-commit `_ is a framework for managing and maintaining
+multi-language pre-commit hooks.
+
+In case you need to port your project from Python 2 to Python 3, you might consider
+using such hook during the transition period.
+
+First:
+
+.. code-block:: bash
+
+ $ pip install pre-commit
+
+and then in your project's directory:
+
+.. code-block:: bash
+
+ $ pre-commit install
+
+Next, you need to add this entry to your ``.pre-commit-config.yaml``
+
+.. code-block:: yaml
+
+ - repo: https://github.com/PythonCharmers/python-future
+ rev: master
+ hooks:
+ - id: futurize
+ args: [--both-stages]
+
+The ``args`` part is optional, by default only stage1 is applied.
+
Licensing
---------
:Author: Ed Schofield, Jordan M. Adler, et al
-:Copyright: 2013-2018 Python Charmers Pty Ltd, Australia.
+:Copyright: 2013-2019 Python Charmers Pty Ltd, Australia.
:Sponsors: Python Charmers Pty Ltd, Australia, and Python Charmers Pte
Ltd, Singapore. http://pythoncharmers.com
-
+
Pinterest https://opensource.pinterest.com/
:Licence: MIT. See ``LICENSE.txt`` or `here `_.
diff --git a/TESTING.txt b/TESTING.txt
index 1c29b6a6..b2ad5c65 100644
--- a/TESTING.txt
+++ b/TESTING.txt
@@ -1,7 +1,11 @@
-Currently the tests are passing on OS X and Linux on Python 2.7 and 3.4.
+A docker image, python-future-builder, is used to do testing and building. The test suite can be run with:
-The test suite can be run with:
+ $ bash build.sh
- $ tox
+which tests the module under a number of different python versions, where available, or with:
-which tests the module under a number of different python versions, where available.
+ $ py.test
+
+To execute a single test:
+
+ $ pytest -k test_chained_exceptions_stacktrace
diff --git a/build.sh b/build.sh
new file mode 100755
index 00000000..df1f00f7
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,17 @@
+# XXX: TODO: we should make this include -e once tests pass
+set -xuo pipefail
+
+DOCKER_IMAGE=jmadler/python-future-builder
+# XXX: TODO: Perhaps this version shouldn't be hardcoded
+version=0.18.3
+
+docker build . -t $DOCKER_IMAGE
+docker push $DOCKER_IMAGE:latest
+
+for i in py26 py27 py33 py34 py35 py36 py37 py38 py39; do
+ docker run -ti -v $(realpath dist):/root/python-future/dist $DOCKER_IMAGE /root/python-future/setup.sh $version $(basename $i)
+done
+
+python setup.py sdist
+python setup.py clean
+echo You may now run: "twine upload dist/*"
diff --git a/docs/changelog.rst b/docs/changelog.rst
index aa317b96..059ad4f5 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -616,10 +616,10 @@ it like this::
$ pip3 install plotrique==0.2.5-7 --no-compile # to ignore SyntaxErrors
$ python3
-Then pass in a whitelist of module name prefixes to the ``past.autotranslate()``
-function. Example::
+Then pass in a whitelist of module name prefixes to the
+``past.translation.autotranslate()`` function. Example::
- >>> from past import autotranslate
+ >>> from past.translation import autotranslate
>>> autotranslate(['plotrique'])
>>> import plotrique
@@ -949,8 +949,8 @@ v0.11.3:
objects as on Py3.
v0.11.2:
- * The ``past.autotranslate`` feature now finds modules to import more
- robustly and works with Python eggs.
+ * The ``past.translation.autotranslate`` feature now finds modules to import
+ more robustly and works with Python eggs.
v0.11.1:
* Update to ``requirements_py26.txt`` for Python 2.6. Small updates to
diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst
index 52a6a8b0..b0cb05a3 100644
--- a/docs/compatible_idioms.rst
+++ b/docs/compatible_idioms.rst
@@ -3,7 +3,7 @@
Cheat Sheet: Writing Python 2-3 compatible code
===============================================
-- **Copyright (c):** 2013-2018 Python Charmers Pty Ltd, Australia.
+- **Copyright (c):** 2013-2019 Python Charmers Pty Ltd, Australia.
- **Author:** Ed Schofield.
- **Licence:** Creative Commons Attribution.
@@ -1270,7 +1270,7 @@ urllib module
~~~~~~~~~~~~~
``urllib`` is the hardest module to use from Python 2/3 compatible code.
-You may like to use Requests (http://python-requests.org) instead.
+You might want to switch to Requests (http://python-requests.org) instead.
.. code:: python
diff --git a/docs/conf.py b/docs/conf.py
index 72911405..6f00536b 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -13,7 +13,6 @@
from __future__ import absolute_import, print_function
import sys, os
-from future import __version__
import sphinx_bootstrap_theme
# If extensions (or modules to document with autodoc) are in another directory,
@@ -51,7 +50,7 @@
# General information about the project.
project = u'Python-Future'
-copyright = u'2013-2018, Python Charmers Pty Ltd, Australia'
+copyright = u'2013-2019, Python Charmers Pty Ltd, Australia'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
diff --git a/docs/credits.rst b/docs/credits.rst
index 3cf1c9c8..275e148e 100644
--- a/docs/credits.rst
+++ b/docs/credits.rst
@@ -8,7 +8,7 @@ Licence
The software is distributed under an MIT licence. The text is as follows
(from ``LICENSE.txt``)::
- Copyright (c) 2013-2018 Python Charmers Pty Ltd, Australia
+ Copyright (c) 2013-2019 Python Charmers Pty Ltd, Australia
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -47,42 +47,68 @@ Authors
-------
Python-Future is largely written by Ed Schofield with the help of various contributors:
+- Jordan Adler
+- Jeroen Akkerman
+- Kyle Altendorf
- Grant Bakker
- Jacob Beck
+- Fumihiro (Ben) Bessho
+- Shiva Bhusal
- Nate Bogdanowicz
+- Tomer Chachamu
+- Christian Clauss
- Denis Cornehl
- Nicolas Delaby
+- Chad Dombrova
- Jon Dufresne
- Corey Farwell
- Eric Firing
+- Joe Gordon
- Maximilian Hils
+- Miro Hrončok
+- Mark Huang
- Martijn Jacobs
- Michael Joseph
- Waldemar Kornewald
- Alexey Kotlyarov
+- Steve Kowalik
- Lion Krischer
- Marcin Kuzminski
- Joshua Landau
- German Larrain
- Chris Lasher
+- ghanshyam lele
- Calum Lind
+- Tobias Megies
+- Anika Mukherji
- Jon Parise
- Matthew Parnell
+- Tom Picton
- Miga Purg
- Éloi Rivard
+- Sesh Sadasivam
- Elliott Sales de Andrade
+- Aiden Scandella
- Yury Selivanov
- Tim Shaffer
+- Sameera Somisetty
- Louis Sautier
+- Gregory P. Smith
+- Chase Sterling
- Daniel Szoska
- Flaviu Tamas
- Jeff Tratner
- Tim Tröndle
- Brad Walker
-- cclaus (GiHub user)
+- Andrew Wason
+- Jeff Widman
+- Dan Yeaw
+- Hackalog (GitHub user)
- lsm (GiHub user)
- Mystic-Mirage (GitHub user)
- str4d (GitHub user)
+- ucodery (GitHub user)
+- urain39 (GitHub user)
- 9seconds (GitHub user)
- Varriount (GitHub user)
diff --git a/docs/futurize.rst b/docs/futurize.rst
index fdbf3026..11520a6c 100644
--- a/docs/futurize.rst
+++ b/docs/futurize.rst
@@ -81,6 +81,7 @@ The complete set of fixers applied by ``futurize --stage1`` is:
lib2to3.fixes.fix_apply
lib2to3.fixes.fix_except
+ lib2to3.fixes.fix_exec
lib2to3.fixes.fix_exitfunc
lib2to3.fixes.fix_funcattrs
lib2to3.fixes.fix_has_key
@@ -106,7 +107,6 @@ The complete set of fixers applied by ``futurize --stage1`` is:
libfuturize.fixes.fix_print_with_import
libfuturize.fixes.fix_raise
-
The following fixers from ``lib2to3`` are not applied:
.. code-block:: python
@@ -224,23 +224,23 @@ becomes::
The complete list of fixers applied in Stage 2 is::
- lib2to3.fixes.fix_basestring
lib2to3.fixes.fix_dict
- lib2to3.fixes.fix_exec
+ lib2to3.fixes.fix_filter
lib2to3.fixes.fix_getcwdu
lib2to3.fixes.fix_input
lib2to3.fixes.fix_itertools
lib2to3.fixes.fix_itertools_imports
- lib2to3.fixes.fix_filter
lib2to3.fixes.fix_long
lib2to3.fixes.fix_map
+ lib2to3.fixes.fix_next
lib2to3.fixes.fix_nonzero
lib2to3.fixes.fix_operator
lib2to3.fixes.fix_raw_input
lib2to3.fixes.fix_zip
+ libfuturize.fixes.fix_basestring
libfuturize.fixes.fix_cmp
- libfuturize.fixes.fix_division
+ libfuturize.fixes.fix_division_safe
libfuturize.fixes.fix_execfile
libfuturize.fixes.fix_future_builtins
libfuturize.fixes.fix_future_standard_library
@@ -269,11 +269,6 @@ Not applied::
lib2to3.fixes.fix_xrange # Custom one because of a bug with Py3.3's lib2to3
-Fixes applied with the ``futurize --conservative`` option::
-
- libfuturize.fixes.fix_division_safe # instead of libfuturize.fixes.fix_division.
-
-
.. Ideally the output of this stage should not be a ``SyntaxError`` on either
.. Python 3 or Python 2.
diff --git a/docs/futurize_cheatsheet.rst b/docs/futurize_cheatsheet.rst
index 1fcca365..82f211c6 100644
--- a/docs/futurize_cheatsheet.rst
+++ b/docs/futurize_cheatsheet.rst
@@ -31,10 +31,10 @@ The goal for this step is to modernize the Python 2 code without introducing any
pip install future
**1b**. Run ``futurize --stage1 -w *.py subdir1/*.py subdir2/*.py``. Note that with
-recursive globbing in ``bash`` or ``zsh``, you can apply stage 1 to all Python
-source files recursively with::
+recursive globbing in ``bash`` or ``zsh``, you can apply stage 1 to all source files
+recursively with::
- futurize --stage1 -w **/*.py
+ futurize --stage1 -w .
**1c**. Commit all changes
@@ -79,10 +79,9 @@ again with the help of the ``future`` package.
futurize --stage2 myfolder1/*.py myfolder2/*.py
-Or, using recursive globbing with ``bash`` or ``zsh``, you can view the stage 2
-changes to all Python source files recursively with::
+You can view the stage 2 changes to all Python source files recursively with::
- futurize --stage2 **/*.py
+ futurize --stage2 .
To apply the changes, add the ``-w`` argument.
diff --git a/docs/futurize_overview.rst b/docs/futurize_overview.rst
index 2beef1ac..769b65c7 100644
--- a/docs/futurize_overview.rst
+++ b/docs/futurize_overview.rst
@@ -51,5 +51,5 @@ use the ``-w`` flag.
For complex projects, it is probably best to divide the porting into two stages.
Stage 1 is for "safe" changes that modernize the code but do not break Python
-2.7 compatibility or introduce a depdendency on the ``future`` package. Stage 2
+2.7 compatibility or introduce a dependency on the ``future`` package. Stage 2
is to complete the process.
diff --git a/docs/notebooks/Writing Python 2-3 compatible code.ipynb b/docs/notebooks/Writing Python 2-3 compatible code.ipynb
index a3e5e156..0f585d29 100644
--- a/docs/notebooks/Writing Python 2-3 compatible code.ipynb
+++ b/docs/notebooks/Writing Python 2-3 compatible code.ipynb
@@ -11,7 +11,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "- **Copyright (c):** 2013-2018 Python Charmers Pty Ltd, Australia.\n",
+ "- **Copyright (c):** 2013-2019 Python Charmers Pty Ltd, Australia.\n",
"- **Author:** Ed Schofield.\n",
"- **Licence:** Creative Commons Attribution.\n",
"\n",
@@ -1747,7 +1747,7 @@
"source": [
"# Python 2 and 3: option 3\n",
"try:\n",
- " import itertools.imap as map\n",
+ " from itertools import imap as map\n",
"except ImportError:\n",
" pass\n",
"\n",
@@ -1845,7 +1845,7 @@
"source": [
"# Python 2 and 3: option 2\n",
"try:\n",
- " import itertools.imap as map\n",
+ " from itertools import imap as map\n",
"except ImportError:\n",
" pass\n",
"\n",
diff --git a/docs/other/find_pattern.py b/docs/other/find_pattern.py
index 679a1d64..1a5da35e 100644
--- a/docs/other/find_pattern.py
+++ b/docs/other/find_pattern.py
@@ -38,6 +38,7 @@
Larger snippets can be placed in a file (as opposed to a command-line
arg) and processed with the -f option.
"""
+from __future__ import print_function
__author__ = "Collin Winter "
@@ -65,7 +66,7 @@ def main(args):
elif len(args) > 1:
tree = driver.parse_stream(StringIO(args[1] + "\n"))
else:
- print >>sys.stderr, "You must specify an input file or an input string"
+ print("You must specify an input file or an input string", file=sys.stderr)
return 1
examine_tree(tree)
@@ -75,10 +76,10 @@ def examine_tree(tree):
for node in tree.post_order():
if isinstance(node, pytree.Leaf):
continue
- print repr(str(node))
+ print(repr(str(node)))
verdict = raw_input()
if verdict.strip():
- print find_pattern(node)
+ print(find_pattern(node))
return
def find_pattern(node):
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index a5e464f9..6042e059 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -132,7 +132,7 @@ environment::
Then add the following code at the top of your (Py3 or Py2/3-compatible)
code::
- from past import autotranslate
+ from past.translation import autotranslate
autotranslate(['mypackagename'])
import mypackagename
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 00000000..c5e7e301
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,2 @@
+sphinx==3.2.1
+sphinx_bootstrap_theme==0.7.1
diff --git a/docs/str_object.rst b/docs/str_object.rst
index 4c5257a0..568b897a 100644
--- a/docs/str_object.rst
+++ b/docs/str_object.rst
@@ -14,7 +14,7 @@ There are also other differences, such as the ``repr`` of unicode strings in
Py2 having a ``u'...'`` prefix, versus simply ``'...'``, and the removal of
the :func:`str.decode` method in Py3.
-:mod:`future` contains a :class:`newstr`` type that is a backport of the
+:mod:`future` contains a :class:`newstr` type that is a backport of the
:mod:`str` object from Python 3. This inherits from the Python 2
:class:`unicode` class but has customizations to improve compatibility with
Python 3's :class:`str` object. You can use it as follows::
diff --git a/docs/translation.rst b/docs/translation.rst
index 49f558b0..632c46b1 100644
--- a/docs/translation.rst
+++ b/docs/translation.rst
@@ -21,9 +21,9 @@ Here is how to use it::
$ python3
Then pass in a whitelist of module name prefixes to the
-``past.autotranslate()`` function. Example::
+``past.translation.autotranslate()`` function. Example::
- >>> from past import autotranslate
+ >>> from past.translation import autotranslate
>>> autotranslate(['plotrique'])
>>> import plotrique
diff --git a/docs/unicode_literals.rst b/docs/unicode_literals.rst
index 7252e4d6..f6eb2839 100644
--- a/docs/unicode_literals.rst
+++ b/docs/unicode_literals.rst
@@ -144,7 +144,7 @@ Others' perspectives
In favour of ``unicode_literals``
*********************************
-Django recommends importing ``unicode_literals`` as its top `porting tip `_ for
+Django recommends importing ``unicode_literals`` as its top `porting tip `_ for
migrating Django extension modules to Python 3. The following `quote
`_ is
from Aymeric Augustin on 23 August 2012 regarding why he chose
diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst
index 68790527..40f7191f 100644
--- a/docs/whatsnew.rst
+++ b/docs/whatsnew.rst
@@ -3,7 +3,108 @@
What's New
**********
-What's new in version 0.17.1 (2019-10-30)
+What's new in version 0.18.3 (2023-01-13)
+=========================================
+This is a minor bug-fix release containing a number of fixes:
+
+- Backport fix for bpo-38804 (c91d70b)
+- Fix bug in fix_print.py fixer (dffc579)
+- Fix bug in fix_raise.py fixer (3401099)
+- Fix newint bool in py3 (fe645ba)
+- Fix bug in super() with metaclasses (6e27aac)
+- docs: fix simple typo, reqest -> request (974eb1f)
+- Correct __eq__ (c780bf5)
+- Pass if lint fails (2abe00d)
+- Update docker image and parcel out to constant variable. Add comment to update version constant (45cf382)
+- fix order (f96a219)
+- Add flake8 to image (046ff18)
+- Make lint.sh executable (58cc984)
+- Add docker push to optimize CI (01e8440)
+- Build System (42b3025)
+- Add docs build status badge to README.md (3f40bd7)
+- Use same docs requirements in tox (18ecc5a)
+- Add docs/requirements.txt (5f9893f)
+- Add PY37_PLUS, PY38_PLUS, and PY39_PLUS (bee0247)
+- fix 2.6 test, better comment (ddedcb9)
+- fix 2.6 test (3f1ff7e)
+- remove nan test (4dbded1)
+- include list test values (e3f1a12)
+- fix other python2 test issues (c051026)
+- fix missing subTest (f006cad)
+- import from old imp library on older python versions (fc84fa8)
+- replace fstrings with format for python 3.4,3.5 (4a687ea)
+- minor style/spelling fixes (8302d8c)
+- improve cmp function, add unittest (0d95a40)
+- Pin typing==3.7.4.1 for Python 3.3 compatiblity (1a48f1b)
+- Fix various py26 unit test failures (9ca5a14)
+- Add initial contributing guide with docs build instruction (e55f915)
+- Add docs building to tox.ini (3ee9e7f)
+- Support NumPy's specialized int types in builtins.round (b4b54f0)
+- Added r""" to the docstring to avoid warnings in python3 (5f94572)
+- Add __subclasscheck__ for past.types.basestring (c9bc0ff)
+- Correct example in README (681e78c)
+- Add simple documentation (6c6e3ae)
+- Add pre-commit hooks (a9c6a37)
+- Handling of __next__ and next by future.utils.get_next was reversed (52b0ff9)
+- Add a test for our fix (461d77e)
+- Compare headers to correct definition of str (3eaa8fd)
+- #322 Add support for negative ndigits in round; additionally, fixing a bug so that it handles passing in Decimal properly (a4911b9)
+- Add tkFileDialog to future.movers.tkinter (f6a6549)
+- Sort before comparing dicts in TestChainMap (6126997)
+- Fix typo (4dfa099)
+- Fix formatting in "What's new" (1663dfa)
+- Fix typo (4236061)
+- Avoid DeprecationWarning caused by invalid escape (e4b7fa1)
+- Fixup broken link to external django documentation re: porting to Python 3 and unicode_literals (d87713e)
+- Fixed newdict checking version every time (99030ec)
+- Add count from 2.7 to 2.6 (1b8ef51)
+
+What's new in version 0.18.2 (2019-10-30)
+=========================================
+This is a minor bug-fix release containing a number of fixes:
+
+- Fix min/max functions with generators, and 'None' default (PR #514)
+- Use BaseException in raise_() (PR #515)
+- Fix builtins.round() for Decimals (Issue #501)
+- Fix raise_from() to prevent failures with immutable classes (PR #518)
+- Make FixInput idempotent (Issue #427)
+- Fix type in newround (PR #521)
+- Support mimetype guessing in urllib2 for Py3.8+ (Issue #508)
+
+Python 3.8 is not yet officially supported.
+
+What's new in version 0.18.1 (2019-10-09)
+=========================================
+This is a minor bug-fix release containing a fix for raise_()
+when passed an exception that's not an Exception (e.g. BaseException
+subclasses)
+
+What's new in version 0.18.0 (2019-10-09)
+=========================================
+This is a major bug-fix and feature release, including:
+
+- Fix collections.abc import for py38+
+- Remove import for isnewbytes() function, reducing CPU cost significantly
+- Fix bug with importing past.translation when importing past which breaks zipped python installations
+- Fix an issue with copyreg import under Py3 that results in unexposed stdlib functionality
+- Export and document types in future.utils
+- Update behavior of newstr.__eq__() to match str.__eq__() as per reference docs
+- Fix raising and the raising fixer to handle cases where the syntax is ambigious
+- Allow "default" parameter in min() and max() (Issue #334)
+- Implement __hash__() in newstr (Issue #454)
+- Future proof some version checks to handle the fact that Py4 won't be a major breaking release
+- Fix urllib.request imports for Python 3.8 compatibility (Issue #447)
+- Fix future import ordering (Issue #445)
+- Fixed bug in fix_division_safe fixture (Issue #434)
+- Do not globally destroy re.ASCII in PY3
+- Fix a bug in email.Message.set_boundary() (Issue #429)
+- Implement format_map() in str
+- Implement readinto() for socket.fp
+
+As well as a number of corrections to a variety of documentation, and updates to
+test infrastructure.
+
+What's new in version 0.17.1 (2018-10-30)
=========================================
This release address a packaging error because of an erroneous declaration that
any built wheels are universal.
@@ -48,7 +149,7 @@ effect on your system.
This releases also fixes these bugs:
-- Fix ``newbytes`` constructor bug. (Issue #163)
+- Fix ``newbytes`` constructor bug. (Issue #171)
- Fix semantics of ``bool()`` with ``newobject``. (Issue #211)
- Fix ``standard_library.install_aliases()`` on PyPy. (Issue #205)
- Fix assertRaises for ``pow`` and ``compile``` on Python 3.5. (Issue #183)
diff --git a/futurize.py b/futurize.py
index 41080cf0..cb446ab2 100755
--- a/futurize.py
+++ b/futurize.py
@@ -13,7 +13,7 @@
Licensing
---------
-Copyright 2013-2018 Python Charmers Pty Ltd, Australia.
+Copyright 2013-2019 Python Charmers Pty Ltd, Australia.
The software is distributed under an MIT licence. See LICENSE.txt.
"""
diff --git a/lint.sh b/lint.sh
new file mode 100755
index 00000000..667b258f
--- /dev/null
+++ b/lint.sh
@@ -0,0 +1,3 @@
+# TODO: Run under Python 2.7 and 3.7
+flake8 . --count --exit-zero --select=E901,E999,F821,F822,F823 --show-source --statistics || true
+flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics || true
diff --git a/pasteurize.py b/pasteurize.py
index c0bd7e09..2b98327c 100755
--- a/pasteurize.py
+++ b/pasteurize.py
@@ -12,7 +12,7 @@
Licensing
---------
-Copyright 2013-2018 Python Charmers Pty Ltd, Australia.
+Copyright 2013-2019 Python Charmers Pty Ltd, Australia.
The software is distributed under an MIT licence. See LICENSE.txt.
"""
diff --git a/setup.py b/setup.py
index 2f7b36a3..11d694c2 100755
--- a/setup.py
+++ b/setup.py
@@ -46,7 +46,6 @@
"past.builtins",
"past.types",
"past.utils",
- # "past.tests",
"past.translation",
"libfuturize",
"libfuturize.fixes",
diff --git a/setup.sh b/setup.sh
new file mode 100755
index 00000000..8e8dc150
--- /dev/null
+++ b/setup.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+set -exo pipefail
+
+version=$1
+pytag=$2
+
+if [ $pytag = 'py33' ]; then
+ pip3 install virtualenv==16.2.0
+fi
+
+source /root/$pytag/bin/activate
+
+if [ $pytag = 'py26' ]; then
+ pip install importlib
+fi
+pip install pytest unittest2
+python setup.py bdist_wheel --python-tag=$pytag
+pip install dist/future-$version-$pytag-none-any.whl
+pytest tests/
diff --git a/src/future/__init__.py b/src/future/__init__.py
index f7a6fbeb..b609299a 100644
--- a/src/future/__init__.py
+++ b/src/future/__init__.py
@@ -68,7 +68,7 @@
Credits
-------
-:Author: Ed Schofield
+:Author: Ed Schofield, Jordan M. Adler, et al
:Sponsor: Python Charmers Pty Ltd, Australia, and Python Charmers Pte
Ltd, Singapore. http://pythoncharmers.com
:Others: See docs/credits.rst or http://python-future.org/credits.html
@@ -76,7 +76,7 @@
Licensing
---------
-Copyright 2013-2018 Python Charmers Pty Ltd, Australia.
+Copyright 2013-2019 Python Charmers Pty Ltd, Australia.
The software is distributed under an MIT licence. See LICENSE.txt.
"""
@@ -84,10 +84,10 @@
__title__ = 'future'
__author__ = 'Ed Schofield'
__license__ = 'MIT'
-__copyright__ = 'Copyright 2013-2018 Python Charmers Pty Ltd'
+__copyright__ = 'Copyright 2013-2019 Python Charmers Pty Ltd'
__ver_major__ = 0
-__ver_minor__ = 17
-__ver_patch__ = 1
+__ver_minor__ = 18
+__ver_patch__ = 3
__ver_sub__ = ''
__version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__,
__ver_patch__, __ver_sub__)
diff --git a/src/future/backports/__init__.py b/src/future/backports/__init__.py
index 68291141..c71e0653 100644
--- a/src/future/backports/__init__.py
+++ b/src/future/backports/__init__.py
@@ -10,7 +10,7 @@
from future.standard_library import import_top_level_modules
-if sys.version_info[0] == 3:
+if sys.version_info[0] >= 3:
import_top_level_modules()
diff --git a/src/future/backports/email/base64mime.py b/src/future/backports/email/base64mime.py
index 416d612e..296392a6 100644
--- a/src/future/backports/email/base64mime.py
+++ b/src/future/backports/email/base64mime.py
@@ -28,6 +28,7 @@
from __future__ import absolute_import
from future.builtins import range
from future.builtins import bytes
+from future.builtins import str
__all__ = [
'body_decode',
diff --git a/src/future/backports/email/message.py b/src/future/backports/email/message.py
index 99715fcc..d8d9615d 100644
--- a/src/future/backports/email/message.py
+++ b/src/future/backports/email/message.py
@@ -800,7 +800,7 @@ def set_boundary(self, boundary):
# There was no Content-Type header, and we don't know what type
# to set it to, so raise an exception.
raise errors.HeaderParseError('No Content-Type header found')
- newparams = []
+ newparams = list()
foundp = False
for pk, pv in params:
if pk.lower() == 'boundary':
@@ -814,10 +814,10 @@ def set_boundary(self, boundary):
# instead???
newparams.append(('boundary', '"%s"' % boundary))
# Replace the existing Content-Type header with the new value
- newheaders = []
+ newheaders = list()
for h, v in self._headers:
if h.lower() == 'content-type':
- parts = []
+ parts = list()
for k, v in newparams:
if v == '':
parts.append(k)
diff --git a/src/future/backports/http/client.py b/src/future/backports/http/client.py
index 5dd983d8..e663d125 100644
--- a/src/future/backports/http/client.py
+++ b/src/future/backports/http/client.py
@@ -79,11 +79,15 @@
import io
import os
import socket
-import collections
from future.backports.urllib.parse import urlsplit
import warnings
from array import array
+if PY2:
+ from collections import Iterable
+else:
+ from collections.abc import Iterable
+
__all__ = ["HTTPResponse", "HTTPConnection",
"HTTPException", "NotConnected", "UnknownProtocol",
"UnknownTransferEncoding", "UnimplementedFileMode",
@@ -696,9 +700,19 @@ def _safe_readinto(self, b):
while total_bytes < len(b):
if MAXAMOUNT < len(mvb):
temp_mvb = mvb[0:MAXAMOUNT]
- n = self.fp.readinto(temp_mvb)
+ if PY2:
+ data = self.fp.read(len(temp_mvb))
+ n = len(data)
+ temp_mvb[:n] = data
+ else:
+ n = self.fp.readinto(temp_mvb)
else:
- n = self.fp.readinto(mvb)
+ if PY2:
+ data = self.fp.read(len(mvb))
+ n = len(data)
+ mvb[:n] = data
+ else:
+ n = self.fp.readinto(mvb)
if not n:
raise IncompleteRead(bytes(mvb[0:total_bytes]), len(b))
mvb = mvb[n:]
@@ -892,7 +906,7 @@ def send(self, data):
try:
self.sock.sendall(data)
except TypeError:
- if isinstance(data, collections.Iterable):
+ if isinstance(data, Iterable):
for d in data:
self.sock.sendall(d)
else:
diff --git a/src/future/backports/http/cookiejar.py b/src/future/backports/http/cookiejar.py
index cad72f9b..0ad80a02 100644
--- a/src/future/backports/http/cookiejar.py
+++ b/src/future/backports/http/cookiejar.py
@@ -33,7 +33,7 @@
from __future__ import division
from __future__ import absolute_import
from future.builtins import filter, int, map, open, str
-from future.utils import as_native_str
+from future.utils import as_native_str, PY2
__all__ = ['Cookie', 'CookieJar', 'CookiePolicy', 'DefaultCookiePolicy',
'FileCookieJar', 'LWPCookieJar', 'LoadError', 'MozillaCookieJar']
@@ -41,7 +41,8 @@
import copy
import datetime
import re
-re.ASCII = 0
+if PY2:
+ re.ASCII = 0
import time
from future.backports.urllib.parse import urlparse, urlsplit, quote
from future.backports.http.client import HTTP_PORT
@@ -224,10 +225,14 @@ def _str2time(day, mon, yr, hr, min, sec, tz):
(?::(\d\d))? # optional seconds
)? # optional clock
\s*
- ([-+]?\d{2,4}|(?![APap][Mm]\b)[A-Za-z]+)? # timezone
+ (?:
+ ([-+]?\d{2,4}|(?![APap][Mm]\b)[A-Za-z]+) # timezone
+ \s*
+ )?
+ (?:
+ \(\w+\) # ASCII representation of timezone in parens.
\s*
- (?:\(\w+\))? # ASCII representation of timezone in parens.
- \s*$""", re.X | re.ASCII)
+ )?$""", re.X | re.ASCII)
def http2time(text):
"""Returns time in seconds since epoch of time represented by a string.
@@ -297,9 +302,11 @@ def http2time(text):
(?::?(\d\d(?:\.\d*)?))? # optional seconds (and fractional)
)? # optional clock
\s*
- ([-+]?\d\d?:?(:?\d\d)?
- |Z|z)? # timezone (Z is "zero meridian", i.e. GMT)
- \s*$""", re.X | re. ASCII)
+ (?:
+ ([-+]?\d\d?:?(:?\d\d)?
+ |Z|z) # timezone (Z is "zero meridian", i.e. GMT)
+ \s*
+ )?$""", re.X | re. ASCII)
def iso2time(text):
"""
As for http2time, but parses the ISO 8601 formats:
diff --git a/src/future/backports/http/cookies.py b/src/future/backports/http/cookies.py
index ae32ed7e..8bb61e22 100644
--- a/src/future/backports/http/cookies.py
+++ b/src/future/backports/http/cookies.py
@@ -138,7 +138,8 @@
# Import our required modules
#
import re
-re.ASCII = 0 # for py2 compatibility
+if PY2:
+ re.ASCII = 0 # for py2 compatibility
import string
__all__ = ["CookieError", "BaseCookie", "SimpleCookie"]
diff --git a/src/future/backports/misc.py b/src/future/backports/misc.py
index ef752078..992b978f 100644
--- a/src/future/backports/misc.py
+++ b/src/future/backports/misc.py
@@ -16,7 +16,6 @@
import subprocess
from math import ceil as oldceil
-from collections import Mapping, MutableMapping
from operator import itemgetter as _itemgetter, eq as _eq
import sys
@@ -25,7 +24,12 @@
from itertools import repeat as _repeat, chain as _chain, starmap as _starmap
from socket import getaddrinfo, SOCK_STREAM, error, socket
-from future.utils import iteritems, itervalues, PY26, PY3
+from future.utils import iteritems, itervalues, PY2, PY26, PY3
+
+if PY2:
+ from collections import Mapping, MutableMapping
+else:
+ from collections.abc import Mapping, MutableMapping
def ceil(x):
@@ -42,6 +46,16 @@ def ceil(x):
from itertools import islice
+if PY26:
+ # itertools.count in Py 2.6 doesn't accept a step parameter
+ def count(start=0, step=1):
+ while True:
+ yield start
+ start += step
+else:
+ from itertools import count
+
+
if PY3:
try:
from _thread import get_ident
@@ -81,6 +95,10 @@ def wrapper(self):
return decorating_function
+# OrderedDict Shim from Raymond Hettinger, python core dev
+# http://code.activestate.com/recipes/576693-ordered-dictionary-for-py24/
+# here to support version 2.6.
+
################################################################################
### OrderedDict
################################################################################
diff --git a/src/future/backports/urllib/request.py b/src/future/backports/urllib/request.py
index b1545ca0..baee5401 100644
--- a/src/future/backports/urllib/request.py
+++ b/src/future/backports/urllib/request.py
@@ -109,11 +109,17 @@
import socket
import sys
import time
-import collections
import tempfile
import contextlib
import warnings
+from future.utils import PY2
+
+if PY2:
+ from collections import Iterable
+else:
+ from collections.abc import Iterable
+
# check for SSL
try:
import ssl
@@ -1221,7 +1227,7 @@ def do_request_(self, request):
mv = memoryview(data)
size = len(mv) * mv.itemsize
except TypeError:
- if isinstance(data, collections.Iterable):
+ if isinstance(data, Iterable):
raise ValueError("Content-Length should be specified "
"for iterable data of type %r %r" % (type(data),
data))
diff --git a/src/future/builtins/__init__.py b/src/future/builtins/__init__.py
index 216465a1..8bc1649d 100644
--- a/src/future/builtins/__init__.py
+++ b/src/future/builtins/__init__.py
@@ -11,7 +11,7 @@
# The isinstance import is no longer needed. We provide it only for
# backward-compatibility with future v0.8.2. It will be removed in future v1.0.
from future.builtins.misc import (ascii, chr, hex, input, isinstance, next,
- oct, open, pow, round, super)
+ oct, open, pow, round, super, max, min)
from future.utils import PY3
if PY3:
@@ -43,7 +43,7 @@
__all__ = ['filter', 'map', 'zip',
'ascii', 'chr', 'hex', 'input', 'next', 'oct', 'open', 'pow',
'round', 'super',
- 'bytes', 'dict', 'int', 'list', 'object', 'range', 'str',
+ 'bytes', 'dict', 'int', 'list', 'object', 'range', 'str', 'max', 'min'
]
else:
diff --git a/src/future/builtins/misc.py b/src/future/builtins/misc.py
index 90dc384a..f86ce5f3 100644
--- a/src/future/builtins/misc.py
+++ b/src/future/builtins/misc.py
@@ -13,6 +13,8 @@
- ``open`` (equivalent to io.open on Py2)
- ``super`` (backport of Py3's magic zero-argument super() function
- ``round`` (new "Banker's Rounding" behaviour from Py3)
+- ``max`` (new default option from Py3.4)
+- ``min`` (new default option from Py3.4)
``isinstance`` is also currently exported for backwards compatibility
with v0.8.2, although this has been deprecated since v0.9.
@@ -59,6 +61,8 @@
from future.builtins.newnext import newnext as next
from future.builtins.newround import newround as round
from future.builtins.newsuper import newsuper as super
+ from future.builtins.new_min_max import newmax as max
+ from future.builtins.new_min_max import newmin as min
from future.types.newint import newint
_SENTINEL = object()
@@ -89,11 +93,12 @@ def pow(x, y, z=_SENTINEL):
else:
return _builtin_pow(x+0j, y, z)
+
# ``future`` doesn't support Py3.0/3.1. If we ever did, we'd add this:
# callable = __builtin__.callable
__all__ = ['ascii', 'chr', 'hex', 'input', 'isinstance', 'next', 'oct',
- 'open', 'pow', 'round', 'super']
+ 'open', 'pow', 'round', 'super', 'max', 'min']
else:
import builtins
@@ -109,8 +114,14 @@ def pow(x, y, z=_SENTINEL):
pow = builtins.pow
round = builtins.round
super = builtins.super
-
- __all__ = []
+ if utils.PY34_PLUS:
+ max = builtins.max
+ min = builtins.min
+ __all__ = []
+ else:
+ from future.builtins.new_min_max import newmax as max
+ from future.builtins.new_min_max import newmin as min
+ __all__ = ['min', 'max']
# The callable() function was removed from Py3.0 and 3.1 and
# reintroduced into Py3.2+. ``future`` doesn't support Py3.0/3.1. If we ever
diff --git a/src/future/builtins/new_min_max.py b/src/future/builtins/new_min_max.py
new file mode 100644
index 00000000..6f0c2a86
--- /dev/null
+++ b/src/future/builtins/new_min_max.py
@@ -0,0 +1,59 @@
+import itertools
+
+from future import utils
+if utils.PY2:
+ from __builtin__ import max as _builtin_max, min as _builtin_min
+else:
+ from builtins import max as _builtin_max, min as _builtin_min
+
+_SENTINEL = object()
+
+
+def newmin(*args, **kwargs):
+ return new_min_max(_builtin_min, *args, **kwargs)
+
+
+def newmax(*args, **kwargs):
+ return new_min_max(_builtin_max, *args, **kwargs)
+
+
+def new_min_max(_builtin_func, *args, **kwargs):
+ """
+ To support the argument "default" introduced in python 3.4 for min and max
+ :param _builtin_func: builtin min or builtin max
+ :param args:
+ :param kwargs:
+ :return: returns the min or max based on the arguments passed
+ """
+
+ for key, _ in kwargs.items():
+ if key not in set(['key', 'default']):
+ raise TypeError('Illegal argument %s', key)
+
+ if len(args) == 0:
+ raise TypeError
+
+ if len(args) != 1 and kwargs.get('default', _SENTINEL) is not _SENTINEL:
+ raise TypeError
+
+ if len(args) == 1:
+ iterator = iter(args[0])
+ try:
+ first = next(iterator)
+ except StopIteration:
+ if kwargs.get('default', _SENTINEL) is not _SENTINEL:
+ return kwargs.get('default')
+ else:
+ raise ValueError('{}() arg is an empty sequence'.format(_builtin_func.__name__))
+ else:
+ iterator = itertools.chain([first], iterator)
+ if kwargs.get('key') is not None:
+ return _builtin_func(iterator, key=kwargs.get('key'))
+ else:
+ return _builtin_func(iterator)
+
+ if len(args) > 1:
+ if kwargs.get('key') is not None:
+ return _builtin_func(args, key=kwargs.get('key'))
+ else:
+ return _builtin_func(args)
diff --git a/src/future/builtins/newround.py b/src/future/builtins/newround.py
index 3943ebb6..b06c1169 100644
--- a/src/future/builtins/newround.py
+++ b/src/future/builtins/newround.py
@@ -2,6 +2,7 @@
``python-future``: pure Python implementation of Python 3 round().
"""
+from __future__ import division
from future.utils import PYPY, PY26, bind_method
# Use the decimal module for simplicity of implementation (and
@@ -29,25 +30,30 @@ def newround(number, ndigits=None):
if hasattr(number, '__round__'):
return number.__round__(ndigits)
- if ndigits < 0:
- raise NotImplementedError('negative ndigits not supported yet')
exponent = Decimal('10') ** (-ndigits)
- if PYPY:
- # Work around issue #24: round() breaks on PyPy with NumPy's types
- if 'numpy' in repr(type(number)):
- number = float(number)
+ # Work around issue #24: round() breaks on PyPy with NumPy's types
+ # Also breaks on CPython with NumPy's specialized int types like uint64
+ if 'numpy' in repr(type(number)):
+ number = float(number)
- if not PY26:
- d = Decimal.from_float(number).quantize(exponent,
- rounding=ROUND_HALF_EVEN)
+ if isinstance(number, Decimal):
+ d = number
+ else:
+ if not PY26:
+ d = Decimal.from_float(number)
+ else:
+ d = from_float_26(number)
+
+ if ndigits < 0:
+ result = newround(d / exponent) * exponent
else:
- d = from_float_26(number).quantize(exponent, rounding=ROUND_HALF_EVEN)
+ result = d.quantize(exponent, rounding=ROUND_HALF_EVEN)
if return_int:
- return int(d)
+ return int(result)
else:
- return float(d)
+ return float(result)
### From Python 2.7's decimal.py. Only needed to support Py2.6:
diff --git a/src/future/builtins/newsuper.py b/src/future/builtins/newsuper.py
index 5d3402bd..3e8cc80f 100644
--- a/src/future/builtins/newsuper.py
+++ b/src/future/builtins/newsuper.py
@@ -60,44 +60,15 @@ def newsuper(typ=_SENTINEL, type_or_obj=_SENTINEL, framedepth=1):
raise RuntimeError('super() used in a function with no args')
try:
- # Get the MRO so we can crawl it.
- mro = type_or_obj.__mro__
- except (AttributeError, RuntimeError): # see issue #160
+ typ = find_owner(type_or_obj, f.f_code)
+ except (AttributeError, RuntimeError, TypeError):
+ # see issues #160, #267
try:
- mro = type_or_obj.__class__.__mro__
+ typ = find_owner(type_or_obj.__class__, f.f_code)
except AttributeError:
- raise RuntimeError('super() used with a non-newstyle class')
-
- # A ``for...else`` block? Yes! It's odd, but useful.
- # If unfamiliar with for...else, see:
- #
- # http://psung.blogspot.com/2007/12/for-else-in-python.html
- for typ in mro:
- # Find the class that owns the currently-executing method.
- for meth in typ.__dict__.values():
- # Drill down through any wrappers to the underlying func.
- # This handles e.g. classmethod() and staticmethod().
- try:
- while not isinstance(meth,FunctionType):
- if isinstance(meth, property):
- # Calling __get__ on the property will invoke
- # user code which might throw exceptions or have
- # side effects
- meth = meth.fget
- else:
- try:
- meth = meth.__func__
- except AttributeError:
- meth = meth.__get__(type_or_obj, typ)
- except (AttributeError, TypeError):
- continue
- if meth.func_code is f.f_code:
- break # Aha! Found you.
- else:
- continue # Not found! Move onto the next class in MRO.
- break # Found! Break out of the search loop.
- else:
- raise RuntimeError('super() called outside a method')
+ raise RuntimeError('super() used with an old-style class')
+ except TypeError:
+ raise RuntimeError('super() called outside a method')
# Dispatch to builtin super().
if type_or_obj is not _SENTINEL:
@@ -105,6 +76,34 @@ def newsuper(typ=_SENTINEL, type_or_obj=_SENTINEL, framedepth=1):
return _builtin_super(typ)
+def find_owner(cls, code):
+ '''Find the class that owns the currently-executing method.
+ '''
+ for typ in cls.__mro__:
+ for meth in typ.__dict__.values():
+ # Drill down through any wrappers to the underlying func.
+ # This handles e.g. classmethod() and staticmethod().
+ try:
+ while not isinstance(meth,FunctionType):
+ if isinstance(meth, property):
+ # Calling __get__ on the property will invoke
+ # user code which might throw exceptions or have
+ # side effects
+ meth = meth.fget
+ else:
+ try:
+ meth = meth.__func__
+ except AttributeError:
+ meth = meth.__get__(cls, typ)
+ except (AttributeError, TypeError):
+ continue
+ if meth.func_code is code:
+ return typ # Aha! Found you.
+ # Not found! Move onto the next class in MRO.
+
+ raise TypeError
+
+
def superm(*args, **kwds):
f = sys._getframe(1)
nm = f.f_code.co_name
diff --git a/src/future/moves/__init__.py b/src/future/moves/__init__.py
index 040fdcf0..0cd60d3d 100644
--- a/src/future/moves/__init__.py
+++ b/src/future/moves/__init__.py
@@ -4,5 +4,5 @@
__future_module__ = True
from future.standard_library import import_top_level_modules
-if sys.version_info[0] == 3:
+if sys.version_info[0] >= 3:
import_top_level_modules()
diff --git a/src/future/moves/copyreg.py b/src/future/moves/copyreg.py
index 21c7a42f..9d08cdc5 100644
--- a/src/future/moves/copyreg.py
+++ b/src/future/moves/copyreg.py
@@ -2,7 +2,11 @@
from future.utils import PY3
if PY3:
- from copyreg import *
+ import copyreg, sys
+ # A "*" import uses Python 3's copyreg.__all__ which does not include
+ # all public names in the API surface for copyreg, this avoids that
+ # problem by just making our module _be_ a reference to the actual module.
+ sys.modules['future.moves.copyreg'] = copyreg
else:
__future_module__ = True
from copy_reg import *
diff --git a/src/future/moves/tkinter/filedialog.py b/src/future/moves/tkinter/filedialog.py
index 973923e2..6a6f03ca 100644
--- a/src/future/moves/tkinter/filedialog.py
+++ b/src/future/moves/tkinter/filedialog.py
@@ -10,3 +10,9 @@
except ImportError:
raise ImportError('The FileDialog module is missing. Does your Py2 '
'installation include tkinter?')
+
+ try:
+ from tkFileDialog import *
+ except ImportError:
+ raise ImportError('The tkFileDialog module is missing. Does your Py2 '
+ 'installation include tkinter?')
diff --git a/src/future/moves/urllib/request.py b/src/future/moves/urllib/request.py
index 60e440a7..972aa4ab 100644
--- a/src/future/moves/urllib/request.py
+++ b/src/future/moves/urllib/request.py
@@ -11,19 +11,8 @@
proxy_bypass,
quote,
request_host,
- splitattr,
- splithost,
- splitpasswd,
- splitport,
- splitquery,
- splittag,
- splittype,
- splituser,
- splitvalue,
thishost,
- to_bytes,
unquote,
- unwrap,
url2pathname,
urlcleanup,
urljoin,
@@ -32,6 +21,18 @@
urlretrieve,
urlsplit,
urlunparse)
+
+ from urllib.parse import (splitattr,
+ splithost,
+ splitpasswd,
+ splitport,
+ splitquery,
+ splittag,
+ splittype,
+ splituser,
+ splitvalue,
+ to_bytes,
+ unwrap)
else:
__future_module__ = True
with suspend_hooks():
diff --git a/src/future/tests/base.py b/src/future/tests/base.py
index 9f4607b6..4ef437ba 100644
--- a/src/future/tests/base.py
+++ b/src/future/tests/base.py
@@ -272,7 +272,11 @@ def convert_check(self, before, expected, stages=(1, 2), all_imports=False,
else:
headers = ''
- self.compare(output, headers + reformat_code(expected),
+ reformatted = reformat_code(expected)
+ if headers in reformatted:
+ headers = ''
+
+ self.compare(output, headers + reformatted,
ignore_imports=ignore_imports)
def unchanged(self, code, **kwargs):
@@ -338,6 +342,10 @@ def _futurize_test_script(self, filename='mytestscript.py', stages=(1, 2),
'----\n%s\n----' % f.read(),
)
ErrorClass = (FuturizeError if 'futurize' in script else PasteurizeError)
+
+ if not hasattr(e, 'output'):
+ # The attribute CalledProcessError.output doesn't exist on Py2.6
+ e.output = None
raise ErrorClass(msg, e.returncode, e.cmd, output=e.output)
return output
diff --git a/src/future/types/newbytes.py b/src/future/types/newbytes.py
index 2a337c86..c9d584a7 100644
--- a/src/future/types/newbytes.py
+++ b/src/future/types/newbytes.py
@@ -5,15 +5,19 @@
different beast to the Python 3 bytes object.
"""
-from collections import Iterable
from numbers import Integral
import string
import copy
-from future.utils import istext, isbytes, PY3, with_metaclass
+from future.utils import istext, isbytes, PY2, PY3, with_metaclass
from future.types import no, issubset
from future.types.newobject import newobject
+if PY2:
+ from collections import Iterable
+else:
+ from collections.abc import Iterable
+
_builtin_bytes = bytes
diff --git a/src/future/types/newdict.py b/src/future/types/newdict.py
index 3f3a559d..d90316cb 100644
--- a/src/future/types/newdict.py
+++ b/src/future/types/newdict.py
@@ -23,7 +23,7 @@
_builtin_dict = dict
-ver = sys.version_info[:2]
+ver = sys.version_info
class BaseNewDict(type):
@@ -38,47 +38,18 @@ class newdict(with_metaclass(BaseNewDict, _builtin_dict)):
"""
A backport of the Python 3 dict object to Py2
"""
- def items(self):
- """
- On Python 2.7+:
- D.items() -> a set-like object providing a view on D's items
- On Python 2.6:
- D.items() -> an iterator over D's items
- """
- if ver == (2, 7):
- return self.viewitems()
- elif ver == (2, 6):
- return self.iteritems()
- elif ver >= (3, 0):
- return self.items()
-
- def keys(self):
- """
- On Python 2.7+:
- D.keys() -> a set-like object providing a view on D's keys
- On Python 2.6:
- D.keys() -> an iterator over D's keys
- """
- if ver == (2, 7):
- return self.viewkeys()
- elif ver == (2, 6):
- return self.iterkeys()
- elif ver >= (3, 0):
- return self.keys()
-
- def values(self):
- """
- On Python 2.7+:
- D.values() -> a set-like object providing a view on D's values
- On Python 2.6:
- D.values() -> an iterator over D's values
- """
- if ver == (2, 7):
- return self.viewvalues()
- elif ver == (2, 6):
- return self.itervalues()
- elif ver >= (3, 0):
- return self.values()
+
+ if ver >= (3,):
+ # Inherit items, keys and values from `dict` in 3.x
+ pass
+ elif ver >= (2, 7):
+ items = dict.viewitems
+ keys = dict.viewkeys
+ values = dict.viewvalues
+ else:
+ items = dict.iteritems
+ keys = dict.iterkeys
+ values = dict.itervalues
def __new__(cls, *args, **kwargs):
"""
@@ -93,13 +64,7 @@ def __new__(cls, *args, **kwargs):
in the keyword argument list. For example: dict(one=1, two=2)
"""
- if len(args) == 0:
- return super(newdict, cls).__new__(cls)
- elif type(args[0]) == newdict:
- value = args[0]
- else:
- value = args[0]
- return super(newdict, cls).__new__(cls, value)
+ return super(newdict, cls).__new__(cls, *args)
def __native__(self):
"""
diff --git a/src/future/types/newint.py b/src/future/types/newint.py
index 705b8fa9..04a411e9 100644
--- a/src/future/types/newint.py
+++ b/src/future/types/newint.py
@@ -8,7 +8,6 @@
from __future__ import division
import struct
-import collections
from future.types.newbytes import newbytes
from future.types.newobject import newobject
@@ -17,6 +16,9 @@
if PY3:
long = int
+ from collections.abc import Iterable
+else:
+ from collections import Iterable
class BaseNewInt(type):
@@ -282,6 +284,9 @@ def __bool__(self):
"""
So subclasses can override this, Py3-style
"""
+ if PY3:
+ return super(newint, self).__bool__()
+
return super(newint, self).__nonzero__()
def __native__(self):
@@ -356,7 +361,7 @@ def from_bytes(cls, mybytes, byteorder='big', signed=False):
raise TypeError("cannot convert unicode objects to bytes")
# mybytes can also be passed as a sequence of integers on Py3.
# Test for this:
- elif isinstance(mybytes, collections.Iterable):
+ elif isinstance(mybytes, Iterable):
mybytes = newbytes(mybytes)
b = mybytes if byteorder == 'big' else mybytes[::-1]
if len(b) == 0:
diff --git a/src/future/types/newmemoryview.py b/src/future/types/newmemoryview.py
index 72c6990a..09f804dc 100644
--- a/src/future/types/newmemoryview.py
+++ b/src/future/types/newmemoryview.py
@@ -1,14 +1,16 @@
"""
A pretty lame implementation of a memoryview object for Python 2.6.
"""
-
-from collections import Iterable
from numbers import Integral
import string
-from future.utils import istext, isbytes, PY3, with_metaclass
+from future.utils import istext, isbytes, PY2, with_metaclass
from future.types import no, issubset
+if PY2:
+ from collections import Iterable
+else:
+ from collections.abc import Iterable
# class BaseNewBytes(type):
# def __instancecheck__(cls, instance):
diff --git a/src/future/types/newobject.py b/src/future/types/newobject.py
index 776d4766..31b84fc1 100644
--- a/src/future/types/newobject.py
+++ b/src/future/types/newobject.py
@@ -112,5 +112,6 @@ def __native__(self):
"""
return object(self)
+ __slots__ = []
__all__ = ['newobject']
diff --git a/src/future/types/newrange.py b/src/future/types/newrange.py
index 9173b050..6d4ebe2f 100644
--- a/src/future/types/newrange.py
+++ b/src/future/types/newrange.py
@@ -19,7 +19,12 @@
"""
from __future__ import absolute_import
-from collections import Sequence, Iterator
+from future.utils import PY2
+
+if PY2:
+ from collections import Sequence, Iterator
+else:
+ from collections.abc import Sequence, Iterator
from itertools import islice
from future.backports.misc import count # with step parameter on Py2.6
@@ -82,7 +87,7 @@ def __eq__(self, other):
return (isinstance(other, newrange) and
(self._len == 0 == other._len or
(self._start, self._step, self._len) ==
- (other._start, other._step, self._len)))
+ (other._start, other._step, other._len)))
def __len__(self):
return self._len
diff --git a/src/future/types/newstr.py b/src/future/types/newstr.py
index e6272fb9..8ca191f9 100644
--- a/src/future/types/newstr.py
+++ b/src/future/types/newstr.py
@@ -40,7 +40,6 @@
"""
-from collections import Iterable
from numbers import Number
from future.utils import PY3, istext, with_metaclass, isnewbytes
@@ -51,6 +50,9 @@
if PY3:
# We'll probably never use newstr on Py3 anyway...
unicode = str
+ from collections.abc import Iterable
+else:
+ from collections import Iterable
class BaseNewStr(type):
@@ -105,6 +107,7 @@ def __repr__(self):
"""
Without the u prefix
"""
+
value = super(newstr, self).__repr__()
# assert value[0] == u'u'
return value[1:]
@@ -290,7 +293,14 @@ def __eq__(self, other):
isinstance(other, bytes) and not isnewbytes(other)):
return super(newstr, self).__eq__(other)
else:
- return False
+ return NotImplemented
+
+ def __hash__(self):
+ if (isinstance(self, unicode) or
+ isinstance(self, bytes) and not isnewbytes(self)):
+ return super(newstr, self).__hash__()
+ else:
+ raise NotImplementedError()
def __ne__(self, other):
if (isinstance(other, unicode) or
diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py
index 906f1e46..ec1b1027 100644
--- a/src/future/utils/__init__.py
+++ b/src/future/utils/__init__.py
@@ -18,8 +18,10 @@
* types:
* text_type: unicode in Python 2, str in Python 3
- * binary_type: str in Python 2, bytes in Python 3
* string_types: basestring in Python 2, str in Python 3
+ * binary_type: str in Python 2, bytes in Python 3
+ * integer_types: (int, long) in Python 2, int in Python 3
+ * class_types: (type, types.ClassType) in Python 2, type in Python 3
* bchr(c):
Take an integer and make a 1-character byte string
@@ -55,9 +57,13 @@
import inspect
-PY3 = sys.version_info[0] == 3
+PY3 = sys.version_info[0] >= 3
+PY34_PLUS = sys.version_info[0:2] >= (3, 4)
PY35_PLUS = sys.version_info[0:2] >= (3, 5)
PY36_PLUS = sys.version_info[0:2] >= (3, 6)
+PY37_PLUS = sys.version_info[0:2] >= (3, 7)
+PY38_PLUS = sys.version_info[0:2] >= (3, 8)
+PY39_PLUS = sys.version_info[0:2] >= (3, 9)
PY2 = sys.version_info[0] == 2
PY26 = sys.version_info[0:2] == (2, 6)
PY27 = sys.version_info[0:2] == (2, 7)
@@ -405,12 +411,34 @@ def raise_(tp, value=None, tb=None):
allows re-raising exceptions with the cls value and traceback on
Python 2 and 3.
"""
- if value is not None and isinstance(tp, Exception):
- raise TypeError("instance exception may not have a separate value")
- if value is not None:
- exc = tp(value)
- else:
+ if isinstance(tp, BaseException):
+ # If the first object is an instance, the type of the exception
+ # is the class of the instance, the instance itself is the value,
+ # and the second object must be None.
+ if value is not None:
+ raise TypeError("instance exception may not have a separate value")
exc = tp
+ elif isinstance(tp, type) and not issubclass(tp, BaseException):
+ # If the first object is a class, it becomes the type of the
+ # exception.
+ raise TypeError("class must derive from BaseException, not %s" % tp.__name__)
+ else:
+ # The second object is used to determine the exception value: If it
+ # is an instance of the class, the instance becomes the exception
+ # value. If the second object is a tuple, it is used as the argument
+ # list for the class constructor; if it is None, an empty argument
+ # list is used, and any other object is treated as a single argument
+ # to the constructor. The instance so created by calling the
+ # constructor is used as the exception value.
+ if isinstance(value, tp):
+ exc = value
+ elif isinstance(value, tuple):
+ exc = tp(*value)
+ elif value is None:
+ exc = tp()
+ else:
+ exc = tp(value)
+
if exc.__traceback__ is not tb:
raise exc.with_traceback(tb)
raise exc
@@ -443,12 +471,14 @@ def raise_from(exc, cause):
e.__suppress_context__ = False
if isinstance(cause, type) and issubclass(cause, Exception):
e.__cause__ = cause()
+ e.__cause__.__traceback__ = sys.exc_info()[2]
e.__suppress_context__ = True
elif cause is None:
e.__cause__ = None
e.__suppress_context__ = True
elif isinstance(cause, BaseException):
e.__cause__ = cause
+ object.__setattr__(e.__cause__, '__traceback__', sys.exc_info()[2])
e.__suppress_context__ = True
else:
raise TypeError("exception causes must derive from BaseException")
@@ -500,9 +530,9 @@ def __next__(self):
return cls
if PY3:
- get_next = lambda x: x.next
-else:
get_next = lambda x: x.__next__
+else:
+ get_next = lambda x: x.next
def encode_filename(filename):
@@ -552,15 +582,14 @@ def isbytes(obj):
def isnewbytes(obj):
"""
- Equivalent to the result of ``isinstance(obj, newbytes)`` were
- ``__instancecheck__`` not overridden on the newbytes subclass. In
- other words, it is REALLY a newbytes instance, not a Py2 native str
+ Equivalent to the result of ``type(obj) == type(newbytes)``
+ in other words, it is REALLY a newbytes instance, not a Py2 native str
object?
+
+ Note that this does not cover subclasses of newbytes, and it is not
+ equivalent to ininstance(obj, newbytes)
"""
- # TODO: generalize this so that it works with subclasses of newbytes
- # Import is here to avoid circular imports:
- from future.types.newbytes import newbytes
- return type(obj) == newbytes
+ return type(obj).__name__ == 'newbytes'
def isint(obj):
@@ -726,16 +755,16 @@ def ensure_new_type(obj):
__all__ = ['PY2', 'PY26', 'PY3', 'PYPY',
- 'as_native_str', 'bind_method', 'bord', 'bstr',
- 'bytes_to_native_str', 'encode_filename', 'ensure_new_type',
- 'exec_', 'get_next', 'getexception', 'implements_iterator',
- 'is_new_style', 'isbytes', 'isidentifier', 'isint',
- 'isnewbytes', 'istext', 'iteritems', 'iterkeys', 'itervalues',
- 'lfilter', 'listitems', 'listvalues', 'lmap', 'lrange',
- 'lzip', 'native', 'native_bytes', 'native_str',
+ 'as_native_str', 'binary_type', 'bind_method', 'bord', 'bstr',
+ 'bytes_to_native_str', 'class_types', 'encode_filename',
+ 'ensure_new_type', 'exec_', 'get_next', 'getexception',
+ 'implements_iterator', 'integer_types', 'is_new_style', 'isbytes',
+ 'isidentifier', 'isint', 'isnewbytes', 'istext', 'iteritems',
+ 'iterkeys', 'itervalues', 'lfilter', 'listitems', 'listvalues',
+ 'lmap', 'lrange', 'lzip', 'native', 'native_bytes', 'native_str',
'native_str_to_bytes', 'old_div',
'python_2_unicode_compatible', 'raise_',
- 'raise_with_traceback', 'reraise', 'text_to_native_str',
- 'tobytes', 'viewitems', 'viewkeys', 'viewvalues',
- 'with_metaclass'
- ]
+ 'raise_with_traceback', 'reraise', 'string_types',
+ 'text_to_native_str', 'text_type', 'tobytes', 'viewitems',
+ 'viewkeys', 'viewvalues', 'with_metaclass'
+ ]
diff --git a/src/html/parser.py b/src/html/parser.py
index 541def39..e3948879 100644
--- a/src/html/parser.py
+++ b/src/html/parser.py
@@ -2,7 +2,7 @@
import sys
__future_module__ = True
-if sys.version_info[0] == 3:
+if sys.version_info[0] >= 3:
raise ImportError('Cannot import module from python-future source folder')
else:
from future.moves.html.parser import *
diff --git a/src/http/client.py b/src/http/client.py
index 7566fe4d..a6a31006 100644
--- a/src/http/client.py
+++ b/src/http/client.py
@@ -11,10 +11,6 @@
from httplib import (HTTP_PORT,
HTTPS_PORT,
- _CS_IDLE,
- _CS_REQ_STARTED,
- _CS_REQ_SENT,
-
CONTINUE,
SWITCHING_PROTOCOLS,
PROCESSING,
@@ -81,6 +77,9 @@
# These may not be available on all versions of Python 2.6.x or 2.7.x
try:
from httplib import (
+ _CS_IDLE,
+ _CS_REQ_STARTED,
+ _CS_REQ_SENT,
_MAXLINE,
_MAXHEADERS,
_is_legal_header_name,
diff --git a/src/libfuturize/fixes/__init__.py b/src/libfuturize/fixes/__init__.py
index 7de304da..0b562501 100644
--- a/src/libfuturize/fixes/__init__.py
+++ b/src/libfuturize/fixes/__init__.py
@@ -50,7 +50,7 @@
'lib2to3.fixes.fix_getcwdu',
# 'lib2to3.fixes.fix_imports', # called by libfuturize.fixes.fix_future_standard_library
# 'lib2to3.fixes.fix_imports2', # we don't handle this yet (dbm)
- 'lib2to3.fixes.fix_input',
+ # 'lib2to3.fixes.fix_input', # Called conditionally by libfuturize.fixes.fix_input
'lib2to3.fixes.fix_itertools',
'lib2to3.fixes.fix_itertools_imports',
'lib2to3.fixes.fix_filter',
@@ -86,6 +86,7 @@
'libfuturize.fixes.fix_future_builtins',
'libfuturize.fixes.fix_future_standard_library',
'libfuturize.fixes.fix_future_standard_library_urllib',
+ 'libfuturize.fixes.fix_input',
'libfuturize.fixes.fix_metaclass',
'libpasteurize.fixes.fix_newstyle',
'libfuturize.fixes.fix_object',
diff --git a/src/libfuturize/fixes/fix_add__future__imports_except_unicode_literals.py b/src/libfuturize/fixes/fix_add__future__imports_except_unicode_literals.py
index 1d419a1c..37d7feec 100644
--- a/src/libfuturize/fixes/fix_add__future__imports_except_unicode_literals.py
+++ b/src/libfuturize/fixes/fix_add__future__imports_except_unicode_literals.py
@@ -21,6 +21,6 @@ class FixAddFutureImportsExceptUnicodeLiterals(fixer_base.BaseFix):
def transform(self, node, results):
# Reverse order:
- future_import(u"print_function", node)
- future_import(u"division", node)
future_import(u"absolute_import", node)
+ future_import(u"division", node)
+ future_import(u"print_function", node)
diff --git a/src/libfuturize/fixes/fix_division_safe.py b/src/libfuturize/fixes/fix_division_safe.py
index 7b0f3cbd..65c8c1da 100644
--- a/src/libfuturize/fixes/fix_division_safe.py
+++ b/src/libfuturize/fixes/fix_division_safe.py
@@ -14,10 +14,8 @@
"""
import re
-import lib2to3.pytree as pytree
from lib2to3.fixer_util import Leaf, Node, Comma
from lib2to3 import fixer_base
-from lib2to3.fixer_util import syms, does_tree_import
from libfuturize.fixer_util import (token, future_import, touch_import_top,
wrap_in_fn_call)
@@ -33,8 +31,8 @@ def match_division(node):
const_re = re.compile('^[0-9]*[.][0-9]*$')
-def is_floaty(node, div_idx):
- return _is_floaty(node.children[0:div_idx]) or _is_floaty(node.children[div_idx+1:])
+def is_floaty(node):
+ return _is_floaty(node.prev_sibling) or _is_floaty(node.next_sibling)
def _is_floaty(expr):
@@ -50,24 +48,6 @@ def _is_floaty(expr):
return expr.children[0].value == u'float'
return False
-def find_division(node):
- for i, child in enumerate(node.children):
- if match_division(child):
- return i
- return False
-
-def clone_div_operands(node, div_idx):
- children = []
- for i, child in enumerate(node.children):
- if i == div_idx:
- children.append(Comma())
- else:
- children.append(child.clone())
-
- # Strip any leading space for the first number:
- children[0].prefix = u''
-
- return children
class FixDivisionSafe(fixer_base.BaseFix):
# BM_compatible = True
@@ -92,13 +72,33 @@ def match(self, node):
matches, we can start discarding matches after the first.
"""
if node.type == self.syms.term:
- div_idx = find_division(node)
- if div_idx is not False:
- # if expr1 or expr2 are obviously floats, we don't need to wrap in
- # old_div, as the behavior of division between any number and a float
- # should be the same in 2 or 3
- if not is_floaty(node, div_idx):
- return clone_div_operands(node, div_idx)
+ matched = False
+ skip = False
+ children = []
+ for child in node.children:
+ if skip:
+ skip = False
+ continue
+ if match_division(child) and not is_floaty(child):
+ matched = True
+
+ # Strip any leading space for the first number:
+ children[0].prefix = u''
+
+ children = [wrap_in_fn_call("old_div",
+ children + [Comma(), child.next_sibling.clone()],
+ prefix=node.prefix)]
+ skip = True
+ else:
+ children.append(child.clone())
+ if matched:
+ # In Python 2.6, `Node` does not have the fixers_applied attribute
+ # https://github.com/python/cpython/blob/8493c0cd66cfc181ac1517268a74f077e9998701/Lib/lib2to3/pytree.py#L235
+ if hasattr(Node, "fixers_applied"):
+ return Node(node.type, children, fixers_applied=node.fixers_applied)
+ else:
+ return Node(node.type, children)
+
return False
def transform(self, node, results):
@@ -106,4 +106,4 @@ def transform(self, node, results):
return
future_import(u"division", node)
touch_import_top(u'past.utils', u'old_div', node)
- return wrap_in_fn_call("old_div", results, prefix=node.prefix)
+ return results
diff --git a/src/libfuturize/fixes/fix_input.py b/src/libfuturize/fixes/fix_input.py
new file mode 100644
index 00000000..8a43882e
--- /dev/null
+++ b/src/libfuturize/fixes/fix_input.py
@@ -0,0 +1,32 @@
+"""
+Fixer for input.
+
+Does a check for `from builtins import input` before running the lib2to3 fixer.
+The fixer will not run when the input is already present.
+
+
+this:
+ a = input()
+becomes:
+ from builtins import input
+ a = eval(input())
+
+and this:
+ from builtins import input
+ a = input()
+becomes (no change):
+ from builtins import input
+ a = input()
+"""
+
+import lib2to3.fixes.fix_input
+from lib2to3.fixer_util import does_tree_import
+
+
+class FixInput(lib2to3.fixes.fix_input.FixInput):
+ def transform(self, node, results):
+
+ if does_tree_import('builtins', 'input', node):
+ return
+
+ return super(FixInput, self).transform(node, results)
diff --git a/src/libfuturize/fixes/fix_print.py b/src/libfuturize/fixes/fix_print.py
index 247b91b8..2554717c 100644
--- a/src/libfuturize/fixes/fix_print.py
+++ b/src/libfuturize/fixes/fix_print.py
@@ -57,6 +57,16 @@ def transform(self, node, results):
if args and args[-1] == Comma():
args = args[:-1]
end = " "
+
+ # try to determine if the string ends in a non-space whitespace character, in which
+ # case there should be no space at the end of the conversion
+ string_leaves = [leaf for leaf in args[-1].leaves() if leaf.type == token.STRING]
+ if (
+ string_leaves
+ and string_leaves[-1].value[0] != "r" # "raw" string
+ and string_leaves[-1].value[-3:-1] in (r"\t", r"\n", r"\r")
+ ):
+ end = ""
if args and args[0] == pytree.Leaf(token.RIGHTSHIFT, u">>"):
assert len(args) >= 2
file = args[1].clone()
diff --git a/src/libfuturize/fixes/fix_raise.py b/src/libfuturize/fixes/fix_raise.py
index 3e8323de..d113401c 100644
--- a/src/libfuturize/fixes/fix_raise.py
+++ b/src/libfuturize/fixes/fix_raise.py
@@ -4,33 +4,39 @@
raise -> raise
raise E -> raise E
-raise E, V -> raise E(V)
+raise E, 5 -> raise E(5)
+raise E, 5, T -> raise E(5).with_traceback(T)
+raise E, None, T -> raise E.with_traceback(T)
-raise (((E, E'), E''), E'''), V -> raise E(V)
+raise (((E, E'), E''), E'''), 5 -> raise E(5)
+raise "foo", V, T -> warns about string exceptions
+raise E, (V1, V2) -> raise E(V1, V2)
+raise E, (V1, V2), T -> raise E(V1, V2).with_traceback(T)
-CAVEATS:
-1) "raise E, V" will be incorrectly translated if V is an exception
- instance. The correct Python 3 idiom is
- raise E from V
+CAVEATS:
+1) "raise E, V, T" cannot be translated safely in general. If V
+ is not a tuple or a (number, string, None) literal, then:
- but since we can't detect instance-hood by syntax alone and since
- any client code would have to be changed as well, we don't automate
- this.
+ raise E, V, T -> from future.utils import raise_
+ raise_(E, V, T)
"""
-# Author: Collin Winter, Armin Ronacher
+# Author: Collin Winter, Armin Ronacher, Mark Huang
# Local imports
from lib2to3 import pytree, fixer_base
from lib2to3.pgen2 import token
-from lib2to3.fixer_util import Name, Call, is_tuple
+from lib2to3.fixer_util import Name, Call, is_tuple, Comma, Attr, ArgList
+
+from libfuturize.fixer_util import touch_import_top
+
class FixRaise(fixer_base.BaseFix):
BM_compatible = True
PATTERN = """
- raise_stmt< 'raise' exc=any [',' val=any] >
+ raise_stmt< 'raise' exc=any [',' val=any [',' tb=any]] >
"""
def transform(self, node, results):
@@ -55,19 +61,47 @@ def transform(self, node, results):
exc = exc.children[1].children[0].clone()
exc.prefix = u" "
- if "val" not in results:
- # One-argument raise
- new = pytree.Node(syms.raise_stmt, [Name(u"raise"), exc])
- new.prefix = node.prefix
- return new
-
- val = results["val"].clone()
- if is_tuple(val):
- args = [c.clone() for c in val.children[1:-1]]
+ if "tb" in results:
+ tb = results["tb"].clone()
+ else:
+ tb = None
+
+ if "val" in results:
+ val = results["val"].clone()
+ if is_tuple(val):
+ # Assume that exc is a subclass of Exception and call exc(*val).
+ args = [c.clone() for c in val.children[1:-1]]
+ exc = Call(exc, args)
+ elif val.type in (token.NUMBER, token.STRING):
+ # Handle numeric and string literals specially, e.g.
+ # "raise Exception, 5" -> "raise Exception(5)".
+ val.prefix = u""
+ exc = Call(exc, [val])
+ elif val.type == token.NAME and val.value == u"None":
+ # Handle None specially, e.g.
+ # "raise Exception, None" -> "raise Exception".
+ pass
+ else:
+ # val is some other expression. If val evaluates to an instance
+ # of exc, it should just be raised. If val evaluates to None,
+ # a default instance of exc should be raised (as above). If val
+ # evaluates to a tuple, exc(*val) should be called (as
+ # above). Otherwise, exc(val) should be called. We can only
+ # tell what to do at runtime, so defer to future.utils.raise_(),
+ # which handles all of these cases.
+ touch_import_top(u"future.utils", u"raise_", node)
+ exc.prefix = u""
+ args = [exc, Comma(), val]
+ if tb is not None:
+ args += [Comma(), tb]
+ return Call(Name(u"raise_"), args, prefix=node.prefix)
+
+ if tb is not None:
+ tb.prefix = ""
+ exc_list = Attr(exc, Name('with_traceback')) + [ArgList([tb])]
else:
- val.prefix = u""
- args = [val]
+ exc_list = [exc]
return pytree.Node(syms.raise_stmt,
- [Name(u"raise"), Call(exc, args)],
+ [Name(u"raise")] + exc_list,
prefix=node.prefix)
diff --git a/src/libpasteurize/fixes/fix_add_all__future__imports.py b/src/libpasteurize/fixes/fix_add_all__future__imports.py
index 37897946..a151f9f1 100644
--- a/src/libpasteurize/fixes/fix_add_all__future__imports.py
+++ b/src/libpasteurize/fixes/fix_add_all__future__imports.py
@@ -18,7 +18,7 @@ class FixAddAllFutureImports(fixer_base.BaseFix):
run_order = 1
def transform(self, node, results):
- future_import(u"unicode_literals", node)
- future_import(u"print_function", node)
- future_import(u"division", node)
future_import(u"absolute_import", node)
+ future_import(u"division", node)
+ future_import(u"print_function", node)
+ future_import(u"unicode_literals", node)
diff --git a/src/past/__init__.py b/src/past/__init__.py
index 3b5d9db1..14713039 100644
--- a/src/past/__init__.py
+++ b/src/past/__init__.py
@@ -61,7 +61,7 @@
$ python3
- >>> from past import autotranslate
+ >>> from past.translation import autotranslate
>>> authotranslate('mypy2module')
>>> import mypy2module
@@ -74,18 +74,16 @@
Credits
-------
-:Author: Ed Schofield
+:Author: Ed Schofield, Jordan M. Adler, et al
:Sponsor: Python Charmers Pty Ltd, Australia: http://pythoncharmers.com
Licensing
---------
-Copyright 2013-2018 Python Charmers Pty Ltd, Australia.
+Copyright 2013-2019 Python Charmers Pty Ltd, Australia.
The software is distributed under an MIT licence. See LICENSE.txt.
"""
-
-from past.translation import install_hooks as autotranslate
from future import __version__, __copyright__, __license__
__title__ = 'past'
diff --git a/src/past/builtins/misc.py b/src/past/builtins/misc.py
index 06fbb92d..3600695c 100644
--- a/src/past/builtins/misc.py
+++ b/src/past/builtins/misc.py
@@ -1,13 +1,19 @@
from __future__ import unicode_literals
-import sys
+
import inspect
-from collections import Mapping
+import math
+import numbers
-from future.utils import PY3, exec_
+from future.utils import PY2, PY3, exec_
+if PY2:
+ from collections import Mapping
+else:
+ from collections.abc import Mapping
if PY3:
import builtins
+ from collections.abc import Mapping
def apply(f, *args, **kw):
return f(*args, **kw)
@@ -25,8 +31,67 @@ def cmp(x, y):
cmp(x, y) -> integer
Return negative if xy.
+ Python2 had looser comparison allowing cmp None and non Numerical types and collections.
+ Try to match the old behavior
"""
- return (x > y) - (x < y)
+ if isinstance(x, set) and isinstance(y, set):
+ raise TypeError('cannot compare sets using cmp()',)
+ try:
+ if isinstance(x, numbers.Number) and math.isnan(x):
+ if not isinstance(y, numbers.Number):
+ raise TypeError('cannot compare float("nan"), {type_y} with cmp'.format(type_y=type(y)))
+ if isinstance(y, int):
+ return 1
+ else:
+ return -1
+ if isinstance(y, numbers.Number) and math.isnan(y):
+ if not isinstance(x, numbers.Number):
+ raise TypeError('cannot compare {type_x}, float("nan") with cmp'.format(type_x=type(x)))
+ if isinstance(x, int):
+ return -1
+ else:
+ return 1
+ return (x > y) - (x < y)
+ except TypeError:
+ if x == y:
+ return 0
+ type_order = [
+ type(None),
+ numbers.Number,
+ dict, list,
+ set,
+ (str, bytes),
+ ]
+ x_type_index = y_type_index = None
+ for i, type_match in enumerate(type_order):
+ if isinstance(x, type_match):
+ x_type_index = i
+ if isinstance(y, type_match):
+ y_type_index = i
+ if cmp(x_type_index, y_type_index) == 0:
+ if isinstance(x, bytes) and isinstance(y, str):
+ return cmp(x.decode('ascii'), y)
+ if isinstance(y, bytes) and isinstance(x, str):
+ return cmp(x, y.decode('ascii'))
+ elif isinstance(x, list):
+ # if both arguments are lists take the comparison of the first non equal value
+ for x_elem, y_elem in zip(x, y):
+ elem_cmp_val = cmp(x_elem, y_elem)
+ if elem_cmp_val != 0:
+ return elem_cmp_val
+ # if all elements are equal, return equal/0
+ return 0
+ elif isinstance(x, dict):
+ if len(x) != len(y):
+ return cmp(len(x), len(y))
+ else:
+ x_key = min(a for a in x if a not in y or x[a] != y[a])
+ y_key = min(b for b in y if b not in x or x[b] != y[b])
+ if x_key != y_key:
+ return cmp(x_key, y_key)
+ else:
+ return cmp(x[x_key], y[y_key])
+ return cmp(x_type_index, y_type_index)
from sys import intern
@@ -38,12 +103,19 @@ def oct(number):
return '0' + builtins.oct(number)[2:]
raw_input = input
- from imp import reload
+
+ try:
+ from importlib import reload
+ except ImportError:
+ # for python2, python3 <= 3.4
+ from imp import reload
+
unicode = str
unichr = chr
xrange = range
else:
import __builtin__
+ from collections import Mapping
apply = __builtin__.apply
chr = __builtin__.chr
cmp = __builtin__.cmp
@@ -76,8 +148,8 @@ def execfile(filename, myglobals=None, mylocals=None):
raise TypeError('globals must be a mapping')
if not isinstance(mylocals, Mapping):
raise TypeError('locals must be a mapping')
- with open(filename, "rbU") as fin:
- source = fin.read()
+ with open(filename, "rb") as fin:
+ source = fin.read()
code = compile(source, filename, "exec")
exec_(code, myglobals, mylocals)
diff --git a/src/past/builtins/noniterators.py b/src/past/builtins/noniterators.py
index 5826b97c..183ffffd 100644
--- a/src/past/builtins/noniterators.py
+++ b/src/past/builtins/noniterators.py
@@ -72,7 +72,7 @@ def oldmap(func, *iterables):
>>> oldmap(None, range(4))
[0, 1, 2, 3]
- More test cases are in past.tests.test_builtins.
+ More test cases are in test_past.test_builtins.
"""
zipped = itertools.zip_longest(*iterables)
l = list(zipped)
diff --git a/src/past/tests/__init__.py b/src/past/tests/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/past/translation/__init__.py b/src/past/translation/__init__.py
index c7ae2b7a..7c678866 100644
--- a/src/past/translation/__init__.py
+++ b/src/past/translation/__init__.py
@@ -16,7 +16,7 @@
Once your Py2 package is installed in the usual module search path, the import
hook is invoked as follows:
- >>> from past import autotranslate
+ >>> from past.translation import autotranslate
>>> autotranslate('mypackagename')
Or:
@@ -219,22 +219,9 @@ def detect_python2(source, pathname):
if source != str(tree)[:-1]: # remove added newline
# The above fixers made changes, so we conclude it's Python 2 code
logger.debug('Detected Python 2 code: {0}'.format(pathname))
- with open('/tmp/original_code.py', 'w') as f:
- f.write('### Original code (detected as py2): %s\n%s' %
- (pathname, source))
- with open('/tmp/py2_detection_code.py', 'w') as f:
- f.write('### Code after running py3 detection (from %s)\n%s' %
- (pathname, str(tree)[:-1]))
return True
else:
logger.debug('Detected Python 3 code: {0}'.format(pathname))
- with open('/tmp/original_code.py', 'w') as f:
- f.write('### Original code (detected as py3): %s\n%s' %
- (pathname, source))
- try:
- os.remove('/tmp/futurize_code.py')
- except OSError:
- pass
return False
@@ -395,9 +382,6 @@ def load_module(self, fullname):
if detect_python2(source, self.pathname):
source = self.transform(source)
- with open('/tmp/futurized_code.py', 'w') as f:
- f.write('### Futurized code (from %s)\n%s' %
- (self.pathname, source))
code = compile(source, self.pathname, 'exec')
@@ -432,7 +416,7 @@ def install_hooks(include_paths=(), exclude_paths=()):
_hook.include(include_paths)
_hook.exclude(exclude_paths)
# _hook.debug = debug
- enable = sys.version_info[0] >= 3 # enabled for all 3.x
+ enable = sys.version_info[0] >= 3 # enabled for all 3.x+
if enable and _hook not in sys.meta_path:
sys.meta_path.insert(0, _hook) # insert at beginning. This could be made a parameter
@@ -495,3 +479,7 @@ def __enter__(self):
def __exit__(self, *args):
if self.hooks_were_installed:
install_hooks()
+
+
+# alias
+autotranslate = install_hooks
diff --git a/src/past/types/basestring.py b/src/past/types/basestring.py
index 1cab22f6..9c21715a 100644
--- a/src/past/types/basestring.py
+++ b/src/past/types/basestring.py
@@ -25,9 +25,8 @@ class BaseBaseString(type):
def __instancecheck__(cls, instance):
return isinstance(instance, (bytes, str))
- def __subclasshook__(cls, thing):
- # TODO: What should go here?
- raise NotImplemented
+ def __subclasscheck__(cls, subclass):
+ return super(BaseBaseString, cls).__subclasscheck__(subclass) or issubclass(subclass, (bytes, str))
class basestring(with_metaclass(BaseBaseString)):
diff --git a/src/past/types/oldstr.py b/src/past/types/oldstr.py
index 7768d328..5a0e3789 100644
--- a/src/past/types/oldstr.py
+++ b/src/past/types/oldstr.py
@@ -2,11 +2,14 @@
Pure-Python implementation of a Python 2-like str object for Python 3.
"""
-from collections import Iterable
from numbers import Integral
from past.utils import PY2, with_metaclass
+if PY2:
+ from collections import Iterable
+else:
+ from collections.abc import Iterable
_builtin_bytes = bytes
@@ -17,7 +20,7 @@ def __instancecheck__(cls, instance):
def unescape(s):
- """
+ r"""
Interprets strings with escape sequences
Example:
diff --git a/src/past/utils/__init__.py b/src/past/utils/__init__.py
index c6606d0b..f6b2642d 100644
--- a/src/past/utils/__init__.py
+++ b/src/past/utils/__init__.py
@@ -16,7 +16,7 @@
import sys
import numbers
-PY3 = sys.version_info[0] == 3
+PY3 = sys.version_info[0] >= 3
PY2 = sys.version_info[0] == 2
PYPY = hasattr(sys, 'pypy_translation_info')
diff --git a/tests/test_future/test_backports.py b/tests/test_future/test_backports.py
index 21ebb202..63b1afea 100644
--- a/tests/test_future/test_backports.py
+++ b/tests/test_future/test_backports.py
@@ -10,7 +10,6 @@
import inspect
import pickle
from random import randrange, shuffle
-from collections import Mapping, MutableMapping
from future.backports.misc import (count,
_count,
@@ -18,9 +17,14 @@
Counter,
ChainMap,
_count_elements)
-from future.utils import PY26
+from future.utils import PY2, PY26
from future.tests.base import unittest, skip26, expectedFailurePY27
+if PY2:
+ from collections import Mapping, MutableMapping
+else:
+ from collections.abc import Mapping, MutableMapping
+
class CountTest(unittest.TestCase):
"""Test the count function."""
@@ -83,7 +87,8 @@ def test_basics(self):
d['b'] = 20
d['c'] = 30
self.assertEqual(d.maps, [{'b':20, 'c':30}, {'a':1, 'b':2}]) # check internal state
- self.assertEqual(d.items(), dict(a=1, b=20, c=30).items()) # check items/iter/getitem
+ self.assertEqual(sorted(d.items()),
+ sorted(dict(a=1, b=20, c=30).items())) # check items/iter/getitem
self.assertEqual(len(d), 3) # check len
for key in 'abc': # check contains
self.assertIn(key, d)
@@ -92,7 +97,8 @@ def test_basics(self):
del d['b'] # unmask a value
self.assertEqual(d.maps, [{'c':30}, {'a':1, 'b':2}]) # check internal state
- self.assertEqual(d.items(), dict(a=1, b=2, c=30).items()) # check items/iter/getitem
+ self.assertEqual(sorted(d.items()),
+ sorted(dict(a=1, b=2, c=30).items())) # check items/iter/getitem
self.assertEqual(len(d), 3) # check len
for key in 'abc': # check contains
self.assertIn(key, d)
diff --git a/tests/test_future/test_builtins.py b/tests/test_future/test_builtins.py
index 05d597a5..3921a608 100644
--- a/tests/test_future/test_builtins.py
+++ b/tests/test_future/test_builtins.py
@@ -6,7 +6,7 @@
from __future__ import absolute_import, division, print_function, unicode_literals
from future.builtins import (bytes, dict, int, range, round, str, super,
ascii, chr, hex, input, next, oct, open, pow,
- filter, map, zip)
+ filter, map, zip, min, max)
from future.utils import PY3, exec_, native_str, implements_iterator
from future.tests.base import (unittest, skip26, expectedFailurePY2,
@@ -146,7 +146,6 @@ def test_round(self):
self.assertTrue(isinstance(round(123.5, 0), float))
self.assertTrue(isinstance(round(123.5), Integral))
- @unittest.skip('negative ndigits not implemented yet')
def test_round_negative_ndigits(self):
self.assertEqual(round(10.1350, 0), 10.0)
self.assertEqual(round(10.1350, -1), 10.0)
@@ -1099,6 +1098,14 @@ def test_max(self):
self.assertEqual(max(data, key=f),
sorted(reversed(data), key=f)[-1])
+ self.assertEqual(max([], default=5), 5)
+ with self.assertRaises(TypeError):
+ max(None, default=5)
+ with self.assertRaises(TypeError):
+ max(1, 2, default=0)
+ self.assertEqual(max([], default=0), 0)
+ self.assertIs(max([], default=None), None)
+
def test_min(self):
self.assertEqual(min('123123'), '1')
self.assertEqual(min(1, 2, 3), 1)
@@ -1116,6 +1123,7 @@ class BadSeq:
def __getitem__(self, index):
raise ValueError
self.assertRaises(ValueError, min, BadSeq())
+ self.assertEqual(max(x for x in [5, 4, 3]), 5)
for stmt in (
"min(key=int)", # no args
@@ -1140,6 +1148,16 @@ def __getitem__(self, index):
f = keys.__getitem__
self.assertEqual(min(data, key=f),
sorted(data, key=f)[0])
+ self.assertEqual(min([], default=5), 5)
+ self.assertEqual(min([], default=0), 0)
+ self.assertIs(min([], default=None), None)
+ with self.assertRaises(TypeError):
+ max(None, default=5)
+ with self.assertRaises(TypeError):
+ max(1, 2, default=0)
+
+ # Test iterables that can only be looped once #510
+ self.assertEqual(min(x for x in [5]), 5)
def test_next(self):
it = iter(range(2))
diff --git a/tests/test_future/test_bytes.py b/tests/test_future/test_bytes.py
index bb90a71c..b9b157d8 100644
--- a/tests/test_future/test_bytes.py
+++ b/tests/test_future/test_bytes.py
@@ -701,12 +701,15 @@ def test_multiple_inheritance(self):
"""
Issue #96 (for newbytes instead of newobject)
"""
- import collections
+ if utils.PY2:
+ from collections import Container
+ else:
+ from collections.abc import Container
class Base(bytes):
pass
- class Foo(Base, collections.Container):
+ class Foo(Base, Container):
def __contains__(self, item):
return False
diff --git a/tests/test_future/test_count.py b/tests/test_future/test_count.py
new file mode 100644
index 00000000..cc849bd5
--- /dev/null
+++ b/tests/test_future/test_count.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+"""
+Tests for the backported class:`range` class.
+"""
+from itertools import count as it_count
+
+from future.backports.misc import count
+from future.tests.base import unittest, skip26
+
+
+class CountTest(unittest.TestCase):
+
+ """Test the count function."""
+
+ def _test_count_func(self, func):
+ self.assertEqual(next(func(1)), 1)
+ self.assertEqual(next(func(start=1)), 1)
+
+ c = func()
+ self.assertEqual(next(c), 0)
+ self.assertEqual(next(c), 1)
+ self.assertEqual(next(c), 2)
+ c = func(1, 1)
+ self.assertEqual(next(c), 1)
+ self.assertEqual(next(c), 2)
+ c = func(step=1)
+ self.assertEqual(next(c), 0)
+ self.assertEqual(next(c), 1)
+ c = func(start=1, step=1)
+ self.assertEqual(next(c), 1)
+ self.assertEqual(next(c), 2)
+
+ c = func(-1)
+ self.assertEqual(next(c), -1)
+ self.assertEqual(next(c), 0)
+ self.assertEqual(next(c), 1)
+ c = func(1, -1)
+ self.assertEqual(next(c), 1)
+ self.assertEqual(next(c), 0)
+ self.assertEqual(next(c), -1)
+ c = func(-1, -1)
+ self.assertEqual(next(c), -1)
+ self.assertEqual(next(c), -2)
+ self.assertEqual(next(c), -3)
+
+ def test_count(self):
+ """Test the count function."""
+ self._test_count_func(count)
+
+ @skip26
+ def test_own_count(self):
+ """Test own count implementation."""
+ self._test_count_func(it_count)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_future/test_dict.py b/tests/test_future/test_dict.py
index 68856828..ff9dd4ab 100644
--- a/tests/test_future/test_dict.py
+++ b/tests/test_future/test_dict.py
@@ -111,12 +111,15 @@ def test_multiple_inheritance(self):
"""
Issue #96 (for newdict instead of newobject)
"""
- import collections
+ if utils.PY2:
+ from collections import Container
+ else:
+ from collections.abc import Container
class Base(dict):
pass
- class Foo(Base, collections.Container):
+ class Foo(Base, Container):
def __contains__(self, item):
return False
diff --git a/tests/test_future/test_email_generation.py b/tests/test_future/test_email_generation.py
new file mode 100644
index 00000000..10e61138
--- /dev/null
+++ b/tests/test_future/test_email_generation.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+"""Tests for email generation."""
+
+from __future__ import unicode_literals
+
+from future.backports.email.mime.multipart import MIMEMultipart
+from future.backports.email.mime.text import MIMEText
+from future.backports.email.utils import formatdate
+from future.tests.base import unittest
+
+
+class EmailGenerationTests(unittest.TestCase):
+ def test_email_custom_header_can_contain_unicode(self):
+ msg = MIMEMultipart()
+ alternative = MIMEMultipart('alternative')
+ alternative.attach(MIMEText('Plain content with Únicødê', _subtype='plain', _charset='utf-8'))
+ alternative.attach(MIMEText('HTML content with Únicødê', _subtype='html', _charset='utf-8'))
+ msg.attach(alternative)
+
+ msg['Subject'] = 'Subject with Únicødê'
+ msg['From'] = 'sender@test.com'
+ msg['To'] = 'recipient@test.com'
+ msg['Date'] = formatdate(None, localtime=True)
+ msg['Message-ID'] = 'anIdWithÚnicødêForThisEmail'
+
+ msg_lines = msg.as_string().split('\n')
+ self.assertEqual(msg_lines[2], 'Subject: =?utf-8?b?U3ViamVjdCB3aXRoIMOabmljw7hkw6o=?=')
+ self.assertEqual(msg_lines[6], 'Message-ID: =?utf-8?b?YW5JZFdpdGjDmm5pY8O4ZMOqRm9yVGhpc0VtYWls?=')
+ self.assertEqual(msg_lines[17], 'UGxhaW4gY29udGVudCB3aXRoIMOabmljw7hkw6o=')
+ self.assertEqual(msg_lines[24], 'SFRNTCBjb250ZW50IHdpdGggw5puaWPDuGTDqg==')
diff --git a/tests/test_future/test_email_multipart.py b/tests/test_future/test_email_multipart.py
new file mode 100644
index 00000000..cbd93b89
--- /dev/null
+++ b/tests/test_future/test_email_multipart.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+"""Tests for multipart emails."""
+
+from future.tests.base import unittest
+import future.backports.email as email
+import future.backports.email.mime.multipart
+from future.builtins import list
+
+class EmailMultiPartTests(unittest.TestCase):
+ """Tests for handling multipart email Messages."""
+
+ def test_multipart_serialize_without_boundary(self):
+ """Tests that serializing an empty multipart email does not fail."""
+ multipart_message = email.mime.multipart.MIMEMultipart()
+ self.assertIsNot(multipart_message.as_string(), None)
+
+ def test_multipart_set_boundary_does_not_change_header_type(self):
+ """
+ Tests that Message.set_boundary() does not cause Python2 errors.
+
+ In particular, tests that set_boundary does not cause the type of the
+ message headers list to be changed from the future built-in list.
+ """
+ multipart_message = email.mime.multipart.MIMEMultipart()
+ headers_type = type(multipart_message._headers)
+ self.assertEqual(headers_type, type(list()))
+
+ boundary = '===============6387699881409002085=='
+ multipart_message.set_boundary(boundary)
+ headers_type = type(multipart_message._headers)
+ self.assertEqual(headers_type, type(list()))
diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py
index 5549a010..c3696a54 100644
--- a/tests/test_future/test_futurize.py
+++ b/tests/test_future/test_futurize.py
@@ -436,6 +436,28 @@ def test_import_builtins(self):
"""
self.convert_check(before, after, ignore_imports=False, run=False)
+ @expectedFailurePY26
+ def test_input_without_import(self):
+ before = """
+ a = input()
+ """
+ after = """
+ from builtins import input
+ a = eval(input())
+ """
+ self.convert_check(before, after, ignore_imports=False, run=False)
+
+ def test_input_with_import(self):
+ before = """
+ from builtins import input
+ a = input()
+ """
+ after = """
+ from builtins import input
+ a = input()
+ """
+ self.convert_check(before, after, ignore_imports=False, run=False)
+
def test_xrange(self):
"""
The ``from builtins import range`` line was being added to the
@@ -1208,6 +1230,10 @@ def total_count(self):
val = float(obj.numer) / obj.denom * 1e-9
obj.numer * obj.denom / val
obj.total_count() * val / 100
+ obj.numer / obj.denom * 1e-9
+ obj.numer / (obj.denom * 1e-9)
+ obj.numer / obj.denom / 1e-9
+ obj.numer / (obj.denom / 1e-9)
original_numer = 1
original_denom = 50
100 * abs(obj.numer - original_numer) / float(max(obj.denom, original_denom))
@@ -1237,13 +1263,17 @@ def total_count(self):
b = 1 + foo[old_div(len(foo) * 3, 4)]
assert a == 51
assert b == 76
- r = old_div(random.randint(0, 1000) * 1.0, 1000)
+ r = random.randint(0, 1000) * 1.0 / 1000
output = { "SUCCESS": 5, "TOTAL": 10 }
old_div(output["SUCCESS"] * 100, output["TOTAL"])
obj = fraction(1, 50)
val = float(obj.numer) / obj.denom * 1e-9
old_div(obj.numer * obj.denom, val)
old_div(obj.total_count() * val, 100)
+ old_div(obj.numer, obj.denom) * 1e-9
+ old_div(obj.numer, (obj.denom * 1e-9))
+ old_div(old_div(obj.numer, obj.denom), 1e-9)
+ old_div(obj.numer, (old_div(obj.denom, 1e-9)))
original_numer = 1
original_denom = 50
100 * abs(obj.numer - original_numer) / float(max(obj.denom, original_denom))
@@ -1361,6 +1391,7 @@ def test_open(self):
"""
self.convert_check(before, after, conservative=True)
+
class TestFuturizeAllImports(CodeHandler):
"""
Tests "futurize --all-imports".
@@ -1378,14 +1409,14 @@ def test_all_imports(self):
print('Hello')
"""
after = """
- from __future__ import unicode_literals
- from __future__ import print_function
- from __future__ import division
from __future__ import absolute_import
+ from __future__ import division
+ from __future__ import print_function
+ from __future__ import unicode_literals
from future import standard_library
standard_library.install_aliases()
- from builtins import range
from builtins import *
+ from builtins import range
import math
import os
l = list(range(10))
@@ -1395,7 +1426,7 @@ def test_all_imports(self):
pass
print('Hello')
"""
- self.convert_check(before, after, all_imports=True)
+ self.convert_check(before, after, all_imports=True, ignore_imports=False)
if __name__ == '__main__':
diff --git a/tests/test_future/test_int.py b/tests/test_future/test_int.py
index 9acbd23a..573a0d53 100644
--- a/tests/test_future/test_int.py
+++ b/tests/test_future/test_int.py
@@ -344,7 +344,7 @@ def __int__(self):
class Foo3(int):
def __int__(self):
- return self
+ return self.real
class Foo4(int):
def __int__(self):
@@ -1069,12 +1069,12 @@ def test_multiple_inheritance(self):
"""
Issue #96 (for newint instead of newobject)
"""
- import collections
+ import collections.abc
class Base(int):
pass
- class Foo(Base, collections.Container):
+ class Foo(Base, collections.abc.Container):
def __add__(self, other):
return 0
diff --git a/tests/test_future/test_libfuturize_fixers.py b/tests/test_future/test_libfuturize_fixers.py
index 8c4a9a3e..2146d1f2 100644
--- a/tests/test_future/test_libfuturize_fixers.py
+++ b/tests/test_future/test_libfuturize_fixers.py
@@ -307,6 +307,37 @@ def test_trailing_comma_3(self):
a = """print(1, end=' ')"""
self.check(b, a)
+ def test_trailing_comma_4(self):
+ b = """print "a ","""
+ a = """print("a ", end=' ')"""
+ self.check(b, a)
+
+ def test_trailing_comma_5(self):
+ b = r"""print "b\t","""
+ a = r"""print("b\t", end='')"""
+ self.check(b, a)
+
+ def test_trailing_comma_6(self):
+ b = r"""print "c\n","""
+ a = r"""print("c\n", end='')"""
+ self.check(b, a)
+
+ def test_trailing_comma_7(self):
+ b = r"""print "d\r","""
+ a = r"""print("d\r", end='')"""
+ self.check(b, a)
+
+ def test_trailing_comma_8(self):
+ b = r"""print "%s\n" % (1,),"""
+ a = r"""print("%s\n" % (1,), end='')"""
+ self.check(b, a)
+
+
+ def test_trailing_comma_9(self):
+ b = r"""print r"e\n","""
+ a = r"""print(r"e\n", end=' ')"""
+ self.check(b, a)
+
# >> stuff
def test_vargs_without_trailing_comma(self):
@@ -702,133 +733,154 @@ def test_with_future_print_function(self):
# except (Exception, SystemExit):
# pass"""
# self.unchanged(s)
-#
-# class Test_raise(FixerTestCase):
-# fixer = "raise"
-#
-# def test_basic(self):
-# b = """raise Exception, 5"""
-# a = """raise Exception(5)"""
-# self.check(b, a)
-#
-# def test_prefix_preservation(self):
-# b = """raise Exception,5"""
-# a = """raise Exception(5)"""
-# self.check(b, a)
-#
-# b = """raise Exception, 5"""
-# a = """raise Exception(5)"""
-# self.check(b, a)
-#
-# def test_with_comments(self):
-# b = """raise Exception, 5 # foo"""
-# a = """raise Exception(5) # foo"""
-# self.check(b, a)
-#
-# b = """raise E, (5, 6) % (a, b) # foo"""
-# a = """raise E((5, 6) % (a, b)) # foo"""
-# self.check(b, a)
-#
-# b = """def foo():
-# raise Exception, 5, 6 # foo"""
-# a = """def foo():
-# raise Exception(5).with_traceback(6) # foo"""
-# self.check(b, a)
-#
-# def test_None_value(self):
-# b = """raise Exception(5), None, tb"""
-# a = """raise Exception(5).with_traceback(tb)"""
-# self.check(b, a)
-#
-# def test_tuple_value(self):
-# b = """raise Exception, (5, 6, 7)"""
-# a = """raise Exception(5, 6, 7)"""
-# self.check(b, a)
-#
-# def test_tuple_detection(self):
-# b = """raise E, (5, 6) % (a, b)"""
-# a = """raise E((5, 6) % (a, b))"""
-# self.check(b, a)
-#
-# def test_tuple_exc_1(self):
-# b = """raise (((E1, E2), E3), E4), V"""
-# a = """raise E1(V)"""
-# self.check(b, a)
-#
-# def test_tuple_exc_2(self):
-# b = """raise (E1, (E2, E3), E4), V"""
-# a = """raise E1(V)"""
-# self.check(b, a)
-#
-# # These should produce a warning
-#
-# def test_string_exc(self):
-# s = """raise 'foo'"""
-# self.warns_unchanged(s, "Python 3 does not support string exceptions")
-#
-# def test_string_exc_val(self):
-# s = """raise "foo", 5"""
-# self.warns_unchanged(s, "Python 3 does not support string exceptions")
-#
-# def test_string_exc_val_tb(self):
-# s = """raise "foo", 5, 6"""
-# self.warns_unchanged(s, "Python 3 does not support string exceptions")
-#
-# # These should result in traceback-assignment
-#
-# def test_tb_1(self):
-# b = """def foo():
-# raise Exception, 5, 6"""
-# a = """def foo():
-# raise Exception(5).with_traceback(6)"""
-# self.check(b, a)
-#
-# def test_tb_2(self):
-# b = """def foo():
-# a = 5
-# raise Exception, 5, 6
-# b = 6"""
-# a = """def foo():
-# a = 5
-# raise Exception(5).with_traceback(6)
-# b = 6"""
-# self.check(b, a)
-#
-# def test_tb_3(self):
-# b = """def foo():
-# raise Exception,5,6"""
-# a = """def foo():
-# raise Exception(5).with_traceback(6)"""
-# self.check(b, a)
-#
-# def test_tb_4(self):
-# b = """def foo():
-# a = 5
-# raise Exception,5,6
-# b = 6"""
-# a = """def foo():
-# a = 5
-# raise Exception(5).with_traceback(6)
-# b = 6"""
-# self.check(b, a)
-#
-# def test_tb_5(self):
-# b = """def foo():
-# raise Exception, (5, 6, 7), 6"""
-# a = """def foo():
-# raise Exception(5, 6, 7).with_traceback(6)"""
-# self.check(b, a)
-#
-# def test_tb_6(self):
-# b = """def foo():
-# a = 5
-# raise Exception, (5, 6, 7), 6
-# b = 6"""
-# a = """def foo():
-# a = 5
-# raise Exception(5, 6, 7).with_traceback(6)
-# b = 6"""
-# self.check(b, a)
+
+class Test_raise(FixerTestCase):
+ fixer = "raise"
+
+ def test_basic(self):
+ b = """raise Exception, 5"""
+ a = """raise Exception(5)"""
+ self.check(b, a)
+
+ def test_prefix_preservation(self):
+ b = """raise Exception,5"""
+ a = """raise Exception(5)"""
+ self.check(b, a)
+
+ b = """raise Exception, 5"""
+ a = """raise Exception(5)"""
+ self.check(b, a)
+
+ def test_with_comments(self):
+ b = """raise Exception, 5 # foo"""
+ a = """raise Exception(5) # foo"""
+ self.check(b, a)
+
+ b = """def foo():
+ raise Exception, 5, 6 # foo"""
+ a = """def foo():
+ raise Exception(5).with_traceback(6) # foo"""
+ self.check(b, a)
+
+ def test_None_value(self):
+ b = """raise Exception(5), None, tb"""
+ a = """raise Exception(5).with_traceback(tb)"""
+ self.check(b, a)
+
+ def test_tuple_value(self):
+ b = """raise Exception, (5, 6, 7)"""
+ a = """raise Exception(5, 6, 7)"""
+ self.check(b, a)
+
+ def test_tuple_exc_1(self):
+ b = """raise (((E1, E2), E3), E4), 5"""
+ a = """raise E1(5)"""
+ self.check(b, a)
+
+ def test_tuple_exc_2(self):
+ b = """raise (E1, (E2, E3), E4), 5"""
+ a = """raise E1(5)"""
+ self.check(b, a)
+
+ def test_unknown_value(self):
+ b = """
+ raise E, V"""
+ a = """
+ from future.utils import raise_
+ raise_(E, V)"""
+ self.check(b, a)
+
+ def test_unknown_value_with_traceback_with_comments(self):
+ b = """
+ raise E, Func(arg1, arg2, arg3), tb # foo"""
+ a = """
+ from future.utils import raise_
+ raise_(E, Func(arg1, arg2, arg3), tb) # foo"""
+ self.check(b, a)
+
+ def test_unknown_value_with_indent(self):
+ b = """
+ while True:
+ print() # another expression in the same block triggers different parsing
+ raise E, V
+ """
+ a = """
+ from future.utils import raise_
+ while True:
+ print() # another expression in the same block triggers different parsing
+ raise_(E, V)
+ """
+ self.check(b, a)
+
+ # These should produce a warning
+
+ def test_string_exc(self):
+ s = """raise 'foo'"""
+ self.warns_unchanged(s, "Python 3 does not support string exceptions")
+
+ def test_string_exc_val(self):
+ s = """raise "foo", 5"""
+ self.warns_unchanged(s, "Python 3 does not support string exceptions")
+
+ def test_string_exc_val_tb(self):
+ s = """raise "foo", 5, 6"""
+ self.warns_unchanged(s, "Python 3 does not support string exceptions")
+
+ # These should result in traceback-assignment
+
+ def test_tb_1(self):
+ b = """def foo():
+ raise Exception, 5, 6"""
+ a = """def foo():
+ raise Exception(5).with_traceback(6)"""
+ self.check(b, a)
+
+ def test_tb_2(self):
+ b = """def foo():
+ a = 5
+ raise Exception, 5, 6
+ b = 6"""
+ a = """def foo():
+ a = 5
+ raise Exception(5).with_traceback(6)
+ b = 6"""
+ self.check(b, a)
+
+ def test_tb_3(self):
+ b = """def foo():
+ raise Exception,5,6"""
+ a = """def foo():
+ raise Exception(5).with_traceback(6)"""
+ self.check(b, a)
+
+ def test_tb_4(self):
+ b = """def foo():
+ a = 5
+ raise Exception,5,6
+ b = 6"""
+ a = """def foo():
+ a = 5
+ raise Exception(5).with_traceback(6)
+ b = 6"""
+ self.check(b, a)
+
+ def test_tb_5(self):
+ b = """def foo():
+ raise Exception, (5, 6, 7), 6"""
+ a = """def foo():
+ raise Exception(5, 6, 7).with_traceback(6)"""
+ self.check(b, a)
+
+ def test_tb_6(self):
+ b = """def foo():
+ a = 5
+ raise Exception, (5, 6, 7), 6
+ b = 6"""
+ a = """def foo():
+ a = 5
+ raise Exception(5, 6, 7).with_traceback(6)
+ b = 6"""
+ self.check(b, a)
#
# class Test_throw(FixerTestCase):
# fixer = "throw"
diff --git a/tests/test_future/test_list.py b/tests/test_future/test_list.py
index 6134c74f..16fb84c5 100644
--- a/tests/test_future/test_list.py
+++ b/tests/test_future/test_list.py
@@ -162,12 +162,15 @@ def test_multiple_inheritance(self):
"""
Issue #96 (for newdict instead of newobject)
"""
- import collections
+ if utils.PY2:
+ from collections import Container
+ else:
+ from collections.abc import Container
class Base(list):
pass
- class Foo(Base, collections.Container):
+ class Foo(Base, Container):
def __contains__(self, item):
return False
diff --git a/tests/test_future/test_object.py b/tests/test_future/test_object.py
index 8352f3a3..4f99cb5a 100644
--- a/tests/test_future/test_object.py
+++ b/tests/test_future/test_object.py
@@ -209,12 +209,15 @@ def test_multiple_inheritance(self):
"""
Issue #96
"""
- import collections
+ if utils.PY2:
+ from collections import Container
+ else:
+ from collections.abc import Container
class Base(object):
pass
- class Foo(Base, collections.Container):
+ class Foo(Base, Container):
def __contains__(self, item):
return False
@@ -271,6 +274,16 @@ def __len__(self):
self.assertFalse(bool(FalseThing()))
+ def test_cannot_assign_new_attributes_to_object(self):
+ """
+ New attributes cannot be assigned to object() instances in Python.
+ The same should apply to newobject.
+ """
+ from builtins import object
+
+ with self.assertRaises(AttributeError):
+ object().arbitrary_attribute_name = True
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_future/test_range.py b/tests/test_future/test_range.py
index 2e9471ae..dba15228 100644
--- a/tests/test_future/test_range.py
+++ b/tests/test_future/test_range.py
@@ -6,9 +6,15 @@
from future.builtins import range
from future.tests.base import unittest
-from collections import Iterator, Sequence
from operator import attrgetter
+from future.utils import PY2
+
+if PY2:
+ from collections import Iterator, Sequence
+else:
+ from collections.abc import Iterator, Sequence
+
class RangeTests(unittest.TestCase):
def test_range(self):
@@ -192,7 +198,7 @@ def test_rev_stepped_slice_rev_stepped_range(self):
def test_slice_zero_step(self):
msg = '^slice step cannot be zero$'
- with self.assertRaisesRegexp(ValueError, msg):
+ with self.assertRaisesRegex(ValueError, msg):
range(8)[::0]
def test_properties(self):
diff --git a/tests/test_future/test_str.py b/tests/test_future/test_str.py
index dcc15628..51085481 100644
--- a/tests/test_future/test_str.py
+++ b/tests/test_future/test_str.py
@@ -363,6 +363,29 @@ def test_eq(self):
self.assertFalse(b'ABCD' == s)
self.assertFalse(bytes(b'ABCD') == s)
+ # We want to ensure comparison against unknown types return
+ # NotImplemented so that the interpreter can rerun the test with the
+ # other class. We expect the operator to return False if both return
+ # NotImplemented.
+ class OurCustomString(object):
+ def __init__(self, string):
+ self.string = string
+
+ def __eq__(self, other):
+ return NotImplemented
+
+ our_str = OurCustomString("foobar")
+ new_str = str("foobar")
+
+ self.assertFalse(our_str == new_str)
+ self.assertFalse(new_str == our_str)
+ self.assertIs(new_str.__eq__(our_str), NotImplemented)
+ self.assertIs(our_str.__eq__(new_str), NotImplemented)
+
+ def test_hash(self):
+ s = str('ABCD')
+ self.assertIsInstance(hash(s),int)
+
def test_ne(self):
s = str('ABCD')
self.assertNotEqual('A', s)
@@ -525,12 +548,15 @@ def test_multiple_inheritance(self):
"""
Issue #96 (for newstr instead of newobject)
"""
- import collections
+ if utils.PY2:
+ from collections import Container
+ else:
+ from collections.abc import Container
class Base(str):
pass
- class Foo(Base, collections.Container):
+ class Foo(Base, Container):
def __contains__(self, item):
return False
diff --git a/tests/test_future/test_super.py b/tests/test_future/test_super.py
index 0376c1d8..3cb23d69 100644
--- a/tests/test_future/test_super.py
+++ b/tests/test_future/test_super.py
@@ -170,6 +170,18 @@ class Elite(Dangerous):
self.assertEqual(Elite().walk(), 'Defused')
+ def test_metaclass(self):
+ class Meta(type):
+ def __init__(cls, name, bases, clsdict):
+ super().__init__(name, bases, clsdict)
+
+ try:
+ class Base(object):
+ __metaclass__ = Meta
+ except Exception as e:
+ self.fail('raised %s with a custom metaclass'
+ % type(e).__name__)
+
class TestSuperFromTestDescrDotPy(unittest.TestCase):
"""
diff --git a/tests/test_future/test_urllib2.py b/tests/test_future/test_urllib2.py
index e7fb4dd7..2d69dad1 100644
--- a/tests/test_future/test_urllib2.py
+++ b/tests/test_future/test_urllib2.py
@@ -691,6 +691,10 @@ def connect_ftp(self, user, passwd, host, port, dirs,
h = NullFTPHandler(data)
h.parent = MockOpener()
+ # MIME guessing works in Python 3.8!
+ guessed_mime = None
+ if sys.hexversion >= 0x03080000:
+ guessed_mime = "image/gif"
for url, host, port, user, passwd, type_, dirs, filename, mimetype in [
("ftp://localhost/foo/bar/baz.html",
"localhost", ftplib.FTP_PORT, "", "", "I",
@@ -709,7 +713,7 @@ def connect_ftp(self, user, passwd, host, port, dirs,
["foo", "bar"], "", None),
("ftp://localhost/baz.gif;type=a",
"localhost", ftplib.FTP_PORT, "", "", "A",
- [], "baz.gif", None), # XXX really this should guess image/gif
+ [], "baz.gif", guessed_mime),
]:
req = Request(url)
req.timeout = None
diff --git a/tests/test_future/test_urllib_response.py b/tests/test_future/test_urllib_response.py
index 27da4a31..e8f4b4f1 100644
--- a/tests/test_future/test_urllib_response.py
+++ b/tests/test_future/test_urllib_response.py
@@ -8,7 +8,7 @@
from future.tests.base import unittest
-class TestFile(object):
+class File(object):
def __init__(self):
self.closed = False
@@ -28,7 +28,7 @@ class Testaddbase(unittest.TestCase):
# TODO(jhylton): Write tests for other functionality of addbase()
def setUp(self):
- self.fp = TestFile()
+ self.fp = File()
self.addbase = urllib_response.addbase(self.fp)
def test_with(self):
diff --git a/tests/test_future/test_urllibnet.py b/tests/test_future/test_urllibnet.py
index f9639bfc..6a7b6d64 100644
--- a/tests/test_future/test_urllibnet.py
+++ b/tests/test_future/test_urllibnet.py
@@ -38,7 +38,7 @@ def testURLread(self):
class urlopenNetworkTests(unittest.TestCase):
- """Tests urllib.reqest.urlopen using the network.
+ """Tests urllib.request.urlopen using the network.
These tests are not exhaustive. Assuming that testing using files does a
good job overall of some of the basic interface features. There are no
diff --git a/tests/test_future/test_utils.py b/tests/test_future/test_utils.py
index 19dc11d3..46f5196c 100644
--- a/tests/test_future/test_utils.py
+++ b/tests/test_future/test_utils.py
@@ -4,7 +4,7 @@
"""
from __future__ import absolute_import, unicode_literals, print_function
-import sys
+import re, sys, traceback
from future.builtins import *
from future.utils import (old_div, istext, isbytes, native, PY2, PY3,
native_str, raise_, as_native_str, ensure_new_type,
@@ -110,18 +110,14 @@ def test_isbytes(self):
self.assertFalse(isbytes(self.s))
self.assertFalse(isbytes(self.s2))
- @unittest.skipIf(PY3, 'test_raise_ currently fails on Py3')
def test_raise_(self):
- """
- The with_value() test currently fails on Py3
- """
- def valerror():
+ def valuerror():
try:
raise ValueError("Apples!")
except Exception as e:
raise_(e)
- self.assertRaises(ValueError, valerror)
+ self.assertRaises(ValueError, valuerror)
def with_value():
raise_(IOError, "This is an error")
@@ -147,6 +143,17 @@ def with_traceback():
except IOError as e:
self.assertEqual(str(e), "An error")
+ class Timeout(BaseException):
+ pass
+
+ self.assertRaises(Timeout, raise_, Timeout)
+ self.assertRaises(Timeout, raise_, Timeout())
+
+ if PY3:
+ self.assertRaisesRegexp(
+ TypeError, "class must derive from BaseException",
+ raise_, int)
+
def test_raise_from_None(self):
try:
try:
@@ -173,6 +180,23 @@ def bar():
pass
# incorrectly raises a TypeError on Py3 as of v0.15.2.
+ def test_raise_custom_exception(self):
+ """
+ Test issue #387.
+ """
+ class CustomException(Exception):
+ def __init__(self, severity, message):
+ super().__init__("custom message of severity %d: %s" % (
+ severity, message))
+
+ def raise_custom_exception():
+ try:
+ raise CustomException(1, "hello")
+ except CustomException:
+ raise_(*sys.exc_info())
+
+ self.assertRaises(CustomException, raise_custom_exception)
+
@skip26
def test_as_native_str(self):
"""
@@ -314,6 +338,69 @@ def __init__(self):
else:
self.fail("No exception raised")
+ def test_single_exception_stacktrace(self):
+ expected = '''Traceback (most recent call last):
+ File "/opt/python-future/tests/test_future/test_utils.py", line 328, in test_single_exception_stacktrace
+ raise CustomException('ERROR')
+'''
+ if PY2:
+ expected += 'CustomException: ERROR\n'
+ else:
+ expected += 'test_future.test_utils.CustomException: ERROR\n'
+
+ try:
+ raise CustomException('ERROR')
+ except:
+ ret = re.sub(r'"[^"]*tests/test_future', '"/opt/python-future/tests/test_future', traceback.format_exc())
+ ret = re.sub(r', line \d+,', ', line 328,', ret)
+ self.assertEqual(expected, ret)
+ else:
+ self.fail('No exception raised')
+
+ if PY2:
+ def test_chained_exceptions_stacktrace(self):
+ expected = '''Traceback (most recent call last):
+ File "/opt/python-future/tests/test_future/test_utils.py", line 1, in test_chained_exceptions_stacktrace
+ raise_from(CustomException('ERROR'), val_err)
+ File "/opt/python-future/src/future/utils/__init__.py", line 1, in raise_from
+ raise e
+CustomException: ERROR
+
+The above exception was the direct cause of the following exception:
+
+ File "/opt/python-future/tests/test_future/test_utils.py", line 1, in test_chained_exceptions_stacktrace
+ raise ValueError('Wooops')
+ValueError: Wooops
+'''
+
+ try:
+ try:
+ raise ValueError('Wooops')
+ except ValueError as val_err:
+ raise_from(CustomException('ERROR'), val_err)
+ except Exception as err:
+ ret = re.sub(r'"[^"]*tests/test_future', '"/opt/python-future/tests/test_future', traceback.format_exc())
+ ret = re.sub(r'"[^"]*future/utils/__init__.py', '"/opt/python-future/src/future/utils/__init__.py', ret)
+ ret = re.sub(r', line \d+,', ', line 1,', ret)
+ self.assertEqual(expected.splitlines(), ret.splitlines())
+ else:
+ self.fail('No exception raised')
+
+
+class CustomException(Exception):
+ if PY2:
+ def __str__(self):
+ try:
+ out = Exception.__str__(self)
+ if hasattr(self, '__cause__') and self.__cause__ and hasattr(self.__cause__, '__traceback__') and self.__cause__.__traceback__:
+ out += '\n\nThe above exception was the direct cause of the following exception:\n\n'
+ out += ''.join(traceback.format_tb(self.__cause__.__traceback__) + ['{0}: {1}'.format(self.__cause__.__class__.__name__, self.__cause__)])
+ return out
+ except Exception as e:
+ print(e)
+ else:
+ pass
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_past/test_basestring.py b/tests/test_past/test_basestring.py
index d002095e..6c224b3e 100644
--- a/tests/test_past/test_basestring.py
+++ b/tests/test_past/test_basestring.py
@@ -19,6 +19,25 @@ def test_isinstance(self):
s2 = oldstr(b'abc')
self.assertTrue(isinstance(s2, basestring))
+ def test_issubclass(self):
+ self.assertTrue(issubclass(str, basestring))
+ self.assertTrue(issubclass(bytes, basestring))
+ self.assertTrue(issubclass(basestring, basestring))
+ self.assertFalse(issubclass(int, basestring))
+ self.assertFalse(issubclass(list, basestring))
+ self.assertTrue(issubclass(basestring, object))
+
+ class CustomString(basestring):
+ pass
+ class NotString(object):
+ pass
+ class OldStyleClass:
+ pass
+ self.assertTrue(issubclass(CustomString, basestring))
+ self.assertFalse(issubclass(NotString, basestring))
+ self.assertFalse(issubclass(OldStyleClass, basestring))
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_past/test_misc.py b/tests/test_past/test_misc.py
new file mode 100644
index 00000000..0367b3db
--- /dev/null
+++ b/tests/test_past/test_misc.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+"""
+Tests for the resurrected Py2-like cmp function
+"""
+
+from __future__ import absolute_import, unicode_literals, print_function
+
+import os.path
+import sys
+import traceback
+from contextlib import contextmanager
+
+from future.tests.base import unittest
+from future.utils import PY3, PY26
+
+if PY3:
+ from past.builtins import cmp
+
+_dir = os.path.dirname(os.path.abspath(__file__))
+sys.path.append(_dir)
+import test_values
+
+
+@contextmanager
+def empty_context_manager(*args, **kwargs):
+ yield dict(args=args, kwargs=kwargs)
+
+
+class TestCmp(unittest.TestCase):
+ def test_cmp(self):
+ for x, y, cmp_python2_value in test_values.cmp_python2_value:
+ if PY26:
+ # set cmp works a bit differently in 2.6, we try to emulate 2.7 behavior, so skip set cmp tests
+ if isinstance(x, set) or isinstance(y, set):
+ continue
+ # to get this to run on python <3.4 which lacks subTest
+ with getattr(self, 'subTest', empty_context_manager)(x=x, y=y):
+ try:
+ past_cmp_value = cmp(x, y)
+ except Exception:
+ past_cmp_value = traceback.format_exc().strip().split('\n')[-1]
+
+ self.assertEqual(cmp_python2_value, past_cmp_value,
+ "expected result matching python2 __builtins__.cmp({x!r},{y!r}) "
+ "== {cmp_python2_value} "
+ "got past.builtins.cmp({x!r},{y!r}) "
+ "== {past_cmp_value} "
+ "".format(x=x, y=y, past_cmp_value=past_cmp_value,
+ cmp_python2_value=cmp_python2_value))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_past/test_values.py b/tests/test_past/test_values.py
new file mode 100644
index 00000000..11872084
--- /dev/null
+++ b/tests/test_past/test_values.py
@@ -0,0 +1,225 @@
+from math import pi
+
+inf, nan = float('inf'), float('nan')
+test_values = [
+ 0, 1, 2, -1, -9999999999, 9999999,
+ 0.0, inf, pi,
+ [], [[]], [1, 2, 3],
+ set(), set([1, 2, 3]),
+ " ", "", "1", "dsada saA.", "2", "dsa", b"", b"dsa", b" ",
+ {5: 3}, dict(), dict(a=99), dict(a=1, b=2, c=3), None
+]
+
+# cmp_python2_values are pre-calculated from running cmp under python2 first values are x and y, last is results of cmp
+cmp_python2_value = [[0, 1, -1], [0, 2, -1], [0, -1, 1], [0, -9999999999999999, 1], [0, 9999999999999999, -1],
+ [0, 0.0, 0], [0, inf, -1], [0, 3.141592653589793, -1], [0, [], -1], [0, [[]], -1],
+ [0, [1, 2, 3], -1], [0, '', -1], [0, ' ', -1], [0, '1', -1], [0, 'a bee cd.', -1], [0, '', -1],
+ [0, ' ', -1], [0, '1', -1], [0, 'a bee cd.', -1], [0, set([]), -1], [0, set([1, 2, 3]), -1],
+ [0, {5: 3}, -1], [0, {}, -1], [0, {'a': 99}, -1], [0, {'a': 1, 'c': 3, 'b': 2}, -1],
+ [0, {'a': 99, 'c': 3, 'b': 5}, -1], [0, None, 1], [1, 0, 1], [1, 2, -1], [1, -1, 1],
+ [1, -9999999999999999, 1], [1, 9999999999999999, -1], [1, 0.0, 1], [1, inf, -1],
+ [1, 3.141592653589793, -1], [1, [], -1], [1, [[]], -1], [1, [1, 2, 3], -1], [1, '', -1],
+ [1, ' ', -1], [1, '1', -1], [1, 'a bee cd.', -1], [1, '', -1], [1, ' ', -1], [1, '1', -1],
+ [1, 'a bee cd.', -1], [1, set([]), -1], [1, set([1, 2, 3]), -1], [1, {5: 3}, -1], [1, {}, -1],
+ [1, {'a': 99}, -1], [1, {'a': 1, 'c': 3, 'b': 2}, -1], [1, {'a': 99, 'c': 3, 'b': 5}, -1],
+ [1, None, 1], [2, 0, 1], [2, 1, 1], [2, -1, 1], [2, -9999999999999999, 1],
+ [2, 9999999999999999, -1], [2, 0.0, 1], [2, inf, -1], [2, 3.141592653589793, -1], [2, [], -1],
+ [2, [[]], -1], [2, [1, 2, 3], -1], [2, '', -1], [2, ' ', -1], [2, '1', -1], [2, 'a bee cd.', -1],
+ [2, '', -1], [2, ' ', -1], [2, '1', -1], [2, 'a bee cd.', -1], [2, set([]), -1],
+ [2, set([1, 2, 3]), -1], [2, {5: 3}, -1], [2, {}, -1], [2, {'a': 99}, -1],
+ [2, {'a': 1, 'c': 3, 'b': 2}, -1], [2, {'a': 99, 'c': 3, 'b': 5}, -1], [2, None, 1], [-1, 0, -1],
+ [-1, 1, -1], [-1, 2, -1], [-1, -9999999999999999, 1], [-1, 9999999999999999, -1], [-1, 0.0, -1],
+ [-1, inf, -1], [-1, 3.141592653589793, -1], [-1, [], -1], [-1, [[]], -1], [-1, [1, 2, 3], -1],
+ [-1, '', -1], [-1, ' ', -1], [-1, '1', -1], [-1, 'a bee cd.', -1], [-1, '', -1], [-1, ' ', -1],
+ [-1, '1', -1], [-1, 'a bee cd.', -1], [-1, set([]), -1], [-1, set([1, 2, 3]), -1],
+ [-1, {5: 3}, -1], [-1, {}, -1], [-1, {'a': 99}, -1], [-1, {'a': 1, 'c': 3, 'b': 2}, -1],
+ [-1, {'a': 99, 'c': 3, 'b': 5}, -1], [-1, None, 1], [-9999999999999999, 0, -1],
+ [-9999999999999999, 1, -1], [-9999999999999999, 2, -1], [-9999999999999999, -1, -1],
+ [-9999999999999999, 9999999999999999, -1], [-9999999999999999, 0.0, -1],
+ [-9999999999999999, inf, -1], [-9999999999999999, 3.141592653589793, -1],
+ [-9999999999999999, [], -1], [-9999999999999999, [[]], -1], [-9999999999999999, [1, 2, 3], -1],
+ [-9999999999999999, '', -1], [-9999999999999999, ' ', -1], [-9999999999999999, '1', -1],
+ [-9999999999999999, 'a bee cd.', -1], [-9999999999999999, '', -1], [-9999999999999999, ' ', -1],
+ [-9999999999999999, '1', -1], [-9999999999999999, 'a bee cd.', -1],
+ [-9999999999999999, set([]), -1], [-9999999999999999, set([1, 2, 3]), -1],
+ [-9999999999999999, {5: 3}, -1], [-9999999999999999, {}, -1], [-9999999999999999, {'a': 99}, -1],
+ [-9999999999999999, {'a': 1, 'c': 3, 'b': 2}, -1],
+ [-9999999999999999, {'a': 99, 'c': 3, 'b': 5}, -1], [-9999999999999999, None, 1],
+ [9999999999999999, 0, 1], [9999999999999999, 1, 1], [9999999999999999, 2, 1],
+ [9999999999999999, -1, 1], [9999999999999999, -9999999999999999, 1], [9999999999999999, 0.0, 1],
+ [9999999999999999, inf, -1], [9999999999999999, 3.141592653589793, 1], [9999999999999999, [], -1],
+ [9999999999999999, [[]], -1], [9999999999999999, [1, 2, 3], -1], [9999999999999999, '', -1],
+ [9999999999999999, ' ', -1], [9999999999999999, '1', -1], [9999999999999999, 'a bee cd.', -1],
+ [9999999999999999, '', -1], [9999999999999999, ' ', -1], [9999999999999999, '1', -1],
+ [9999999999999999, 'a bee cd.', -1], [9999999999999999, set([]), -1],
+ [9999999999999999, set([1, 2, 3]), -1], [9999999999999999, {5: 3}, -1], [9999999999999999, {}, -1],
+ [9999999999999999, {'a': 99}, -1], [9999999999999999, {'a': 1, 'c': 3, 'b': 2}, -1],
+ [9999999999999999, {'a': 99, 'c': 3, 'b': 5}, -1], [9999999999999999, None, 1], [0.0, 0, 0],
+ [0.0, 1, -1], [0.0, 2, -1], [0.0, -1, 1], [0.0, -9999999999999999, 1], [0.0, 9999999999999999, -1],
+ [0.0, inf, -1], [0.0, 3.141592653589793, -1], [0.0, [], -1], [0.0, [[]], -1], [0.0, [1, 2, 3], -1],
+ [0.0, '', -1], [0.0, ' ', -1], [0.0, '1', -1], [0.0, 'a bee cd.', -1], [0.0, '', -1],
+ [0.0, ' ', -1], [0.0, '1', -1], [0.0, 'a bee cd.', -1], [0.0, set([]), -1],
+ [0.0, set([1, 2, 3]), -1], [0.0, {5: 3}, -1], [0.0, {}, -1], [0.0, {'a': 99}, -1],
+ [0.0, {'a': 1, 'c': 3, 'b': 2}, -1], [0.0, {'a': 99, 'c': 3, 'b': 5}, -1], [0.0, None, 1],
+ [inf, 0, 1], [inf, 1, 1], [inf, 2, 1], [inf, -1, 1], [inf, -9999999999999999, 1],
+ [inf, 9999999999999999, 1], [inf, 0.0, 1], [inf, 3.141592653589793, 1], [inf, [], -1],
+ [inf, [[]], -1], [inf, [1, 2, 3], -1], [inf, '', -1], [inf, ' ', -1], [inf, '1', -1],
+ [inf, 'a bee cd.', -1], [inf, '', -1], [inf, ' ', -1], [inf, '1', -1], [inf, 'a bee cd.', -1],
+ [inf, set([]), -1], [inf, set([1, 2, 3]), -1], [inf, {5: 3}, -1], [inf, {}, -1],
+ [inf, {'a': 99}, -1], [inf, {'a': 1, 'c': 3, 'b': 2}, -1], [inf, {'a': 99, 'c': 3, 'b': 5}, -1],
+ [inf, None, 1], [3.141592653589793, 0, 1], [3.141592653589793, 1, 1], [3.141592653589793, 2, 1],
+ [3.141592653589793, -1, 1], [3.141592653589793, -9999999999999999, 1],
+ [3.141592653589793, 9999999999999999, -1], [3.141592653589793, 0.0, 1],
+ [3.141592653589793, inf, -1], [3.141592653589793, [], -1], [3.141592653589793, [[]], -1],
+ [3.141592653589793, [1, 2, 3], -1], [3.141592653589793, '', -1], [3.141592653589793, ' ', -1],
+ [3.141592653589793, '1', -1], [3.141592653589793, 'a bee cd.', -1], [3.141592653589793, '', -1],
+ [3.141592653589793, ' ', -1], [3.141592653589793, '1', -1], [3.141592653589793, 'a bee cd.', -1],
+ [3.141592653589793, set([]), -1], [3.141592653589793, set([1, 2, 3]), -1],
+ [3.141592653589793, {5: 3}, -1], [3.141592653589793, {}, -1], [3.141592653589793, {'a': 99}, -1],
+ [3.141592653589793, {'a': 1, 'c': 3, 'b': 2}, -1],
+ [3.141592653589793, {'a': 99, 'c': 3, 'b': 5}, -1], [3.141592653589793, None, 1], [[], 0, 1],
+ [[], 1, 1], [[], 2, 1], [[], -1, 1], [[], -9999999999999999, 1], [[], 9999999999999999, 1],
+ [[], 0.0, 1], [[], inf, 1], [[], 3.141592653589793, 1], [[], [[]], -1], [[], [1, 2, 3], -1],
+ [[], '', -1], [[], ' ', -1], [[], '1', -1], [[], 'a bee cd.', -1], [[], '', -1], [[], ' ', -1],
+ [[], '1', -1], [[], 'a bee cd.', -1], [[], set([]), -1], [[], set([1, 2, 3]), -1], [[], {5: 3}, 1],
+ [[], {}, 1], [[], {'a': 99}, 1], [[], {'a': 1, 'c': 3, 'b': 2}, 1],
+ [[], {'a': 99, 'c': 3, 'b': 5}, 1], [[], None, 1], [[[]], 0, 1], [[[]], 1, 1], [[[]], 2, 1],
+ [[[]], -1, 1], [[[]], -9999999999999999, 1], [[[]], 9999999999999999, 1], [[[]], 0.0, 1],
+ [[[]], inf, 1], [[[]], 3.141592653589793, 1], [[[]], [], 1], [[[]], [1, 2, 3], 1], [[[]], '', -1],
+ [[[]], ' ', -1], [[[]], '1', -1], [[[]], 'a bee cd.', -1], [[[]], '', -1], [[[]], ' ', -1],
+ [[[]], '1', -1], [[[]], 'a bee cd.', -1], [[[]], set([]), -1], [[[]], set([1, 2, 3]), -1],
+ [[[]], {5: 3}, 1], [[[]], {}, 1], [[[]], {'a': 99}, 1], [[[]], {'a': 1, 'c': 3, 'b': 2}, 1],
+ [[[]], {'a': 99, 'c': 3, 'b': 5}, 1], [[[]], None, 1], [[1, 2, 3], 0, 1], [[1, 2, 3], 1, 1],
+ [[1, 2, 3], 2, 1], [[1, 2, 3], -1, 1], [[1, 2, 3], -9999999999999999, 1],
+ [[1, 2, 3], 9999999999999999, 1], [[1, 2, 3], 0.0, 1], [[1, 2, 3], inf, 1],
+ [[1, 2, 3], 3.141592653589793, 1], [[1, 2, 3], [], 1], [[1, 2, 3], [[]], -1], [[1, 2, 3], '', -1],
+ [[1, 2, 3], ' ', -1], [[1, 2, 3], '1', -1], [[1, 2, 3], 'a bee cd.', -1], [[1, 2, 3], '', -1],
+ [[1, 2, 3], ' ', -1], [[1, 2, 3], '1', -1], [[1, 2, 3], 'a bee cd.', -1], [[1, 2, 3], set([]), -1],
+ [[1, 2, 3], set([1, 2, 3]), -1], [[1, 2, 3], {5: 3}, 1], [[1, 2, 3], {}, 1],
+ [[1, 2, 3], {'a': 99}, 1], [[1, 2, 3], {'a': 1, 'c': 3, 'b': 2}, 1],
+ [[1, 2, 3], {'a': 99, 'c': 3, 'b': 5}, 1], [[1, 2, 3], None, 1], ['', 0, 1], ['', 1, 1],
+ ['', 2, 1], ['', -1, 1], ['', -9999999999999999, 1], ['', 9999999999999999, 1], ['', 0.0, 1],
+ ['', inf, 1], ['', 3.141592653589793, 1], ['', [], 1], ['', [[]], 1], ['', [1, 2, 3], 1],
+ ['', ' ', -1], ['', '1', -1], ['', 'a bee cd.', -1], ['', '', 0], ['', ' ', -1], ['', '1', -1],
+ ['', 'a bee cd.', -1], ['', set([]), 1], ['', set([1, 2, 3]), 1], ['', {5: 3}, 1], ['', {}, 1],
+ ['', {'a': 99}, 1], ['', {'a': 1, 'c': 3, 'b': 2}, 1], ['', {'a': 99, 'c': 3, 'b': 5}, 1],
+ ['', None, 1], [' ', 0, 1], [' ', 1, 1], [' ', 2, 1], [' ', -1, 1], [' ', -9999999999999999, 1],
+ [' ', 9999999999999999, 1], [' ', 0.0, 1], [' ', inf, 1], [' ', 3.141592653589793, 1],
+ [' ', [], 1], [' ', [[]], 1], [' ', [1, 2, 3], 1], [' ', '', 1], [' ', '1', -1],
+ [' ', 'a bee cd.', -1], [' ', '', 1], [' ', ' ', 0], [' ', '1', -1], [' ', 'a bee cd.', -1],
+ [' ', set([]), 1], [' ', set([1, 2, 3]), 1], [' ', {5: 3}, 1], [' ', {}, 1], [' ', {'a': 99}, 1],
+ [' ', {'a': 1, 'c': 3, 'b': 2}, 1], [' ', {'a': 99, 'c': 3, 'b': 5}, 1], [' ', None, 1],
+ ['1', 0, 1], ['1', 1, 1], ['1', 2, 1], ['1', -1, 1], ['1', -9999999999999999, 1],
+ ['1', 9999999999999999, 1], ['1', 0.0, 1], ['1', inf, 1], ['1', 3.141592653589793, 1],
+ ['1', [], 1], ['1', [[]], 1], ['1', [1, 2, 3], 1], ['1', '', 1], ['1', ' ', 1],
+ ['1', 'a bee cd.', -1], ['1', '', 1], ['1', ' ', 1], ['1', '1', 0], ['1', 'a bee cd.', -1],
+ ['1', set([]), 1], ['1', set([1, 2, 3]), 1], ['1', {5: 3}, 1], ['1', {}, 1], ['1', {'a': 99}, 1],
+ ['1', {'a': 1, 'c': 3, 'b': 2}, 1], ['1', {'a': 99, 'c': 3, 'b': 5}, 1], ['1', None, 1],
+ ['a bee cd.', 0, 1], ['a bee cd.', 1, 1], ['a bee cd.', 2, 1], ['a bee cd.', -1, 1],
+ ['a bee cd.', -9999999999999999, 1], ['a bee cd.', 9999999999999999, 1], ['a bee cd.', 0.0, 1],
+ ['a bee cd.', inf, 1], ['a bee cd.', 3.141592653589793, 1], ['a bee cd.', [], 1],
+ ['a bee cd.', [[]], 1], ['a bee cd.', [1, 2, 3], 1], ['a bee cd.', '', 1], ['a bee cd.', ' ', 1],
+ ['a bee cd.', '1', 1], ['a bee cd.', '', 1], ['a bee cd.', ' ', 1], ['a bee cd.', '1', 1],
+ ['a bee cd.', 'a bee cd.', 0], ['a bee cd.', set([]), 1], ['a bee cd.', set([1, 2, 3]), 1],
+ ['a bee cd.', {5: 3}, 1], ['a bee cd.', {}, 1], ['a bee cd.', {'a': 99}, 1],
+ ['a bee cd.', {'a': 1, 'c': 3, 'b': 2}, 1], ['a bee cd.', {'a': 99, 'c': 3, 'b': 5}, 1],
+ ['a bee cd.', None, 1], ['', 0, 1], ['', 1, 1], ['', 2, 1], ['', -1, 1],
+ ['', -9999999999999999, 1], ['', 9999999999999999, 1], ['', 0.0, 1], ['', inf, 1],
+ ['', 3.141592653589793, 1], ['', [], 1], ['', [[]], 1], ['', [1, 2, 3], 1], ['', '', 0],
+ ['', ' ', -1], ['', '1', -1], ['', 'a bee cd.', -1], ['', ' ', -1], ['', '1', -1],
+ ['', 'a bee cd.', -1], ['', set([]), 1], ['', set([1, 2, 3]), 1], ['', {5: 3}, 1], ['', {}, 1],
+ ['', {'a': 99}, 1], ['', {'a': 1, 'c': 3, 'b': 2}, 1], ['', {'a': 99, 'c': 3, 'b': 5}, 1],
+ ['', None, 1], [' ', 0, 1], [' ', 1, 1], [' ', 2, 1], [' ', -1, 1], [' ', -9999999999999999, 1],
+ [' ', 9999999999999999, 1], [' ', 0.0, 1], [' ', inf, 1], [' ', 3.141592653589793, 1],
+ [' ', [], 1], [' ', [[]], 1], [' ', [1, 2, 3], 1], [' ', '', 1], [' ', ' ', 0], [' ', '1', -1],
+ [' ', 'a bee cd.', -1], [' ', '', 1], [' ', '1', -1], [' ', 'a bee cd.', -1], [' ', set([]), 1],
+ [' ', set([1, 2, 3]), 1], [' ', {5: 3}, 1], [' ', {}, 1], [' ', {'a': 99}, 1],
+ [' ', {'a': 1, 'c': 3, 'b': 2}, 1], [' ', {'a': 99, 'c': 3, 'b': 5}, 1], [' ', None, 1],
+ ['1', 0, 1], ['1', 1, 1], ['1', 2, 1], ['1', -1, 1], ['1', -9999999999999999, 1],
+ ['1', 9999999999999999, 1], ['1', 0.0, 1], ['1', inf, 1], ['1', 3.141592653589793, 1],
+ ['1', [], 1], ['1', [[]], 1], ['1', [1, 2, 3], 1], ['1', '', 1], ['1', ' ', 1], ['1', '1', 0],
+ ['1', 'a bee cd.', -1], ['1', '', 1], ['1', ' ', 1], ['1', 'a bee cd.', -1], ['1', set([]), 1],
+ ['1', set([1, 2, 3]), 1], ['1', {5: 3}, 1], ['1', {}, 1], ['1', {'a': 99}, 1],
+ ['1', {'a': 1, 'c': 3, 'b': 2}, 1], ['1', {'a': 99, 'c': 3, 'b': 5}, 1], ['1', None, 1],
+ ['a bee cd.', 0, 1], ['a bee cd.', 1, 1], ['a bee cd.', 2, 1], ['a bee cd.', -1, 1],
+ ['a bee cd.', -9999999999999999, 1], ['a bee cd.', 9999999999999999, 1], ['a bee cd.', 0.0, 1],
+ ['a bee cd.', inf, 1], ['a bee cd.', 3.141592653589793, 1], ['a bee cd.', [], 1],
+ ['a bee cd.', [[]], 1], ['a bee cd.', [1, 2, 3], 1], ['a bee cd.', '', 1], ['a bee cd.', ' ', 1],
+ ['a bee cd.', '1', 1], ['a bee cd.', 'a bee cd.', 0], ['a bee cd.', '', 1], ['a bee cd.', ' ', 1],
+ ['a bee cd.', '1', 1], ['a bee cd.', set([]), 1], ['a bee cd.', set([1, 2, 3]), 1],
+ ['a bee cd.', {5: 3}, 1], ['a bee cd.', {}, 1], ['a bee cd.', {'a': 99}, 1],
+ ['a bee cd.', {'a': 1, 'c': 3, 'b': 2}, 1], ['a bee cd.', {'a': 99, 'c': 3, 'b': 5}, 1],
+ ['a bee cd.', None, 1], [set([]), 0, 1], [set([]), 1, 1], [set([]), 2, 1], [set([]), -1, 1],
+ [set([]), -9999999999999999, 1], [set([]), 9999999999999999, 1], [set([]), 0.0, 1],
+ [set([]), inf, 1], [set([]), 3.141592653589793, 1], [set([]), [], 1], [set([]), [[]], 1],
+ [set([]), [1, 2, 3], 1], [set([]), '', -1], [set([]), ' ', -1], [set([]), '1', -1],
+ [set([]), 'a bee cd.', -1], [set([]), '', -1], [set([]), ' ', -1], [set([]), '1', -1],
+ [set([]), 'a bee cd.', -1],
+ [set([]), set([1, 2, 3]), 'TypeError: cannot compare sets using cmp()'], [set([]), {5: 3}, 1],
+ [set([]), {}, 1], [set([]), {'a': 99}, 1], [set([]), {'a': 1, 'c': 3, 'b': 2}, 1],
+ [set([]), {'a': 99, 'c': 3, 'b': 5}, 1], [set([]), None, 1], [set([1, 2, 3]), 0, 1],
+ [set([1, 2, 3]), 1, 1], [set([1, 2, 3]), 2, 1], [set([1, 2, 3]), -1, 1],
+ [set([1, 2, 3]), -9999999999999999, 1], [set([1, 2, 3]), 9999999999999999, 1],
+ [set([1, 2, 3]), 0.0, 1], [set([1, 2, 3]), inf, 1], [set([1, 2, 3]), 3.141592653589793, 1],
+ [set([1, 2, 3]), [], 1], [set([1, 2, 3]), [[]], 1], [set([1, 2, 3]), [1, 2, 3], 1],
+ [set([1, 2, 3]), '', -1], [set([1, 2, 3]), ' ', -1], [set([1, 2, 3]), '1', -1],
+ [set([1, 2, 3]), 'a bee cd.', -1], [set([1, 2, 3]), '', -1], [set([1, 2, 3]), ' ', -1],
+ [set([1, 2, 3]), '1', -1], [set([1, 2, 3]), 'a bee cd.', -1],
+ [set([1, 2, 3]), set([]), 'TypeError: cannot compare sets using cmp()'],
+ [set([1, 2, 3]), {5: 3}, 1], [set([1, 2, 3]), {}, 1], [set([1, 2, 3]), {'a': 99}, 1],
+ [set([1, 2, 3]), {'a': 1, 'c': 3, 'b': 2}, 1], [set([1, 2, 3]), {'a': 99, 'c': 3, 'b': 5}, 1],
+ [set([1, 2, 3]), None, 1], [{5: 3}, 0, 1], [{5: 3}, 1, 1], [{5: 3}, 2, 1], [{5: 3}, -1, 1],
+ [{5: 3}, -9999999999999999, 1], [{5: 3}, 9999999999999999, 1], [{5: 3}, 0.0, 1], [{5: 3}, inf, 1],
+ [{5: 3}, 3.141592653589793, 1], [{5: 3}, [], -1], [{5: 3}, [[]], -1], [{5: 3}, [1, 2, 3], -1],
+ [{5: 3}, '', -1], [{5: 3}, ' ', -1], [{5: 3}, '1', -1], [{5: 3}, 'a bee cd.', -1],
+ [{5: 3}, '', -1], [{5: 3}, ' ', -1], [{5: 3}, '1', -1], [{5: 3}, 'a bee cd.', -1],
+ [{5: 3}, set([]), -1], [{5: 3}, set([1, 2, 3]), -1], [{5: 3}, {}, 1], [{5: 3}, {'a': 99}, -1],
+ [{5: 3}, {'a': 1, 'c': 3, 'b': 2}, -1], [{5: 3}, {'a': 99, 'c': 3, 'b': 5}, -1], [{5: 3}, None, 1],
+ [{}, 0, 1], [{}, 1, 1], [{}, 2, 1], [{}, -1, 1], [{}, -9999999999999999, 1],
+ [{}, 9999999999999999, 1], [{}, 0.0, 1], [{}, inf, 1], [{}, 3.141592653589793, 1], [{}, [], -1],
+ [{}, [[]], -1], [{}, [1, 2, 3], -1], [{}, '', -1], [{}, ' ', -1], [{}, '1', -1],
+ [{}, 'a bee cd.', -1], [{}, '', -1], [{}, ' ', -1], [{}, '1', -1], [{}, 'a bee cd.', -1],
+ [{}, set([]), -1], [{}, set([1, 2, 3]), -1], [{}, {5: 3}, -1], [{}, {'a': 99}, -1],
+ [{}, {'a': 1, 'c': 3, 'b': 2}, -1], [{}, {'a': 99, 'c': 3, 'b': 5}, -1], [{}, None, 1],
+ [{'a': 99}, 0, 1], [{'a': 99}, 1, 1], [{'a': 99}, 2, 1], [{'a': 99}, -1, 1],
+ [{'a': 99}, -9999999999999999, 1], [{'a': 99}, 9999999999999999, 1], [{'a': 99}, 0.0, 1],
+ [{'a': 99}, inf, 1], [{'a': 99}, 3.141592653589793, 1], [{'a': 99}, [], -1], [{'a': 99}, [[]], -1],
+ [{'a': 99}, [1, 2, 3], -1], [{'a': 99}, '', -1], [{'a': 99}, ' ', -1], [{'a': 99}, '1', -1],
+ [{'a': 99}, 'a bee cd.', -1], [{'a': 99}, '', -1], [{'a': 99}, ' ', -1], [{'a': 99}, '1', -1],
+ [{'a': 99}, 'a bee cd.', -1], [{'a': 99}, set([]), -1], [{'a': 99}, set([1, 2, 3]), -1],
+ [{'a': 99}, {5: 3}, 1], [{'a': 99}, {}, 1], [{'a': 99}, {'a': 1, 'c': 3, 'b': 2}, -1],
+ [{'a': 99}, {'a': 99, 'c': 3, 'b': 5}, -1], [{'a': 99}, None, 1], [{'a': 1, 'c': 3, 'b': 2}, 0, 1],
+ [{'a': 1, 'c': 3, 'b': 2}, 1, 1], [{'a': 1, 'c': 3, 'b': 2}, 2, 1],
+ [{'a': 1, 'c': 3, 'b': 2}, -1, 1], [{'a': 1, 'c': 3, 'b': 2}, -9999999999999999, 1],
+ [{'a': 1, 'c': 3, 'b': 2}, 9999999999999999, 1], [{'a': 1, 'c': 3, 'b': 2}, 0.0, 1],
+ [{'a': 1, 'c': 3, 'b': 2}, inf, 1], [{'a': 1, 'c': 3, 'b': 2}, 3.141592653589793, 1],
+ [{'a': 1, 'c': 3, 'b': 2}, [], -1], [{'a': 1, 'c': 3, 'b': 2}, [[]], -1],
+ [{'a': 1, 'c': 3, 'b': 2}, [1, 2, 3], -1], [{'a': 1, 'c': 3, 'b': 2}, '', -1],
+ [{'a': 1, 'c': 3, 'b': 2}, ' ', -1], [{'a': 1, 'c': 3, 'b': 2}, '1', -1],
+ [{'a': 1, 'c': 3, 'b': 2}, 'a bee cd.', -1], [{'a': 1, 'c': 3, 'b': 2}, '', -1],
+ [{'a': 1, 'c': 3, 'b': 2}, ' ', -1], [{'a': 1, 'c': 3, 'b': 2}, '1', -1],
+ [{'a': 1, 'c': 3, 'b': 2}, 'a bee cd.', -1], [{'a': 1, 'c': 3, 'b': 2}, set([]), -1],
+ [{'a': 1, 'c': 3, 'b': 2}, set([1, 2, 3]), -1], [{'a': 1, 'c': 3, 'b': 2}, {5: 3}, 1],
+ [{'a': 1, 'c': 3, 'b': 2}, {}, 1], [{'a': 1, 'c': 3, 'b': 2}, {'a': 99}, 1],
+ [{'a': 1, 'c': 3, 'b': 2}, {'a': 99, 'c': 3, 'b': 5}, -1], [{'a': 1, 'c': 3, 'b': 2}, None, 1],
+ [{'a': 99, 'c': 3, 'b': 5}, 0, 1], [{'a': 99, 'c': 3, 'b': 5}, 1, 1],
+ [{'a': 99, 'c': 3, 'b': 5}, 2, 1], [{'a': 99, 'c': 3, 'b': 5}, -1, 1],
+ [{'a': 99, 'c': 3, 'b': 5}, -9999999999999999, 1],
+ [{'a': 99, 'c': 3, 'b': 5}, 9999999999999999, 1], [{'a': 99, 'c': 3, 'b': 5}, 0.0, 1],
+ [{'a': 99, 'c': 3, 'b': 5}, inf, 1], [{'a': 99, 'c': 3, 'b': 5}, 3.141592653589793, 1],
+ [{'a': 99, 'c': 3, 'b': 5}, [], -1], [{'a': 99, 'c': 3, 'b': 5}, [[]], -1],
+ [{'a': 99, 'c': 3, 'b': 5}, [1, 2, 3], -1], [{'a': 99, 'c': 3, 'b': 5}, '', -1],
+ [{'a': 99, 'c': 3, 'b': 5}, ' ', -1], [{'a': 99, 'c': 3, 'b': 5}, '1', -1],
+ [{'a': 99, 'c': 3, 'b': 5}, 'a bee cd.', -1], [{'a': 99, 'c': 3, 'b': 5}, '', -1],
+ [{'a': 99, 'c': 3, 'b': 5}, ' ', -1], [{'a': 99, 'c': 3, 'b': 5}, '1', -1],
+ [{'a': 99, 'c': 3, 'b': 5}, 'a bee cd.', -1], [{'a': 99, 'c': 3, 'b': 5}, set([]), -1],
+ [{'a': 99, 'c': 3, 'b': 5}, set([1, 2, 3]), -1], [{'a': 99, 'c': 3, 'b': 5}, {5: 3}, 1],
+ [{'a': 99, 'c': 3, 'b': 5}, {}, 1], [{'a': 99, 'c': 3, 'b': 5}, {'a': 99}, 1],
+ [{'a': 99, 'c': 3, 'b': 5}, {'a': 1, 'c': 3, 'b': 2}, 1], [{'a': 99, 'c': 3, 'b': 5}, None, 1],
+ [None, 0, -1], [None, 1, -1], [None, 2, -1], [None, -1, -1], [None, -9999999999999999, -1],
+ [None, 9999999999999999, -1], [None, 0.0, -1], [None, inf, -1], [None, 3.141592653589793, -1],
+ [None, [], -1], [None, [[]], -1], [None, [1, 2, 3], -1], [None, '', -1], [None, ' ', -1],
+ [None, '1', -1], [None, 'a bee cd.', -1], [None, '', -1], [None, ' ', -1], [None, '1', -1],
+ [None, 'a bee cd.', -1], [None, set([]), -1], [None, set([1, 2, 3]), -1], [None, {5: 3}, -1],
+ [None, {}, -1], [None, {'a': 99}, -1], [None, {'a': 1, 'c': 3, 'b': 2}, -1],
+ [None, {'a': 99, 'c': 3, 'b': 5}, -1]]
diff --git a/tox.ini b/tox.ini
deleted file mode 100644
index f5c013f8..00000000
--- a/tox.ini
+++ /dev/null
@@ -1,9 +0,0 @@
-[tox]
-envlist = py26,py27,py33,py34,py35,py36,py37
-
-[testenv]
-deps =
- pytest
- unittest2
- py26: importlib
-commands = pytest {posargs}