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/.github/SECURITY.md b/.github/SECURITY.md
new file mode 100644
index 00000000..e4f0e0b3
--- /dev/null
+++ b/.github/SECURITY.md
@@ -0,0 +1,13 @@
+# Security Policy
+
+## Supported Versions
+
+Security updates are applied only to the latest release.
+
+## Reporting a Vulnerability
+
+If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released.
+
+Please disclose it at [security advisory](https://github.com/PythonCharmers/python-future/security/advisories/new).
+
+This project is maintained by a team of volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..bcefff65
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,46 @@
+name: CI
+
+on:
+ pull_request:
+ push:
+
+concurrency:
+ group: ${{ github.head_ref || github.run_id }}
+ cancel-in-progress: true
+
+jobs:
+ test:
+ strategy:
+ fail-fast: false
+ matrix:
+ versions:
+ # - python: "2.6"
+ - python: "2.7"
+ - python: "3.3"
+ - python: "3.4"
+ - python: "3.5"
+ - python: "3.6"
+ - python: "3.7"
+ - python: "3.8"
+ - python: "3.9"
+
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - if: ${{ matrix.versions.python != '2.6' }}
+ run: |
+ docker build \
+ . \
+ --build-arg PYTHON_VERSION=${{ matrix.versions.python }} \
+ -t jmadler/python-future-builder:${{ matrix.versions.python }}
+ - if: ${{ matrix.versions.python == '2.6' }}
+ run: |
+ docker build \
+ . \
+ -f 2.6.Dockerfile \
+ -t jmadler/python-future-builder:${{ matrix.versions.python }}
+ - run: |
+ docker run \
+ -e PYTHON_VERSION=${{ matrix.versions.python }} \
+ jmadler/python-future-builder:${{ matrix.versions.python }} \
+ /root/python-future/test.sh
diff --git a/.gitignore b/.gitignore
index e211af6a..1b8eaeb5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,8 @@ develop-eggs
.installed.cfg
lib
lib64
+MANIFEST
+MANIFEST.in
# Backup files
*.bak
@@ -42,3 +44,8 @@ nosetests.xml
.project
.pydevproject
+# PyCharm / IntelliJ
+.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
deleted file mode 100644
index 3747af6a..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-language: python
-
-python:
- - "3.4"
- - "3.3"
- - "2.7"
- - "2.6"
-
-# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
-# These packages only exist on Ubuntu 13.04 and newer:
-# before_install:
-# - sudo apt-get install -qq libpython2.7-testsuite libpython3.3-testsuite
-# No dependencies currently unless using Python 2.6.
-
-install:
- - if [[ $TRAVIS_PYTHON_VERSION == 2.6* ]]; then pip install -r requirements_py26.txt --use-mirrors; fi
- - python setup.py install
-
-# command to run tests, e.g. python setup.py test
-
-script:
- # We might like to get out of the source directory before running tests to
- # avoid PYTHONPATH confusion? As an example, see here:
- # https://github.com/tornadoweb/tornado/blob/master/.travis.yml
- - python setup.py test
diff --git a/2.6.Dockerfile b/2.6.Dockerfile
new file mode 100644
index 00000000..efaf3809
--- /dev/null
+++ b/2.6.Dockerfile
@@ -0,0 +1,26 @@
+FROM mrupgrade/deadsnakes:2.6
+
+RUN mkdir -p ~/.pip/ && echo '[global] \n\
+trusted-host = pypi.python.org\n\
+ pypi.org\n\
+ files.pythonhosted.org\n\
+' >> ~/.pip/pip.conf
+
+RUN apt-get update && \
+ apt-get install -y curl
+
+RUN mkdir -p /root/pip && \
+ cd /root/pip && \
+ curl -O https://files.pythonhosted.org/packages/8a/e9/8468cd68b582b06ef554be0b96b59f59779627131aad48f8a5bce4b13450/wheel-0.29.0-py2.py3-none-any.whl && \
+ curl -O https://files.pythonhosted.org/packages/31/77/3781f65cafe55480b56914def99022a5d2965a4bb269655c89ef2f1de3cd/importlib-1.0.4.zip && \
+ curl -O https://files.pythonhosted.org/packages/ef/41/d8a61f1b2ba308e96b36106e95024977e30129355fd12087f23e4b9852a1/pytest-3.2.5-py2.py3-none-any.whl && \
+ curl -O https://files.pythonhosted.org/packages/f2/94/3af39d34be01a24a6e65433d19e107099374224905f1e0cc6bbe1fd22a2f/argparse-1.4.0-py2.py3-none-any.whl && \
+ curl -O https://files.pythonhosted.org/packages/72/20/7f0f433060a962200b7272b8c12ba90ef5b903e218174301d0abfd523813/unittest2-1.1.0-py2.py3-none-any.whl && \
+ curl -O https://files.pythonhosted.org/packages/53/67/9620edf7803ab867b175e4fd23c7b8bd8eba11cb761514dcd2e726ef07da/py-1.4.34-py2.py3-none-any.whl && \
+ curl -O https://files.pythonhosted.org/packages/53/25/ef88e8e45db141faa9598fbf7ad0062df8f50f881a36ed6a0073e1572126/ordereddict-1.1.tar.gz && \
+ curl -O https://files.pythonhosted.org/packages/17/0a/6ac05a3723017a967193456a2efa0aa9ac4b51456891af1e2353bb9de21e/traceback2-1.4.0-py2.py3-none-any.whl && \
+ curl -O https://files.pythonhosted.org/packages/65/26/32b8464df2a97e6dd1b656ed26b2c194606c16fe163c695a992b36c11cdf/six-1.13.0-py2.py3-none-any.whl && \
+ curl -O https://files.pythonhosted.org/packages/c7/a3/c5da2a44c85bfbb6eebcfc1dde24933f8704441b98fdde6528f4831757a6/linecache2-1.0.0-py2.py3-none-any.whl
+
+WORKDIR /root/python-future
+ADD . /root/python-future
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..c859757f
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,7 @@
+ARG PYTHON_VERSION
+FROM python:${PYTHON_VERSION}-slim
+
+ENV LC_ALL=C.UTF-8
+
+WORKDIR /root/python-future
+ADD . /root/python-future
diff --git a/LICENSE.txt b/LICENSE.txt
index 65c70446..275cafd3 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,4 +1,4 @@
-Copyright (c) 2013-2014 Python Charmers Pty Ltd, Australia
+Copyright (c) 2013-2024 Python Charmers, 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/MANIFEST.in b/MANIFEST.in
index 5b62df76..d0e9f3d1 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -9,6 +9,7 @@ recursive-include docs README
recursive-include docs *.conf
recursive-include docs *.css_t
recursive-include docs *.html
+recursive-include docs *.ico
recursive-include docs *.inc
recursive-include docs *.ipynb
recursive-include docs *.png
@@ -24,4 +25,3 @@ recursive-include tests *.au
recursive-include tests *.gif
recursive-include tests *.py
recursive-include tests *.txt
-
diff --git a/README.rst b/README.rst
index 13706189..a3aceb7d 100644
--- a/README.rst
+++ b/README.rst
@@ -3,6 +3,9 @@
Overview: Easy, clean, reliable Python 2/3 compatibility
========================================================
+.. image:: https://github.com/PythonCharmers/python-future/actions/workflows/ci.yml/badge.svg?branch=master
+ :target: https://github.com/PythonCharmers/python-future/actions/workflows/ci.yml?query=branch%3Amaster
+
``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.
@@ -13,18 +16,27 @@ ports of features from Python 3 and 2. It also comes with ``futurize`` and
either Py2 or Py3 code easily to support both Python 2 and 3 in a single
clean Py3-style codebase, module by module.
-Notable projects that use ``python-future`` for Python 3/2 compatibility
-are `Mezzanine `_ and `ObsPy
-`_.
+The ``python-future`` project has been downloaded over 1.7 billion times.
+
+.. _status
+
+Status
+------
+
+The ``python-future`` project was created in 2013 to attempt to save Python from
+the schism of version incompatibility that was threatening to tear apart the
+language (as Perl 6 contributed to the death of Perl).
+
+That time is now past. Thanks to a huge porting effort across the Python
+community, Python 3 eventually thrived. Python 2 reached its end of life in
+2020 and the ``python-future`` package should no longer be necessary. Use it to
+help with porting legacy code to Python 3 but don't depend on it for new code.
.. _features:
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
@@ -42,7 +54,7 @@ Features
- ``past.translation`` package supports transparent translation of Python 2
modules to Python 3 upon import. [This feature is currently in alpha.]
-- 920+ unit tests, including many from the Py3.3 source tree.
+- 1000+ unit tests, including many from the Py3.3 source tree.
- ``futurize`` and ``pasteurize`` scripts based on ``2to3`` and parts of
``3to2`` and ``python-modernize``, for automatic conversion from either Py2
@@ -53,6 +65,11 @@ Features
``past.utils`` selected from Py2/3 compatibility interfaces from projects
like ``six``, ``IPython``, ``Jinja2``, ``Django``, and ``Pandas``.
+- support for the ``surrogateescape`` error handler when encoding and
+ decoding the backported ``str`` and ``bytes`` objects. [This feature is
+ currently in alpha.]
+
+- support for pre-commit hooks
.. _code-examples:
@@ -65,7 +82,7 @@ statements. For example, this code behaves identically on Python 2.6/2.7 after
these imports as it does on Python 3.3+:
.. code-block:: python
-
+
from __future__ import absolute_import, division, print_function
from builtins import (bytes, str, open, super, range,
zip, round, input, int, pow, object)
@@ -90,8 +107,8 @@ these imports as it does on Python 3.3+:
# Extra arguments for the open() function
f = open('japanese.txt', encoding='utf-8', errors='replace')
-
- # New simpler super() function:
+
+ # New zero-argument super() function:
class VerboseList(list):
def append(self, item):
print('Adding an item')
@@ -100,15 +117,15 @@ these imports as it does on Python 3.3+:
# New iterable range object with slicing support
for i in range(10**15)[:10]:
pass
-
+
# Other iterators: map, zip, filter
my_iter = zip(range(3), ['a', 'b', 'c'])
assert my_iter != list(my_iter)
-
+
# The round() function behaves as it does in Python 3, using
# "Banker's Rounding" to the nearest even last digit:
assert round(0.1250, 2) == 0.12
-
+
# input() replaces Py2's raw_input() (with no eval()):
name = input('What is your name? ')
print('Hello ' + name)
@@ -149,9 +166,13 @@ interface works like this:
from future import standard_library
standard_library.install_aliases()
- # Then, as usual:
- from itertools import filterfalse
+ # Then, for example:
+ from itertools import filterfalse, zip_longest
from urllib.request import urlopen
+ from collections import ChainMap
+ from collections import UserDict, UserList, UserString
+ from subprocess import getoutput, getstatusoutput
+ from collections import Counter, OrderedDict # backported to Py2.6
Automatic conversion to Py2/3-compatible code
@@ -159,7 +180,7 @@ Automatic conversion to Py2/3-compatible code
``python-future`` comes with two scripts called ``futurize`` and
``pasteurize`` to aid in making Python 2 code or Python 3 code compatible with
-both platforms (Py2&3). It is based on 2to3 and uses fixers from ``lib2to3``,
+both platforms (Py2/3). It is based on 2to3 and uses fixers from ``lib2to3``,
``lib3to2``, and ``python-modernize``, as well as custom fixers.
``futurize`` passes Python 2 code through all the appropriate fixers to turn it
@@ -180,11 +201,10 @@ Futurize: 2 to both
For example, running ``futurize -w mymodule.py`` turns this Python 2 code:
.. code-block:: python
-
+
import Queue
from urllib2 import urlopen
-
def greet(name):
print 'Hello',
print name
@@ -196,14 +216,13 @@ For example, running ``futurize -w mymodule.py`` turns this Python 2 code:
into this code which runs on both Py2 and Py3:
.. code-block:: python
-
+
from __future__ import print_function
from future import standard_library
standard_library.install_aliases()
from builtins import input
import queue
from urllib.request import urlopen
-
def greet(name):
print('Hello', end=' ')
@@ -213,13 +232,16 @@ into this code which runs on both Py2 and Py3:
name = input()
greet(name)
+The first four lines have no effect under Python 3 and can be removed from
+the codebase when Python 2 compatibility is no longer required.
+
See :ref:`forwards-conversion` and :ref:`backwards-conversion` for more details.
Automatic translation
----------------------
+~~~~~~~~~~~~~~~~~~~~~
-The ``past`` package can now automatically translate some simple Python 2
+The ``past`` package can automatically translate some simple Python 2
modules to Python 3 upon import. The goal is to support the "long tail" of
real-world Python 2 modules (e.g. on PyPI) that have not been ported yet. For
example, here is how to use a Python 2-only package called ``plotrique`` on
@@ -228,17 +250,17 @@ Python 3. First install it:
.. code-block:: bash
$ pip3 install plotrique==0.2.5-7 --no-compile # to ignore SyntaxErrors
-
+
(or use ``pip`` if this points to your Py3 environment.)
Then pass a whitelist of module name prefixes to the ``autotranslate()`` function.
Example:
.. code-block:: bash
-
+
$ python3
- >>> from past import autotranslate
+ >>> from past.translation import autotranslate
>>> autotranslate(['plotrique'])
>>> import plotrique
@@ -251,32 +273,67 @@ last resort; ideally Python 2-only dependencies should be ported
properly to a Python 2/3 compatible codebase using a tool like
``futurize`` and the changes should be pushed to the upstream project.
-Note: the translation feature is still in alpha and needs more testing and
-development.
+Note: the auto-translation feature is still in alpha; it needs more testing and
+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
+:Author: Ed Schofield, Jordan M. Adler, et al
-:Copyright: 2013-2014 Python Charmers Pty Ltd, Australia.
+:Copyright: 2013-2024 Python Charmers, Australia.
-:Sponsor: Python Charmers Pty Ltd, Australia, and Python Charmers Pte
- Ltd, Singapore. http://pythoncharmers.com
+:Sponsors: Python Charmers: https://pythoncharmers.com
-:Licence: MIT. See ``LICENSE.txt`` or `here `_.
+ Pinterest https://opensource.pinterest.com
-:Other credits: See `here `_.
+:Licence: MIT. See ``LICENSE.txt`` or `here `_.
+:Other credits: See `here `_.
+
+Docs
+----
+See the docs `here `_.
Next steps
----------
If you are new to Python-Future, check out the `Quickstart Guide
-`_.
+`_.
For an update on changes in the latest version, see the `What's New
-`_ page.
-
+`_ page.
diff --git a/TESTING.txt b/TESTING.txt
index 13aeca83..b2ad5c65 100644
--- a/TESTING.txt
+++ b/TESTING.txt
@@ -1,9 +1,11 @@
-Currently the tests are passing on OS X and Linux on Python 2.6, 2.7, 3.3 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 either with:
+ $ bash build.sh
- $ python setup.py test
-
-which uses the unittest module's test discovery mechanism, or with:
+which tests the module under a number of different python versions, where available, or with:
$ py.test
+
+To execute a single test:
+
+ $ pytest -k test_chained_exceptions_stacktrace
diff --git a/discover_tests.py b/discover_tests.py
deleted file mode 100644
index 9f2e581d..00000000
--- a/discover_tests.py
+++ /dev/null
@@ -1,58 +0,0 @@
-"""
-Simple auto test discovery.
-
-From http://stackoverflow.com/a/17004409
-"""
-import os
-import sys
-import unittest
-
-if not hasattr(unittest.defaultTestLoader, 'discover'):
- try:
- import unittest2 as unittest
- except ImportError:
- raise ImportError('The unittest2 module is required to run tests on Python 2.6')
-
-def additional_tests():
- setup_file = sys.modules['__main__'].__file__
- setup_dir = os.path.abspath(os.path.dirname(setup_file))
- test_dir = os.path.join(setup_dir, 'tests')
- test_suite = unittest.defaultTestLoader.discover(test_dir)
- blacklist = []
- if '/home/travis' in __file__:
- # Skip some tests that fail on travis-ci
- blacklist.append('test_command')
- return exclude_tests(test_suite, blacklist)
-
-
-class SkipCase(unittest.TestCase):
- def skeleton_run_test(self):
- raise unittest.SkipTest("Test fails spuriously on travis-ci")
-
-
-def exclude_tests(suite, blacklist):
- """
- Example:
-
- blacklist = [
- 'test_some_test_that_should_be_skipped',
- 'test_another_test_that_should_be_skipped'
- ]
- """
- new_suite = unittest.TestSuite()
-
- for test_group in suite._tests:
- for test in test_group:
- if not hasattr(test, '_tests'):
- # e.g. ModuleImportFailure
- new_suite.addTest(test)
- continue
- for subtest in test._tests:
- method = subtest._testMethodName
- if method in blacklist:
- setattr(test,
- method,
- getattr(SkipCase(), 'skeleton_run_test'))
- new_suite.addTest(test)
- return new_suite
-
diff --git a/docs/3rd-party-py3k-compat-code/ipython_py3compat.py b/docs/3rd-party-py3k-compat-code/ipython_py3compat.py
index f80a6963..c9fbb2c1 100755
--- a/docs/3rd-party-py3k-compat-code/ipython_py3compat.py
+++ b/docs/3rd-party-py3k-compat-code/ipython_py3compat.py
@@ -41,9 +41,9 @@ def wrapper(func_or_str):
else:
func = func_or_str
doc = func.__doc__
-
+
doc = str_change_func(doc)
-
+
if func:
func.__doc__ = doc
return func
@@ -52,97 +52,97 @@ def wrapper(func_or_str):
if sys.version_info[0] >= 3:
PY3 = True
-
+
input = input
builtin_mod_name = "builtins"
-
+
str_to_unicode = no_code
unicode_to_str = no_code
str_to_bytes = encode
bytes_to_str = decode
cast_bytes_py2 = no_code
-
+
def isidentifier(s, dotted=False):
if dotted:
return all(isidentifier(a) for a in s.split("."))
return s.isidentifier()
-
+
open = orig_open
-
+
MethodType = types.MethodType
-
+
def execfile(fname, glob, loc=None):
loc = loc if (loc is not None) else glob
exec compile(open(fname, 'rb').read(), fname, 'exec') in glob, loc
-
+
# Refactor print statements in doctests.
_print_statement_re = re.compile(r"\bprint (?P.*)$", re.MULTILINE)
def _print_statement_sub(match):
expr = match.groups('expr')
return "print(%s)" % expr
-
+
@_modify_str_or_docstring
def doctest_refactor_print(doc):
"""Refactor 'print x' statements in a doctest to print(x) style. 2to3
unfortunately doesn't pick up on our doctests.
-
+
Can accept a string or a function, so it can be used as a decorator."""
return _print_statement_re.sub(_print_statement_sub, doc)
-
+
# Abstract u'abc' syntax:
@_modify_str_or_docstring
def u_format(s):
""""{u}'abc'" --> "'abc'" (Python 3)
-
+
Accepts a string or a function, so it can be used as a decorator."""
return s.format(u='')
else:
PY3 = False
-
+
input = raw_input
builtin_mod_name = "__builtin__"
-
+
str_to_unicode = decode
unicode_to_str = encode
str_to_bytes = no_code
bytes_to_str = no_code
cast_bytes_py2 = cast_bytes
-
+
import re
_name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$")
def isidentifier(s, dotted=False):
if dotted:
return all(isidentifier(a) for a in s.split("."))
return bool(_name_re.match(s))
-
+
class open(object):
"""Wrapper providing key part of Python 3 open() interface."""
def __init__(self, fname, mode="r", encoding="utf-8"):
self.f = orig_open(fname, mode)
self.enc = encoding
-
+
def write(self, s):
return self.f.write(s.encode(self.enc))
-
+
def read(self, size=-1):
return self.f.read(size).decode(self.enc)
-
+
def close(self):
return self.f.close()
-
+
def __enter__(self):
return self
-
+
def __exit__(self, etype, value, traceback):
self.f.close()
-
+
def MethodType(func, instance):
return types.MethodType(func, instance, type(instance))
-
+
# don't override system execfile on 2.x:
execfile = execfile
-
+
def doctest_refactor_print(func_or_str):
return func_or_str
@@ -151,7 +151,7 @@ def doctest_refactor_print(func_or_str):
@_modify_str_or_docstring
def u_format(s):
""""{u}'abc'" --> "u'abc'" (Python 2)
-
+
Accepts a string or a function, so it can be used as a decorator."""
return s.format(u='u')
diff --git a/docs/3rd-party-py3k-compat-code/jinja2_compat.py b/docs/3rd-party-py3k-compat-code/jinja2_compat.py
index 1326cbc6..0456faae 100644
--- a/docs/3rd-party-py3k-compat-code/jinja2_compat.py
+++ b/docs/3rd-party-py3k-compat-code/jinja2_compat.py
@@ -85,7 +85,7 @@ def encode_filename(filename):
def with_metaclass(meta, *bases):
# This requires a bit of explanation: the basic idea is to make a
- # dummy metaclass for one level of class instanciation that replaces
+ # dummy metaclass for one level of class instantiation that replaces
# itself with the actual metaclass. Because of internal type checks
# we also need to make sure that we downgrade the custom metaclass
# for one level to something closer to type (that's why __call__ and
diff --git a/docs/3rd-party-py3k-compat-code/pandas_py3k.py b/docs/3rd-party-py3k-compat-code/pandas_py3k.py
index 6070c0e9..2a8eb5ae 100755
--- a/docs/3rd-party-py3k-compat-code/pandas_py3k.py
+++ b/docs/3rd-party-py3k-compat-code/pandas_py3k.py
@@ -14,7 +14,7 @@
* Uses the original method if available, otherwise uses items, keys, values.
* types:
* text_type: unicode in Python 2, str in Python 3
- * binary_type: str in Python 2, bythes in Python 3
+ * binary_type: str in Python 2, bytes in Python 3
* string_types: basestring in Python 2, str in Python 3
* bind_method: binds functions to classes
diff --git a/docs/_templates/navbar.html b/docs/_templates/navbar.html
index b77fb767..fc96b5ca 100644
--- a/docs/_templates/navbar.html
+++ b/docs/_templates/navbar.html
@@ -12,7 +12,6 @@
-
{% if theme_navbar_title -%}{{ theme_navbar_title|e }}{%- else -%}{{ project|e }}{%- endif -%}
{{ version|e }}
diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html
index d2372bf6..25325ec3 100644
--- a/docs/_templates/sidebarintro.html
+++ b/docs/_templates/sidebarintro.html
@@ -1,7 +1,7 @@
Easy, clean, reliable Python 2/3 compatibility
- Table of Contents
+ Table of Contents
diff --git a/docs/_templates/sidebartoc.html b/docs/_templates/sidebartoc.html
index 0d119afc..629fb6a1 100644
--- a/docs/_templates/sidebartoc.html
+++ b/docs/_templates/sidebartoc.html
@@ -1 +1 @@
-{{ toctree(maxdepth=theme_globaltoc_depth|toint, collapse=True, includehidden=theme_globaltoc_includehidden|tobool) }}
+{{ toctree(maxdepth=2, collapse=True, includehidden=True) }}
diff --git a/docs/_themes/future/static/future.css_t b/docs/_themes/future/static/future.css_t
index 6130f5c3..593da466 100644
--- a/docs/_themes/future/static/future.css_t
+++ b/docs/_themes/future/static/future.css_t
@@ -14,11 +14,11 @@
{% set sidebar_width = '220px' %}
{% set font_family = 'Geneva, sans serif' %}
{% set header_font_family = 'Oxygen, ' ~ font_family %}
-
+
@import url("basic.css");
-
+
/* -- page layout ----------------------------------------------------------- */
-
+
body {
font-family: {{ font_family }};
font-size: 17px;
@@ -49,7 +49,7 @@ div.sphinxsidebar {
hr {
border: 1px solid #B1B4B6;
}
-
+
div.body {
background-color: #ffffff;
color: #3E4349;
@@ -60,7 +60,7 @@ img.floatingflask {
padding: 0 0 10px 10px;
float: right;
}
-
+
div.footer {
width: {{ page_width }};
margin: 20px auto 30px auto;
@@ -76,7 +76,7 @@ div.footer a {
div.related {
display: none;
}
-
+
div.sphinxsidebar a {
color: #444;
text-decoration: none;
@@ -86,7 +86,7 @@ div.sphinxsidebar a {
div.sphinxsidebar a:hover {
border-bottom: 1px solid #999;
}
-
+
div.sphinxsidebar {
font-size: 15px;
line-height: 1.5;
@@ -101,7 +101,7 @@ div.sphinxsidebarwrapper p.logo {
margin: 0;
text-align: center;
}
-
+
div.sphinxsidebar h3,
div.sphinxsidebar h4 {
font-family: {{ font_family }};
@@ -115,7 +115,7 @@ div.sphinxsidebar h4 {
div.sphinxsidebar h4 {
font-size: 20px;
}
-
+
div.sphinxsidebar h3 a {
color: #444;
}
@@ -126,7 +126,7 @@ div.sphinxsidebar p.logo a:hover,
div.sphinxsidebar h3 a:hover {
border: none;
}
-
+
div.sphinxsidebar p {
color: #555;
margin: 10px 0;
@@ -137,7 +137,7 @@ div.sphinxsidebar ul {
padding: 0;
color: #000;
}
-
+
div.sphinxsidebar input {
border: 1px solid #ccc;
font-family: {{ font_family }};
@@ -147,19 +147,19 @@ div.sphinxsidebar input {
div.sphinxsidebar form.search input[name="q"] {
width: 130px;
}
-
+
/* -- body styles ----------------------------------------------------------- */
-
+
a {
color: #aa0000;
text-decoration: underline;
}
-
+
a:hover {
color: #dd0000;
text-decoration: underline;
}
-
+
div.body h1,
div.body h2,
div.body h3,
@@ -172,25 +172,25 @@ div.body h6 {
padding: 0;
color: black;
}
-
+
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
div.body h2 { font-size: 180%; }
div.body h3 { font-size: 150%; }
div.body h4 { font-size: 130%; }
div.body h5 { font-size: 100%; }
div.body h6 { font-size: 100%; }
-
+
a.headerlink {
color: #ddd;
padding: 0 4px;
text-decoration: none;
}
-
+
a.headerlink:hover {
color: #444;
background: #eaeaea;
}
-
+
div.body p, div.body dd, div.body li {
line-height: 1.4em;
}
@@ -237,20 +237,20 @@ div.note {
background-color: #eee;
border: 1px solid #ccc;
}
-
+
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
-
+
div.topic {
background-color: #eee;
}
-
+
p.admonition-title {
display: inline;
}
-
+
p.admonition-title:after {
content: ":";
}
@@ -344,7 +344,7 @@ ul, ol {
margin: 10px 0 10px 30px;
padding: 0;
}
-
+
pre {
background: #eee;
padding: 7px 30px;
@@ -361,7 +361,7 @@ dl dl pre {
margin-left: -90px;
padding-left: 90px;
}
-
+
tt {
background-color: #E8EFF0;
color: #222;
diff --git a/docs/automatic_conversion.rst b/docs/automatic_conversion.rst
index 7821101a..5c718da5 100644
--- a/docs/automatic_conversion.rst
+++ b/docs/automatic_conversion.rst
@@ -1,11 +1,11 @@
.. _automatic-conversion:
-Automatic conversion to Py2&3
+Automatic conversion to Py2/3
=============================
The ``future`` source tree includes scripts called ``futurize`` and
``pasteurize`` to aid in making Python 2 code or Python 3 code compatible with
-both platforms (Py2&3) using the :mod:`future` module. These are based on
+both platforms (Py2/3) using the :mod:`future` module. These are based on
``lib2to3`` and use fixers from ``2to3``, ``3to2``, and ``python-modernize``.
``futurize`` passes Python 2 code through all the appropriate fixers to turn it
@@ -22,7 +22,8 @@ mostly unchanged on both Python 2 and Python 3.
.. include:: futurize.rst
+.. include:: futurize_cheatsheet.rst
+
.. include:: pasteurize.rst
.. include:: conversion_limitations.rst
-
diff --git a/docs/bind_method.rst b/docs/bind_method.rst
index 7eb91a43..d737384c 100644
--- a/docs/bind_method.rst
+++ b/docs/bind_method.rst
@@ -9,10 +9,10 @@ from the language. To bind a method to a class compatibly across Python
3 and Python 2, you can use the :func:`bind_method` helper function::
from future.utils import bind_method
-
+
class Greeter(object):
pass
-
+
def greet(self, message):
print(message)
@@ -24,6 +24,6 @@ from the language. To bind a method to a class compatibly across Python
On Python 3, calling ``bind_method(cls, name, func)`` is equivalent to
calling ``setattr(cls, name, func)``. On Python 2 it is equivalent to::
-
+
import types
setattr(cls, name, types.MethodType(func, None, cls))
diff --git a/docs/bytes_object.rst b/docs/bytes_object.rst
index 556edb4b..110280ad 100644
--- a/docs/bytes_object.rst
+++ b/docs/bytes_object.rst
@@ -26,7 +26,7 @@ strict separation of unicode strings and byte strings as Python 3's
Traceback (most recent call last):
File "", line 1, in
TypeError: argument can't be unicode string
-
+
>>> bytes(b',').join([u'Fred', u'Bill'])
Traceback (most recent call last):
File "", line 1, in
@@ -47,14 +47,14 @@ behaviours to Python 3's :class:`bytes`::
b = bytes(b'ABCD')
assert list(b) == [65, 66, 67, 68]
assert repr(b) == "b'ABCD'"
- assert b.split(b'b') == [b'A', b'CD']
+ assert b.split(b'B') == [b'A', b'CD']
Currently the easiest way to ensure identical behaviour of byte-strings
in a Py2/3 codebase is to wrap all byte-string literals ``b'...'`` in a
:func:`~bytes` call as follows::
-
+
from builtins import bytes
-
+
# ...
b = bytes(b'This is my bytestring')
@@ -66,26 +66,15 @@ code incompatibilities caused by the many differences between Py3 bytes
and Py2 strings.
-..
- .. _bytes-test-results:
-
- bytes test results
- ~~~~~~~~~~~~~~~~~~
-
- For reference, when using Py2's default :class:`bytes` (i.e.
- :class:`str`), running the ``bytes`` unit tests from Python 3.3's
- ``test_bytes.py`` on Py2 (after fixing imports) gives this::
-
- --------------------------------------------------------------
- Ran 203 tests in 0.209s
-
- FAILED (failures=31, errors=55, skipped=1)
- --------------------------------------------------------------
-
- Using :mod:`future`'s backported :class:`bytes` object passes most of
- the same Python 3.3 tests on Py2, except those requiring specific
- wording in exception messages.
-
- See ``future/tests/test_bytes.py`` in the source for the actual set
- of unit tests that are actually run.
+The :class:`bytes` type from :mod:`builtins` also provides support for the
+``surrogateescape`` error handler on Python 2.x. Here is an example that works
+identically on Python 2.x and 3.x::
+
+ >>> from builtins import bytes
+ >>> b = bytes(b'\xff')
+ >>> b.decode('utf-8', 'surrogateescape')
+ '\udcc3'
+This feature is in alpha. Please leave feedback `here
+`_ about whether this
+works for you.
diff --git a/docs/changelog.rst b/docs/changelog.rst
index b540b0dc..420e2bc4 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -3,62 +3,479 @@
Changes in previous versions
****************************
-Changes in the most recent two versions are here: :ref:`whats-new`.
+Changes in the most recent major version are here: :ref:`whats-new`.
+
+Changes 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)
+
+Changes 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.
+
+Changes 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)
+
+Changes 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 ambiguous
+- 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.
+
+Changes 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.
+
+Changes in version 0.17.0 (2018-10-19)
+======================================
+
+This is a major bug-fix release, including:
+
+- Fix ``from collections import ChainMap`` after install_aliases() (issue #226)
+- Fix multiple import from ``__future__`` bug in futurize (issue #113)
+- Add support for proper %s formatting of newbytes
+- Properly implement iterator protocol for newrange object
+- Fix ``past.translation`` on read-only file systems
+- Fix Tkinter import bug introduced in Python 2.7.4 (issue #262)
+- Correct TypeError to ValueError in a specific edge case for newrange
+- Support inequality tests between newstrs and newbytes
+- Add type check to __get__ in newsuper
+- Fix fix_divsion_safe to support better conversion of complex expressions, and
+ skip obvious float division.
+
+As well as a number of corrections to a variety of documentation, and updates to
+test infrastructure.
+
+Changes in version 0.16.0 (2016-10-27)
+======================================
+
+This release removes the ``configparser`` package as an alias for
+``ConfigParser`` on Py2 to improve compatibility with the backported
+`configparser package `. Previously
+``python-future`` and the PyPI ``configparser`` backport clashed, causing
+various compatibility issues. (Issues #118, #181)
+
+If your code previously relied on ``configparser`` being supplied by
+``python-future``, the recommended upgrade path is to run ``pip install
+configparser`` or add ``configparser`` to your ``requirements.txt`` file.
+
+Note that, if you are upgrading ``future`` with ``pip``, you may need to
+uninstall the old version of future or manually remove the
+``site-packages/future-0.15.2-py2.7.egg`` folder for this change to take
+effect on your system.
+
+This releases also fixes these bugs:
+
+- 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)
+- Fix return argument of ``future.utils.ensure_new_type`` if conversion to
+ new type does not exist. (Issue #185)
+- Add missing ``cmp_to_key`` for Py2.6. (Issue #189)
+- Allow the ``old_div`` fixer to be disabled. (Issue #190)
+- Improve compatibility with Google App Engine. (Issue #231)
+- Add some missing imports to the ``tkinter`` and ``tkinter.filedialog``
+ package namespaces. (Issues #212 and #233)
+- More complete implementation of ``raise_from`` on PY3. (Issues #141,
+ #213 and #235, fix provided by Varriount)
+
+
+Changes in version 0.15.2 (2015-09-11)
+======================================
+
+This is a minor bug-fix release:
+
+- Fix ``socket.create_connection()`` backport on Py2.6 (issue #162)
+- Add more tests of ``urllib.request`` etc.
+- Fix ``newsuper()`` calls from the ``__init__`` method of PyQt subclassses
+ (issue #160, thanks to Christopher Arndt)
+
+Changes in version 0.15.1 (2015-09-09)
+======================================
+
+This is a minor bug-fix release:
+
+- Use 3-argument ``socket.create_connection()`` backport to restore Py2.6
+ compatibility in ``urllib.request.urlopen()`` (issue #162)
+- Remove breakpoint in ``future.backports.http.client`` triggered on certain
+ data (issue #164)
+- Move ``exec`` fixer to stage 1 of ``futurize`` because the forward-compatible ``exec(a, b)``
+ idiom is supported in Python 2.6 and 2.7. See
+ https://docs.python.org/2/reference/simple_stmts.html#exec.
+
+
+Changes in version 0.15.0 (2015-07-25)
+======================================
+
+This release fixes compatibility bugs with CherryPy's Py2/3 compat layer and
+the latest version of the ``urllib3`` package. It also adds some additional
+backports for Py2.6 and Py2.7 from Py3.4's standard library.
+
+New features:
+
+- ``install_aliases()`` now exposes full backports of the Py3 urllib submodules
+ (``parse``, ``request`` etc.) from ``future.backports.urllib`` as submodules
+ of ``urllib`` on Py2. This implies, for example, that
+ ``urllib.parse.unquote`` now takes an optional encoding argument as it does
+ on Py3. This improves compatibility with CherryPy's Py2/3 compat layer (issue
+ #158).
+- ``tkinter.ttk`` support (issue #151)
+- Backport of ``collections.ChainMap`` (issue #150)
+- Backport of ``itertools.count`` for Py2.6 (issue #152)
+- Enable and document support for the ``surrogateescape`` error handler for ``newstr`` and ``newbytes`` objects on Py2.x (issue #116). This feature is currently in alpha.
+- Add constants to ``http.client`` such as ``HTTP_PORT`` and ``BAD_REQUEST`` (issue #137)
+- Backport of ``reprlib.recursive_repr`` to Py2
+
+Bug fixes:
+
+- Add ``HTTPMessage`` to ``http.client``, which is missing from ``httplib.__all__`` on Python <= 2.7.10. This restores compatibility with the latest ``urllib3`` package (issue #159, thanks to Waldemar Kornewald)
+- Expand newint.__divmod__ and newint.__rdivmod__ to fall back to
+ implementations where appropriate (issue #146 - thanks to Matt Bogosian)
+- Fix newrange slicing for some slice/range combos (issue #132, thanks to Brad Walker)
+- Small doc fixes (thanks to Michael Joseph and Tim Tröndle)
+- Improve robustness of test suite against opening .pyc files as text on Py2
+- Update backports of ``Counter`` and ``OrderedDict`` to use the newer
+ implementations from Py3.4. This fixes ``.copy()`` preserving subclasses etc.
+- ``futurize`` no longer breaks working Py2 code by changing ``basestring`` to
+ ``str``. Instead it imports the ``basestring`` forward-port from
+ ``past.builtins`` (issues #127 and #156)
+- ``future.utils``: add ``string_types`` etc. and update docs (issue #126)
+
+
+.. _whats-new-0.14.x:
+
+Changes in version 0.14.3 (2014-12-15)
+======================================
+
+This is a bug-fix release:
+
+- Expose contents of ``thread`` (not ``dummy_thread``) as ``_thread`` on Py2 (Issue #124)
+- Add signed support for ``newint.to_bytes()`` (Issue #128)
+- Fix ``OrderedDict.clear()`` on Py2.6 (Issue #125)
+- Improve ``newrange``: equality and slicing, start/stop/step properties, refactoring (Issues #129, #130)
+- Minor doc updates
+
+Changes in version 0.14.2 (2014-11-21)
+======================================
+
+This is a bug-fix release:
+
+- Speed up importing of ``past.translation`` (Issue #117)
+- ``html.escape()``: replace function with the more robust one from Py3.4
+- ``futurize``: avoid displacing encoding comments by ``__future__`` imports (Issues #97, #10, #121)
+- ``futurize``: don't swallow exit code (Issue #119)
+- Packaging: don't forcibly remove the old build dir in ``setup.py`` (Issue #108)
+- Docs: update further docs and tests to refer to ``install_aliases()`` instead of
+ ``install_hooks()``
+- Docs: fix ``iteritems`` import error in cheat sheet (Issue #120)
+- Tests: don't rely on presence of ``test.test_support`` on Py2 or ``test.support`` on Py3 (Issue #109)
+- Tests: don't override existing ``PYTHONPATH`` for tests (PR #111)
+
+Changes in version 0.14.1 (2014-10-02)
+======================================
+
+This is a minor bug-fix release:
+
+- Docs: add a missing template file for building docs (Issue #108)
+- Tests: fix a bug in error handling while reporting failed script runs (Issue #109)
+- ``install_aliases()``: don't assume that the ``test.test_support`` module always
+ exists on Py2 (Issue #109)
+
+
+Changes in version 0.14.0 (2014-10-02)
+======================================
+
+This is a major new release that offers a cleaner interface for most imports in
+Python 2/3 compatible code.
+
+Instead of this interface::
+
+ >>> from future.builtins import str, open, range, dict
+
+ >>> from future.standard_library import hooks
+ >>> with hooks():
+ ... import queue
+ ... import configparser
+ ... import tkinter.dialog
+ ... # etc.
+
+You can now use the following interface for much Python 2/3 compatible code::
+
+ >>> # Alias for future.builtins on Py2:
+ >>> from builtins import str, open, range, dict
+
+ >>> # Alias for future.moves.* on Py2:
+ >>> import queue
+ >>> import configparser
+ >>> import tkinter.dialog
+ >>> etc.
+
+Notice that the above code will run on Python 3 even without the presence of the
+``future`` package. Of the 44 standard library modules that were refactored with
+PEP 3108, 30 are supported with direct imports in this manner. (These are listed
+here: :ref:`direct-imports`.)
+
+The other 14 standard library modules that kept the same top-level names in
+Py3.x are not supported with this direct import interface on Py2. These include
+the 5 modules in the Py3 ``urllib`` package. These modules are accessible through
+the following interface (as well as the interfaces offered in previous versions
+of ``python-future``)::
+
+ from future.standard_library import install_aliases
+ install_aliases()
+
+ from collections import UserDict, UserList, UserString
+ import dbm.gnu
+ from itertools import filterfalse, zip_longest
+ from subprocess import getoutput, getstatusoutput
+ from sys import intern
+ import test.support
+ from urllib.request import urlopen
+ from urllib.parse import urlparse
+ # etc.
+ from collections import Counter, OrderedDict # backported to Py2.6
+
+The complete list of packages supported with this interface is here:
+:ref:`list-standard-library-refactored`.
+
+For more information on these and other interfaces to the standard library, see
+:ref:`standard-library-imports`.
+
+Bug fixes
+---------
+
+- This release expands the ``future.moves`` package to include most of the remaining
+ modules that were moved in the standard library reorganization (PEP 3108).
+ (Issue #104)
+
+- This release also removes the broken ``--doctests_only`` option from the ``futurize``
+ and ``pasteurize`` scripts for now. (Issue #103)
+
+Internal cleanups
+-----------------
+
+The project folder structure has changed. Top-level packages are now in a
+``src`` folder and the tests have been moved into a project-level ``tests``
+folder.
+
+The following deprecated internal modules have been removed (Issue #80):
+
+- ``future.utils.encoding`` and ``future.utils.six``.
+
+Deprecations
+------------
+
+The following internal functions have been deprecated and will be removed in a future release:
+
+- ``future.standard_library.scrub_py2_sys_modules``
+- ``future.standard_library.scrub_future_sys_modules``
+
+
+.. _whats-new-0.13.x:
+
+Changes in version 0.13.1 (2014-09-23)
+======================================
+
+This is a bug-fix release:
+
+- Fix (multiple) inheritance of ``future.builtins.object`` with metaclasses (Issues #91, #96)
+- Fix ``futurize``'s refactoring of ``urllib`` imports (Issue #94)
+- Fix ``futurize --all-imports`` (Issue #101)
+- Fix ``futurize --output-dir`` logging (Issue #102)
+- Doc formatting fix (Issues #98, #100)
+
+
+Changes in version 0.13.0 (2014-08-13)
+======================================
+
+This is mostly a clean-up release. It adds some small new compatibility features
+and fixes several bugs.
+
+Deprecations
+------------
+
+The following unused internal modules are now deprecated. They will be removed in a
+future release:
+
+- ``future.utils.encoding`` and ``future.utils.six``.
+
+(Issue #80). See `here `_
+for the rationale for unbundling them.
+
+
+New features
+------------
+
+- Docs: Add :ref:`compatible-idioms` from Ed Schofield's PyConAU 2014 talk.
+- Add ``newint.to_bytes()`` and ``newint.from_bytes()``. (Issue #85)
+- Add ``future.utils.raise_from`` as an equivalent to Py3's ``raise ... from
+ ...`` syntax. (Issue #86)
+- Add ``past.builtins.oct()`` function.
+- Add backports for Python 2.6 of ``subprocess.check_output()``,
+ ``itertools.combinations_with_replacement()``, and ``functools.cmp_to_key()``.
+
+Bug fixes
+---------
+
+- Use a private logger instead of the global logger in
+ ``future.standard_library`` (Issue #82). This restores compatibility of the
+ standard library hooks with ``flask``. (Issue #79)
+- Stage 1 of ``futurize`` no longer renames ``next`` methods to ``__next__``
+ (Issue #81). It still converts ``obj.next()`` method calls to
+ ``next(obj)`` correctly.
+- Prevent introduction of a second set of parentheses in ``print()`` calls in
+ some further cases.
+- Fix ``isinstance`` checks for subclasses of future types. (Issue #89)
+- Be explicit about encoding file contents as UTF-8 in unit tests. (Issue #63)
+ Useful for building RPMs and in other environments where ``LANG=C``.
+- Fix for 3-argument ``pow(x, y, z)`` with ``newint`` arguments. (Thanks to @str4d.)
+ (Issue #87)
+
.. _whats-new-0.12.4:
-Changes in version 0.12.4
-=========================
+Changes in version 0.12.4 (2014-07-18)
+======================================
-- Fix upcasting behaviour of newint (issue #76).
+- Fix upcasting behaviour of ``newint``. (Issue #76)
.. _whats-new-0.12.3:
-Changes in version 0.12.3
-=========================
+Changes in version 0.12.3 (2014-06-19)
+======================================
- Add "official Python 3.4 support": Py3.4 is now listed among the PyPI Trove
- classifiers and the tests now run successfully on Py3.4 (issue #67).
+ classifiers and the tests now run successfully on Py3.4. (Issue #67)
- Add backports of ``collections.OrderedDict`` and
- ``collections.Counter`` for Python 2.6 (issue #52).
+ ``collections.Counter`` for Python 2.6. (Issue #52)
-- Add ``--version`` option for ``futurize`` and ``pasteurize`` scripts
- (issue #57).
+- Add ``--version`` option for ``futurize`` and ``pasteurize`` scripts.
+ (Issue #57)
-- Fix ``future.utils.ensure_new_type`` with ``long`` input (issue #65).
+- Fix ``future.utils.ensure_new_type`` with ``long`` input. (Issue #65)
- Remove some false alarms on checks for ambiguous fixer names with
``futurize -f ...``.
- Testing fixes:
- - Don't hard-code Python interpreter command in tests (issue #62).
- - Fix deprecated ``unittest`` usage in Py3 (also issue #62).
+ - Don't hard-code Python interpreter command in tests. (Issue #62)
+ - Fix deprecated ``unittest`` usage in Py3. (Issue #62)
- Be explicit about encoding temporary file contents as UTF-8 for
- when LANG=C (e.g. when building an RPM) (issue #63).
+ when ``LANG=C`` (e.g., when building an RPM). (Issue #63)
- All undecorated tests are now passing again on Python 2.6, 2.7, 3.3,
and 3.4 (thanks to Elliott Sales de Andrade).
- Docs:
- - Add list of fixers used by ``futurize`` (issue #58).
+ - Add list of fixers used by ``futurize``. (Issue #58)
- Add list of contributors to the Credits page.
.. _whats-new-0.12.2:
-Changes in version 0.12.2
-=========================
+Changes in version 0.12.2 (2014-05-25)
+======================================
-- Add ``bytes.maketrans()`` method (issue #51).
-- Add support for Python versions between 2.7.0 and 2.7.3 (inclusive)
- (issue #53).
-- Bug fix for ``newlist(newlist([1, 2, 3]))`` (issue #50).
+- Add ``bytes.maketrans()`` method. (Issue #51)
+- Add support for Python versions between 2.7.0 and 2.7.3 (inclusive).
+ (Issue #53)
+- Bug fix for ``newlist(newlist([1, 2, 3]))``. (Issue #50)
.. _whats-new-0.12.1:
-Changes in version 0.12.1
-=========================
+Changes in version 0.12.1 (2014-05-14)
+======================================
- Python 2.6 support: ``future.standard_library`` now isolates the ``importlib``
dependency to one function (``import_``) so the ``importlib`` backport may
@@ -69,8 +486,8 @@ Changes in version 0.12.1
.. _whats-new-0.12:
-Changes in version 0.12.0
-=========================
+Changes in version 0.12.0 (2014-05-06)
+======================================
The major new feature in this version is improvements in the support for the
reorganized standard library (PEP 3108) and compatibility of the import
@@ -116,7 +533,7 @@ with tools like ``py2exe``.
-------------------------------------------------------------------------
There is a new ``future.types.newobject`` base class (available as
-``future.builtins.object``) that can streamline Py3/2 compatible code by
+``future.builtins.object``) that can streamline Py2/3 compatible code by
providing fallback Py2-compatible special methods for its subclasses. It
currently provides ``next()`` and ``__nonzero__()`` as fallback methods on Py2
when its subclasses define the corresponding Py3-style ``__next__()`` and
@@ -129,7 +546,7 @@ Py3-style ``__next__`` method.
In this example, the code defines a Py3-style iterator with a ``__next__``
method. The ``object`` class defines a ``next`` method for Python 2 that maps
to ``__next__``::
-
+
from future.builtins import object
class Upper(object):
@@ -163,7 +580,7 @@ functions like ``map()`` and ``filter()`` now behave as they do on Py2 with with
The ``past.builtins`` module has also been extended to add Py3 support for
additional Py2 constructs that are not adequately handled by ``lib2to3`` (see
-issue #37). This includes new ``execfile()`` and ``cmp()`` functions.
+Issue #37). This includes new ``execfile()`` and ``cmp()`` functions.
``futurize`` now invokes imports of these functions from ``past.builtins``.
@@ -191,7 +608,7 @@ dictionaries in both Python 2 and Python 3.
These came out of the discussion around Nick Coghlan's now-withdrawn PEP 469.
-There is no corresponding ``listkeys(d)`` function. Use ``list(d)`` for this case.
+There is no corresponding ``listkeys(d)`` function; use ``list(d)`` instead.
Tests
@@ -220,7 +637,7 @@ Use them like this::
from future.backports.urllib.request import Request # etc.
from future.backports.http import server as http_server
-or with this new interface::
+Or with this new interface::
from future.standard_library import import_, from_import
@@ -244,7 +661,7 @@ of these types. For example::
>>> type(b)
future.types.newbytes.newbytes
-instead of::
+Instead of::
>>> type(b) # prior to v0.12
future.builtins.types.newbytes.newbytes
@@ -261,7 +678,7 @@ Many small improvements and fixes have been made across the project. Some highli
- Scrubbing of the ``sys.modules`` cache performed by ``remove_hooks()`` (also
called by the ``suspend_hooks`` and ``hooks`` context managers) is now more
conservative.
-
+
.. Is this still true?
.. It now removes only modules with Py3 names (such as
.. ``urllib.parse``) and not the corresponding ``future.standard_library.*``
@@ -272,9 +689,9 @@ Many small improvements and fixes have been made across the project. Some highli
- ``futurize``: Shebang lines such as ``#!/usr/bin/env python`` and source code
file encoding declarations like ``# -*- coding=utf-8 -*-`` are no longer occasionally
- displaced by ``from __future__ import ...`` statements. (Issue #10.)
+ displaced by ``from __future__ import ...`` statements. (Issue #10)
-- Improved compatibility with py2exe (`issue #31 `_).
+- Improved compatibility with ``py2exe`` (`Issue #31 `_).
- The ``future.utils.bytes_to_native_str`` function now returns a platform-native string
object and ``future.utils.native_str_to_bytes`` returns a ``newbytes`` object on Py2.
@@ -287,17 +704,17 @@ Many small improvements and fixes have been made across the project. Some highli
.. _whats-new-0.11.4:
-Changes in version 0.11.4
-=========================
+Changes in version 0.11.4 (2014-05-25)
+======================================
This release contains various small improvements and fixes:
-- This release restores Python 2.6 compatibility. (Issue #42).
+- This release restores Python 2.6 compatibility. (Issue #42)
- The ``fix_absolute_import`` fixer now supports Cython ``.pyx`` modules. (Issue
- #35).
+ #35)
-- Right-division with ``newint`` objects is fixed. (Issue #38).
+- Right-division with ``newint`` objects is fixed. (Issue #38)
- The ``fix_dict`` fixer has been moved to stage2 of ``futurize``.
@@ -307,15 +724,15 @@ This release contains various small improvements and fixes:
- The 0-argument ``super()`` function now works from inside static methods such
- as ``__new__``. (Issue #36).
+ as ``__new__``. (Issue #36)
- ``future.utils.native(d)`` calls now work for ``future.builtins.dict`` objects.
.. _whats-new-0.11.3:
-Changes in version 0.11.3
-=========================
+Changes in version 0.11.3 (2014-02-27)
+======================================
This release has improvements in the standard library import hooks mechanism and
its compatibility with 3rd-party modules:
@@ -339,8 +756,8 @@ is now possible on Python 2 and 3::
Previously, this required manually removing ``http`` and ``http.client`` from
-``sys.modules`` before importing ``requests`` on Python 2.x. (Issue #19).
-
+``sys.modules`` before importing ``requests`` on Python 2.x. (Issue #19)
+
This change should also improve the compatibility of the standard library hooks
with any other module that provides its own Python 2/3 compatibility code.
@@ -370,22 +787,22 @@ compatibility code.
There is a new ``--unicode-literals`` flag to ``futurize`` that adds the
import::
-
+
from __future__ import unicode_literals
to the top of each converted module. Without this flag, ``futurize`` now no
-longer adds this import. (Issue #22).
+longer adds this import. (Issue #22)
The ``pasteurize`` script for converting from Py3 to Py2/3 still adds
-``unicode_literals``. (See the comments in issue #22 for an explanation.)
+``unicode_literals``. (See the comments in Issue #22 for an explanation.)
.. _whats-new-0.11:
-Changes in version 0.11
-=======================
+Changes in version 0.11 (2014-01-28)
+====================================
-There are several major new features in version 0.11.
+There are several major new features in version 0.11.
``past`` package
@@ -425,11 +842,11 @@ 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::
-
- >>> from past import autotranslate
+
+Then pass in a whitelist of module name prefixes to the
+``past.translation.autotranslate()`` function. Example::
+
+ >>> from past.translation import autotranslate
>>> autotranslate(['plotrique'])
>>> import plotrique
@@ -451,16 +868,16 @@ The functionality from ``futurize --from3`` is now in a separate script called
2/3 compatible source. For more information, see :ref:`backwards-conversion`.
-pow()
------
+``pow()``
+---------
There is now a ``pow()`` function in ``future.builtins.misc`` that behaves like
the Python 3 ``pow()`` function when raising a negative number to a fractional
power (returning a complex number).
-input() no longer disabled globally on Py2
-------------------------------------------
+``input()`` no longer disabled globally on Py2
+----------------------------------------------
Previous versions of ``future`` deleted the ``input()`` function from
``__builtin__`` on Python 2 as a security measure. This was because
@@ -485,18 +902,18 @@ deprecated in order to improve robustness and compatibility with modules like
``requests`` that already perform their own single-source Python 2/3
compatibility.
-As of v0.12 of ``python-future``, importing ``future.standard_library``
+As of v0.12, importing ``future.standard_library``
will no longer install import hooks by default. Instead, please install the
import hooks explicitly as follows::
-
+
from future import standard_library
standard_library.install_hooks()
-and uninstall them after your import statements using::
+And uninstall them after your import statements using::
standard_library.remove_hooks()
-*Note*: this will be a backward-incompatible change.
+*Note*: This is a backward-incompatible change.
@@ -510,11 +927,11 @@ types but not their use.
.. _whats-new-0.10.2:
-Changes in version 0.10.2
-=========================
+Changes in version 0.10.2 (2014-01-11)
+======================================
-New context-manager interface to standard_library hooks
--------------------------------------------------------
+New context-manager interface to ``standard_library.hooks``
+-----------------------------------------------------------
There is a new context manager ``future.standard_library.hooks``. Use it like
this::
@@ -531,27 +948,27 @@ If not using this context manager, it is now encouraged to add an explicit call
from future import standard_library
standard_library.install_hooks()
-
+
import queue
import html
import http.client
# etc.
-and to remove the hooks afterwards with::
+And to remove the hooks afterwards with::
standard_library.remove_hooks()
The functions ``install_hooks()`` and ``remove_hooks()`` were previously
called ``enable_hooks()`` and ``disable_hooks()``. The old names are
-still available as aliases, but are deprecated.
+deprecated (but are still available as aliases).
As usual, this feature has no effect on Python 3.
.. _whats-new-0.10:
-Changes in version 0.10.0
-=========================
+Changes in version 0.10.0 (2013-12-02)
+======================================
Backported ``dict`` type
------------------------
@@ -564,7 +981,7 @@ over large dictionaries. For example::
from __future__ import print_function
from future.builtins import dict, range
-
+
squares = dict({i: i**2 for i in range(10**7)})
assert not isinstance(d.items(), list)
@@ -588,15 +1005,15 @@ A portable ``exec_()`` function has been added to ``future.utils`` from
Bugfixes
--------
-- Fixed newint.__divmod__
+- Fixed ``newint.__divmod__``
- Improved robustness of installing and removing import hooks in :mod:`future.standard_library`
- v0.10.1: Fixed broken ``pip install future`` on Py3
.. _whats-new-0.9:
-Changes in version 0.9
-======================
+Changes in version 0.9 (2013-11-06)
+===================================
``isinstance`` checks are supported natively with backported types
@@ -635,8 +1052,8 @@ byte-strings and unicode strings, such as ``os.path.join`` in ``posixpath.py``.
Python 3 when attempting to mix it with ``future.builtins.bytes``.
-suspend_hooks() context manager added to ``future.standard_library``
---------------------------------------------------------------------
+``suspend_hooks()`` context manager added to ``future.standard_library``
+------------------------------------------------------------------------
Pychecker (as of v0.6.1)'s ``checker.py`` attempts to import the ``builtins``
module as a way of determining whether Python 3 is running. Since this
@@ -655,8 +1072,8 @@ To work around this, ``future`` now provides a context manager called
.. _whats-new-0.8:
-Changes in version 0.8
-======================
+Changes in version 0.8 (2013-10-28)
+===================================
Python 2.6 support
------------------
@@ -683,13 +1100,13 @@ alongside each other easily if needed.
The unused ``hacks`` module has also been removed from the source tree.
-isinstance() added to :mod:`future.builtins` (v0.8.2)
------------------------------------------------------
+``isinstance()`` added to :mod:`future.builtins` (v0.8.2)
+---------------------------------------------------------
-It is now possible to use ``isinstance()`` calls normally after importing ``isinstance`` from
+It is now possible to use ``isinstance()`` calls normally after importing ``isinstance`` from
``future.builtins``. On Python 2, this is specially defined to be compatible with
``future``'s backported ``int``, ``str``, and ``bytes`` types, as well as
-handling Python 2's int/long distinction.
+handling Python 2's ``int``/``long`` distinction.
The result is that code that uses ``isinstance`` to perform type-checking of
ints, strings, and bytes should now work identically on Python 2 as on Python 3.
@@ -704,7 +1121,23 @@ deprecated.
Summary of all changes
======================
-v0.14:
+v0.15.0:
+ * Full backports of ``urllib.parse`` and other ``urllib`` submodules are exposed by ``install_aliases()``.
+ * ``tkinter.ttk`` support
+ * Initial ``surrogateescape`` support
+ * Additional backports: ``collections``, ``http`` constants, etc.
+ * Bug fixes
+
+v0.14.3:
+ * Bug fixes
+
+v0.14.2:
+ * Bug fixes
+
+v0.14.1:
+ * Bug fixes
+
+v0.14.0:
* New top-level ``builtins`` package on Py2 for cleaner imports. Equivalent to
``future.builtins``
* New top-level packages on Py2 with the same names as Py3 standard modules:
@@ -720,7 +1153,7 @@ v0.13.0:
v0.12.0:
* Add ``newobject`` and ``newlist`` types
- * Improve compatibility of import hooks with Requests, py2exe
+ * Improve compatibility of import hooks with ``Requests``, ``py2exe``
* No more auto-installation of import hooks by ``future.standard_library``
* New ``future.moves`` package
* ``past.builtins`` improved
@@ -743,8 +1176,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
@@ -786,7 +1219,7 @@ v0.8.1:
* Move a few more safe ``futurize`` fixes from stage2 to stage1
* Bug fixes to :mod:`future.utils`
-
+
v0.8:
* Added Python 2.6 support
@@ -795,12 +1228,12 @@ v0.8:
* Removed undocumented functions from :mod:`future.utils`
v0.7:
- * Added a backported Py3-like ``int`` object (inherits from long).
+ * Added a backported Py3-like ``int`` object (inherits from ``long``).
* Added utility functions for type-checking and docs about
``isinstance`` uses/alternatives.
- * Fixes and stricter type-checking for bytes and str objects
+ * Fixes and stricter type-checking for ``bytes`` and ``str`` objects
* Added many more tests for the ``futurize`` script
@@ -811,7 +1244,7 @@ v0.7:
v0.6:
* Added a backported Py3-like ``str`` object (inherits from Py2's ``unicode``)
- * Removed support for the form ``from future import *``: use ``from future.builtins import *`` instead
+ * Removed support for the form ``from future import *``; use ``from future.builtins import *`` instead
v0.5.3:
* Doc improvements
@@ -824,7 +1257,7 @@ v0.5.1:
* :mod:`http.server` module backported
- * bytes.split() and .rsplit() bugfixes
+ * ``bytes.split()`` and ``.rsplit()`` bugfixes
v0.5.0:
* Added backported Py3-like ``bytes`` object
@@ -855,26 +1288,26 @@ v0.3.5:
v0.3.4:
* Added ``itertools.zip_longest``
- * Updated 2to3_backcompat tests to use futurize.py
+ * Updated ``2to3_backcompat`` tests to use ``futurize.py``
- * Improved libfuturize fixers: correct order of imports; add imports only when necessary (except absolute_import currently)
+ * Improved ``libfuturize`` fixers: correct order of imports; add imports only when necessary (except ``absolute_import`` currently)
v0.3.3:
* Added ``python-futurize`` console script
* Added ``itertools.filterfalse``
- * Removed docs about unfinished backports (urllib etc.)
+ * Removed docs about unfinished backports (``urllib`` etc.)
- * Removed old Py2 syntax in some files that breaks py3 setup.py install
+ * Removed old Py2 syntax in some files that breaks py3 ``setup.py install``
v0.3.2:
- * Added test.support module
+ * Added ``test.support`` module
- * Added UserList, UserString, UserDict classes to collections module
+ * Added ``UserList``, ``UserString``, ``UserDict`` classes to ``collections`` module
* Removed ``int`` -> ``long`` mapping
-
+
* Added backported ``_markupbase.py`` etc. with new-style classes to fix travis-ci build problems
* Added working ``html`` and ``http.client`` backported modules
@@ -896,7 +1329,7 @@ v0.2.1:
* Small bug fixes
v0.2.0:
- * Features module renamed to modified_builtins
+ * ``Features`` module renamed to ``modified_builtins``
* New functions added: :func:`round`, :func:`input`
@@ -907,7 +1340,7 @@ v0.2.0:
should have no effect on Python 3. On Python 2, it only shadows the
builtins; it doesn't introduce any new names.
- * End-to-end tests with Python 2 code and 2to3 now work
+ * End-to-end tests with Python 2 code and ``2to3`` now work
v0.1.0:
* first version with tests!
diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst
index 9fe1cc9a..ab478ed8 100644
--- a/docs/compatible_idioms.rst
+++ b/docs/compatible_idioms.rst
@@ -1,23 +1,23 @@
.. _compatible-idioms:
-
+
Cheat Sheet: Writing Python 2-3 compatible code
===============================================
-- **Copyright (c):** 2013-2014 Python Charmers Pty Ltd, Australia.
+- **Copyright (c):** 2013-2024 Python Charmers, Australia.
- **Author:** Ed Schofield.
- **Licence:** Creative Commons Attribution.
-A PDF version is here: http://python-future.org/compatible\_idioms.pdf
+A PDF version is here: https://python-future.org/compatible\_idioms.pdf
This notebook shows you idioms for writing future-proof code that is
compatible with both versions of Python: 2 and 3. It accompanies Ed
Schofield's talk at PyCon AU 2014, "Writing 2/3 compatible code". (The
-video is here: http://www.youtube.com/watch?v=KOqk8j11aAI&t=10m14s.)
+video is here: https://www.youtube.com/watch?v=KOqk8j11aAI&t=10m14s.)
Minimum versions:
-- Python 2: 2.6+
-- Python 3: 3.3+
+- Python 2: 2.7+
+- Python 3: 3.4+
Setup
-----
@@ -38,7 +38,7 @@ The following scripts are also ``pip``-installable:
futurize # pip install future
pasteurize # pip install future
-See http://python-future.org and https://pythonhosted.org/six/ for more
+See https://python-future.org and https://pythonhosted.org/six/ for more
information.
Essential syntax differences
@@ -66,7 +66,7 @@ interpreting it as a tuple:
# Python 2 and 3:
from __future__ import print_function # (at top of module)
-
+
print('Hello', 'Guido')
.. code:: python
@@ -76,7 +76,7 @@ interpreting it as a tuple:
# Python 2 and 3:
from __future__ import print_function
-
+
print('Hello', file=sys.stderr)
.. code:: python
@@ -86,7 +86,7 @@ interpreting it as a tuple:
# Python 2 and 3:
from __future__ import print_function
-
+
print('Hello', end='')
Raising exceptions
~~~~~~~~~~~~~~~~~~
@@ -116,14 +116,14 @@ Raising exceptions with a traceback:
from six import reraise as raise_
# or
from future.utils import raise_
-
+
traceback = sys.exc_info()[2]
raise_(ValueError, "dodgy value", traceback)
.. code:: python
# Python 2 and 3: option 2
from future.utils import raise_with_traceback
-
+
raise_with_traceback(ValueError("dodgy value"))
Exception chaining (PEP 3134):
@@ -145,7 +145,7 @@ Exception chaining (PEP 3134):
# Python 2 and 3:
from future.utils import raise_from
-
+
class FileDatabase:
def __init__(self, filename):
try:
@@ -199,7 +199,7 @@ Integer division (rounding down):
# Python 2 and 3:
from __future__ import division # (at top of module)
-
+
assert 3 / 2 == 1.5
"Old division" (i.e. compatible with Py2 behaviour):
@@ -211,7 +211,7 @@ Integer division (rounding down):
# Python 2 and 3:
from past.utils import old_div
-
+
a = old_div(b, c) # always same as / on Py2
Long integers
~~~~~~~~~~~~~
@@ -223,14 +223,14 @@ Short integers are gone in Python 3 and ``long`` has become ``int``
# Python 2 only
k = 9223372036854775808L
-
+
# Python 2 and 3:
k = 9223372036854775808
.. code:: python
# Python 2 only
bigint = 1L
-
+
# Python 2 and 3
from builtins import int
bigint = int(1)
@@ -241,20 +241,20 @@ To test whether a value is an integer (of any kind):
# Python 2 only:
if isinstance(x, (int, long)):
...
-
+
# Python 3 only:
if isinstance(x, int):
...
-
+
# Python 2 and 3: option 1
from builtins import int # subclass of long on Py2
-
+
if isinstance(x, int): # matches both int and long on Py2
...
-
+
# Python 2 and 3: option 2
from past.builtins import long
-
+
if isinstance(x, (int, long)):
...
Octal constants
@@ -282,7 +282,7 @@ Metaclasses
class BaseForm(object):
pass
-
+
class FormType(type):
pass
.. code:: python
@@ -302,7 +302,7 @@ Metaclasses
from six import with_metaclass
# or
from future.utils import with_metaclass
-
+
class Form(with_metaclass(FormType, BaseForm)):
pass
Strings and bytes
@@ -320,7 +320,7 @@ prefixes:
# Python 2 only
s1 = 'The Zen of Python'
s2 = u'きたないのよりきれいな方がいい\n'
-
+
# Python 2 and 3
s1 = u'The Zen of Python'
s2 = u'きたないのよりきれいな方がいい\n'
@@ -334,10 +334,10 @@ this idiom to make all string literals in a module unicode strings:
# Python 2 and 3
from __future__ import unicode_literals # at top of module
-
+
s1 = 'The Zen of Python'
s2 = 'きたないのよりきれいな方がいい\n'
-See http://python-future.org/unicode\_literals.html for more discussion
+See https://python-future.org/unicode\_literals.html for more discussion
on which style to use.
Byte-string literals
@@ -347,7 +347,7 @@ Byte-string literals
# Python 2 only
s = 'This must be a byte-string'
-
+
# Python 2 and 3
s = b'This must be a byte-string'
To loop over a byte-string with possible high-bit characters, obtaining
@@ -358,11 +358,11 @@ each character as a byte-string of length 1:
# Python 2 only:
for bytechar in 'byte-string with high-bit chars like \xf9':
...
-
+
# Python 3 only:
for myint in b'byte-string with high-bit chars like \xf9':
bytechar = bytes([myint])
-
+
# Python 2 and 3:
from builtins import bytes
for myint in bytes(b'byte-string with high-bit chars like \xf9'):
@@ -376,7 +376,7 @@ convert an int into a 1-char byte string:
for myint in b'byte-string with high-bit chars like \xf9':
char = chr(myint) # returns a unicode string
bytechar = char.encode('latin-1')
-
+
# Python 2 and 3:
from builtins import bytes, chr
for myint in bytes(b'byte-string with high-bit chars like \xf9'):
@@ -391,10 +391,10 @@ basestring
a = u'abc'
b = 'def'
assert (isinstance(a, basestring) and isinstance(b, basestring))
-
+
# Python 2 and 3: alternative 1
from past.builtins import basestring # pip install future
-
+
a = u'abc'
b = b'def'
assert (isinstance(a, basestring) and isinstance(b, basestring))
@@ -402,7 +402,7 @@ basestring
# Python 2 and 3: alternative 2: refactor the code to avoid considering
# byte-strings as strings.
-
+
from builtins import str
a = u'abc'
b = b'def'
@@ -435,7 +435,7 @@ StringIO
from StringIO import StringIO
# or:
from cStringIO import StringIO
-
+
# Python 2 and 3:
from io import BytesIO # for handling byte strings
from io import StringIO # for handling unicode strings
@@ -450,13 +450,13 @@ Suppose the package is:
__init__.py
submodule1.py
submodule2.py
-
+
and the code below is in ``submodule1.py``:
.. code:: python
- # Python 2 only:
+ # Python 2 only:
import submodule2
.. code:: python
@@ -505,17 +505,17 @@ Iterable dict values:
# Python 2 and 3: option 1
from builtins import dict
-
+
heights = dict(Fred=175, Anne=166, Joe=192)
for key in heights.values(): # efficient on Py2 and Py3
...
.. code:: python
# Python 2 and 3: option 2
- from builtins import itervalues
+ from future.utils import itervalues
# or
from six import itervalues
-
+
for key in itervalues(heights):
...
Iterable dict items:
@@ -528,15 +528,22 @@ Iterable dict items:
.. code:: python
# Python 2 and 3: option 1
- for (key, value) in heights.items(): # inefficient on Py2
+ for (key, value) in heights.items(): # inefficient on Py2
...
.. code:: python
# Python 2 and 3: option 2
- from builtins import iteritems
+ from future.utils import viewitems
+
+ for (key, value) in viewitems(heights): # also behaves like a set
+ ...
+.. code:: python
+
+ # Python 2 and 3: option 3
+ from future.utils import iteritems
# or
from six import iteritems
-
+
for (key, value) in iteritems(heights):
...
dict keys/values/items as a list
@@ -570,14 +577,14 @@ dict values as a list:
# Python 2 and 3: option 2
from builtins import dict
-
+
heights = dict(Fred=175, Anne=166, Joe=192)
valuelist = list(heights.values())
.. code:: python
# Python 2 and 3: option 3
from future.utils import listvalues
-
+
valuelist = listvalues(heights)
.. code:: python
@@ -585,7 +592,7 @@ dict values as a list:
from future.utils import itervalues
# or
from six import itervalues
-
+
valuelist = list(itervalues(heights))
dict items as a list:
@@ -597,7 +604,7 @@ dict items as a list:
# Python 2 and 3: option 2
from future.utils import listitems
-
+
itemlist = listitems(heights)
.. code:: python
@@ -605,7 +612,7 @@ dict items as a list:
from future.utils import iteritems
# or
from six import iteritems
-
+
itemlist = list(iteritems(heights))
Custom class behaviour
----------------------
@@ -623,7 +630,7 @@ Custom iterators
return self._iter.next().upper()
def __iter__(self):
return self
-
+
itr = Upper('hello')
assert itr.next() == 'H' # Py2-style
assert list(itr) == list('ELLO')
@@ -631,7 +638,7 @@ Custom iterators
# Python 2 and 3: option 1
from builtins import object
-
+
class Upper(object):
def __init__(self, iterable):
self._iter = iter(iterable)
@@ -639,7 +646,7 @@ Custom iterators
return next(self._iter).upper() # builtin next() function calls
def __iter__(self):
return self
-
+
itr = Upper('hello')
assert next(itr) == 'H' # compatible style
assert list(itr) == list('ELLO')
@@ -647,7 +654,7 @@ Custom iterators
# Python 2 and 3: option 2
from future.utils import implements_iterator
-
+
@implements_iterator
class Upper(object):
def __init__(self, iterable):
@@ -656,7 +663,7 @@ Custom iterators
return next(self._iter).upper() # builtin next() function calls
def __iter__(self):
return self
-
+
itr = Upper('hello')
assert next(itr) == 'H'
assert list(itr) == list('ELLO')
@@ -671,19 +678,19 @@ Custom ``__str__`` methods
return 'Unicode string: \u5b54\u5b50'
def __str__(self):
return unicode(self).encode('utf-8')
-
+
a = MyClass()
print(a) # prints encoded string
.. code:: python
# Python 2 and 3:
from future.utils import python_2_unicode_compatible
-
+
@python_2_unicode_compatible
class MyClass(object):
def __str__(self):
return u'Unicode string: \u5b54\u5b50'
-
+
a = MyClass()
print(a) # prints string encoded as utf-8 on Py2
@@ -703,20 +710,20 @@ Custom ``__nonzero__`` vs ``__bool__`` method:
self.l = l
def __nonzero__(self):
return all(self.l)
-
+
container = AllOrNothing([0, 100, 200])
assert not bool(container)
.. code:: python
# Python 2 and 3:
from builtins import object
-
+
class AllOrNothing(object):
def __init__(self, l):
self.l = l
def __bool__(self):
return all(self.l)
-
+
container = AllOrNothing([0, 100, 200])
assert not bool(container)
Lists versus iterators
@@ -759,21 +766,21 @@ range
# Python 2 and 3: forward-compatible: option 2
from builtins import range
-
+
mylist = list(range(5))
assert mylist == [0, 1, 2, 3, 4]
.. code:: python
# Python 2 and 3: option 3
from future.utils import lrange
-
+
mylist = lrange(5)
assert mylist == [0, 1, 2, 3, 4]
.. code:: python
# Python 2 and 3: backward compatible
from past.builtins import range
-
+
mylist = range(5)
assert mylist == [0, 1, 2, 3, 4]
map
@@ -794,7 +801,7 @@ map
# Python 2 and 3: option 2
from builtins import map
-
+
mynewlist = list(map(f, myoldlist))
assert mynewlist == [f(x) for x in myoldlist]
.. code:: python
@@ -804,21 +811,21 @@ map
import itertools.imap as map
except ImportError:
pass
-
+
mynewlist = list(map(f, myoldlist)) # inefficient on Py2
assert mynewlist == [f(x) for x in myoldlist]
.. code:: python
# Python 2 and 3: option 4
from future.utils import lmap
-
+
mynewlist = lmap(f, myoldlist)
assert mynewlist == [f(x) for x in myoldlist]
.. code:: python
# Python 2 and 3: option 5
from past.builtins import map
-
+
mynewlist = map(f, myoldlist)
assert mynewlist == [f(x) for x in myoldlist]
imap
@@ -828,7 +835,7 @@ imap
# Python 2 only:
from itertools import imap
-
+
myiter = imap(func, myoldlist)
assert isinstance(myiter, iter)
.. code:: python
@@ -840,7 +847,7 @@ imap
# Python 2 and 3: option 1
from builtins import map
-
+
myiter = map(func, myoldlist)
assert isinstance(myiter, iter)
.. code:: python
@@ -850,9 +857,17 @@ imap
import itertools.imap as map
except ImportError:
pass
-
+
+ myiter = map(func, myoldlist)
+ assert isinstance(myiter, iter)
+.. code:: python
+
+ # Python 2 and 3: option 3
+ from six.moves import map
+
myiter = map(func, myoldlist)
assert isinstance(myiter, iter)
+
zip, izip
~~~~~~~~~
@@ -875,13 +890,13 @@ File IO with open()
f = open('myfile.txt')
data = f.read() # as a byte string
text = data.decode('utf-8')
-
+
# Python 2 and 3: alternative 1
from io import open
f = open('myfile.txt', 'rb')
data = f.read() # as bytes
text = data.decode('utf-8') # unicode, not bytes
-
+
# Python 2 and 3: alternative 2
from io import open
f = open('myfile.txt', encoding='utf-8')
@@ -897,7 +912,7 @@ reduce()
# Python 2 and 3:
from functools import reduce
-
+
assert reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) == 1+2+3+4+5
raw\_input()
~~~~~~~~~~~~
@@ -911,7 +926,7 @@ raw\_input()
# Python 2 and 3:
from builtins import input
-
+
name = input('What is your name? ')
assert isinstance(name, str) # native str on Py2 and Py3
input()
@@ -939,12 +954,39 @@ file()
# Python 2 and 3:
f = open(pathname)
-
+
# But preferably, use this:
from io import open
f = open(pathname, 'rb') # if f.read() should return bytes
# or
f = open(pathname, 'rt') # if f.read() should return unicode text
+exec
+~~~~
+
+.. code:: python
+
+ # Python 2 only:
+ exec 'x = 10'
+
+ # Python 2 and 3:
+ exec('x = 10')
+.. code:: python
+
+ # Python 2 only:
+ g = globals()
+ exec 'x = 10' in g
+
+ # Python 2 and 3:
+ g = globals()
+ exec('x = 10', g)
+.. code:: python
+
+ # Python 2 only:
+ l = locals()
+ exec 'x = 10' in g, l
+
+ # Python 2 and 3:
+ exec('x = 10', g, l)
execfile()
~~~~~~~~~~
@@ -956,13 +998,13 @@ execfile()
# Python 2 and 3: alternative 1
from past.builtins import execfile
-
+
execfile('myfile.py')
.. code:: python
# Python 2 and 3: alternative 2
exec(compile(open('myfile.py').read()))
-
+
# This can sometimes cause this:
# SyntaxError: function ... uses import * and bare exec ...
# See https://github.com/PythonCharmers/python-future/issues/37
@@ -1056,7 +1098,7 @@ chr()
# Python 2 and 3: option 1
from builtins import chr
-
+
assert chr(64).encode('latin-1') == b'@'
assert chr(0xc8).encode('latin-1') == b'\xc8'
.. code:: python
@@ -1068,7 +1110,7 @@ chr()
# Python 2 and 3: option 2
from builtins import bytes
-
+
assert bytes([64]) == b'@'
assert bytes([0xc8]) == b'\xc8'
cmp()
@@ -1114,22 +1156,22 @@ dbm modules
import dbm
import dumbdbm
import gdbm
-
+
# Python 2 and 3: alternative 1
from future import standard_library
standard_library.install_aliases()
-
+
import dbm
import dbm.ndbm
import dbm.dumb
import dbm.gnu
-
+
# Python 2 and 3: alternative 2
from future.moves import dbm
from future.moves.dbm import dumb
from future.moves.dbm import ndbm
from future.moves.dbm import gnu
-
+
# Python 2 and 3: alternative 3
from six.moves import dbm_gnu
# (others not supported)
@@ -1140,44 +1182,12 @@ commands / subprocess modules
# Python 2 only
from commands import getoutput, getstatusoutput
-
- # Python 2 and 3
- from future import standard_library
- standard_library.install_aliases()
-
- from subprocess import getoutput, getstatusoutput
-subprocess.check\_output()
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. code:: python
- # Python 2.7 and above
- from subprocess import check_output
-
- # Python 2.6 and above: alternative 1
- from future.moves.subprocess import check_output
-
- # Python 2.6 and above: alternative 2
+ # Python 2 and 3
from future import standard_library
standard_library.install_aliases()
-
- from subprocess import check_output
-collections: Counter and OrderedDict
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. code:: python
-
- # Python 2.7 and above
- from collections import Counter, OrderedDict
-
- # Python 2.6 and above: alternative 1
- from future.moves.collections import Counter, OrderedDict
-
- # Python 2.6 and above: alternative 2
- from future import standard_library
- standard_library.install_aliases()
-
- from collections import Counter, OrderedDict
+ from subprocess import getoutput, getstatusoutput
StringIO module
~~~~~~~~~~~~~~~
@@ -1203,7 +1213,7 @@ http module
import BaseHTTPServer
import SimpleHTTPServer
import CGIHttpServer
-
+
# Python 2 and 3 (after ``pip install future``):
import http.client
import http.cookies
@@ -1217,14 +1227,14 @@ xmlrpc module
# Python 2 only:
import DocXMLRPCServer
import SimpleXMLRPCServer
-
+
# Python 2 and 3 (after ``pip install future``):
import xmlrpc.server
.. code:: python
# Python 2 only:
import xmlrpclib
-
+
# Python 2 and 3 (after ``pip install future``):
import xmlrpc.client
html escaping and entities
@@ -1234,13 +1244,13 @@ html escaping and entities
# Python 2 and 3:
from cgi import escape
-
+
# Safer (Python 2 and 3, after ``pip install future``):
from html import escape
-
+
# Python 2 only:
from htmlentitydefs import codepoint2name, entitydefs, name2codepoint
-
+
# Python 2 and 3 (after ``pip install future``):
from html.entities import codepoint2name, entitydefs, name2codepoint
html parsing
@@ -1250,17 +1260,17 @@ html parsing
# Python 2 only:
from HTMLParser import HTMLParser
-
+
# Python 2 and 3 (after ``pip install future``)
from html.parser import HTMLParser
-
+
# Python 2 and 3 (alternative 2):
from future.moves.html.parser import HTMLParser
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
@@ -1279,7 +1289,7 @@ You may like to use Requests (http://python-requests.org) instead.
# Python 2 and 3: easiest option
from future.standard_library import install_aliases
install_aliases()
-
+
from urllib.parse import urlparse, urlencode
from urllib.request import urlopen, Request
from urllib.error import HTTPError
@@ -1287,7 +1297,7 @@ You may like to use Requests (http://python-requests.org) instead.
# Python 2 and 3: alternative 2
from future.standard_library import hooks
-
+
with hooks():
from urllib.parse import urlparse, urlencode
from urllib.request import urlopen, Request
@@ -1324,21 +1334,22 @@ Tkinter
import FileDialog
import ScrolledText
import SimpleDialog
- import Tix
+ import Tix
import Tkconstants
- import Tkdnd
+ import Tkdnd
import tkColorChooser
import tkCommonDialog
import tkFileDialog
import tkFont
import tkMessageBox
import tkSimpleDialog
-
+ import ttk
+
# Python 2 and 3 (after ``pip install future``):
import tkinter
import tkinter.dialog
import tkinter.filedialog
- import tkinter.scolledtext
+ import tkinter.scrolledtext
import tkinter.simpledialog
import tkinter.tix
import tkinter.constants
@@ -1349,6 +1360,7 @@ Tkinter
import tkinter.font
import tkinter.messagebox
import tkinter.simpledialog
+ import tkinter.ttk
socketserver
~~~~~~~~~~~~
@@ -1356,7 +1368,7 @@ socketserver
# Python 2 only:
import SocketServer
-
+
# Python 2 and 3 (after ``pip install future``):
import socketserver
copy\_reg, copyreg
@@ -1366,7 +1378,7 @@ copy\_reg, copyreg
# Python 2 only:
import copy_reg
-
+
# Python 2 and 3 (after ``pip install future``):
import copyreg
configparser
@@ -1376,8 +1388,8 @@ configparser
# Python 2 only:
from ConfigParser import ConfigParser
-
- # Python 2 and 3 (after ``pip install future``):
+
+ # Python 2 and 3 (after ``pip install configparser``):
from configparser import ConfigParser
queue
~~~~~
@@ -1386,7 +1398,7 @@ queue
# Python 2 only:
from Queue import Queue, heapq, deque
-
+
# Python 2 and 3 (after ``pip install future``):
from queue import Queue, heapq, deque
repr, reprlib
@@ -1396,7 +1408,7 @@ repr, reprlib
# Python 2 only:
from repr import aRepr, repr
-
+
# Python 2 and 3 (after ``pip install future``):
from reprlib import aRepr, repr
UserDict, UserList, UserString
@@ -1408,16 +1420,16 @@ UserDict, UserList, UserString
from UserDict import UserDict
from UserList import UserList
from UserString import UserString
-
+
# Python 3 only:
from collections import UserDict, UserList, UserString
-
+
# Python 2 and 3: alternative 1
from future.moves.collections import UserDict, UserList, UserString
-
+
# Python 2 and 3: alternative 2
from six.moves import UserDict, UserList, UserString
-
+
# Python 2 and 3: alternative 3
from future.standard_library import install_aliases
install_aliases()
@@ -1429,16 +1441,16 @@ itertools: filterfalse, zip\_longest
# Python 2 only:
from itertools import ifilterfalse, izip_longest
-
+
# Python 3 only:
from itertools import filterfalse, zip_longest
-
+
# Python 2 and 3: alternative 1
from future.moves.itertools import filterfalse, zip_longest
-
+
# Python 2 and 3: alternative 2
from six.moves import filterfalse, zip_longest
-
+
# Python 2 and 3: alternative 3
from future.standard_library import install_aliases
install_aliases()
diff --git a/docs/conf.py b/docs/conf.py
index e2482230..cf4606c7 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -13,8 +13,7 @@
from __future__ import absolute_import, print_function
import sys, os
-from future import __version__
-import sphinx_bootstrap_theme
+# import sphinx_bootstrap_theme
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
@@ -33,7 +32,8 @@
'sphinx.ext.intersphinx',
'sphinx.ext.ifconfig',
'sphinx.ext.viewcode',
- # 'sphinxcontrib.napoleon' # see https://readthedocs.org/projects/sphinxcontrib-napoleon/
+ 'pallets_sphinx_themes',
+ # 'sphinxcontrib.napoleon' # see https://sphinxcontrib-napoleon.readthedocs.io/
# 'sphinx.ext.napoleon' # use this in Sphinx 1.3+
]
@@ -51,7 +51,7 @@
# General information about the project.
project = u'Python-Future'
-copyright = u'2013-2014, 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
@@ -101,8 +101,8 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'bootstrap'
-html_theme_path = sphinx_bootstrap_theme.get_html_theme_path()
+html_theme = 'jinja'
+# html_theme_path = sphinx_bootstrap_theme.get_html_theme_path()
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
diff --git a/docs/contents.rst.inc b/docs/contents.rst.inc
index 2b118454..869b3642 100644
--- a/docs/contents.rst.inc
+++ b/docs/contents.rst.inc
@@ -1,29 +1,26 @@
-Contents:
----------
+Contents
+========
.. toctree::
- :maxdepth: 2
+ :maxdepth: 3
whatsnew
overview
quickstart
compatible_idioms
imports
- standard_library_imports
what_else
automatic_conversion
- futurize_cheatsheet
- translation
- stdlib_incompatibilities
faq
+ stdlib_incompatibilities
+ older_interfaces
changelog
credits
reference
Indices and tables
-------------------
+******************
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
-
diff --git a/docs/conversion_limitations.rst b/docs/conversion_limitations.rst
index 4f091355..c2b15303 100644
--- a/docs/conversion_limitations.rst
+++ b/docs/conversion_limitations.rst
@@ -1,26 +1,19 @@
-How well do ``futurize`` and ``pasteurize`` work?
--------------------------------------------------
+.. _futurize-limitations:
-They are still incomplete and make some mistakes, like 2to3, on which they are
-based.
+Known limitations
+-----------------
-Nevertheless, ``futurize`` and ``pasteurize`` are useful to automate much of the
+``futurize`` and ``pasteurize`` are useful to automate much of the
work of porting, particularly the boring repetitive text substitutions. They also
help to flag which parts of the code require attention.
-Please report bugs on `GitHub
-`_.
-
-Contributions to the ``lib2to3``-based fixers for ``futurize`` and
-``pasteurize`` are particularly welcome! Please see :ref:`contributing`.
-
-
-.. _futurize-limitations:
-
-Known limitations
------------------
+Nevertheless, ``futurize`` and ``pasteurize`` are still incomplete and make
+some mistakes, like 2to3, on which they are based. Please report bugs on
+`GitHub `_. Contributions to
+the ``lib2to3``-based fixers for ``futurize`` and ``pasteurize`` are
+particularly welcome! Please see :ref:`contributing`.
-``futurize`` doesn't currently make this change automatically:
+``futurize`` doesn't currently make the following change automatically:
1. Strings containing ``\U`` produce a ``SyntaxError`` on Python 3. An example is::
diff --git a/docs/credits.rst b/docs/credits.rst
index 1f1b2416..4c029efd 100644
--- a/docs/credits.rst
+++ b/docs/credits.rst
@@ -8,18 +8,18 @@ Licence
The software is distributed under an MIT licence. The text is as follows
(from ``LICENSE.txt``)::
- Copyright (c) 2013-2014 Python Charmers Pty Ltd, Australia
-
+ Copyright (c) 2013-2024 Python Charmers, 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
-
+
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
-
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -30,52 +30,128 @@ The software is distributed under an MIT licence. The text is as follows
.. _sponsor:
-Sponsor
--------
-Python Charmers Pty Ltd, Australia, and Python Charmers Pte Ltd, Singapore.
-http://pythoncharmers.com
+Sponsors
+--------
+Python Charmers: https://pythoncharmers.com
.. _authors:
-Authors
+Author
-------
-Python-Future is written and maintained by Ed Schofield and various contributors:
+Python-Future was largely written by Ed Schofield .
+
+Maintainers
+-----------
-Development Lead
-~~~~~~~~~~~~~~~~
+The project is no longer being actively maintained. Like Python 2, it should be
+considered end-of-life.
-- Ed Schofield
+Past maintainers include:
-Patches
-~~~~~~~
+- Jordan M. Adler
+- Liuyang Wan
+- Ed Schofield
+Contributors
+------------
+
+Thanks to the following people for helping to improve the package:
+
+- Jordan Adler
+- Jeroen Akkerman
+- Bruno Alla
+- Kyle Altendorf
+- Nuno André
+- Kian-Meng Ang
+- Grant Bakker
+- Jacob Beck
+- David Bern
+- Fumihiro (Ben) Bessho
+- Shiva Bhusal
+- Andrew Bjonnes
+- Nate Bogdanowicz
+- Tomer Chachamu
+- Christian Clauss
- Denis Cornehl
+- Joseph Curtis
- Nicolas Delaby
+- Chad Dombrova
+- Jon Dufresne
- Corey Farwell
- Eric Firing
+- Joe Gordon
+- Gabriela Gutierrez
+- Maximilian Hils
+- Tomáš Hrnčiar
+- 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
+- Sebastian Potasiak
+- Miga Purg
+- Éloi Rivard
+- Greg Roodt
+- Sesh Sadasivam
- Elliott Sales de Andrade
+- Aiden Scandella
+- Yury Selivanov
+- Alexander Shadchin
- Tim Shaffer
+- Christopher Slycord
+- Sameera Somisetty
+- Nicola Soranzo
+- Louis Sautier
+- Will Shanks
+- Gregory P. Smith
+- Chase Sterling
+- Matthew Stidham
- Daniel Szoska
+- Flaviu Tamas
+- Roman A. Taycher
- Jeff Tratner
-- Mystic-Mirage (GitHub)
-- str4d (GitHub)
-- 9seconds (GitHub)
+- Tim Tröndle
+- Brad Walker
+- Liuyang Wan
+- 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)
+- zihzihtw (GitHub user)
Suggestions and Feedback
~~~~~~~~~~~~~~~~~~~~~~~~
- Chris Adams
- Martijn Faassen
+- Joe Gordon
- Lion Krischer
-- wluebbe (GitHub)
+- Danielle Madeley
+- Val Markovic
+- wluebbe (GitHub user)
Other Credits
@@ -96,9 +172,10 @@ Other Credits
- The ``raise_`` and ``raise_with_traceback`` functions were contributed by
Jeff Tratner.
+- A working version of ``raise_from`` was contributed by Varriount (GitHub).
+
- Documentation is generated with `Sphinx `_ using the
``sphinx-bootstrap`` theme.
- ``past.translation`` is inspired by and borrows some code from Sanjay Vinip's
``uprefix`` module.
-
diff --git a/docs/custom_iterators.rst b/docs/custom_iterators.rst
index db87b0f3..6ff389a4 100644
--- a/docs/custom_iterators.rst
+++ b/docs/custom_iterators.rst
@@ -14,7 +14,7 @@ would on Python 3. On Python 2, ``object`` then refers to the
method that calls your ``__next__``. Use it as follows::
from builtins import object
-
+
class Upper(object):
def __init__(self, iterable):
self._iter = iter(iterable)
@@ -92,4 +92,3 @@ the iterator as follows::
return self
On Python 3, as usual, this decorator does nothing.
-
diff --git a/docs/dev_notes.rst b/docs/dev_notes.rst
index d0584c46..6985bca4 100644
--- a/docs/dev_notes.rst
+++ b/docs/dev_notes.rst
@@ -1,6 +1,6 @@
Notes
-----
-This module only supports Python 2.6, Python 2.7, and Python 3.1+.
+This module only supports Python 2.7, and Python 3.4+.
The following renames are already supported on Python 2.7 without any
additional work from us::
@@ -14,10 +14,3 @@ Old things that can one day be fixed automatically by futurize.py::
string.uppercase -> string.ascii_uppercase # works on either Py2.7 or Py3+
sys.maxint -> sys.maxsize # but this isn't identical
-
-TODO: Check out these:
-Not available on Py2.6:
- unittest2 -> unittest?
- buffer -> memoryview?
-
-
diff --git a/docs/development.rst b/docs/development.rst
index 530d2948..a12f2ca5 100644
--- a/docs/development.rst
+++ b/docs/development.rst
@@ -10,13 +10,10 @@ The easiest way to start developing ``python-future`` is as follows:
2. Run::
conda install -n future2 python=2.7 pip
- conda install -n future3 python=3.3 pip
-
- git clone https://github.com/PythonCharmers/python-future
+ conda install -n future3 python=3.4 pip
+
+ git clone https://github.com/PythonCharmers/python-future
3. If you are using Anaconda Python distribution, this comes without a ``test``
module on Python 2.x. Copy ``Python-2.7.6/Lib/test`` from the Python source tree
to ``~/anaconda/envs/yourenvname/lib/python2.7/site-packages/`.
-
-
-
diff --git a/docs/dict_object.rst b/docs/dict_object.rst
index 0142b240..165cf763 100644
--- a/docs/dict_object.rst
+++ b/docs/dict_object.rst
@@ -10,7 +10,7 @@ methods which return memory-efficient set-like iterator objects, not lists.
If your dictionaries are small, performance is not critical, and you don't need
the set-like behaviour of iterator objects from Python 3, you can of course
stick with standard Python 3 code in your Py2/3 compatible codebase::
-
+
# Assuming d is a native dict ...
for key in d:
@@ -18,35 +18,31 @@ stick with standard Python 3 code in your Py2/3 compatible codebase::
for item in d.items():
# code here
-
+
for value in d.values():
# code here
-In this case there will be memory overhead of list creation for each call to
-``items``, ``values`` or ``keys``.
+In this case there will be memory overhead of list creation on Py2 for each
+call to ``items``, ``values`` or ``keys``.
For improved efficiency, ``future.builtins`` (aliased to ``builtins``) provides
a Python 2 ``dict`` subclass whose :func:`keys`, :func:`values`, and
-:func:`items` methods return iterators on all versions of Python >= 2.6. On
+:func:`items` methods return iterators on all versions of Python >= 2.7. On
Python 2.7, these iterators also have the same set-like view behaviour as
dictionaries in Python 3. This can streamline code that iterates over large
dictionaries. For example::
from __future__ import print_function
from builtins import dict, range
-
+
# Memory-efficient construction:
d = dict((i, i**2) for i in range(10**7))
-
+
assert not isinstance(d.items(), list)
-
+
# Because items() is memory-efficient, so is this:
d2 = dict((v, k) for (k, v) in d.items())
-
-On Python 2.6, these methods currently return iterators but do not support the
-new Py3 set-like behaviour.
-
As usual, on Python 3 ``dict`` imported from either ``builtins`` or
``future.builtins`` is just the built-in ``dict`` class.
@@ -70,7 +66,7 @@ The memory-efficient (and CPU-efficient) alternatives are:
- to construct a dictionary from an iterator. The above line could use a
generator like this::
- d = dict((i, i**2) for i in range(10**7)
+ d = dict((i, i**2) for i in range(10**7))
- to construct an empty dictionary with a ``dict()`` call using
``builtins.dict`` (rather than ``{}``) and then update it;
@@ -82,16 +78,15 @@ The memory-efficient (and CPU-efficient) alternatives are:
for (key, value) in viewitems(hugedictionary):
# some code here
-
+
# Set intersection:
d = {i**2: i for i in range(1000)}
both = viewkeys(d) & set(range(0, 1000, 7))
-
+
# Set union:
both = viewvalues(d1) | viewvalues(d2)
-For Python 2.6 compatibility, the functions ``iteritems`` etc. are also
-available in :mod:`future.utils`. These are equivalent to the functions of the
-same names in ``six``, which is equivalent to calling the ``iteritems`` etc.
-methods on Python 2, or to calling ``items`` etc. on Python 3.
-
+For compatibility, the functions ``iteritems`` etc. are also available in
+:mod:`future.utils`. These are equivalent to the functions of the same names in
+``six``, which is equivalent to calling the ``iteritems`` etc. methods on
+Python 2, or to calling ``items`` etc. on Python 3.
diff --git a/docs/faq.rst b/docs/faq.rst
index 6ee3decd..e49adf61 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -5,7 +5,7 @@ Who is this for?
================
1. People with existing or new Python 3 codebases who wish to provide
-ongoing Python 2.6 / 2.7 support easily and with little maintenance burden.
+ongoing Python 2.7 support easily and with little maintenance burden.
2. People who wish to ease and accelerate migration of their Python 2 codebases
to Python 3.3+, module by module, without giving up Python 2 compatibility.
@@ -33,7 +33,7 @@ and powerful new features like the `asyncio
.. Unicode handling is also much easier. For example, see `this page
.. `_
.. describing some of the problems with handling Unicode on Python 2 that
-.. Python 3 mostly solves.
+.. Python 3 mostly solves.
Porting philosophy
@@ -92,7 +92,7 @@ What inspired this project?
---------------------------
In our Python training courses, we at `Python Charmers
-`_ faced a dilemma: teach people Python 3, which was
+`_ faced a dilemma: teach people Python 3, which was
future-proof but not as useful to them today because of weaker 3rd-party
package support, or teach people Python 2, which was more useful today but
would require them to change their code and unlearn various habits soon. We
@@ -118,35 +118,30 @@ Maturity
How well has it been tested?
----------------------------
-``future`` is used by several major projects, including `mezzanine
-`_ and `ObsPy `_. It is also
-currently being used to help with porting 800,000 lines of Python 2 code in
-`Sage `_ to Python 2/3.
+``future`` is used by thousands of projects and has been downloaded over 1.7 billion times. Some projects like Sage have used it to port 800,000+ lines of Python 2 code to Python 2/3.
-Currently ``python-future`` has 920+ unit tests. Many of these are straight
-from the Python 3.3 test suite.
+Currently ``python-future`` has over 1000 unit tests. Many of these are straight
+from the Python 3.3 and 3.4 test suites.
In general, the ``future`` package itself is in good shape, whereas the
-``futurize`` script for automatic porting is incomplete and imperfect.
-(Chances are it will require some manual cleanup afterwards.) The ``past``
-package also needs to be expanded.
+``futurize`` script for automatic porting is imperfect; chances are it will
+require some manual cleanup afterwards. The ``past`` package also needs to be
+expanded.
Is the API stable?
------------------
-Not yet; ``future`` is still in beta. Where possible, we will try not to break
-anything which was documented and used to work. After version 1.0 is released,
-the API will not change in backward-incompatible ways until a hypothetical
-version 2.0.
+Yes; ``future`` is mature. We'll make very few changes from here, trying not to
+break anything which was documented and used to work.
..
Are there any example of Python 2 packages ported to Python 3 using ``future`` and ``futurize``?
------------------------------------------------------------------------------------------------
-
+
Yes, an example is the port of ``xlwt``, available `here
`_.
-
+
The code also contains backports for several Py3 standard library
modules under ``future/standard_library/``.
@@ -189,12 +184,12 @@ Can I maintain a Python 2 codebase and use 2to3 to automatically convert to Pyth
This was originally the approach recommended by Python's core developers,
but it has some large drawbacks:
-
+
1. First, your actual working codebase will be stuck with Python 2's
warts and smaller feature set for as long as you need to retain Python 2
compatibility. This may be at least 5 years for many projects, possibly
much longer.
-
+
2. Second, this approach carries the significant disadvantage that you
cannot apply patches submitted by Python 3 users against the
auto-generated Python 3 code. (See `this talk
@@ -217,15 +212,15 @@ complete set of support for Python 3's features, including backports of
Python 3 builtins such as the ``bytes`` object (which is very different
to Python 2's ``str`` object) and several standard library modules.
-``python-future`` supports only Python 2.6+ and Python 3.3+, whereas ``six``
+``python-future`` supports only Python 2.7+ and Python 3.4+, whereas ``six``
supports all versions of Python from 2.4 onwards. (See
:ref:`supported-versions`.) If you must support older Python versions,
-``six`` will be esssential for you. However, beware that maintaining
+``six`` will be essential for you. However, beware that maintaining
single-source compatibility with older Python versions is ugly and `not
fun `_.
If you can drop support for older Python versions, ``python-future`` leverages
-some important features introduced into Python 2.6 and 2.7, such as
+some important features introduced into Python 2.7, such as
import hooks, and a comprehensive and well-tested set of backported
functionality, to allow you to write more idiomatic, maintainable code with
fewer compatibility hacks.
@@ -237,7 +232,7 @@ What is the relationship between ``python-future`` and ``python-modernize``?
``python-future`` contains, in addition to the ``future`` compatibility
package, a ``futurize`` script that is similar to ``python-modernize.py``
in intent and design. Both are based heavily on ``2to3``.
-
+
Whereas ``python-modernize`` converts Py2 code into a common subset of
Python 2 and 3, with ``six`` as a run-time dependency, ``futurize``
converts either Py2 or Py3 code into (almost) standard Python 3 code,
@@ -257,26 +252,14 @@ Platform and version support
Which versions of Python does ``python-future`` support?
--------------------------------------------------------
-Python 2.6, 2.7, and 3.3+ only.
+Python 2.6 and 3.3+ only. Python 2.7 and Python 3.4+ are preferred.
-Python 2.6 and 2.7 introduced many important forward-compatibility
-features (such as import hooks, ``b'...'`` literals and ``__future__``
-definitions) that greatly reduce the maintenance burden for single-source
-Py2/3 compatible code. ``future`` leverages these features and aims to
-close the remaining gap between Python 3 and 2.6 / 2.7.
-
-Python 2.6 does not offer the following features which help with Py3
-compatibility:
-- ``surrogateescape`` error handler for string encoding or decoding;
-- ``memoryview`` objects.
-
-Otherwise Python 2.6 is mostly supported.
-
-Python 3.2 could perhaps be supported too, although the illegal unicode
-literal ``u'...'`` syntax may be inconvenient to work around. The Py3.2
-userbase is very small, however. Please let us know via GitHub `issue #29
-`_ if you
-would like to see Py3.2 support.
+You may be able to use Python 2.6 but writing Py2/3 compatible code is not as
+easy. Python 2.7 introduced many important forward-compatibility features (such
+as import hooks, ``b'...'`` literals and ``__future__`` definitions) that
+greatly reduce the maintenance burden for single-source Py2/3 compatible code.
+``future`` leverages these features and aims to close the remaining gap between
+Python 3 and 2.7.
Do you support Pypy?
@@ -300,8 +283,7 @@ Support
Is there a mailing list?
------------------------
-Yes, please ask any questions on the `python-porting
-`_ mailing list.
+There was a `python-porting` mailing list, but it's now dead.
.. _contributing:
@@ -313,12 +295,13 @@ Can I help?
-----------
Yes please :) We welcome bug reports, additional tests, pull requests,
-and stories of either success or failure with using it. Help with the fixers
-for the ``futurize`` script is particularly welcome.
+and stories of either success or failure with using it.
+
+However, please note that the project is not very actively maintained. It
+should be considered done, like Python 2.
Where is the repo?
------------------
``_.
-
diff --git a/docs/future-builtins.rst b/docs/future-builtins.rst
index 6d2271e0..df8ff79d 100644
--- a/docs/future-builtins.rst
+++ b/docs/future-builtins.rst
@@ -15,4 +15,3 @@ The ``future.builtins`` module is also accessible as ``builtins`` on Py2.
>>> from builtins import round
>>> assert round(0.1250, 2) == 0.12
-
diff --git a/docs/futurize.rst b/docs/futurize.rst
index 12bfae04..11520a6c 100644
--- a/docs/futurize.rst
+++ b/docs/futurize.rst
@@ -1,6 +1,6 @@
.. _forwards-conversion:
-``futurize``: Py2 to Py2&3
+``futurize``: Py2 to Py2/3
--------------------------
.. include:: futurize_overview.rst
@@ -23,7 +23,7 @@ This applies fixes that modernize Python 2 code without changing the effect of
the code. With luck, this will not introduce any bugs into the code, or will at
least be trivial to fix. The changes are those that bring the Python code
up-to-date without breaking Py2 compatibility. The resulting code will be
-modern Python 2.6-compatible code plus ``__future__`` imports from the
+modern Python 2.7-compatible code plus ``__future__`` imports from the
following set:
.. code-block:: python
@@ -68,7 +68,7 @@ Implicit relative imports fixed, e.g.::
.. (This last step can be prevented using --no-bytes-literals if you already have b'...' markup in your code, whose meaning would otherwise be lost.)
Stage 1 does not add any imports from the ``future`` package. The output of
-stage 1 will probably not (yet) run on Python 3.
+stage 1 will probably not (yet) run on Python 3.
The goal for this stage is to create most of the ``diff`` for the entire
porting process, but without introducing any bugs. It should be uncontroversial
@@ -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
@@ -151,7 +151,7 @@ method on exceptions.
lib2to3.fixes.fix_set_literal
-This converts ``set([1, 2, 3]``) to ``{1, 2, 3}``, breaking Python 2.6 support.
+This converts ``set([1, 2, 3]``) to ``{1, 2, 3}``.
.. code-block:: python
@@ -210,7 +210,7 @@ Stage 2 also renames standard-library imports to their Py3 names and adds these
two lines::
from future import standard_library
- standard_library.install_hooks()
+ standard_library.install_aliases()
For example::
@@ -219,28 +219,28 @@ For example::
becomes::
from future import standard_library
- standard_library.install_hooks()
+ standard_library.install_aliases()
import configparser
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.
@@ -297,9 +292,9 @@ example::
Any unadorned string literals will then represent native platform strings
(byte-strings on Py2, unicode strings on Py3).
-An alternative is to pass the ``--unicode_literals`` flag::
-
- $ futurize --unicode_literals mypython2script.py
+An alternative is to pass the ``--unicode-literals`` flag::
+
+ $ futurize --unicode-literals mypython2script.py
After running this, all string literals that were not explicitly marked up as
``b''`` will mean text (Python 3 ``str`` or Python 2 ``unicode``).
@@ -311,11 +306,9 @@ After running this, all string literals that were not explicitly marked up as
Post-conversion
~~~~~~~~~~~~~~~
-After running ``futurize``, we recommend first running your tests on Python 3 and making further code changes until they pass on Python 3.
+After running ``futurize``, we recommend first running your tests on Python 3 and making further code changes until they pass on Python 3.
The next step would be manually tweaking the code to re-enable Python 2
compatibility with the help of the ``future`` package. For example, you can add
the ``@python_2_unicode_compatible`` decorator to any classes that define custom
``__str__`` methods. See :ref:`what-else` for more info.
-
-
diff --git a/docs/futurize_cheatsheet.rst b/docs/futurize_cheatsheet.rst
index 0ecad572..82f211c6 100644
--- a/docs/futurize_cheatsheet.rst
+++ b/docs/futurize_cheatsheet.rst
@@ -1,41 +1,40 @@
.. _futurize_cheatsheet:
-``futurize`` quick-start: automatic conversion from Py2 to Py2&3
-================================================================
+``futurize`` quick-start guide
+------------------------------
-Instructions and notes on converting code from supporting only Python 2 to
-supporting both Python 3 and 2 with a single codebase using ``futurize``:
+How to convert Py2 code to Py2/3 code using ``futurize``:
.. _porting-setup:
Step 0: setup
--------------
+~~~~~~~~~~~~~
Step 0 goal: set up and see the tests passing on Python 2 and failing on Python 3.
a. Clone the package from github/bitbucket. Optionally rename your repo to ``package-future``. Examples: ``reportlab-future``, ``paramiko-future``, ``mezzanine-future``.
-b. Create and activate a Python 2 conda environment or virtualenv. Install the package with ``python setup.py install`` and run its test suite on Py2.7 or Py2.6 (e.g. ``python setup.py test`` or ``py.test`` or ``nosetests``)
-c. Optionally: if there is a ``.travis.yml`` file, add Python version 3.3 and remove any versions < 2.6.
-d. Install Python 3.3 with e.g. ``sudo apt-get install python3``. On other platforms, an easy way is to use `Miniconda `_. Then e.g.::
-
- conda create -n py33 python=3.3 pip
+b. Create and activate a Python 2 conda environment or virtualenv. Install the package with ``python setup.py install`` and run its test suite on Py2.7 (e.g. ``python setup.py test`` or ``py.test``)
+c. Optionally: if there is a ``.travis.yml`` file, add Python version 3.6 and remove any versions < 2.6.
+d. Install Python 3 with e.g. ``sudo apt-get install python3``. On other platforms, an easy way is to use `Miniconda `_. Then e.g.::
+
+ conda create -n py36 python=3.6 pip
.. _porting-step1:
Step 1: modern Py2 code
------------------------
+~~~~~~~~~~~~~~~~~~~~~~~
The goal for this step is to modernize the Python 2 code without introducing any dependencies (on ``future`` or e.g. ``six``) at this stage.
**1a**. Install ``future`` into the virtualenv using::
-
+
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
@@ -45,12 +44,12 @@ See :ref:`forwards-conversion-stage1` for more info.
Example error
-~~~~~~~~~~~~~
+*************
One relatively common error after conversion is::
Traceback (most recent call last):
- ...
+ ...
File "/home/user/Install/BleedingEdge/reportlab/tests/test_encrypt.py", line 19, in
from .test_pdfencryption import parsedoc
ValueError: Attempted relative import in non-package
@@ -71,7 +70,7 @@ imports.)
.. _porting-step2:
Step 2: working Py3 code that still supports Py2
-------------------------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The goal for this step is to get the tests passing first on Py3 and then on Py2
again with the help of the ``future`` package.
@@ -80,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.
@@ -111,7 +109,7 @@ Python 3 semantics on Python 2, invoke it like this::
futurize --stage2 --all-imports myfolder/*.py
-
+
**2b**. Re-run your tests on Py3 now. Make changes until your tests pass on Python 3.
**2c**. Commit your changes! :)
diff --git a/docs/futurize_overview.rst b/docs/futurize_overview.rst
index 1192b9da..769b65c7 100644
--- a/docs/futurize_overview.rst
+++ b/docs/futurize_overview.rst
@@ -27,7 +27,7 @@ into this code which runs on both Py2 and Py3:
from __future__ import print_function
from future import standard_library
- standard_library.install_hooks()
+ standard_library.install_aliases()
from future.builtins import next
from future.builtins import object
import configparser # Py3-style import
@@ -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.6 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/hindsight.rst b/docs/hindsight.rst
index a7b283a1..b4654c6a 100644
--- a/docs/hindsight.rst
+++ b/docs/hindsight.rst
@@ -1,4 +1,3 @@
In a perfect world, the new metaclass syntax should ideally be available in
Python 2 as a `__future__`` import like ``from __future__ import
new_metaclass_syntax``.
-
diff --git a/docs/imports.rst b/docs/imports.rst
index cde02a2d..f7dcd9fc 100644
--- a/docs/imports.rst
+++ b/docs/imports.rst
@@ -24,7 +24,7 @@ standard feature of Python, see the following docs:
- print_function: `PEP 3105: Make print a function `_
- unicode_literals: `PEP 3112: Bytes literals in Python 3000 `_
-These are all available in Python 2.6 and up, and enabled by default in Python 3.x.
+These are all available in Python 2.7 and up, and enabled by default in Python 3.x.
.. _builtins-imports:
@@ -44,7 +44,7 @@ at the top of every module::
from builtins import *
On Python 3, this has no effect. (It shadows builtins with globals of the same
-names.)
+names.)
On Python 2, this import line shadows 18 builtins (listed below) to
provide their Python 3 semantics.
@@ -59,18 +59,13 @@ Explicit forms of the imports are often preferred and are necessary for using
certain automated code-analysis tools.
The complete set of imports of builtins from ``future`` is::
-
- from future.builtins import (ascii, bytes, chr, dict, filter, hex, input,
- int, map, next, oct, open, pow, range, round,
- str, super, zip)
-
-The contents of the ``future.builtins`` module are also accessible under the
-``builtins`` namespace as follows::
from builtins import (ascii, bytes, chr, dict, filter, hex, input,
int, map, next, oct, open, pow, range, round,
str, super, zip)
+These are also available under the ``future.builtins`` namespace for backward compatibility.
+
Importing only some of the builtins is cleaner but increases the risk of
introducing Py2/3 portability bugs as your code evolves over time. For example,
be aware of forgetting to import ``input``, which could expose a security
@@ -84,12 +79,12 @@ The internal API is currently as follows::
from future.types import bytes, dict, int, range, str
from future.builtins.misc import (ascii, chr, hex, input, next,
- oct, open, round, super)
+ oct, open, pow, round, super)
from future.builtins.iterators import filter, map, zip
-To understand the details of the backported builtins on Python 2, see the
-docs for these modules. Please note that this internal API is evolving and may
-not be stable between different versions of ``future``.
+Please note that this internal API is evolving and may not be stable between
+different versions of ``future``. To understand the details of the backported
+builtins on Python 2, see the docs for these modules.
For more information on what the backported types provide, see :ref:`what-else`.
@@ -99,7 +94,7 @@ For more information on what the backported types provide, see :ref:`what-else`.
.. _obsolete-builtins:
Obsolete Python 2 builtins
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+__________________________
Twelve Python 2 builtins have been removed from Python 3. To aid with
porting code to Python 3 module by module, you can use the following
@@ -120,5 +115,12 @@ equivalent Python 3 forms and then adds ``future`` imports to resurrect
Python 2 support, as described in :ref:`forwards-conversion-stage2`.
+.. include:: standard_library_imports.rst
+
+.. include:: translation.rst
+
.. include:: unicode_literals.rst
+Next steps
+----------
+See :ref:`what-else`.
diff --git a/docs/index.rst b/docs/index.rst
index b9ec0888..cc84c9b7 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -7,4 +7,3 @@ codebase to support both Python 2 and Python 3 with minimal overhead.
.. include:: contents.rst.inc
-
diff --git a/docs/int_object.rst b/docs/int_object.rst
index e52c35a0..f774784b 100644
--- a/docs/int_object.rst
+++ b/docs/int_object.rst
@@ -66,4 +66,3 @@ Without ``future`` (or with ``future`` < 0.7), this might be::
return list(map(int, data)) # same as returning data, but with up-front typechecking
else:
return list(map(long, data))
-
diff --git a/docs/isinstance.rst b/docs/isinstance.rst
index 5e6a20b0..2bb5084a 100644
--- a/docs/isinstance.rst
+++ b/docs/isinstance.rst
@@ -4,7 +4,7 @@ isinstance
----------
The following tests all pass on Python 3::
-
+
>>> assert isinstance(2**62, int)
>>> assert isinstance(2**63, int)
>>> assert isinstance(b'my byte-string', bytes)
@@ -69,7 +69,7 @@ the types from ``future`` to their native superclasses on Py2.
The ``native`` function in ``future.utils`` is provided for this. Here is how
to use it. (The output showing is from Py2)::
- >>> from builtins import *
+ >>> from builtins import int, bytes, str
>>> from future.utils import native
>>> a = int(10**20) # Py3-like long int
@@ -81,7 +81,7 @@ to use it. (The output showing is from Py2)::
100000000000000000000L
>>> type(native(a))
long
-
+
>>> b = bytes(b'ABC')
>>> type(b)
future.types.newbytes.newbytes
@@ -89,7 +89,7 @@ to use it. (The output showing is from Py2)::
'ABC'
>>> type(native(b))
str
-
+
>>> s = str(u'ABC')
>>> type(s)
future.types.newstr.newstr
@@ -115,4 +115,3 @@ The objects ``native_str`` and ``native_bytes`` are available in
The functions ``native_str_to_bytes`` and ``bytes_to_native_str`` are also
available for more explicit conversions.
-
diff --git a/docs/limitations.rst b/docs/limitations.rst
index 5b13ad81..0d13805d 100644
--- a/docs/limitations.rst
+++ b/docs/limitations.rst
@@ -1,4 +1,3 @@
-
limitations of the ``future`` module and differences between Py2 and Py3 that are not (yet) handled
===================================================================================================
@@ -39,7 +38,7 @@ Also:
b'\x00'[0] != 0
b'\x01'[0] != 1
-
+
``futurize`` does not yet wrap all byte-string literals in a ``bytes()``
call. This is on the to-do list. See :ref:`bytes-object` for more information.
@@ -51,5 +50,3 @@ Notes
adds this back in automatically, but ensure you do this too
when writing your classes, otherwise weird breakage when e.g. calling
``super()`` may occur.
-
-
diff --git a/docs/metaclasses.rst b/docs/metaclasses.rst
index c4bcdd00..d40c5a46 100644
--- a/docs/metaclasses.rst
+++ b/docs/metaclasses.rst
@@ -5,16 +5,14 @@ Python 3 and Python 2 syntax for metaclasses are incompatible.
``future`` provides a function (from ``jinja2/_compat.py``) called
:func:`with_metaclass` that can assist with specifying metaclasses
portably across Py3 and Py2. Use it like this::
-
+
from future.utils import with_metaclass
class BaseForm(object):
pass
-
+
class FormType(type):
pass
-
+
class Form(with_metaclass(FormType, BaseForm)):
pass
-
-
diff --git a/docs/notebooks/Writing Python 2-3 compatible code.ipynb b/docs/notebooks/Writing Python 2-3 compatible code.ipynb
index d7c98a0b..663ede44 100644
--- a/docs/notebooks/Writing Python 2-3 compatible code.ipynb
+++ b/docs/notebooks/Writing Python 2-3 compatible code.ipynb
@@ -1,2993 +1,3167 @@
{
- "metadata": {
- "name": "",
- "signature": "sha256:7fe04ffd8c478c4d6b7653aab35c47f3117c50ae383326dfeca599d53603d031"
- },
- "nbformat": 3,
- "nbformat_minor": 0,
- "worksheets": [
+ "cells": [
{
- "cells": [
- {
- "cell_type": "heading",
- "level": 1,
- "metadata": {},
- "source": [
- "Cheat Sheet: Writing Python 2-3 compatible code"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "- **Copyright (c):** 2013-2014 Python Charmers Pty Ltd, Australia.\n",
- "- **Author:** Ed Schofield.\n",
- "- **Licence:** Creative Commons Attribution.\n",
- "\n",
- "A PDF version is here: http://python-future.org/compatible_idioms.pdf\n",
- "\n",
- "This notebook shows you idioms for writing future-proof code that is compatible with both versions of Python: 2 and 3. It accompanies Ed Schofield's talk at PyCon AU 2014, \"Writing 2/3 compatible code\". (The video is here: .)\n",
- "\n",
- "Minimum versions:\n",
- "\n",
- " - Python 2: 2.6+\n",
- " - Python 3: 3.3+"
- ]
- },
- {
- "cell_type": "heading",
- "level": 2,
- "metadata": {},
- "source": [
- "Setup"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The imports below refer to these ``pip``-installable packages on PyPI:\n",
- "\n",
- " import future # pip install future\n",
- " import builtins # pip install future\n",
- " import past # pip install future\n",
- " import six # pip install six\n",
- "\n",
- "The following scripts are also ``pip``-installable:\n",
- "\n",
- " futurize # pip install future\n",
- " pasteurize # pip install future\n",
- "\n",
- "See http://python-future.org and https://pythonhosted.org/six/ for more information."
- ]
- },
- {
- "cell_type": "heading",
- "level": 2,
- "metadata": {},
- "source": [
- "Essential syntax differences"
- ]
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "print"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "print 'Hello'"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "print('Hello')"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "To print multiple strings, import ``print_function`` to prevent Py2 from interpreting it as a tuple:"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "print 'Hello', 'Guido'"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "from __future__ import print_function # (at top of module)\n",
- "\n",
- "print('Hello', 'Guido')"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "print >> sys.stderr, 'Hello'"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "from __future__ import print_function\n",
- "\n",
- "print('Hello', file=sys.stderr)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "print 'Hello',"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "from __future__ import print_function\n",
- "\n",
- "print('Hello', end='')"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "Raising exceptions"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "raise ValueError, \"dodgy value\""
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "raise ValueError(\"dodgy value\")"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Raising exceptions with a traceback:"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "traceback = sys.exc_info()[2]\n",
- "raise ValueError, \"dodgy value\", traceback"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 3 only:\n",
- "raise ValueError(\"dodgy value\").with_traceback()"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 1\n",
- "from six import reraise as raise_\n",
- "# or\n",
- "from future.utils import raise_\n",
- "\n",
- "traceback = sys.exc_info()[2]\n",
- "raise_(ValueError, \"dodgy value\", traceback)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 2\n",
- "from future.utils import raise_with_traceback\n",
- "\n",
- "raise_with_traceback(ValueError(\"dodgy value\"))"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Exception chaining (PEP 3134):"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Setup:\n",
- "class DatabaseError(Exception):\n",
- " pass"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [],
- "prompt_number": 3
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 3 only\n",
- "class FileDatabase:\n",
- " def __init__(self, filename):\n",
- " try:\n",
- " self.file = open(filename)\n",
- " except IOError as exc:\n",
- " raise DatabaseError('failed to open') from exc"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "from future.utils import raise_from\n",
- "\n",
- "class FileDatabase:\n",
- " def __init__(self, filename):\n",
- " try:\n",
- " self.file = open(filename)\n",
- " except IOError as exc:\n",
- " raise_from(DatabaseError('failed to open'), exc)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [],
- "prompt_number": 16
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Testing the above:\n",
- "try:\n",
- " fd = FileDatabase('non_existent_file.txt')\n",
- "except Exception as e:\n",
- " assert isinstance(e.__cause__, IOError) # FileNotFoundError on Py3.3+ inherits from IOError"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [],
- "prompt_number": 17
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "Catching exceptions"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "try:\n",
- " ...\n",
- "except ValueError, e:\n",
- " ..."
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "try:\n",
- " ...\n",
- "except ValueError as e:\n",
- " ..."
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "Division"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Integer division (rounding down):"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "assert 2 / 3 == 0"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "assert 2 // 3 == 0"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\"True division\" (float division):"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 3 only:\n",
- "assert 3 / 2 == 1.5"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "from __future__ import division # (at top of module)\n",
- "\n",
- "assert 3 / 2 == 1.5"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\"Old division\" (i.e. compatible with Py2 behaviour):"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "a = b / c # with any types"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "from past.utils import old_div\n",
- "\n",
- "a = old_div(b, c) # always same as / on Py2"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "Long integers"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Short integers are gone in Python 3 and ``long`` has become ``int`` (without the trailing ``L`` in the ``repr``)."
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only\n",
- "k = 9223372036854775808L\n",
- "\n",
- "# Python 2 and 3:\n",
- "k = 9223372036854775808"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only\n",
- "bigint = 1L\n",
- "\n",
- "# Python 2 and 3\n",
- "from builtins import int\n",
- "bigint = int(1)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "To test whether a value is an integer (of any kind):"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "if isinstance(x, (int, long)):\n",
- " ...\n",
- "\n",
- "# Python 3 only:\n",
- "if isinstance(x, int):\n",
- " ...\n",
- "\n",
- "# Python 2 and 3: option 1\n",
- "from builtins import int # subclass of long on Py2\n",
- "\n",
- "if isinstance(x, int): # matches both int and long on Py2\n",
- " ...\n",
- "\n",
- "# Python 2 and 3: option 2\n",
- "from past.builtins import long\n",
- "\n",
- "if isinstance(x, (int, long)):\n",
- " ..."
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "Octal constants"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "0644 # Python 2 only"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "0o644 # Python 2 and 3"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "Backtick repr"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "`x` # Python 2 only"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "repr(x) # Python 2 and 3"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "Metaclasses"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "class BaseForm(object):\n",
- " pass\n",
- "\n",
- "class FormType(type):\n",
- " pass"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "class Form(BaseForm):\n",
- " __metaclass__ = FormType\n",
- " pass"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 3 only:\n",
- "class Form(BaseForm, metaclass=FormType):\n",
- " pass"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "from six import with_metaclass\n",
- "# or\n",
- "from future.utils import with_metaclass\n",
- "\n",
- "class Form(with_metaclass(FormType, BaseForm)):\n",
- " pass"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 2,
- "metadata": {},
- "source": [
- "Strings and bytes"
- ]
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "Unicode (text) string literals"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "If you are upgrading an existing Python 2 codebase, it may be preferable to mark up all string literals as unicode explicitly with ``u`` prefixes:"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only\n",
- "s1 = 'The Zen of Python'\n",
- "s2 = u'\u304d\u305f\u306a\u3044\u306e\u3088\u308a\u304d\u308c\u3044\u306a\u65b9\u304c\u3044\u3044\\n'\n",
- "\n",
- "# Python 2 and 3\n",
- "s1 = u'The Zen of Python'\n",
- "s2 = u'\u304d\u305f\u306a\u3044\u306e\u3088\u308a\u304d\u308c\u3044\u306a\u65b9\u304c\u3044\u3044\\n'"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The ``futurize`` and ``python-modernize`` tools do not currently offer an option to do this automatically."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "If you are writing code for a new project or new codebase, you can use this idiom to make all string literals in a module unicode strings:"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3\n",
- "from __future__ import unicode_literals # at top of module\n",
- "\n",
- "s1 = 'The Zen of Python'\n",
- "s2 = '\u304d\u305f\u306a\u3044\u306e\u3088\u308a\u304d\u308c\u3044\u306a\u65b9\u304c\u3044\u3044\\n'"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "See http://python-future.org/unicode_literals.html for more discussion on which style to use."
- ]
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "Byte-string literals"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only\n",
- "s = 'This must be a byte-string'\n",
- "\n",
- "# Python 2 and 3\n",
- "s = b'This must be a byte-string'"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "To loop over a byte-string with possible high-bit characters, obtaining each character as a byte-string of length 1:"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "for bytechar in 'byte-string with high-bit chars like \\xf9':\n",
- " ...\n",
- "\n",
- "# Python 3 only:\n",
- "for myint in b'byte-string with high-bit chars like \\xf9':\n",
- " bytechar = bytes([myint])\n",
- "\n",
- "# Python 2 and 3:\n",
- "from builtins import bytes\n",
- "for myint in bytes(b'byte-string with high-bit chars like \\xf9'):\n",
- " bytechar = bytes([myint])"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "As an alternative, ``chr()`` and ``.encode('latin-1')`` can be used to convert an int into a 1-char byte string:"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 3 only:\n",
- "for myint in b'byte-string with high-bit chars like \\xf9':\n",
- " char = chr(myint) # returns a unicode string\n",
- " bytechar = char.encode('latin-1')\n",
- "\n",
- "# Python 2 and 3:\n",
- "from builtins import bytes, chr\n",
- "for myint in bytes(b'byte-string with high-bit chars like \\xf9'):\n",
- " char = chr(myint) # returns a unicode string\n",
- " bytechar = char.encode('latin-1') # forces returning a byte str"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "basestring"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "a = u'abc'\n",
- "b = 'def'\n",
- "assert (isinstance(a, basestring) and isinstance(b, basestring))\n",
- "\n",
- "# Python 2 and 3: alternative 1\n",
- "from past.builtins import basestring # pip install future\n",
- "\n",
- "a = u'abc'\n",
- "b = b'def'\n",
- "assert (isinstance(a, basestring) and isinstance(b, basestring))"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: alternative 2: refactor the code to avoid considering\n",
- "# byte-strings as strings.\n",
- "\n",
- "from builtins import str\n",
- "a = u'abc'\n",
- "b = b'def'\n",
- "c = b.decode()\n",
- "assert isinstance(a, str) and isinstance(c, str)\n",
- "# ..."
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "unicode"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "templates = [u\"blog/blog_post_detail_%s.html\" % unicode(slug)]"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: alternative 1\n",
- "from builtins import str\n",
- "templates = [u\"blog/blog_post_detail_%s.html\" % str(slug)]"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: alternative 2\n",
- "from builtins import str as text\n",
- "templates = [u\"blog/blog_post_detail_%s.html\" % text(slug)]"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "StringIO"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "from StringIO import StringIO\n",
- "# or:\n",
- "from cStringIO import StringIO\n",
- "\n",
- "# Python 2 and 3:\n",
- "from io import BytesIO # for handling byte strings\n",
- "from io import StringIO # for handling unicode strings"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 2,
- "metadata": {},
- "source": [
- "Imports relative to a package"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Suppose the package is:\n",
- "\n",
- " mypackage/\n",
- " __init__.py\n",
- " submodule1.py\n",
- " submodule2.py\n",
- " \n",
- "and the code below is in ``submodule1.py``:"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only: \n",
- "import submodule2"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "from . import submodule2"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "# To make Py2 code safer (more like Py3) by preventing\n",
- "# implicit relative imports, you can also add this to the top:\n",
- "from __future__ import absolute_import"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 2,
- "metadata": {},
- "source": [
- "Dictionaries"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "heights = {'Fred': 175, 'Anne': 166, 'Joe': 192}"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "Iterating through ``dict`` keys/values/items"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Iterable dict keys:"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "for key in heights.iterkeys():\n",
- " ..."
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "for key in heights:\n",
- " ..."
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Iterable dict values:"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "for value in heights.itervalues():\n",
- " ..."
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Idiomatic Python 3\n",
- "for value in heights.values(): # extra memory overhead on Py2\n",
- " ..."
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 1\n",
- "from builtins import dict\n",
- "\n",
- "heights = dict(Fred=175, Anne=166, Joe=192)\n",
- "for key in heights.values(): # efficient on Py2 and Py3\n",
- " ..."
- ],
- "language": "python",
- "metadata": {},
- "outputs": [],
- "prompt_number": 8
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 2\n",
- "from builtins import itervalues\n",
- "# or\n",
- "from six import itervalues\n",
- "\n",
- "for key in itervalues(heights):\n",
- " ..."
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Iterable dict items:"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "for (key, value) in heights.iteritems():\n",
- " ..."
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 1\n",
- "for (key, value) in heights.items(): # inefficient on Py2 \n",
- " ..."
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 2\n",
- "from builtins import iteritems\n",
- "# or\n",
- "from six import iteritems\n",
- "\n",
- "for (key, value) in iteritems(heights):\n",
- " ..."
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "dict keys/values/items as a list"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "dict keys as a list:"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "keylist = heights.keys()\n",
- "assert isinstance(keylist, list)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "keylist = list(heights)\n",
- "assert isinstance(keylist, list)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "dict values as a list:"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "heights = {'Fred': 175, 'Anne': 166, 'Joe': 192}\n",
- "valuelist = heights.values()\n",
- "assert isinstance(valuelist, list)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 1\n",
- "valuelist = list(heights.values()) # inefficient on Py2"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 2\n",
- "from builtins import dict\n",
- "\n",
- "heights = dict(Fred=175, Anne=166, Joe=192)\n",
- "valuelist = list(heights.values())"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 3\n",
- "from future.utils import listvalues\n",
- "\n",
- "valuelist = listvalues(heights)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 4\n",
- "from future.utils import itervalues\n",
- "# or\n",
- "from six import itervalues\n",
- "\n",
- "valuelist = list(itervalues(heights))"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "dict items as a list:"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 1\n",
- "itemlist = list(heights.items()) # inefficient on Py2"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 2\n",
- "from future.utils import listitems\n",
- "\n",
- "itemlist = listitems(heights)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 3\n",
- "from future.utils import iteritems\n",
- "# or\n",
- "from six import iteritems\n",
- "\n",
- "itemlist = list(iteritems(heights))"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 2,
- "metadata": {},
- "source": [
- "Custom class behaviour"
- ]
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "Custom iterators"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only\n",
- "class Upper(object):\n",
- " def __init__(self, iterable):\n",
- " self._iter = iter(iterable)\n",
- " def next(self): # Py2-style\n",
- " return self._iter.next().upper()\n",
- " def __iter__(self):\n",
- " return self\n",
- "\n",
- "itr = Upper('hello')\n",
- "assert itr.next() == 'H' # Py2-style\n",
- "assert list(itr) == list('ELLO')"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 1\n",
- "from builtins import object\n",
- "\n",
- "class Upper(object):\n",
- " def __init__(self, iterable):\n",
- " self._iter = iter(iterable)\n",
- " def __next__(self): # Py3-style iterator interface\n",
- " return next(self._iter).upper() # builtin next() function calls\n",
- " def __iter__(self):\n",
- " return self\n",
- "\n",
- "itr = Upper('hello')\n",
- "assert next(itr) == 'H' # compatible style\n",
- "assert list(itr) == list('ELLO')"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 2\n",
- "from future.utils import implements_iterator\n",
- "\n",
- "@implements_iterator\n",
- "class Upper(object):\n",
- " def __init__(self, iterable):\n",
- " self._iter = iter(iterable)\n",
- " def __next__(self): # Py3-style iterator interface\n",
- " return next(self._iter).upper() # builtin next() function calls\n",
- " def __iter__(self):\n",
- " return self\n",
- "\n",
- "itr = Upper('hello')\n",
- "assert next(itr) == 'H'\n",
- "assert list(itr) == list('ELLO')"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "Custom ``__str__`` methods"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "class MyClass(object):\n",
- " def __unicode__(self):\n",
- " return 'Unicode string: \\u5b54\\u5b50'\n",
- " def __str__(self):\n",
- " return unicode(self).encode('utf-8')\n",
- "\n",
- "a = MyClass()\n",
- "print(a) # prints encoded string"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "from future.utils import python_2_unicode_compatible\n",
- "\n",
- "@python_2_unicode_compatible\n",
- "class MyClass(object):\n",
- " def __str__(self):\n",
- " return u'Unicode string: \\u5b54\\u5b50'\n",
- "\n",
- "a = MyClass()\n",
- "print(a) # prints string encoded as utf-8 on Py2"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [
- {
- "output_type": "stream",
- "stream": "stdout",
- "text": [
- "Unicode string: \u5b54\u5b50\n"
- ]
- }
- ],
- "prompt_number": 11
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "Custom ``__nonzero__`` vs ``__bool__`` method:"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "class AllOrNothing(object):\n",
- " def __init__(self, l):\n",
- " self.l = l\n",
- " def __nonzero__(self):\n",
- " return all(self.l)\n",
- "\n",
- "container = AllOrNothing([0, 100, 200])\n",
- "assert not bool(container)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "from builtins import object\n",
- "\n",
- "class AllOrNothing(object):\n",
- " def __init__(self, l):\n",
- " self.l = l\n",
- " def __bool__(self):\n",
- " return all(self.l)\n",
- "\n",
- "container = AllOrNothing([0, 100, 200])\n",
- "assert not bool(container)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 2,
- "metadata": {},
- "source": [
- "Lists versus iterators"
- ]
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "xrange"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "for i in xrange(10**8):\n",
- " ..."
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: forward-compatible\n",
- "from builtins import range\n",
- "for i in range(10**8):\n",
- " ..."
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: backward-compatible\n",
- "from past.builtins import xrange\n",
- "for i in xrange(10**8):\n",
- " ..."
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "range"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only\n",
- "mylist = range(5)\n",
- "assert mylist == [0, 1, 2, 3, 4]"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: forward-compatible: option 1\n",
- "mylist = list(range(5)) # copies memory on Py2\n",
- "assert mylist == [0, 1, 2, 3, 4]"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: forward-compatible: option 2\n",
- "from builtins import range\n",
- "\n",
- "mylist = list(range(5))\n",
- "assert mylist == [0, 1, 2, 3, 4]"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 3\n",
- "from future.utils import lrange\n",
- "\n",
- "mylist = lrange(5)\n",
- "assert mylist == [0, 1, 2, 3, 4]"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: backward compatible\n",
- "from past.builtins import range\n",
- "\n",
- "mylist = range(5)\n",
- "assert mylist == [0, 1, 2, 3, 4]"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "map"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "mynewlist = map(f, myoldlist)\n",
- "assert mynewlist == [f(x) for x in myoldlist]"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 1\n",
- "# Idiomatic Py3, but inefficient on Py2\n",
- "mynewlist = list(map(f, myoldlist))\n",
- "assert mynewlist == [f(x) for x in myoldlist]"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 2\n",
- "from builtins import map\n",
- "\n",
- "mynewlist = list(map(f, myoldlist))\n",
- "assert mynewlist == [f(x) for x in myoldlist]"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 3\n",
- "try:\n",
- " import itertools.imap as map\n",
- "except ImportError:\n",
- " pass\n",
- "\n",
- "mynewlist = list(map(f, myoldlist)) # inefficient on Py2\n",
- "assert mynewlist == [f(x) for x in myoldlist]"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 4\n",
- "from future.utils import lmap\n",
- "\n",
- "mynewlist = lmap(f, myoldlist)\n",
- "assert mynewlist == [f(x) for x in myoldlist]"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 5\n",
- "from past.builtins import map\n",
- "\n",
- "mynewlist = map(f, myoldlist)\n",
- "assert mynewlist == [f(x) for x in myoldlist]"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "imap"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "from itertools import imap\n",
- "\n",
- "myiter = imap(func, myoldlist)\n",
- "assert isinstance(myiter, iter)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 3 only:\n",
- "myiter = map(func, myoldlist)\n",
- "assert isinstance(myiter, iter)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 1\n",
- "from builtins import map\n",
- "\n",
- "myiter = map(func, myoldlist)\n",
- "assert isinstance(myiter, iter)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 2\n",
- "try:\n",
- " import itertools.imap as map\n",
- "except ImportError:\n",
- " pass\n",
- "\n",
- "myiter = map(func, myoldlist)\n",
- "assert isinstance(myiter, iter)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "zip, izip"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "As above with ``zip`` and ``itertools.izip``."
- ]
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "filter, ifilter"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "As above with ``filter`` and ``itertools.ifilter`` too."
- ]
- },
- {
- "cell_type": "heading",
- "level": 2,
- "metadata": {},
- "source": [
- "Other builtins"
- ]
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "File IO with open()"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": true,
- "input": [
- "# Python 2 only\n",
- "f = open('myfile.txt')\n",
- "data = f.read() # as a byte string\n",
- "text = data.decode('utf-8')\n",
- "\n",
- "# Python 2 and 3: alternative 1\n",
- "from io import open\n",
- "f = open('myfile.txt', 'rb')\n",
- "data = f.read() # as bytes\n",
- "text = data.decode('utf-8') # unicode, not bytes\n",
- "\n",
- "# Python 2 and 3: alternative 2\n",
- "from io import open\n",
- "f = open('myfile.txt', encoding='utf-8')\n",
- "text = f.read() # unicode, not bytes"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "reduce()"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "assert reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) == 1+2+3+4+5"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "from functools import reduce\n",
- "\n",
- "assert reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) == 1+2+3+4+5"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "raw_input()"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "name = raw_input('What is your name? ')\n",
- "assert isinstance(name, str) # native str"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "from builtins import input\n",
- "\n",
- "name = input('What is your name? ')\n",
- "assert isinstance(name, str) # native str on Py2 and Py3"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "input()"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "input(\"Type something safe please: \")"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3\n",
- "from builtins import input\n",
- "eval(input(\"Type something safe please: \"))"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Warning: using either of these is **unsafe** with untrusted input."
- ]
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "file()"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "f = file(pathname)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "f = open(pathname)\n",
- "\n",
- "# But preferably, use this:\n",
- "from io import open\n",
- "f = open(pathname, 'rb') # if f.read() should return bytes\n",
- "# or\n",
- "f = open(pathname, 'rt') # if f.read() should return unicode text"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "execfile()"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "execfile('myfile.py')"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: alternative 1\n",
- "from past.builtins import execfile\n",
- "\n",
- "execfile('myfile.py')"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: alternative 2\n",
- "exec(compile(open('myfile.py').read()))\n",
- "\n",
- "# This can sometimes cause this:\n",
- "# SyntaxError: function ... uses import * and bare exec ...\n",
- "# See https://github.com/PythonCharmers/python-future/issues/37"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "unichr()"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "assert unichr(8364) == '\u20ac'"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 3 only:\n",
- "assert chr(8364) == '\u20ac'"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "from builtins import chr\n",
- "assert chr(8364) == '\u20ac'"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "intern()"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "intern('mystring')"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 3 only:\n",
- "from sys import intern\n",
- "intern('mystring')"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: alternative 1\n",
- "from past.builtins import intern\n",
- "intern('mystring')"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: alternative 2\n",
- "from six.moves import intern\n",
- "intern('mystring')"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: alternative 3\n",
- "from future.standard_library import install_aliases\n",
- "install_aliases()\n",
- "from sys import intern\n",
- "intern('mystring')"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: alternative 2\n",
- "try:\n",
- " from sys import intern\n",
- "except ImportError:\n",
- " pass\n",
- "intern('mystring')"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "apply()"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "args = ('a', 'b')\n",
- "kwargs = {'kwarg1': True}"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "apply(f, args, kwargs)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: alternative 1\n",
- "f(*args, **kwargs)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: alternative 2\n",
- "from past.builtins import apply\n",
- "apply(f, args, kwargs)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "chr()"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "assert chr(64) == b'@'\n",
- "assert chr(200) == b'\\xc8'"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 3 only: option 1\n",
- "assert chr(64).encode('latin-1') == b'@'\n",
- "assert chr(0xc8).encode('latin-1') == b'\\xc8'"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 1\n",
- "from builtins import chr\n",
- "\n",
- "assert chr(64).encode('latin-1') == b'@'\n",
- "assert chr(0xc8).encode('latin-1') == b'\\xc8'"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 3 only: option 2\n",
- "assert bytes([64]) == b'@'\n",
- "assert bytes([0xc8]) == b'\\xc8'"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: option 2\n",
- "from builtins import bytes\n",
- "\n",
- "assert bytes([64]) == b'@'\n",
- "assert bytes([0xc8]) == b'\\xc8'"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "cmp()"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "assert cmp('a', 'b') < 0 and cmp('b', 'a') > 0 and cmp('c', 'c') == 0"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: alternative 1\n",
- "from past.builtins import cmp\n",
- "assert cmp('a', 'b') < 0 and cmp('b', 'a') > 0 and cmp('c', 'c') == 0"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: alternative 2\n",
- "cmp = lambda(x, y): (x > y) - (x < y)\n",
- "assert cmp('a', 'b') < 0 and cmp('b', 'a') > 0 and cmp('c', 'c') == 0"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "reload()"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "reload(mymodule)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3\n",
- "from imp import reload\n",
- "reload(mymodule)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 2,
- "metadata": {},
- "source": [
- "Standard library"
- ]
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "dbm modules"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only\n",
- "import anydbm\n",
- "import whichdb\n",
- "import dbm\n",
- "import dumbdbm\n",
- "import gdbm\n",
- "\n",
- "# Python 2 and 3: alternative 1\n",
- "from future import standard_library\n",
- "standard_library.install_aliases()\n",
- "\n",
- "import dbm\n",
- "import dbm.ndbm\n",
- "import dbm.dumb\n",
- "import dbm.gnu\n",
- "\n",
- "# Python 2 and 3: alternative 2\n",
- "from future.moves import dbm\n",
- "from future.moves.dbm import dumb\n",
- "from future.moves.dbm import ndbm\n",
- "from future.moves.dbm import gnu\n",
- "\n",
- "# Python 2 and 3: alternative 3\n",
- "from six.moves import dbm_gnu\n",
- "# (others not supported)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "commands / subprocess modules"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only\n",
- "from commands import getoutput, getstatusoutput\n",
- "\n",
- "# Python 2 and 3\n",
- "from future import standard_library\n",
- "standard_library.install_aliases()\n",
- "\n",
- "from subprocess import getoutput, getstatusoutput"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "subprocess.check_output()"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2.7 and above\n",
- "from subprocess import check_output\n",
- "\n",
- "# Python 2.6 and above: alternative 1\n",
- "from future.moves.subprocess import check_output\n",
- "\n",
- "# Python 2.6 and above: alternative 2\n",
- "from future import standard_library\n",
- "standard_library.install_aliases()\n",
- "\n",
- "from subprocess import check_output"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "collections: Counter and OrderedDict"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2.7 and above\n",
- "from collections import Counter, OrderedDict\n",
- "\n",
- "# Python 2.6 and above: alternative 1\n",
- "from future.moves.collections import Counter, OrderedDict\n",
- "\n",
- "# Python 2.6 and above: alternative 2\n",
- "from future import standard_library\n",
- "standard_library.install_aliases()\n",
- "\n",
- "from collections import Counter, OrderedDict"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [],
- "prompt_number": 6
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "StringIO module"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only\n",
- "from StringIO import StringIO\n",
- "from cStringIO import StringIO"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3\n",
- "from io import BytesIO\n",
- "# and refactor StringIO() calls to BytesIO() if passing byte-strings"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "http module"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "import httplib\n",
- "import Cookie\n",
- "import cookielib\n",
- "import BaseHTTPServer\n",
- "import SimpleHTTPServer\n",
- "import CGIHttpServer\n",
- "\n",
- "# Python 2 and 3 (after ``pip install future``):\n",
- "import http.client\n",
- "import http.cookies\n",
- "import http.cookiejar\n",
- "import http.server"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "xmlrpc module"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "import DocXMLRPCServer\n",
- "import SimpleXMLRPCServer\n",
- "\n",
- "# Python 2 and 3 (after ``pip install future``):\n",
- "import xmlrpc.server"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "import xmlrpclib\n",
- "\n",
- "# Python 2 and 3 (after ``pip install future``):\n",
- "import xmlrpc.client"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "html escaping and entities"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3:\n",
- "from cgi import escape\n",
- "\n",
- "# Safer (Python 2 and 3, after ``pip install future``):\n",
- "from html import escape\n",
- "\n",
- "# Python 2 only:\n",
- "from htmlentitydefs import codepoint2name, entitydefs, name2codepoint\n",
- "\n",
- "# Python 2 and 3 (after ``pip install future``):\n",
- "from html.entities import codepoint2name, entitydefs, name2codepoint"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "html parsing"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "from HTMLParser import HTMLParser\n",
- "\n",
- "# Python 2 and 3 (after ``pip install future``)\n",
- "from html.parser import HTMLParser\n",
- "\n",
- "# Python 2 and 3 (alternative 2):\n",
- "from future.moves.html.parser import HTMLParser"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "urllib module"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "``urllib`` is the hardest module to use from Python 2/3 compatible code. You may like to use Requests (http://python-requests.org) instead."
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "from urlparse import urlparse\n",
- "from urllib import urlencode\n",
- "from urllib2 import urlopen, Request, HTTPError"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 3 only:\n",
- "from urllib.parse import urlparse, urlencode\n",
- "from urllib.request import urlopen, Request\n",
- "from urllib.error import HTTPError"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [],
- "prompt_number": 2
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: easiest option\n",
- "from future.standard_library import install_aliases\n",
- "install_aliases()\n",
- "\n",
- "from urllib.parse import urlparse, urlencode\n",
- "from urllib.request import urlopen, Request\n",
- "from urllib.error import HTTPError"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: alternative 2\n",
- "from future.standard_library import hooks\n",
- "\n",
- "with hooks():\n",
- " from urllib.parse import urlparse, urlencode\n",
- " from urllib.request import urlopen, Request\n",
- " from urllib.error import HTTPError"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: alternative 3\n",
- "from future.moves.urllib.parse import urlparse, urlencode\n",
- "from future.moves.urllib.request import urlopen, Request\n",
- "from future.moves.urllib.error import HTTPError\n",
- "# or\n",
- "from six.moves.urllib.parse import urlparse, urlencode\n",
- "from six.moves.urllib.request import urlopen\n",
- "from six.moves.urllib.error import HTTPError"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 and 3: alternative 4\n",
- "try:\n",
- " from urllib.parse import urlparse, urlencode\n",
- " from urllib.request import urlopen, Request\n",
- " from urllib.error import HTTPError\n",
- "except ImportError:\n",
- " from urlparse import urlparse\n",
- " from urllib import urlencode\n",
- " from urllib2 import urlopen, Request, HTTPError"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "Tkinter"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "import Tkinter\n",
- "import Dialog\n",
- "import FileDialog\n",
- "import ScrolledText\n",
- "import SimpleDialog\n",
- "import Tix \n",
- "import Tkconstants\n",
- "import Tkdnd \n",
- "import tkColorChooser\n",
- "import tkCommonDialog\n",
- "import tkFileDialog\n",
- "import tkFont\n",
- "import tkMessageBox\n",
- "import tkSimpleDialog\n",
- "\n",
- "# Python 2 and 3 (after ``pip install future``):\n",
- "import tkinter\n",
- "import tkinter.dialog\n",
- "import tkinter.filedialog\n",
- "import tkinter.scolledtext\n",
- "import tkinter.simpledialog\n",
- "import tkinter.tix\n",
- "import tkinter.constants\n",
- "import tkinter.dnd\n",
- "import tkinter.colorchooser\n",
- "import tkinter.commondialog\n",
- "import tkinter.filedialog\n",
- "import tkinter.font\n",
- "import tkinter.messagebox\n",
- "import tkinter.simpledialog"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "socketserver"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "import SocketServer\n",
- "\n",
- "# Python 2 and 3 (after ``pip install future``):\n",
- "import socketserver"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "copy_reg, copyreg"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "import copy_reg\n",
- "\n",
- "# Python 2 and 3 (after ``pip install future``):\n",
- "import copyreg"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "configparser"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "from ConfigParser import ConfigParser\n",
- "\n",
- "# Python 2 and 3 (after ``pip install future``):\n",
- "from configparser import ConfigParser"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "queue"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "from Queue import Queue, heapq, deque\n",
- "\n",
- "# Python 2 and 3 (after ``pip install future``):\n",
- "from queue import Queue, heapq, deque"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "repr, reprlib"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "from repr import aRepr, repr\n",
- "\n",
- "# Python 2 and 3 (after ``pip install future``):\n",
- "from reprlib import aRepr, repr"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "UserDict, UserList, UserString"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "from UserDict import UserDict\n",
- "from UserList import UserList\n",
- "from UserString import UserString\n",
- "\n",
- "# Python 3 only:\n",
- "from collections import UserDict, UserList, UserString\n",
- "\n",
- "# Python 2 and 3: alternative 1\n",
- "from future.moves.collections import UserDict, UserList, UserString\n",
- "\n",
- "# Python 2 and 3: alternative 2\n",
- "from six.moves import UserDict, UserList, UserString\n",
- "\n",
- "# Python 2 and 3: alternative 3\n",
- "from future.standard_library import install_aliases\n",
- "install_aliases()\n",
- "from collections import UserDict, UserList, UserString"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "itertools: filterfalse, zip_longest"
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Cheat Sheet: Writing Python 2-3 compatible code"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "- **Copyright (c):** 2013-2024 Python Charmers, Australia.\n",
+ "- **Author:** Ed Schofield.\n",
+ "- **Licence:** Creative Commons Attribution.\n",
+ "\n",
+ "A PDF version is here: https://python-future.org/compatible_idioms.pdf\n",
+ "\n",
+ "This notebook shows you idioms for writing future-proof code that is compatible with both versions of Python: 2 and 3. It accompanies Ed Schofield's talk at PyCon AU 2014, \"Writing 2/3 compatible code\". (The video is here: .)\n",
+ "\n",
+ "Minimum versions:\n",
+ "\n",
+ " - Python 2: 2.6+\n",
+ " - Python 3: 3.3+"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The imports below refer to these ``pip``-installable packages on PyPI:\n",
+ "\n",
+ " import future # pip install future\n",
+ " import builtins # pip install future\n",
+ " import past # pip install future\n",
+ " import six # pip install six\n",
+ "\n",
+ "The following scripts are also ``pip``-installable:\n",
+ "\n",
+ " futurize # pip install future\n",
+ " pasteurize # pip install future\n",
+ "\n",
+ "See https://python-future.org and https://pythonhosted.org/six/ for more information."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Essential syntax differences"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### print"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "print 'Hello'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "print('Hello')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "To print multiple strings, import ``print_function`` to prevent Py2 from interpreting it as a tuple:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "print 'Hello', 'Guido'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "from __future__ import print_function # (at top of module)\n",
+ "\n",
+ "print('Hello', 'Guido')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "print >> sys.stderr, 'Hello'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "from __future__ import print_function\n",
+ "\n",
+ "print('Hello', file=sys.stderr)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "print 'Hello',"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "from __future__ import print_function\n",
+ "\n",
+ "print('Hello', end='')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Raising exceptions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "raise ValueError, \"dodgy value\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "raise ValueError(\"dodgy value\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Raising exceptions with a traceback:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "traceback = sys.exc_info()[2]\n",
+ "raise ValueError, \"dodgy value\", traceback"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 3 only:\n",
+ "raise ValueError(\"dodgy value\").with_traceback()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 1\n",
+ "from six import reraise as raise_\n",
+ "# or\n",
+ "from future.utils import raise_\n",
+ "\n",
+ "traceback = sys.exc_info()[2]\n",
+ "raise_(ValueError, \"dodgy value\", traceback)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 2\n",
+ "from future.utils import raise_with_traceback\n",
+ "\n",
+ "raise_with_traceback(ValueError(\"dodgy value\"))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Exception chaining (PEP 3134):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Setup:\n",
+ "class DatabaseError(Exception):\n",
+ " pass"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 3 only\n",
+ "class FileDatabase:\n",
+ " def __init__(self, filename):\n",
+ " try:\n",
+ " self.file = open(filename)\n",
+ " except IOError as exc:\n",
+ " raise DatabaseError('failed to open') from exc"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "from future.utils import raise_from\n",
+ "\n",
+ "class FileDatabase:\n",
+ " def __init__(self, filename):\n",
+ " try:\n",
+ " self.file = open(filename)\n",
+ " except IOError as exc:\n",
+ " raise_from(DatabaseError('failed to open'), exc)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Testing the above:\n",
+ "try:\n",
+ " fd = FileDatabase('non_existent_file.txt')\n",
+ "except Exception as e:\n",
+ " assert isinstance(e.__cause__, IOError) # FileNotFoundError on Py3.3+ inherits from IOError"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Catching exceptions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "try:\n",
+ " ...\n",
+ "except ValueError, e:\n",
+ " ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "try:\n",
+ " ...\n",
+ "except ValueError as e:\n",
+ " ..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Division"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Integer division (rounding down):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "assert 2 / 3 == 0"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "assert 2 // 3 == 0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\"True division\" (float division):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 3 only:\n",
+ "assert 3 / 2 == 1.5"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "from __future__ import division # (at top of module)\n",
+ "\n",
+ "assert 3 / 2 == 1.5"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\"Old division\" (i.e. compatible with Py2 behaviour):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "a = b / c # with any types"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "from past.utils import old_div\n",
+ "\n",
+ "a = old_div(b, c) # always same as / on Py2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Long integers"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Short integers are gone in Python 3 and ``long`` has become ``int`` (without the trailing ``L`` in the ``repr``)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only\n",
+ "k = 9223372036854775808L\n",
+ "\n",
+ "# Python 2 and 3:\n",
+ "k = 9223372036854775808"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only\n",
+ "bigint = 1L\n",
+ "\n",
+ "# Python 2 and 3\n",
+ "from builtins import int\n",
+ "bigint = int(1)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "To test whether a value is an integer (of any kind):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "if isinstance(x, (int, long)):\n",
+ " ...\n",
+ "\n",
+ "# Python 3 only:\n",
+ "if isinstance(x, int):\n",
+ " ...\n",
+ "\n",
+ "# Python 2 and 3: option 1\n",
+ "from builtins import int # subclass of long on Py2\n",
+ "\n",
+ "if isinstance(x, int): # matches both int and long on Py2\n",
+ " ...\n",
+ "\n",
+ "# Python 2 and 3: option 2\n",
+ "from past.builtins import long\n",
+ "\n",
+ "if isinstance(x, (int, long)):\n",
+ " ..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Octal constants"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "0644 # Python 2 only"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "0o644 # Python 2 and 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Backtick repr"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "`x` # Python 2 only"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "repr(x) # Python 2 and 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Metaclasses"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "class BaseForm(object):\n",
+ " pass\n",
+ "\n",
+ "class FormType(type):\n",
+ " pass"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "class Form(BaseForm):\n",
+ " __metaclass__ = FormType\n",
+ " pass"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 3 only:\n",
+ "class Form(BaseForm, metaclass=FormType):\n",
+ " pass"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "from six import with_metaclass\n",
+ "# or\n",
+ "from future.utils import with_metaclass\n",
+ "\n",
+ "class Form(with_metaclass(FormType, BaseForm)):\n",
+ " pass"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Strings and bytes"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Unicode (text) string literals"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "If you are upgrading an existing Python 2 codebase, it may be preferable to mark up all string literals as unicode explicitly with ``u`` prefixes:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only\n",
+ "s1 = 'The Zen of Python'\n",
+ "s2 = u'きたないのよりきれいな方がいい\\n'\n",
+ "\n",
+ "# Python 2 and 3\n",
+ "s1 = u'The Zen of Python'\n",
+ "s2 = u'きたないのよりきれいな方がいい\\n'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The ``futurize`` and ``python-modernize`` tools do not currently offer an option to do this automatically."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "If you are writing code for a new project or new codebase, you can use this idiom to make all string literals in a module unicode strings:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3\n",
+ "from __future__ import unicode_literals # at top of module\n",
+ "\n",
+ "s1 = 'The Zen of Python'\n",
+ "s2 = 'きたないのよりきれいな方がいい\\n'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "See https://python-future.org/unicode_literals.html for more discussion on which style to use."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Byte-string literals"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only\n",
+ "s = 'This must be a byte-string'\n",
+ "\n",
+ "# Python 2 and 3\n",
+ "s = b'This must be a byte-string'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "To loop over a byte-string with possible high-bit characters, obtaining each character as a byte-string of length 1:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "for bytechar in 'byte-string with high-bit chars like \\xf9':\n",
+ " ...\n",
+ "\n",
+ "# Python 3 only:\n",
+ "for myint in b'byte-string with high-bit chars like \\xf9':\n",
+ " bytechar = bytes([myint])\n",
+ "\n",
+ "# Python 2 and 3:\n",
+ "from builtins import bytes\n",
+ "for myint in bytes(b'byte-string with high-bit chars like \\xf9'):\n",
+ " bytechar = bytes([myint])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "As an alternative, ``chr()`` and ``.encode('latin-1')`` can be used to convert an int into a 1-char byte string:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 3 only:\n",
+ "for myint in b'byte-string with high-bit chars like \\xf9':\n",
+ " char = chr(myint) # returns a unicode string\n",
+ " bytechar = char.encode('latin-1')\n",
+ "\n",
+ "# Python 2 and 3:\n",
+ "from builtins import bytes, chr\n",
+ "for myint in bytes(b'byte-string with high-bit chars like \\xf9'):\n",
+ " char = chr(myint) # returns a unicode string\n",
+ " bytechar = char.encode('latin-1') # forces returning a byte str"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### basestring"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "a = u'abc'\n",
+ "b = 'def'\n",
+ "assert (isinstance(a, basestring) and isinstance(b, basestring))\n",
+ "\n",
+ "# Python 2 and 3: alternative 1\n",
+ "from past.builtins import basestring # pip install future\n",
+ "\n",
+ "a = u'abc'\n",
+ "b = b'def'\n",
+ "assert (isinstance(a, basestring) and isinstance(b, basestring))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: alternative 2: refactor the code to avoid considering\n",
+ "# byte-strings as strings.\n",
+ "\n",
+ "from builtins import str\n",
+ "a = u'abc'\n",
+ "b = b'def'\n",
+ "c = b.decode()\n",
+ "assert isinstance(a, str) and isinstance(c, str)\n",
+ "# ..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### unicode"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "templates = [u\"blog/blog_post_detail_%s.html\" % unicode(slug)]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: alternative 1\n",
+ "from builtins import str\n",
+ "templates = [u\"blog/blog_post_detail_%s.html\" % str(slug)]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: alternative 2\n",
+ "from builtins import str as text\n",
+ "templates = [u\"blog/blog_post_detail_%s.html\" % text(slug)]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### StringIO"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "from StringIO import StringIO\n",
+ "# or:\n",
+ "from cStringIO import StringIO\n",
+ "\n",
+ "# Python 2 and 3:\n",
+ "from io import BytesIO # for handling byte strings\n",
+ "from io import StringIO # for handling unicode strings"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Imports relative to a package"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Suppose the package is:\n",
+ "\n",
+ " mypackage/\n",
+ " __init__.py\n",
+ " submodule1.py\n",
+ " submodule2.py\n",
+ " \n",
+ "and the code below is in ``submodule1.py``:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only: \n",
+ "import submodule2"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "from . import submodule2"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "# To make Py2 code safer (more like Py3) by preventing\n",
+ "# implicit relative imports, you can also add this to the top:\n",
+ "from __future__ import absolute_import"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Dictionaries"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "heights = {'Fred': 175, 'Anne': 166, 'Joe': 192}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Iterating through ``dict`` keys/values/items"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Iterable dict keys:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "for key in heights.iterkeys():\n",
+ " ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "for key in heights:\n",
+ " ..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Iterable dict values:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "for value in heights.itervalues():\n",
+ " ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Idiomatic Python 3\n",
+ "for value in heights.values(): # extra memory overhead on Py2\n",
+ " ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 1\n",
+ "from builtins import dict\n",
+ "\n",
+ "heights = dict(Fred=175, Anne=166, Joe=192)\n",
+ "for key in heights.values(): # efficient on Py2 and Py3\n",
+ " ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 2\n",
+ "from future.utils import itervalues\n",
+ "# or\n",
+ "from six import itervalues\n",
+ "\n",
+ "for key in itervalues(heights):\n",
+ " ..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Iterable dict items:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "for (key, value) in heights.iteritems():\n",
+ " ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 1\n",
+ "for (key, value) in heights.items(): # inefficient on Py2 \n",
+ " ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 2\n",
+ "from future.utils import viewitems\n",
+ "\n",
+ "for (key, value) in viewitems(heights): # also behaves like a set\n",
+ " ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 3\n",
+ "from future.utils import iteritems\n",
+ "# or\n",
+ "from six import iteritems\n",
+ "\n",
+ "for (key, value) in iteritems(heights):\n",
+ " ..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### dict keys/values/items as a list"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "dict keys as a list:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "keylist = heights.keys()\n",
+ "assert isinstance(keylist, list)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "keylist = list(heights)\n",
+ "assert isinstance(keylist, list)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "dict values as a list:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "heights = {'Fred': 175, 'Anne': 166, 'Joe': 192}\n",
+ "valuelist = heights.values()\n",
+ "assert isinstance(valuelist, list)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 1\n",
+ "valuelist = list(heights.values()) # inefficient on Py2"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 2\n",
+ "from builtins import dict\n",
+ "\n",
+ "heights = dict(Fred=175, Anne=166, Joe=192)\n",
+ "valuelist = list(heights.values())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 3\n",
+ "from future.utils import listvalues\n",
+ "\n",
+ "valuelist = listvalues(heights)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 4\n",
+ "from future.utils import itervalues\n",
+ "# or\n",
+ "from six import itervalues\n",
+ "\n",
+ "valuelist = list(itervalues(heights))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "dict items as a list:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 1\n",
+ "itemlist = list(heights.items()) # inefficient on Py2"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 2\n",
+ "from future.utils import listitems\n",
+ "\n",
+ "itemlist = listitems(heights)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 3\n",
+ "from future.utils import iteritems\n",
+ "# or\n",
+ "from six import iteritems\n",
+ "\n",
+ "itemlist = list(iteritems(heights))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Custom class behaviour"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Custom iterators"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only\n",
+ "class Upper(object):\n",
+ " def __init__(self, iterable):\n",
+ " self._iter = iter(iterable)\n",
+ " def next(self): # Py2-style\n",
+ " return self._iter.next().upper()\n",
+ " def __iter__(self):\n",
+ " return self\n",
+ "\n",
+ "itr = Upper('hello')\n",
+ "assert itr.next() == 'H' # Py2-style\n",
+ "assert list(itr) == list('ELLO')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 1\n",
+ "from builtins import object\n",
+ "\n",
+ "class Upper(object):\n",
+ " def __init__(self, iterable):\n",
+ " self._iter = iter(iterable)\n",
+ " def __next__(self): # Py3-style iterator interface\n",
+ " return next(self._iter).upper() # builtin next() function calls\n",
+ " def __iter__(self):\n",
+ " return self\n",
+ "\n",
+ "itr = Upper('hello')\n",
+ "assert next(itr) == 'H' # compatible style\n",
+ "assert list(itr) == list('ELLO')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 2\n",
+ "from future.utils import implements_iterator\n",
+ "\n",
+ "@implements_iterator\n",
+ "class Upper(object):\n",
+ " def __init__(self, iterable):\n",
+ " self._iter = iter(iterable)\n",
+ " def __next__(self): # Py3-style iterator interface\n",
+ " return next(self._iter).upper() # builtin next() function calls\n",
+ " def __iter__(self):\n",
+ " return self\n",
+ "\n",
+ "itr = Upper('hello')\n",
+ "assert next(itr) == 'H'\n",
+ "assert list(itr) == list('ELLO')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Custom ``__str__`` methods"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "class MyClass(object):\n",
+ " def __unicode__(self):\n",
+ " return 'Unicode string: \\u5b54\\u5b50'\n",
+ " def __str__(self):\n",
+ " return unicode(self).encode('utf-8')\n",
+ "\n",
+ "a = MyClass()\n",
+ "print(a) # prints encoded string"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Unicode string: 孔子\n"
]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Python 2 only:\n",
- "from itertools import ifilterfalse, izip_longest\n",
- "\n",
- "# Python 3 only:\n",
- "from itertools import filterfalse, zip_longest\n",
- "\n",
- "# Python 2 and 3: alternative 1\n",
- "from future.moves.itertools import filterfalse, zip_longest\n",
- "\n",
- "# Python 2 and 3: alternative 2\n",
- "from six.moves import filterfalse, zip_longest\n",
- "\n",
- "# Python 2 and 3: alternative 3\n",
- "from future.standard_library import install_aliases\n",
- "install_aliases()\n",
- "from itertools import filterfalse, zip_longest"
- ],
- "language": "python",
- "metadata": {},
- "outputs": []
}
],
- "metadata": {}
+ "source": [
+ "# Python 2 and 3:\n",
+ "from future.utils import python_2_unicode_compatible\n",
+ "\n",
+ "@python_2_unicode_compatible\n",
+ "class MyClass(object):\n",
+ " def __str__(self):\n",
+ " return u'Unicode string: \\u5b54\\u5b50'\n",
+ "\n",
+ "a = MyClass()\n",
+ "print(a) # prints string encoded as utf-8 on Py2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Custom ``__nonzero__`` vs ``__bool__`` method:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "class AllOrNothing(object):\n",
+ " def __init__(self, l):\n",
+ " self.l = l\n",
+ " def __nonzero__(self):\n",
+ " return all(self.l)\n",
+ "\n",
+ "container = AllOrNothing([0, 100, 200])\n",
+ "assert not bool(container)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "from builtins import object\n",
+ "\n",
+ "class AllOrNothing(object):\n",
+ " def __init__(self, l):\n",
+ " self.l = l\n",
+ " def __bool__(self):\n",
+ " return all(self.l)\n",
+ "\n",
+ "container = AllOrNothing([0, 100, 200])\n",
+ "assert not bool(container)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Lists versus iterators"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### xrange"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "for i in xrange(10**8):\n",
+ " ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: forward-compatible\n",
+ "from builtins import range\n",
+ "for i in range(10**8):\n",
+ " ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: backward-compatible\n",
+ "from past.builtins import xrange\n",
+ "for i in xrange(10**8):\n",
+ " ..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### range"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only\n",
+ "mylist = range(5)\n",
+ "assert mylist == [0, 1, 2, 3, 4]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: forward-compatible: option 1\n",
+ "mylist = list(range(5)) # copies memory on Py2\n",
+ "assert mylist == [0, 1, 2, 3, 4]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: forward-compatible: option 2\n",
+ "from builtins import range\n",
+ "\n",
+ "mylist = list(range(5))\n",
+ "assert mylist == [0, 1, 2, 3, 4]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 3\n",
+ "from future.utils import lrange\n",
+ "\n",
+ "mylist = lrange(5)\n",
+ "assert mylist == [0, 1, 2, 3, 4]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: backward compatible\n",
+ "from past.builtins import range\n",
+ "\n",
+ "mylist = range(5)\n",
+ "assert mylist == [0, 1, 2, 3, 4]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### map"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "mynewlist = map(f, myoldlist)\n",
+ "assert mynewlist == [f(x) for x in myoldlist]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 1\n",
+ "# Idiomatic Py3, but inefficient on Py2\n",
+ "mynewlist = list(map(f, myoldlist))\n",
+ "assert mynewlist == [f(x) for x in myoldlist]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 2\n",
+ "from builtins import map\n",
+ "\n",
+ "mynewlist = list(map(f, myoldlist))\n",
+ "assert mynewlist == [f(x) for x in myoldlist]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 3\n",
+ "try:\n",
+ " from itertools import imap as map\n",
+ "except ImportError:\n",
+ " pass\n",
+ "\n",
+ "mynewlist = list(map(f, myoldlist)) # inefficient on Py2\n",
+ "assert mynewlist == [f(x) for x in myoldlist]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 4\n",
+ "from future.utils import lmap\n",
+ "\n",
+ "mynewlist = lmap(f, myoldlist)\n",
+ "assert mynewlist == [f(x) for x in myoldlist]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 5\n",
+ "from past.builtins import map\n",
+ "\n",
+ "mynewlist = map(f, myoldlist)\n",
+ "assert mynewlist == [f(x) for x in myoldlist]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### imap"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "from itertools import imap\n",
+ "\n",
+ "myiter = imap(func, myoldlist)\n",
+ "assert isinstance(myiter, iter)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 3 only:\n",
+ "myiter = map(func, myoldlist)\n",
+ "assert isinstance(myiter, iter)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 1\n",
+ "from builtins import map\n",
+ "\n",
+ "myiter = map(func, myoldlist)\n",
+ "assert isinstance(myiter, iter)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 2\n",
+ "try:\n",
+ " from itertools import imap as map\n",
+ "except ImportError:\n",
+ " pass\n",
+ "\n",
+ "myiter = map(func, myoldlist)\n",
+ "assert isinstance(myiter, iter)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### zip, izip"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "As above with ``zip`` and ``itertools.izip``."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### filter, ifilter"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "As above with ``filter`` and ``itertools.ifilter`` too."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Other builtins"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### File IO with open()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only\n",
+ "f = open('myfile.txt')\n",
+ "data = f.read() # as a byte string\n",
+ "text = data.decode('utf-8')\n",
+ "\n",
+ "# Python 2 and 3: alternative 1\n",
+ "from io import open\n",
+ "f = open('myfile.txt', 'rb')\n",
+ "data = f.read() # as bytes\n",
+ "text = data.decode('utf-8') # unicode, not bytes\n",
+ "\n",
+ "# Python 2 and 3: alternative 2\n",
+ "from io import open\n",
+ "f = open('myfile.txt', encoding='utf-8')\n",
+ "text = f.read() # unicode, not bytes"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### reduce()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "assert reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) == 1+2+3+4+5"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "from functools import reduce\n",
+ "\n",
+ "assert reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) == 1+2+3+4+5"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### raw_input()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "name = raw_input('What is your name? ')\n",
+ "assert isinstance(name, str) # native str"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "from builtins import input\n",
+ "\n",
+ "name = input('What is your name? ')\n",
+ "assert isinstance(name, str) # native str on Py2 and Py3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### input()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "input(\"Type something safe please: \")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3\n",
+ "from builtins import input\n",
+ "eval(input(\"Type something safe please: \"))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Warning: using either of these is **unsafe** with untrusted input."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### file()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "f = file(pathname)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "f = open(pathname)\n",
+ "\n",
+ "# But preferably, use this:\n",
+ "from io import open\n",
+ "f = open(pathname, 'rb') # if f.read() should return bytes\n",
+ "# or\n",
+ "f = open(pathname, 'rt') # if f.read() should return unicode text"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### exec"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "exec 'x = 10'\n",
+ "\n",
+ "# Python 2 and 3:\n",
+ "exec('x = 10')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "g = globals()\n",
+ "exec 'x = 10' in g\n",
+ "\n",
+ "# Python 2 and 3:\n",
+ "g = globals()\n",
+ "exec('x = 10', g)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "l = locals()\n",
+ "exec 'x = 10' in g, l\n",
+ "\n",
+ "# Python 2 and 3:\n",
+ "exec('x = 10', g, l)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "But note that Py3's `exec()` is less powerful (and less dangerous) than Py2's `exec` statement."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### execfile()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "execfile('myfile.py')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: alternative 1\n",
+ "from past.builtins import execfile\n",
+ "\n",
+ "execfile('myfile.py')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: alternative 2\n",
+ "exec(compile(open('myfile.py').read()))\n",
+ "\n",
+ "# This can sometimes cause this:\n",
+ "# SyntaxError: function ... uses import * and bare exec ...\n",
+ "# See https://github.com/PythonCharmers/python-future/issues/37"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### unichr()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "assert unichr(8364) == '€'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 3 only:\n",
+ "assert chr(8364) == '€'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "from builtins import chr\n",
+ "assert chr(8364) == '€'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### intern()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "intern('mystring')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 3 only:\n",
+ "from sys import intern\n",
+ "intern('mystring')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: alternative 1\n",
+ "from past.builtins import intern\n",
+ "intern('mystring')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: alternative 2\n",
+ "from six.moves import intern\n",
+ "intern('mystring')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: alternative 3\n",
+ "from future.standard_library import install_aliases\n",
+ "install_aliases()\n",
+ "from sys import intern\n",
+ "intern('mystring')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: alternative 2\n",
+ "try:\n",
+ " from sys import intern\n",
+ "except ImportError:\n",
+ " pass\n",
+ "intern('mystring')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### apply()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "args = ('a', 'b')\n",
+ "kwargs = {'kwarg1': True}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "apply(f, args, kwargs)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: alternative 1\n",
+ "f(*args, **kwargs)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: alternative 2\n",
+ "from past.builtins import apply\n",
+ "apply(f, args, kwargs)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### chr()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "assert chr(64) == b'@'\n",
+ "assert chr(200) == b'\\xc8'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 3 only: option 1\n",
+ "assert chr(64).encode('latin-1') == b'@'\n",
+ "assert chr(0xc8).encode('latin-1') == b'\\xc8'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 1\n",
+ "from builtins import chr\n",
+ "\n",
+ "assert chr(64).encode('latin-1') == b'@'\n",
+ "assert chr(0xc8).encode('latin-1') == b'\\xc8'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 3 only: option 2\n",
+ "assert bytes([64]) == b'@'\n",
+ "assert bytes([0xc8]) == b'\\xc8'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: option 2\n",
+ "from builtins import bytes\n",
+ "\n",
+ "assert bytes([64]) == b'@'\n",
+ "assert bytes([0xc8]) == b'\\xc8'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### cmp()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "assert cmp('a', 'b') < 0 and cmp('b', 'a') > 0 and cmp('c', 'c') == 0"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: alternative 1\n",
+ "from past.builtins import cmp\n",
+ "assert cmp('a', 'b') < 0 and cmp('b', 'a') > 0 and cmp('c', 'c') == 0"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: alternative 2\n",
+ "cmp = lambda(x, y): (x > y) - (x < y)\n",
+ "assert cmp('a', 'b') < 0 and cmp('b', 'a') > 0 and cmp('c', 'c') == 0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### reload()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "reload(mymodule)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3\n",
+ "from imp import reload\n",
+ "reload(mymodule)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Standard library"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### dbm modules"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only\n",
+ "import anydbm\n",
+ "import whichdb\n",
+ "import dbm\n",
+ "import dumbdbm\n",
+ "import gdbm\n",
+ "\n",
+ "# Python 2 and 3: alternative 1\n",
+ "from future import standard_library\n",
+ "standard_library.install_aliases()\n",
+ "\n",
+ "import dbm\n",
+ "import dbm.ndbm\n",
+ "import dbm.dumb\n",
+ "import dbm.gnu\n",
+ "\n",
+ "# Python 2 and 3: alternative 2\n",
+ "from future.moves import dbm\n",
+ "from future.moves.dbm import dumb\n",
+ "from future.moves.dbm import ndbm\n",
+ "from future.moves.dbm import gnu\n",
+ "\n",
+ "# Python 2 and 3: alternative 3\n",
+ "from six.moves import dbm_gnu\n",
+ "# (others not supported)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### commands / subprocess modules"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only\n",
+ "from commands import getoutput, getstatusoutput\n",
+ "\n",
+ "# Python 2 and 3\n",
+ "from future import standard_library\n",
+ "standard_library.install_aliases()\n",
+ "\n",
+ "from subprocess import getoutput, getstatusoutput"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### subprocess.check_output()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2.7 and above\n",
+ "from subprocess import check_output\n",
+ "\n",
+ "# Python 2.6 and above: alternative 1\n",
+ "from future.moves.subprocess import check_output\n",
+ "\n",
+ "# Python 2.6 and above: alternative 2\n",
+ "from future import standard_library\n",
+ "standard_library.install_aliases()\n",
+ "\n",
+ "from subprocess import check_output"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### collections: Counter, OrderedDict, ChainMap"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2.7 and above\n",
+ "from collections import Counter, OrderedDict, ChainMap\n",
+ "\n",
+ "# Python 2.6 and above: alternative 1\n",
+ "from future.backports import Counter, OrderedDict, ChainMap\n",
+ "\n",
+ "# Python 2.6 and above: alternative 2\n",
+ "from future import standard_library\n",
+ "standard_library.install_aliases()\n",
+ "\n",
+ "from collections import Counter, OrderedDict, ChainMap"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### StringIO module"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only\n",
+ "from StringIO import StringIO\n",
+ "from cStringIO import StringIO"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3\n",
+ "from io import BytesIO\n",
+ "# and refactor StringIO() calls to BytesIO() if passing byte-strings"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### http module"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "import httplib\n",
+ "import Cookie\n",
+ "import cookielib\n",
+ "import BaseHTTPServer\n",
+ "import SimpleHTTPServer\n",
+ "import CGIHttpServer\n",
+ "\n",
+ "# Python 2 and 3 (after ``pip install future``):\n",
+ "import http.client\n",
+ "import http.cookies\n",
+ "import http.cookiejar\n",
+ "import http.server"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### xmlrpc module"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "import DocXMLRPCServer\n",
+ "import SimpleXMLRPCServer\n",
+ "\n",
+ "# Python 2 and 3 (after ``pip install future``):\n",
+ "import xmlrpc.server"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "import xmlrpclib\n",
+ "\n",
+ "# Python 2 and 3 (after ``pip install future``):\n",
+ "import xmlrpc.client"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### html escaping and entities"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3:\n",
+ "from cgi import escape\n",
+ "\n",
+ "# Safer (Python 2 and 3, after ``pip install future``):\n",
+ "from html import escape\n",
+ "\n",
+ "# Python 2 only:\n",
+ "from htmlentitydefs import codepoint2name, entitydefs, name2codepoint\n",
+ "\n",
+ "# Python 2 and 3 (after ``pip install future``):\n",
+ "from html.entities import codepoint2name, entitydefs, name2codepoint"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### html parsing"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "from HTMLParser import HTMLParser\n",
+ "\n",
+ "# Python 2 and 3 (after ``pip install future``)\n",
+ "from html.parser import HTMLParser\n",
+ "\n",
+ "# Python 2 and 3 (alternative 2):\n",
+ "from future.moves.html.parser import HTMLParser"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### urllib module"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "``urllib`` is the hardest module to use from Python 2/3 compatible code. You may like to use Requests (https://python-requests.org) instead."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "from urlparse import urlparse\n",
+ "from urllib import urlencode\n",
+ "from urllib2 import urlopen, Request, HTTPError"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 3 only:\n",
+ "from urllib.parse import urlparse, urlencode\n",
+ "from urllib.request import urlopen, Request\n",
+ "from urllib.error import HTTPError"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: easiest option\n",
+ "from future.standard_library import install_aliases\n",
+ "install_aliases()\n",
+ "\n",
+ "from urllib.parse import urlparse, urlencode\n",
+ "from urllib.request import urlopen, Request\n",
+ "from urllib.error import HTTPError"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: alternative 2\n",
+ "from future.standard_library import hooks\n",
+ "\n",
+ "with hooks():\n",
+ " from urllib.parse import urlparse, urlencode\n",
+ " from urllib.request import urlopen, Request\n",
+ " from urllib.error import HTTPError"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: alternative 3\n",
+ "from future.moves.urllib.parse import urlparse, urlencode\n",
+ "from future.moves.urllib.request import urlopen, Request\n",
+ "from future.moves.urllib.error import HTTPError\n",
+ "# or\n",
+ "from six.moves.urllib.parse import urlparse, urlencode\n",
+ "from six.moves.urllib.request import urlopen\n",
+ "from six.moves.urllib.error import HTTPError"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 and 3: alternative 4\n",
+ "try:\n",
+ " from urllib.parse import urlparse, urlencode\n",
+ " from urllib.request import urlopen, Request\n",
+ " from urllib.error import HTTPError\n",
+ "except ImportError:\n",
+ " from urlparse import urlparse\n",
+ " from urllib import urlencode\n",
+ " from urllib2 import urlopen, Request, HTTPError"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Tkinter"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "import Tkinter\n",
+ "import Dialog\n",
+ "import FileDialog\n",
+ "import ScrolledText\n",
+ "import SimpleDialog\n",
+ "import Tix \n",
+ "import Tkconstants\n",
+ "import Tkdnd \n",
+ "import tkColorChooser\n",
+ "import tkCommonDialog\n",
+ "import tkFileDialog\n",
+ "import tkFont\n",
+ "import tkMessageBox\n",
+ "import tkSimpleDialog\n",
+ "import ttk\n",
+ "\n",
+ "# Python 2 and 3 (after ``pip install future``):\n",
+ "import tkinter\n",
+ "import tkinter.dialog\n",
+ "import tkinter.filedialog\n",
+ "import tkinter.scrolledtext\n",
+ "import tkinter.simpledialog\n",
+ "import tkinter.tix\n",
+ "import tkinter.constants\n",
+ "import tkinter.dnd\n",
+ "import tkinter.colorchooser\n",
+ "import tkinter.commondialog\n",
+ "import tkinter.filedialog\n",
+ "import tkinter.font\n",
+ "import tkinter.messagebox\n",
+ "import tkinter.simpledialog\n",
+ "import tkinter.ttk"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### socketserver"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "import SocketServer\n",
+ "\n",
+ "# Python 2 and 3 (after ``pip install future``):\n",
+ "import socketserver"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### copy_reg, copyreg"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "import copy_reg\n",
+ "\n",
+ "# Python 2 and 3 (after ``pip install future``):\n",
+ "import copyreg"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### configparser"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "from ConfigParser import ConfigParser\n",
+ "\n",
+ "# Python 2 and 3 (after ``pip install future``):\n",
+ "from configparser import ConfigParser"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### queue"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "from Queue import Queue, heapq, deque\n",
+ "\n",
+ "# Python 2 and 3 (after ``pip install future``):\n",
+ "from queue import Queue, heapq, deque"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### repr, reprlib"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "from repr import aRepr, repr\n",
+ "\n",
+ "# Python 2 and 3 (after ``pip install future``):\n",
+ "from reprlib import aRepr, repr"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### UserDict, UserList, UserString"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "from UserDict import UserDict\n",
+ "from UserList import UserList\n",
+ "from UserString import UserString\n",
+ "\n",
+ "# Python 3 only:\n",
+ "from collections import UserDict, UserList, UserString\n",
+ "\n",
+ "# Python 2 and 3: alternative 1\n",
+ "from future.moves.collections import UserDict, UserList, UserString\n",
+ "\n",
+ "# Python 2 and 3: alternative 2\n",
+ "from six.moves import UserDict, UserList, UserString\n",
+ "\n",
+ "# Python 2 and 3: alternative 3\n",
+ "from future.standard_library import install_aliases\n",
+ "install_aliases()\n",
+ "from collections import UserDict, UserList, UserString"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### itertools: filterfalse, zip_longest"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Python 2 only:\n",
+ "from itertools import ifilterfalse, izip_longest\n",
+ "\n",
+ "# Python 3 only:\n",
+ "from itertools import filterfalse, zip_longest\n",
+ "\n",
+ "# Python 2 and 3: alternative 1\n",
+ "from future.moves.itertools import filterfalse, zip_longest\n",
+ "\n",
+ "# Python 2 and 3: alternative 2\n",
+ "from six.moves import filterfalse, zip_longest\n",
+ "\n",
+ "# Python 2 and 3: alternative 3\n",
+ "from future.standard_library import install_aliases\n",
+ "install_aliases()\n",
+ "from itertools import filterfalse, zip_longest"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.4.3"
}
- ]
-}
\ No newline at end of file
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/docs/notebooks/object special methods (next, bool, ...).ipynb b/docs/notebooks/object special methods (next, bool, ...).ipynb
index 5729ddc5..7da31856 100644
--- a/docs/notebooks/object special methods (next, bool, ...).ipynb
+++ b/docs/notebooks/object special methods (next, bool, ...).ipynb
@@ -63,7 +63,7 @@
"collapsed": false,
"input": [
"# Py3-style iterators written as new-style classes (subclasses of\n",
- "# future.builtins.object) are backward compatibile with Py2:\n",
+ "# future.builtins.object) are backward compatible with Py2:\n",
"class Upper(object):\n",
" def __init__(self, iterable):\n",
" self._iter = iter(iterable)\n",
diff --git a/docs/older_interfaces.rst b/docs/older_interfaces.rst
new file mode 100644
index 00000000..546f92b9
--- /dev/null
+++ b/docs/older_interfaces.rst
@@ -0,0 +1,141 @@
+.. _older-standard-library-interfaces:
+
+Older interfaces
+~~~~~~~~~~~~~~~~
+
+In addition to the direct and ``install_aliases()`` interfaces (described in
+:ref:`standard-library-imports`), ``future`` supports four other interfaces to
+the reorganized standard library. This is largely for historical reasons (for
+versions prior to 0.14).
+
+
+``future.moves`` interface
+__________________________
+
+The ``future.moves`` interface avoids import hooks. It may therefore be more
+robust, at the cost of less idiomatic code. Use it as follows::
+
+ from future.moves import queue
+ from future.moves import socketserver
+ from future.moves.http.client import HTTPConnection
+ # etc.
+
+If you wish to achieve the effect of a two-level import such as this::
+
+ import http.client
+
+portably on both Python 2 and Python 3, note that Python currently does not
+support syntax like this::
+
+ from future.moves import http.client
+
+One workaround is to replace the dot with an underscore::
+
+ import future.moves.http.client as http_client
+
+
+Comparing future.moves and six.moves
+++++++++++++++++++++++++++++++++++++
+
+``future.moves`` and ``six.moves`` provide a similar Python 3-style
+interface to the native standard library module definitions.
+
+The major difference is that the ``future.moves`` package is a real Python package
+(``future/moves/__init__.py``) with real modules provided as ``.py`` files, whereas
+``six.moves`` constructs fake ``_LazyModule`` module objects within the Python
+code and injects them into the ``sys.modules`` cache.
+
+The advantage of ``six.moves`` is that the code fits in a single module that can be
+copied into a project that seeks to eliminate external dependencies.
+
+The advantage of ``future.moves`` is that it is likely to be more robust in the
+face of magic like Django's auto-reloader and tools like ``py2exe`` and
+``cx_freeze``. See issues #51, #53, #56, and #63 in the ``six`` project for
+more detail of bugs related to the ``six.moves`` approach.
+
+
+``import_`` and ``from_import`` functions
+_________________________________________
+
+The functional interface is to use the ``import_`` and ``from_import``
+functions from ``future.standard_library`` as follows::
+
+ from future.standard_library import import_, from_import
+
+ http = import_('http.client')
+ urllib = import_('urllib.request')
+
+ urlopen, urlsplit = from_import('urllib.request', 'urlopen', 'urlsplit')
+
+This interface also works with two-level imports.
+
+
+Context-manager for import hooks
+________________________________
+
+The context-manager interface is via a context-manager called ``hooks``::
+
+ from future.standard_library import hooks
+ with hooks():
+ import socketserver
+ import queue
+ import configparser
+ import test.support
+ import html.parser
+ from collections import UserList
+ from itertools import filterfalse, zip_longest
+ from http.client import HttpConnection
+ import urllib.request
+ # and other moved modules and definitions
+
+This interface is straightforward and effective, using PEP 302 import
+hooks. However, there are reports that this sometimes leads to problems
+(see issue #238). Until this is resolved, it is probably safer to use direct
+imports or one of the other import mechanisms listed above.
+
+
+install_hooks() call (deprecated)
+_________________________________
+
+The last interface to the reorganized standard library is via a call to
+``install_hooks()``::
+
+ from future import standard_library
+ standard_library.install_hooks()
+
+ import urllib
+ f = urllib.request.urlopen('http://www.python.org/')
+
+ standard_library.remove_hooks()
+
+If you use this interface, it is recommended to disable the import hooks again
+after use by calling ``remove_hooks()``, in order to prevent the futurized
+modules from being invoked inadvertently by other modules. (Python does not
+automatically disable import hooks at the end of a module, but keeps them
+active for the life of a process unless removed.)
+
+.. The call to ``scrub_future_sys_modules()`` removes any modules from the
+.. ``sys.modules`` cache (on Py2 only) that have Py3-style names, like ``http.client``.
+.. This can prevent libraries that have their own Py2/3 compatibility code from
+.. importing the ``future.moves`` or ``future.backports`` modules unintentionally.
+.. Code such as this will then fall through to using the Py2 standard library
+.. modules on Py2::
+..
+.. try:
+.. from http.client import HTTPConnection
+.. except ImportError:
+.. from httplib import HTTPConnection
+..
+.. **Requests**: The above snippet is from the `requests
+.. `_ library. As of v0.12, the
+.. ``future.standard_library`` import hooks are compatible with Requests.
+
+
+.. If you wish to avoid changing every reference of ``http.client`` to
+.. ``http_client`` in your code, an alternative is this::
+..
+.. from future.standard_library import http
+.. from future.standard_library.http import client as _client
+.. http.client = client
+
+.. but it has the advantage that it can be used by automatic translation scripts such as ``futurize`` and ``pasteurize``.
diff --git a/docs/open_function.rst b/docs/open_function.rst
index 91308c2a..7915d8a8 100644
--- a/docs/open_function.rst
+++ b/docs/open_function.rst
@@ -5,15 +5,15 @@ open()
The Python 3 builtin :func:`open` function for opening files returns file
contents as (unicode) strings unless the binary (``b``) flag is passed, as in::
-
+
open(filename, 'rb')
in which case its methods like :func:`read` return Py3 :class:`bytes` objects.
-On Py2, ``future.builtins`` (and ``builtins``) provides an ``open`` function
-that is mostly compatible with that on Python 3 (e.g. it offers keyword
-arguments like ``encoding``). This maps to the ``open`` backport available in
-the standard library :mod:`io` module on Py2.6 and Py2.7.
+On Py2 with ``future`` installed, the :mod:`builtins` module provides an
+``open`` function that is mostly compatible with that on Python 3 (e.g. it
+offers keyword arguments like ``encoding``). This maps to the ``open`` backport
+available in the standard library :mod:`io` module on Py2.7.
One difference to be aware of between the Python 3 ``open`` and
``future.builtins.open`` on Python 2 is that the return types of methods such
@@ -37,4 +37,3 @@ cast it explicitly as follows::
assert data[4] == 13 # integer
# Raises TypeError:
# data + u''
-
diff --git a/docs/other/auto2to3.py b/docs/other/auto2to3.py
index 3abd3703..1f56aa14 100644
--- a/docs/other/auto2to3.py
+++ b/docs/other/auto2to3.py
@@ -19,7 +19,11 @@
import argparse
import os
import sys
-import imp
+# imp was deprecated in python 3.6
+if sys.version_info >= (3, 6):
+ import importlib as imp
+else:
+ import imp
import runpy
from io import StringIO
from pkgutil import ImpImporter, ImpLoader
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/other/lessons.txt b/docs/other/lessons.txt
index 5794f496..ede523cb 100644
--- a/docs/other/lessons.txt
+++ b/docs/other/lessons.txt
@@ -30,7 +30,7 @@ Python 2:
Python 3:
>>> array.array(b'b')
TypeError: must be a unicode character, not bytes
-
+
>>> array.array(u'b')
array('b')
@@ -47,5 +47,3 @@ Running test_bytes.py from Py3 on Py2 (after fixing imports) gives this:
Ran 203 tests in 0.209s
FAILED (failures=31, errors=55, skipped=1)
-
-
diff --git a/docs/other/upload_future_docs.sh b/docs/other/upload_future_docs.sh
index 83b79d77..04470f3f 100644
--- a/docs/other/upload_future_docs.sh
+++ b/docs/other/upload_future_docs.sh
@@ -1,23 +1,23 @@
-On the local machine
---------------------
+# On the local machine
-git checkout v0.7.0
+git checkout v0.16.0 # or whatever
rm -Rf docs/build/
cd docs; make html
+cp cheatsheet.pdf ~/shared/
cd build
-touch /shared/python-future-html-docs.zip
-rm /shared/python-future-html-docs.zip
-zip -r /shared/python-future-html-docs.zip *
+touch ~/shared/python-future-html-docs.zip
+rm ~/shared/python-future-html-docs.zip
+zip -r ~/shared/python-future-html-docs.zip *
-cd /shared
-scp python-future-html-docs.zip python-future.org:
-ssh python-future.org
+scp ~/shared/python-future-html-docs.zip ubuntu@python-future.org:
+scp ~/shared/cheatsheet.pdf ubuntu@python-future.org:
+ssh ubuntu@python-future.org
-On the remote machine:
-----------------------
+# On the remote machine:
-cd /var/www/python-future/
+cd /var/www/python-future.org/
unzip -o ~/python-future-html-docs.zip
chmod a+r * html/* html/_static/*
-
+cp ~/cheatsheet.pdf ./html/compatible_idioms.pdf
+cp ~/cheatsheet.pdf ./html/cheatsheet.pdf
diff --git a/docs/other/useful_links.txt b/docs/other/useful_links.txt
index 8dec2f9b..abb96849 100644
--- a/docs/other/useful_links.txt
+++ b/docs/other/useful_links.txt
@@ -23,7 +23,7 @@ http://lucumr.pocoo.org/2011/12/7/thoughts-on-python3/
http://python3porting.com/fixers.html
http://washort.twistedmatrix.com/2010/11/unicode-in-python-and-how-to-prevent-it.html
http://docs.python.org/release/3.0.1/whatsnew/3.0.html
-https://pypi.python.org/pypi/unicode-nazi
+https://pypi.org/project/unicode-nazi/
http://www.rmi.net/~lutz/strings30.html
"Porting your code to Python 3": Alexandre Vassalotti: peadrop.com/slides/mp5.pdf
@@ -43,7 +43,7 @@ python-modernize: https://github.com/mitsuhiko/python-modernize
2to3 docs describing the different fixers: http://docs.python.org/2/library/2to3.html
-Injecting code into running Python processes (hopefully not needed): https://pypi.python.org/pypi/pyrasite/2.0
+Injecting code into running Python processes (hopefully not needed): https://pypi.org/project/pyrasite/2.0/
Withdrawn PEP to help with the Py3k standard library transition: http://www.peps.io/364/
@@ -52,7 +52,7 @@ Import hooks
http://www.peps.io/302/
"Hacking Python imports ... for fun and profit": blog post from 2012-05: http://xion.org.pl/2012/05/06/hacking-python-imports/
-Full importlib backport to Py2: https://pypi.python.org/pypi/backport_importlib/0...1
+Full importlib backport to Py2: https://pypi.org/project/backport_importlib/0...1/
Python 2.7 importlib subset: http://docs.python.org/2/whatsnew/2.7.html#importlib-section
@@ -78,7 +78,7 @@ PEPs: 358, 3112, 3137, 3138
http://python3porting.com/noconv.html#unicode-section
Unicode literals u'...' back in Python 3.3: http://www.python.org/dev/peps/pep-0414/
https://github.com/django/django/blob/master/django/utils/encoding.py
-https://pypi.python.org/pypi/unicode-nazi
+https://pypi.org/project/unicode-nazi/
http://docs.python.org/3/library/stdtypes.html#bytes-methods
http://wolfprojects.altervista.org/talks/unicode-and-python-3/
Buffer protocol (which bytes and bytes-like objects obey): http://docs.python.org/3.3/c-api/buffer.html#bufferobjects
@@ -86,7 +86,7 @@ Buffer protocol (which bytes and bytes-like objects obey): http://docs.python.or
Python's future
----------------
-https://ncoghlan_devs-python-notes.readthedocs.org/en/latest/python3/questions_and_answers.html
+https://ncoghlan-devs-python-notes.readthedocs.io/en/latest/python3/questions_and_answers.html
http://www.ironfroggy.com/software/i-am-worried-about-the-future-of-python
@@ -104,8 +104,7 @@ Also: typecheck module on PyPI
To categorize
-------------
-https://pypi.python.org/pypi/awkwardduet/1.1a4
+https://pypi.org/project/awkwardduet/1.1a4/
https://github.com/campadrenalin/persei/blob/master/persei.py
http://slideshare.net/dabeaz/mastering-python-3-io
http://rmi.net/~lutz/strings30.html
-
diff --git a/docs/overview.rst b/docs/overview.rst
index 0aa732a1..72a33558 100644
--- a/docs/overview.rst
+++ b/docs/overview.rst
@@ -1,2 +1 @@
.. include:: ../README.rst
-
diff --git a/docs/pasteurize.rst b/docs/pasteurize.rst
index 52719020..070b5d1a 100644
--- a/docs/pasteurize.rst
+++ b/docs/pasteurize.rst
@@ -1,32 +1,41 @@
.. _backwards-conversion:
-``pasteurize``: Py3 to Py2&3
+``pasteurize``: Py3 to Py2/3
----------------------------
Running ``pasteurize -w mypy3module.py`` turns this Python 3 code::
-
+
import configparser
-
+ import copyreg
+
class Blah:
pass
print('Hello', end=None)
into this code which runs on both Py2 and Py3::
-
+
from __future__ import print_function
from future import standard_library
standard_library.install_hooks()
-
+
import configparser
+ import copyreg
class Blah(object):
pass
print('Hello', end=None)
Notice that both ``futurize`` and ``pasteurize`` create explicit new-style
-classes that inherit from ``object`` on both Python versions, and both
+classes that inherit from ``object`` on both Python versions, and both
refer to stdlib modules (as well as builtins) under their Py3 names.
+Note also that the ``configparser`` module is a special case; there is a full
+backport available on PyPI (https://pypi.org/project/configparser/), so, as
+of v0.16.0, ``python-future`` no longer provides a ``configparser`` package
+alias. To use the resulting code on Py2, install the ``configparser`` backport
+with ``pip install configparser`` or by adding it to your ``requirements.txt``
+file.
+
``pasteurize`` also handles the following Python 3 features:
- keyword-only arguments
@@ -34,6 +43,3 @@ refer to stdlib modules (as well as builtins) under their Py3 names.
- extended tuple unpacking (PEP 3132)
To handle function annotations (PEP 3107), see :ref:`func_annotations`.
-
-
-
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index 3a802714..8461a1a2 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -19,16 +19,6 @@ To install the latest stable version, type::
If you would prefer the latest development version, it is available `here
`_.
-On Python 2.6, three packages containing backports of standard library modules
-in Python 2.7+ are needed for small parts of the code::
-
- pip install importlib # for future.standard_library.import_ function only
- pip install unittest2 # to run the test suite
- pip install argparse # for the backported http.server module from Py3.3
-
-Unless these features are used on Python 2.6 (only), ``future`` has no
-dependencies.
-
If you are writing code from scratch
------------------------------------
@@ -40,9 +30,10 @@ The easiest way is to start each new module with these lines::
from builtins import *
Then write standard Python 3 code. The :mod:`future` package will
-provide support for running your code on Python 2.6 and 2.7 mostly unchanged.
+provide support for running your code on Python 2.7, and 3.4+ mostly
+unchanged.
-- For explicit import forms, see :ref:`imports`.
+- For explicit import forms, see :ref:`explicit-imports`.
- For more details, see :ref:`what-else`.
- For a cheat sheet, see :ref:`compatible-idioms`.
@@ -65,7 +56,7 @@ module::
from future import standard_library
standard_library.install_aliases()
-
+
and converts several Python 3-only constructs (like keyword-only arguments) to a
form compatible with both Py3 and Py2. Most remaining Python 3 code should
simply work on Python 2.
@@ -92,16 +83,15 @@ Standard library reorganization
:mod:`future` supports the standard library reorganization (PEP 3108) via
one of several mechanisms, allowing most moved standard library modules
to be accessed under their Python 3 names and locations in Python 2::
-
+
from future import standard_library
- standard_library.aliases()
+ standard_library.install_aliases()
# Then these Py3-style imports work on both Python 2 and Python 3:
import socketserver
import queue
- import configparser
from collections import UserDict, UserList, UserString
- from collections import Counter, OrderedDict # even on Py2.6
+ from collections import ChainMap # even on Py2.7
from itertools import filterfalse, zip_longest
import html
@@ -123,7 +113,7 @@ to be accessed under their Python 3 names and locations in Python 2::
import xmlrpc.client
import xmlrpc.server
-and others. For a complete list, see :ref:`list-standard-library-renamed`.
+and others. For a complete list, see :ref:`direct-imports`.
.. _py2-dependencies:
@@ -136,21 +126,19 @@ upon import. First, install the Python 2-only package into your Python 3
environment::
$ pip3 install mypackagename --no-compile # to ignore SyntaxErrors
-
+
(or use ``pip`` if this points to your Py3 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
This feature is experimental, and we would appreciate your feedback on
how well this works or doesn't work for you. Please file an issue `here
-`_ or post to the
-`python-porting `_
-mailing list.
+`_.
For more information on the automatic translation feature, see :ref:`translation`.
diff --git a/docs/reference.rst b/docs/reference.rst
index 4e052014..d9ac5e12 100644
--- a/docs/reference.rst
+++ b/docs/reference.rst
@@ -63,20 +63,19 @@ Forward-ported types from Python 2
.. bytes
.. -----
.. .. automodule:: future.types.newbytes
-..
+..
.. dict
.. -----
.. .. automodule:: future.types.newdict
-..
+..
.. int
.. ---
.. .. automodule:: future.builtins.backports.newint
-..
+..
.. range
.. -----
.. .. automodule:: future.types.newrange
-..
+..
.. str
.. ---
.. .. automodule:: future.types.newstr
-
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 00000000..265642f4
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,3 @@
+sphinx==3.2.1
+Pallets-Sphinx-Themes==2.2.1
+setuptools==70.0.0
diff --git a/docs/roadmap.rst b/docs/roadmap.rst
index 8ef317c4..c5020d5e 100644
--- a/docs/roadmap.rst
+++ b/docs/roadmap.rst
@@ -12,7 +12,7 @@ futurize script
- Compatible metaclass syntax on Py3
- Explicit inheritance from object on Py3
-
+
- Bold might make assumptions about which strings on Py2 should be
unicode strings and which should be bytestrings.
@@ -44,4 +44,3 @@ Experimental:
should import a custom str is a Py3 str-like object which inherits from unicode and
removes the decode() method and has any other Py3-like behaviours
(possibly stricter casting?)
-
diff --git a/docs/standard_library_imports.rst b/docs/standard_library_imports.rst
index 961c6765..c09e9e30 100644
--- a/docs/standard_library_imports.rst
+++ b/docs/standard_library_imports.rst
@@ -1,49 +1,43 @@
.. _standard-library-imports:
Standard library imports
-========================
+------------------------
:mod:`future` supports the standard library reorganization (PEP 3108) through
several mechanisms.
+.. _direct-imports:
+
Direct imports
---------------
+~~~~~~~~~~~~~~
As of version 0.14, the ``future`` package comes with top-level packages for
Python 2.x that provide access to the reorganized standard library modules
under their Python 3.x names.
-Direct imports are the preferred mechanism for accesing the renamed standard library
-modules in Python 2/3 compatible code. For example, the following clean Python
-3 code runs unchanged on Python 2 after installing ``future``::
+Direct imports are the preferred mechanism for accessing the renamed standard
+library modules in Python 2/3 compatible code. For example, the following clean
+Python 3 code runs unchanged on Python 2 after installing ``future``::
>>> # Alias for future.builtins on Py2:
>>> from builtins import str, open, range, dict
>>> # Top-level packages with Py3 names provided on Py2:
>>> import queue
- >>> import configparser
>>> import tkinter.dialog
>>> etc.
-Notice that this code actually runs on Python 3 without the presence of the ``future``
-package.
-
-
-.. _list-standard-library-renamed:
-
-List of renamed standard library modules
-----------------------------------------
+Notice that this code actually runs on Python 3 without the presence of the
+``future`` package.
Of the 44 modules that were refactored with PEP 3108 (standard library
-reorganization), 30 are supported with direct imports in the above manner. The
+reorganization), 29 are supported with direct imports in the above manner. The
complete list is here::
### Renamed modules:
import builtins
- import configparser
import copyreg
import html
@@ -72,6 +66,7 @@ complete list is here::
from tkinter import scrolledtext
from tkinter import simpledialog
from tkinter import tix
+ from tkinter import ttk
import winreg # Windows only
@@ -82,185 +77,73 @@ complete list is here::
import _markupbase
import _thread
+Note that, as of v0.16.0, ``python-future`` no longer includes an alias for the
+``configparser`` module because a full backport exists (see https://pypi.org/project/configparser/).
.. _list-standard-library-refactored:
-List of refactored standard library modules
--------------------------------------------
+Aliased imports
+~~~~~~~~~~~~~~~
-The following 14 modules were refactored or extended from Python 2.6/2.7 to 3.x
-but were neither renamed nor were the new APIs backported. The ``future``
-package makes the Python 3.x APIs available on Python 2.x as follows::
+The following 14 modules were refactored or extended from Python 2.7 to 3.x
+but were neither renamed in Py3.x nor were the new APIs backported to Py2.x.
+This precludes compatibility interfaces that work out-of-the-box. Instead, the
+``future`` package makes the Python 3.x APIs available on Python 2.x as
+follows::
from future.standard_library import install_aliases
install_aliases()
- from collections import Counter, OrderedDict # backported to Py2.6
from collections import UserDict, UserList, UserString
+ import urllib.parse
+ import urllib.request
+ import urllib.response
+ import urllib.robotparser
+ import urllib.error
+
import dbm
import dbm.dumb
- import dbm.gnu # requires Python dbm support
- import dbm.ndbm # requires Python dbm support
+ import dbm.gnu # requires Python dbm support
+ import dbm.ndbm # requires Python dbm support
from itertools import filterfalse, zip_longest
- from subprocess import check_output # backported to Py2.6
from subprocess import getoutput, getstatusoutput
from sys import intern
import test.support
- import urllib.error
- import urllib.parse
- import urllib.request
- import urllib.response
- import urllib.robotparser
-
-
-.. _older-standard-library-interfaces:
-
-Older interfaces
-----------------
-
-In addition to the above interfaces, ``future`` supports four other interfaces to
-the reorganized standard library. This is largely for historical reasons (for
-versions prior to 0.14).
-
-
-Context-manager for import hooks
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The context-manager interface is via a context-manager called ``hooks``::
-
- from future.standard_library import hooks
- with hooks():
- import socketserver
- import queue
- import configparser
- import test.support
- import html.parser
- from collections import UserList
- from itertools import filterfalse, zip_longest
- from http.client import HttpConnection
- import urllib.request
- # and other moved modules and definitions
-
-This interface is straightforward and effective, using PEP 302 import
-hooks.
-
-
-``future.moves`` interface
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The ``future.moves`` interface avoids import hooks. It may therefore be more
-robust, at the cost of less idiomatic code. Use it as follows::
-
- from future.moves import queue
- from future.moves import socketserver
- from future.moves.http.client import HTTPConnection
- # etc.
-If you wish to achieve the effect of a two-level import such as this::
+The newly exposed ``urllib`` submodules are backports of those from Py3.x.
+This means, for example, that ``urllib.parse.unquote()`` now exists and takes
+an optional ``encoding`` argument on Py2.x as it does on Py3.x.
- import http.client
+**Limitation:** Note that the ``http``-based backports do not currently support
+HTTPS (as of 2015-09-11) because the SSL support changed considerably in Python
+3.x. If you need HTTPS support, please use this idiom for now::
-portably on both Python 2 and Python 3, note that Python currently does not
-support syntax like this::
+ from future.moves.urllib.request import urlopen
- from future.moves import http.client
+Backports also exist of the following features from Python 3.4:
-One workaround is to replace the dot with an underscore::
+- ``math.ceil`` returns an int on Py3
+- ``collections.ChainMap`` (for 2.7)
+- ``reprlib.recursive_repr`` (for 2.7)
- import future.moves.http.client as http_client
+These can then be imported on Python 2.7+ as follows::
+ from future.standard_library import install_aliases
+ install_aliases()
-``import_`` and ``from_import`` functions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The functional interface is to use the ``import_`` and ``from_import``
-functions from ``future.standard_library`` as follows::
-
- from future.standard_library import import_, from_import
-
- http = import_('http.client')
- urllib = import_('urllib.request')
-
- urlopen, urlsplit = from_import('urllib.request', 'urlopen', 'urlsplit')
-
-This interface also works with two-level imports.
-
-
-install_hooks() call
-~~~~~~~~~~~~~~~~~~~~
-
-The last interface to the reorganized standard library is via a call to
-``install_hooks()``::
-
- from future import standard_library
- standard_library.install_hooks()
-
- import urllib
- f = urllib.request.urlopen('http://www.python.org/')
-
- standard_library.remove_hooks()
-
-If you use this interface, it is recommended to disable the import hooks again
-after use by calling ``remove_hooks()``, in order to prevent the futurized
-modules from being invoked inadvertently by other modules. (Python does not
-automatically disable import hooks at the end of a module, but keeps them
-active for the life of a process unless removed.)
-
-.. The call to ``scrub_future_sys_modules()`` removes any modules from the
-.. ``sys.modules`` cache (on Py2 only) that have Py3-style names, like ``http.client``.
-.. This can prevent libraries that have their own Py2/3 compatibility code from
-.. importing the ``future.moves`` or ``future.backports`` modules unintentionally.
-.. Code such as this will then fall through to using the Py2 standard library
-.. modules on Py2::
-..
-.. try:
-.. from http.client import HTTPConnection
-.. except ImportError:
-.. from httplib import HTTPConnection
-..
-.. **Requests**: The above snippet is from the `requests
-.. `_ library. As of v0.12, the
-.. ``future.standard_library`` import hooks are compatible with Requests.
-
-
-.. If you wish to avoid changing every reference of ``http.client`` to
-.. ``http_client`` in your code, an alternative is this::
-..
-.. from future.standard_library import http
-.. from future.standard_library.http import client as _client
-.. http.client = client
-
-.. but it has the advantage that it can be used by automatic translation scripts such as ``futurize`` and ``pasteurize``.
-
-
-Comparing future.moves and six.moves
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-``future.moves`` and ``six.moves`` provide a similar Python 3-style
-interface to the native standard library module definitions.
-
-The major difference is that the ``future.moves`` package is a real Python package
-(``future/moves/__init__.py``) with real modules provided as ``.py`` files, whereas
-``six.moves`` constructs fake ``_LazyModule`` module objects within the Python
-code and injects them into the ``sys.modules`` cache.
-
-The advantage of ``six.moves`` is that the code fits in a single module that can be
-copied into a project that seeks to eliminate external dependencies.
-
-The advantage of ``future.moves`` is that it is likely to be more robust in the
-face of magic like Django's auto-reloader and tools like ``py2exe`` and
-``cx_freeze``. See issues #51, #53, #56, and #63 in the ``six`` project for
-more detail of bugs related to the ``six.moves`` approach.
+ from math import ceil # now returns an int
+ from collections import ChainMap
+ from reprlib import recursive_repr
-External backports
-------------------
+External standard-library backports
+-----------------------------------
Backports of the following modules from the Python 3.x standard library are
available independently of the python-future project::
@@ -269,24 +152,15 @@ available independently of the python-future project::
import singledispatch # pip install singledispatch
import pathlib # pip install pathlib
-A few modules from Python 3.4 and 3.3 are also available in the ``backports``
+A few modules from Python 3.4 are also available in the ``backports``
package namespace after ``pip install backports.lzma`` etc.::
from backports import lzma
from backports import functools_lru_cache as lru_cache
-The following Python 2.6 backports of standard library packages from Python 2.7+
-are also available::
-
- import argparse # pip install argparse
- import importlib # pip install importlib
- import unittest2 as unittest # pip install unittest2
-
-These are included in Python 2.7 and Python 3.x.
-
-Included backports
-------------------
+Included full backports
+-----------------------
Alpha-quality full backports of the following modules from Python 3.3's
standard library to Python 2.x are also available in ``future.backports``::
@@ -298,11 +172,10 @@ standard library to Python 2.x are also available in ``future.backports``::
urllib
xmlrpc.client
xmlrpc.server
-
+
The goal for these modules, unlike the modules in the ``future.moves`` package
or top-level namespace, is to backport new functionality introduced in Python
3.3.
If you need the full backport of one of these packages, please open an issue `here
`_.
-
diff --git a/docs/stdlib_incompatibilities.rst b/docs/stdlib_incompatibilities.rst
index ec72b4aa..e93f96ba 100644
--- a/docs/stdlib_incompatibilities.rst
+++ b/docs/stdlib_incompatibilities.rst
@@ -18,8 +18,7 @@ Here we will attempt to document these, together with known workarounds:
``base64``, ``decodebytes()`` function, :ref:`stdlib-base64-decodebytes`
``re``, ``ASCII`` mode, :ref:`stdlib-re-ASCII`
-To contribute to this, please email the python-porting list or send a
-pull request. See :ref:`contributing`.
+To contribute to this list, please send a pull request. See :ref:`contributing`.
.. _stdlib-array-constructor:
@@ -33,14 +32,14 @@ platform string: unicode string on Python 3, byte string on Python 2.
Python 2::
>>> array.array(b'b')
array.array(b'b')
-
+
>>> array.array(u'u')
TypeError: must be char, not unicode
Python 3::
>>> array.array(b'b')
TypeError: must be a unicode character, not bytes
-
+
>>> array.array(u'b')
array('b')
@@ -54,9 +53,12 @@ You can use the following code on both Python 3 and Python 2::
import array
# ...
-
+
a = array.array(bytes_to_native_str(b'b'))
+This was `fixed in Python 2.7.11
+`_.
+Since then, ``array.array()`` now also accepts unicode format typecode.
.. _stdlib-array-read:
@@ -88,14 +90,16 @@ This enables 'ASCII mode' for regular expressions (see the docs `here
struct.pack()
-------------
-The :func:`struct.pack` function must take a native string as its format argument. For example::
+Before Python version 2.7.7, the :func:`struct.pack` function
+required a native string as its format argument. For example::
>>> from __future__ import unicode_literals
>>> from struct import pack
- >>> pack('<4H2I', version, rec_type, build, year, file_hist_flags, ver_can_read)
-
-raises ``TypeError: Struct() argument 1 must be string, not unicode`` on Python
-2. To work around this, pass the format string argument as e.g.
-``future.utils.native('<4H2I')``.
+ >>> pack('<4H2I', version, rec_type, build, year, file_hist_flags, ver_can_read)
+raised ``TypeError: Struct() argument 1 must be string, not unicode``.
+This was `fixed in Python 2.7.7
+`_.
+Since then, ``struct.pack()`` now also accepts unicode format
+strings.
diff --git a/docs/str_object.rst b/docs/str_object.rst
index 9be2d00d..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::
@@ -67,13 +67,13 @@ they are unicode. (See ``posixpath.py``.) Another example is the
.. For example, this is permissible on Py2::
-..
+..
.. >>> u'u' > 10
.. True
-..
+..
.. >>> u'u' <= b'u'
.. True
-..
+..
.. On Py3, these raise TypeErrors.
In most other ways, these :class:`builtins.str` objects on Py2 have the
@@ -84,21 +84,16 @@ same behaviours as Python 3's :class:`str`::
>>> assert list(s) == ['A', 'B', 'C', 'D']
>>> assert s.split('B') == ['A', 'CD']
-.. If you must ensure identical use of (unicode) strings across Py3 and Py2 in a
-.. single-source codebase, you can wrap string literals in a :func:`~str` call,
-.. as follows::
-..
-.. from __future__ import unicode_literals
-.. from future.builtins import *
-..
-.. # ...
-..
-.. s = str('This absolutely must behave like a Py3 string')
-..
-.. # ...
-..
-.. Most of the time this is unnecessary, but the stricter type-checking of the
-.. ``future.builtins.str`` object is useful for ensuring the same consistent
-.. separation between unicode and byte strings on Py2 as on Py3. This is
-.. important when writing protocol handlers, for example.
+The :class:`str` type from :mod:`builtins` also provides support for the
+``surrogateescape`` error handler on Python 2.x. Here is an example that works
+identically on Python 2.x and 3.x::
+
+ >>> from builtins import str
+ >>> s = str(u'\udcff')
+ >>> s.encode('utf-8', 'surrogateescape')
+ b'\xff'
+
+This feature is in alpha. Please leave feedback `here
+`_ about whether this
+works for you.
diff --git a/docs/translation.rst b/docs/translation.rst
index 9e844a1c..632c46b1 100644
--- a/docs/translation.rst
+++ b/docs/translation.rst
@@ -1,7 +1,7 @@
.. _translation:
Using Python 2-only dependencies on Python 3
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+--------------------------------------------
The ``past`` module provides an experimental ``translation`` package to help
with importing and using old Python 2 modules in a Python 3 environment.
@@ -19,11 +19,11 @@ Here is how to use it::
$ 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::
-
- >>> from past import autotranslate
+``past.translation.autotranslate()`` function. Example::
+
+ >>> from past.translation import autotranslate
>>> autotranslate(['plotrique'])
>>> import plotrique
@@ -40,19 +40,19 @@ This will translate, import and run Python 2 code such as the following::
# Print statements are translated transparently to functions:
print 'Hello from a print statement'
-
+
# xrange() is translated to Py3's range():
total = 0
for i in xrange(10):
total += i
print 'Total is: %d' % total
-
+
# Dictionary methods like .keys() and .items() are supported and
# return lists as on Python 2:
d = {'a': 1, 'b': 2}
assert d.keys() == ['a', 'b']
assert isinstance(d.items(), list)
-
+
# Functions like range, reduce, map, filter also return lists:
assert isinstance(range(10), list)
@@ -72,7 +72,7 @@ This will translate, import and run Python 2 code such as the following::
The attributes of the module are then accessible normally from Python 3.
For example::
-
+
# This Python 3 code works
>>> type(mypy2module.d)
builtins.dict
@@ -87,7 +87,7 @@ This is a standard Python 3 data type, so, when called from Python 3 code,
.. _translation-limitations:
Known limitations of ``past.translation``
-*******************************************
+*****************************************
- It currently requires a newline at the end of the module or it throws a
``ParseError``.
@@ -110,5 +110,3 @@ Known limitations of ``past.translation``
Please report any bugs you find on the ``python-future`` `bug tracker
`_.
-
-
diff --git a/docs/unicode_literals.rst b/docs/unicode_literals.rst
index b54b7cb6..f6eb2839 100644
--- a/docs/unicode_literals.rst
+++ b/docs/unicode_literals.rst
@@ -1,4 +1,3 @@
-
.. _unicode-literals:
Should I import unicode_literals?
@@ -7,15 +6,13 @@ Should I import unicode_literals?
The ``future`` package can be used with or without ``unicode_literals``
imports.
-There is some disagreement in the community about whether it is advisable to
-import ``unicode_literals`` from ``__future__`` in a Python 2/3 compatible
-codebase. In general, it is more compelling to use ``unicode_literals`` when
+In general, it is more compelling to use ``unicode_literals`` when
back-porting new or existing Python 3 code to Python 2/3 than when porting
existing Python 2 code to 2/3. In the latter case, explicitly marking up all
unicode string literals with ``u''`` prefixes would help to avoid
-unintentionally changing the existing Python 2 API. If changing the existing
-Python 2 API is not a concern, using ``unicode_literals`` may speed up the
-porting process.
+unintentionally changing the existing Python 2 API. However, if changing the
+existing Python 2 API is not a concern, using ``unicode_literals`` may speed up
+the porting process.
This section summarizes the benefits and drawbacks of using
``unicode_literals``. To avoid confusion, we recommend using
@@ -30,7 +27,7 @@ Benefits
1. String literals are unicode on Python 3. Making them unicode on Python 2
leads to more consistency of your string types across the two
runtimes. This can make it easier to understand and debug your code.
-
+
2. Code without ``u''`` prefixes is cleaner, one of the claimed advantages
of Python 3. Even though some unicode strings would require a function
call to invert them to native strings for some Python 2 APIs (see
@@ -59,7 +56,7 @@ Drawbacks
of the Linux kernel.)
.. This is a larger-scale change than adding explicit ``u''`` prefixes to
-.. all strings that should be Unicode.
+.. all strings that should be Unicode.
2. Changing to ``unicode_literals`` will likely introduce regressions on
Python 2 that require an initial investment of time to find and fix. The
@@ -84,7 +81,7 @@ Drawbacks
change the return type of the ``unix_style_path`` function from ``str`` to
``unicode`` in the user code, which is difficult to anticipate and probably
unintended.
-
+
The counter-argument is that this code is broken, in a portability
sense; we see this from Python 3 raising a ``TypeError`` upon passing the
function a byte-string. The code needs to be changed to make explicit
@@ -124,16 +121,16 @@ Drawbacks
>>> def f():
... u"Author: Martin von Löwis"
-
+
>>> help(f)
-
+
/Users/schofield/Install/anaconda/python.app/Contents/lib/python2.7/pydoc.pyc in pipepager(text, cmd)
1376 pipe = os.popen(cmd, 'w')
1377 try:
-> 1378 pipe.write(text)
1379 pipe.close()
1380 except IOError:
-
+
UnicodeEncodeError: 'ascii' codec can't encode character u'\xf6' in position 71: ordinal not in range(128)
See `this Stack Overflow thread
@@ -147,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
@@ -158,18 +155,18 @@ codebase.:
``u''`` prefixes for unicode literals on Python 3.3+] is at odds with
the porting philosophy I've applied to Django, and why I would have
vetoed taking advantage of it.
-
+
"I believe that aiming for a Python 2 codebase with Python 3
compatibility hacks is a counter-productive way to port a project. You
end up with all the drawbacks of Python 2 (including the legacy `u`
prefixes) and none of the advantages Python 3 (especially the sane
string handling).
-
+
"Working to write Python 3 code, with legacy compatibility for Python
2, is much more rewarding. Of course it takes more effort, but the
results are much cleaner and much more maintainable. It's really about
looking towards the future or towards the past.
-
+
"I understand the reasons why PEP 414 was proposed and why it was
accepted. It makes sense for legacy software that is minimally
maintained. I hope nobody puts Django in this category!"
@@ -184,19 +181,17 @@ Against ``unicode_literals``
where there are unicode characters in the filesystem path."
-- Armin Ronacher
-
+
"+1 from me for avoiding the unicode_literals future, as it can have
very strange side effects in Python 2.... This is one of the key
reasons I backed Armin's PEP 414."
-- Nick Coghlan
-
+
"Yeah, one of the nuisances of the WSGI spec is that the header values
IIRC are the str or StringType on both py2 and py3. With
unicode_literals this causes hard-to-spot bugs, as some WSGI servers
might be more tolerant than others, but usually using unicode in python
2 for WSGI headers will cause the response to fail."
-
- -- Antti Haapala
-
+ -- Antti Haapala
diff --git a/docs/upgrading.rst b/docs/upgrading.rst
index 098d42cf..0d8afca6 100644
--- a/docs/upgrading.rst
+++ b/docs/upgrading.rst
@@ -10,4 +10,3 @@ We strive to support compatibility between versions of ``python-future``. Part o
Upgrading to v0.12
==================
-
diff --git a/docs/utilities.rst b/docs/utilities.rst
index 24170e12..e3f1e9c6 100644
--- a/docs/utilities.rst
+++ b/docs/utilities.rst
@@ -46,4 +46,3 @@ Examples::
# prints ['H', 'E', 'L', 'L', 'O']
On Python 3 these decorators are no-ops.
-
diff --git a/docs/what_else.rst b/docs/what_else.rst
index 655d953d..51f19869 100644
--- a/docs/what_else.rst
+++ b/docs/what_else.rst
@@ -23,4 +23,3 @@ compatible code.
.. include:: metaclasses.rst
..
-
diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst
index 41be242d..d706b2e5 100644
--- a/docs/whatsnew.rst
+++ b/docs/whatsnew.rst
@@ -3,170 +3,28 @@
What's New
**********
-.. _whats-new-0.14.x:
+What's new in version 1.0.0 (2024-02-21)
+========================================
-What's new in version 0.14.1
-============================
+The new version number of 1.0.0 indicates that the python-future project, like
+Python 2, is now done.
-This is a minor bug-fix release:
+The most important change in this release is adding support for Python 3.12
+(ba1cc50 and a6222d2 and bcced95).
-- Docs: add a missing template file for building docs (issue #108)
-- Tests: fix a bug in error handling while reporting failed script runs (issue #109)
-- install_aliases(): don't assume that the ``test.test_support`` module always
- exists on Py2 (issue #109)
+This release also includes these fixes:
-
-What's new in version 0.14.0
-============================
-
-This is a major new release that offers a cleaner interface for most imports in
-Python 2/3 compatible code.
-
-Instead of this interface::
-
- >>> from future.builtins import str, open, range, dict
-
- >>> from future.standard_library import hooks
- >>> with hooks():
- ... import queue
- ... import configparser
- ... import tkinter.dialog
- ... # etc.
-
-you can now use the following interface for Python 2/3 compatible code::
-
- >>> # Alias for future.builtins on Py2:
- >>> from builtins import str, open, range, dict
-
- >>> # Alias for future.moves.* on Py2:
- >>> import queue
- >>> import configparser
- >>> import tkinter.dialog
- >>> etc.
-
-Notice that the above code will run on Python 3 even without the presence of the
-``future`` package. Of the 44 standard library modules that were refactored with
-PEP 3108, 30 are supported with direct imports in this manner. (These are listed
-here: :ref:`list-standard-library-renamed`.)
-
-The other 14 standard library modules that kept the same top-level names in
-Py3.x are not supported with this direct import interface on Py2. These include
-the 5 modules in the Py3 ``urllib`` package. These modules are accessible through
-the following interface (as well as the interfaces offered in previous versions
-of ``python-future``)::
-
- from future.standard_library import install_aliases
- install_aliases()
-
- from collections import UserDict, UserList, UserString
- import dbm.gnu
- from itertools import filterfalse, zip_longest
- from subprocess import getoutput, getstatusoutput
- from sys import intern
- import test.support
- from urllib.request import urlopen
- from urllib.parse import urlparse
- # etc.
- from collections import Counter, OrderedDict # backported to Py2.6
-
-The complete list of packages supported with this interface is here:
-:ref:`list-standard-library-refactored`.
-
-For more information on these and other interfaces to the standard library, see
-:ref:`standard-library-imports`.
-
-Bug fixes
----------
-
-- This release expands the ``future.moves`` package to include most of the remaining
- modules that were moved in the standard library reorganization (PEP 3108).
- (Issue #104). See :ref:`list-standard-library-renamed` for an updated list.
-
-- This release also removes the broken ``--doctests_only`` option from the ``futurize``
- and ``pasteurize`` scripts for now (issue #103).
-
-Internal cleanups
------------------
-
-The project folder structure has changed. Top-level packages are now in a
-``src`` folder and the tests have been moved into a project-level ``tests``
-folder.
-
-The following deprecated internal modules have been removed (issue #80):
-
-- ``future.utils.encoding`` and ``future.utils.six``.
-
-Deprecations
-------------
-
-The following internal functions have been deprecated and will be removed in a future release:
-
-- ``future.standard_library.scrub_py2_sys_modules``
-- ``future.standard_library.scrub_future_sys_modules``
-
-
-.. _whats-new-0.13.x:
-
-What's new in version 0.13.1
-============================
-
-This is a bug-fix release:
-
-- Fix (multiple) inheritance of ``future.builtins.object`` with metaclasses (issues #91 and #96)
-- Fix ``futurize``'s refactoring of ``urllib`` imports (issue #94)
-- Fix ``futurize --all-imports`` (issue #101)
-- Fix ``futurize --output-dir`` logging (issue #102)
-- Doc formatting fix (issues #98, 100)
-
-
-What's new in version 0.13
-==========================
-
-This is mostly a clean-up release. It adds some small new compatibility features
-and fixes several bugs.
-
-Deprecations
-------------
-
-The following unused internal modules are now deprecated. They will be removed in a
-future release:
-
-- ``future.utils.encoding`` and ``future.utils.six``.
-
-(Issue #80). See `here `_
-for the rationale for unbundling them.
-
-
-New features
-------------
-
-- Docs: Add :ref:`compatible-idioms` from Ed Schofield's PyConAU 2014 talk.
-- Add ``newint.to_bytes()`` and ``newint.from_bytes()`` (issue #85)
-- Add ``future.utils.raise_from`` as an equivalent to Py3's ``raise ... from
- ...`` syntax (issue #86).
-- Add ``past.builtins.oct()`` function.
-- Add backports for Python 2.6 of ``subprocess.check_output()``,
- ``itertools.combinations_with_replacement()``, and ``functools.cmp_to_key()``.
-
-Bug fixes
----------
-
-- Use a private logger instead of the global logger in
- ``future.standard_library`` (issue #82). This restores compatibility of the
- standard library hooks with ``flask`` (issue #79).
-- Stage 1 of ``futurize`` no longer renames ``next`` methods to ``__next__``
- (issue #81). It still converts ``obj.next()`` method calls to
- ``next(obj)`` correctly.
-- Prevent introduction of a second set of parentheses in ``print()`` calls in
- some further cases.
-- Fix isinstance checks for subclasses of future types (issue #89).
-- Be explicit about encoding file contents as UTF-8 in unit tests (issue #63).
- Useful for building RPMs and in other environments where ``LANG=C``.
-- Fix for 3-argument ``pow(x, y, z)`` with ``newint`` arguments (issue #87).
- (Thanks to @str4d).
+- Small updates to the docs
+- Add SECURITY.md describing security policy (0598d1b)
+- Fix pasteurize: NameError: name 'unicode' is not defined (de68c10)
+- Move CI to GitHub Actions (8cd11e8)
+- Add setuptools to requirements for building docs (0c347ff)
+- Fix typos in docs (350e87a)
+- Make the fix_unpacking fixer more robust (de68c10)
+- Small improvements to shell scripts according to shellcheck (6153844)
Previous versions
=================
-See the :ref:`whats-old` for versions prior to v0.13.
+See :ref:`whats-old`.
diff --git a/docs/why_python3.rst b/docs/why_python3.rst
index 37dc605a..a4b535f4 100644
--- a/docs/why_python3.rst
+++ b/docs/why_python3.rst
@@ -56,11 +56,11 @@ Standard library:
~~~~~~~~~~~~~~~~~
- SSL contexts in http.client
--
+-
Non-arguments for Python 3
==========================
--
+-
diff --git a/futurize.py b/futurize.py
index 1b3f34a0..09feaf59 100755
--- a/futurize.py
+++ b/futurize.py
@@ -13,15 +13,12 @@
Licensing
---------
-Copyright 2013 Python Charmers Pty Ltd, Australia.
+Copyright 2013-2024 Python Charmers, Australia.
The software is distributed under an MIT licence. See LICENSE.txt.
"""
-import os
+import sys
from libfuturize.main import main
-# We use os._exit() because sys.exit() seems to interact badly with
-# subprocess.check_output() ...
-os._exit(main())
-
+sys.exit(main())
diff --git a/pasteurize.py b/pasteurize.py
index 4e24731c..658955f6 100755
--- a/pasteurize.py
+++ b/pasteurize.py
@@ -12,15 +12,12 @@
Licensing
---------
-Copyright 2013-2014 Python Charmers Pty Ltd, Australia.
+Copyright 2013-2024 Python Charmers, Australia.
The software is distributed under an MIT licence. See LICENSE.txt.
"""
-import os
+import sys
from libpasteurize.main import main
-# We use os._exit() because sys.exit() seems to interact badly with
-# subprocess.check_output() ...
-os._exit(main())
-
+sys.exit(main())
diff --git a/requirements_py26.txt b/requirements_py26.txt
deleted file mode 100644
index 5b618903..00000000
--- a/requirements_py26.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-unittest2
-argparse # for the http.server module
-importlib
-
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 00000000..498ec14a
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,2 @@
+[metadata]
+license_file = LICENSE.txt
diff --git a/setup.py b/setup.py
index a0148f3a..13b0f435 100755
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
import os
import os.path
@@ -13,11 +13,6 @@
from distutils.core import setup
-if sys.argv[-1] == 'publish':
- os.system('python setup.py sdist upload')
- sys.exit()
-
-
NAME = "future"
PACKAGES = ["future",
"future.builtins",
@@ -46,7 +41,6 @@
"past.builtins",
"past.types",
"past.utils",
- # "past.tests",
"past.translation",
"libfuturize",
"libfuturize.fixes",
@@ -58,7 +52,7 @@
if sys.version_info[:2] < (3, 0):
PACKAGES += [
"builtins",
- "configparser",
+ # "configparser", # removed in v0.16.0
"copyreg",
"html",
"http",
@@ -78,18 +72,12 @@
'LICENSE.txt',
'futurize.py',
'pasteurize.py',
- 'discover_tests.py',
'check_rst.sh',
'TESTING.txt',
],
'tests': ['*.py'],
}
-REQUIRES = []
-TEST_REQUIRES = []
-if sys.version_info[:2] == (2, 6):
- REQUIRES += ['importlib', 'argparse']
- TEST_REQUIRES += ['unittest2']
import src.future
VERSION = src.future.__version__
DESCRIPTION = "Clean single-source support for Python 3 and 2"
@@ -101,14 +89,23 @@
KEYWORDS = "future past python3 migration futurize backport six 2to3 modernize pasteurize 3to2"
CLASSIFIERS = [
"Programming Language :: Python",
+ "Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
"License :: OSI Approved",
"License :: OSI Approved :: MIT License",
- "Development Status :: 4 - Beta",
+ "Development Status :: 6 - Mature",
"Intended Audience :: Developers",
]
@@ -120,19 +117,53 @@
# user's Py3 installation if they run "python2 setup.py
# build" and then "python3 setup.py install".
-import distutils.dir_util
-print('removing old build dir')
try:
- distutils.dir_util.remove_tree('build')
+ # If the user happens to run:
+ # python2 setup.py build
+ # python3 setup.py install
+ # then folders like "copyreg" will be in build/lib.
+ # If so, we CANNOT let the user install this, because
+ # this may break his/her Python 3 install, depending on the folder order in
+ # sys.path. (Running "import html" etc. may pick up our Py2
+ # substitute packages, instead of the intended system stdlib modules.)
+ SYSTEM_MODULES = set([
+ '_dummy_thread',
+ '_markupbase',
+ '_thread',
+ 'builtins',
+ # Catch the case that configparser is in the build folder
+ # from a previous version of `future`:
+ 'configparser',
+ 'copyreg',
+ 'html',
+ 'http',
+ 'queue',
+ 'reprlib',
+ 'socketserver',
+ 'tkinter',
+ 'winreg',
+ 'xmlrpc'
+ ])
+
+ if sys.version_info[0] >= 3:
+ # Do any of the above folders exist in build/lib?
+ files = os.listdir(os.path.join('build', 'lib'))
+ if len(set(files) & set(SYSTEM_MODULES)) > 0:
+ print('ERROR: Your build folder is in an inconsistent state for '
+ 'a Python 3.x install. Please remove it manually and run '
+ 'setup.py again.', file=sys.stderr)
+ sys.exit(1)
except OSError:
pass
-
setup(name=NAME,
version=VERSION,
author=AUTHOR,
author_email=AUTHOR_EMAIL,
url=URL,
+ project_urls={
+ 'Source': 'https://github.com/PythonCharmers/python-future',
+ },
description=DESCRIPTION,
long_description=LONG_DESC,
license=LICENSE,
@@ -147,9 +178,7 @@
packages=PACKAGES,
package_data=PACKAGE_DATA,
include_package_data=True,
- install_requires=REQUIRES,
+ python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*",
classifiers=CLASSIFIERS,
- test_suite = "discover_tests",
- tests_require=TEST_REQUIRES,
**setup_kwds
)
diff --git a/src/_dummy_thread/__init__.py b/src/_dummy_thread/__init__.py
index f1932cbd..63dced6e 100644
--- a/src/_dummy_thread/__init__.py
+++ b/src/_dummy_thread/__init__.py
@@ -5,4 +5,6 @@
if sys.version_info[0] < 3:
from dummy_thread import *
else:
- raise ImportError('Cannot import module from python-future source folder')
+ raise ImportError('This package should not be accessible on Python 3. '
+ 'Either you are trying to run from the python-future src folder '
+ 'or your installation of python-future is corrupted.')
diff --git a/src/_markupbase/__init__.py b/src/_markupbase/__init__.py
index 6220d72e..29090654 100644
--- a/src/_markupbase/__init__.py
+++ b/src/_markupbase/__init__.py
@@ -5,4 +5,6 @@
if sys.version_info[0] < 3:
from markupbase import *
else:
- raise ImportError('Cannot import module from python-future source folder')
+ raise ImportError('This package should not be accessible on Python 3. '
+ 'Either you are trying to run from the python-future src folder '
+ 'or your installation of python-future is corrupted.')
diff --git a/src/_thread/__init__.py b/src/_thread/__init__.py
index f1932cbd..9f2a51c7 100644
--- a/src/_thread/__init__.py
+++ b/src/_thread/__init__.py
@@ -3,6 +3,8 @@
__future_module__ = True
if sys.version_info[0] < 3:
- from dummy_thread import *
+ from thread import *
else:
- raise ImportError('Cannot import module from python-future source folder')
+ raise ImportError('This package should not be accessible on Python 3. '
+ 'Either you are trying to run from the python-future src folder '
+ 'or your installation of python-future is corrupted.')
diff --git a/src/builtins/__init__.py b/src/builtins/__init__.py
index 824b77b1..4f936f28 100644
--- a/src/builtins/__init__.py
+++ b/src/builtins/__init__.py
@@ -7,4 +7,6 @@
# Overwrite any old definitions with the equivalent future.builtins ones:
from future.builtins import *
else:
- raise ImportError('Cannot import module from python-future source folder')
+ raise ImportError('This package should not be accessible on Python 3. '
+ 'Either you are trying to run from the python-future src folder '
+ 'or your installation of python-future is corrupted.')
diff --git a/src/configparser/__init__.py b/src/configparser/__init__.py
deleted file mode 100644
index 9bae099c..00000000
--- a/src/configparser/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from __future__ import absolute_import
-import sys
-
-if sys.version_info[0] < 3:
- from ConfigParser import *
-else:
- raise ImportError('Cannot import module from python-future source folder')
diff --git a/src/copyreg/__init__.py b/src/copyreg/__init__.py
index c32870c3..51bd4b9a 100644
--- a/src/copyreg/__init__.py
+++ b/src/copyreg/__init__.py
@@ -1,9 +1,9 @@
from __future__ import absolute_import
-from future.utils import PY3
+import sys
-if PY3:
- raise ImportError('Cannot import module from python-future source folder')
- from copyreg import *
-else:
- __future_module__ = True
+if sys.version_info[0] < 3:
from copy_reg import *
+else:
+ raise ImportError('This package should not be accessible on Python 3. '
+ 'Either you are trying to run from the python-future src folder '
+ 'or your installation of python-future is corrupted.')
diff --git a/src/future/__init__.py b/src/future/__init__.py
index 2ba49608..b097fd81 100644
--- a/src/future/__init__.py
+++ b/src/future/__init__.py
@@ -1,23 +1,20 @@
"""
-future: Easy, safe support for Python 3/2 compatibility
+future: Easy, safe support for Python 2/3 compatibility
=======================================================
-``future`` is the missing compatibility layer between Python 3 and Python
-2. It allows you to use a single, clean Python 3.x-compatible codebase to
-support both Python 3 and Python 2 with minimal overhead.
-
-Notable projects that use ``future`` for Python 2/3 compatibility are
-`Mezzanine `_ and `ObsPy `_.
+``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.
It is designed to be used as follows::
from __future__ import (absolute_import, division,
print_function, unicode_literals)
- from future.builtins import (
- bytes, dict, int, list, object, range, str,
- ascii, chr, hex, input, next, oct, open,
- pow, round, super,
- filter, map, zip)
+ from builtins import (
+ bytes, dict, int, list, object, range, str,
+ ascii, chr, hex, input, next, oct, open,
+ pow, round, super,
+ filter, map, zip)
followed by predominantly standard, idiomatic Python 3 code that then runs
similarly on Python 2.6/2.7 and Python 3.3+.
@@ -30,15 +27,32 @@
Standard library reorganization
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-``from future import standard_library`` provides a context-manager called
-``hooks`` that installs import hooks (PEP 3108) to allow renamed and
-moved standard library modules to be imported from their new Py3 locations.
+``future`` supports the standard library reorganization (PEP 3108) through the
+following Py3 interfaces:
+
+ >>> # Top-level packages with Py3 names provided on Py2:
+ >>> import html.parser
+ >>> import queue
+ >>> import tkinter.dialog
+ >>> import xmlrpc.client
+ >>> # etc.
+
+ >>> # Aliases provided for extensions to existing Py2 module names:
+ >>> from future.standard_library import install_aliases
+ >>> install_aliases()
+
+ >>> from collections import Counter, OrderedDict # backported to Py2.6
+ >>> from collections import UserDict, UserList, UserString
+ >>> import urllib.request
+ >>> from itertools import filterfalse, zip_longest
+ >>> from subprocess import getoutput, getstatusoutput
Automatic conversion
--------------------
+
An included script called `futurize
-`_ aids in converting
+`_ aids in converting
code (from either Python 2 or Python 3) to code compatible with both
platforms. It is similar to ``python-modernize`` but goes further in
providing Python 3 compatibility through the use of the backported types
@@ -48,21 +62,20 @@
Documentation
-------------
-See: http://python-future.org
+See: https://python-future.org
Credits
-------
-:Author: Ed Schofield
-: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
+:Author: Ed Schofield, Jordan M. Adler, et al
+:Sponsor: Python Charmers: https://pythoncharmers.com
+:Others: See docs/credits.rst or https://python-future.org/credits.html
Licensing
---------
-Copyright 2013-2014 Python Charmers Pty Ltd, Australia.
+Copyright 2013-2024 Python Charmers, Australia.
The software is distributed under an MIT licence. See LICENSE.txt.
"""
@@ -70,10 +83,10 @@
__title__ = 'future'
__author__ = 'Ed Schofield'
__license__ = 'MIT'
-__copyright__ = 'Copyright 2014 Python Charmers Pty Ltd'
-__ver_major__ = 0
-__ver_minor__ = 14
-__ver_patch__ = 1
+__copyright__ = 'Copyright 2013-2024 Python Charmers (https://pythoncharmers.com)'
+__ver_major__ = 1
+__ver_minor__ = 0
+__ver_patch__ = 0
__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 d68c0f48..c71e0653 100644
--- a/src/future/backports/__init__.py
+++ b/src/future/backports/__init__.py
@@ -1,8 +1,26 @@
-# future.backports package
+"""
+future.backports package
+"""
+
from __future__ import absolute_import
+
import sys
+
__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()
+
+
+from .misc import (ceil,
+ OrderedDict,
+ Counter,
+ ChainMap,
+ check_output,
+ count,
+ recursive_repr,
+ _count_elements,
+ cmp_to_key
+ )
diff --git a/src/future/backports/datetime.py b/src/future/backports/datetime.py
index 3261014e..8cd62ddf 100644
--- a/src/future/backports/datetime.py
+++ b/src/future/backports/datetime.py
@@ -689,7 +689,7 @@ def today(cls):
@classmethod
def fromordinal(cls, n):
- """Contruct a date from a proleptic Gregorian ordinal.
+ """Construct a date from a proleptic Gregorian ordinal.
January 1 of year 1 is day 1. Only the year, month and day are
non-zero in the result.
diff --git a/src/future/backports/email/_header_value_parser.py b/src/future/backports/email/_header_value_parser.py
index 43957edc..59b1b318 100644
--- a/src/future/backports/email/_header_value_parser.py
+++ b/src/future/backports/email/_header_value_parser.py
@@ -2867,7 +2867,7 @@ def parse_content_type_header(value):
_find_mime_parameters(ctype, value)
return ctype
ctype.append(token)
- # XXX: If we really want to follow the formal grammer we should make
+ # XXX: If we really want to follow the formal grammar we should make
# mantype and subtype specialized TokenLists here. Probably not worth it.
if not value or value[0] != '/':
ctype.defects.append(errors.InvalidHeaderDefect(
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/email/parser.py b/src/future/backports/email/parser.py
index df1c6e28..79f0e5a3 100644
--- a/src/future/backports/email/parser.py
+++ b/src/future/backports/email/parser.py
@@ -26,7 +26,7 @@ def __init__(self, _class=Message, **_3to2kwargs):
textual representation of the message.
The string must be formatted as a block of RFC 2822 headers and header
- continuation lines, optionally preceeded by a `Unix-from' header. The
+ continuation lines, optionally preceded by a `Unix-from' header. The
header block is terminated either by the end of the string or by a
blank line.
@@ -92,7 +92,7 @@ def __init__(self, *args, **kw):
textual representation of the message.
The input must be formatted as a block of RFC 2822 headers and header
- continuation lines, optionally preceeded by a `Unix-from' header. The
+ continuation lines, optionally preceded by a `Unix-from' header. The
header block is terminated either by the end of the input or by a
blank line.
diff --git a/src/future/backports/html/__init__.py b/src/future/backports/html/__init__.py
index 837afce1..58e133fd 100644
--- a/src/future/backports/html/__init__.py
+++ b/src/future/backports/html/__init__.py
@@ -25,4 +25,3 @@ def escape(s, quote=True):
if quote:
return s.translate(_escape_map_full)
return s.translate(_escape_map)
-
diff --git a/src/future/backports/html/entities.py b/src/future/backports/html/entities.py
index 6798187c..5c73f692 100644
--- a/src/future/backports/html/entities.py
+++ b/src/future/backports/html/entities.py
@@ -2512,4 +2512,3 @@
entitydefs[name] = chr(codepoint)
del name, codepoint
-
diff --git a/src/future/backports/html/parser.py b/src/future/backports/html/parser.py
index 7b8cdba6..fb652636 100644
--- a/src/future/backports/html/parser.py
+++ b/src/future/backports/html/parser.py
@@ -534,4 +534,3 @@ def replaceEntities(s):
return re.sub(r"&(#?[xX]?(?:[0-9a-fA-F]+;|\w{1,32};?))",
replaceEntities, s)
-
diff --git a/src/future/backports/http/client.py b/src/future/backports/http/client.py
index 6cde7833..e663d125 100644
--- a/src/future/backports/http/client.py
+++ b/src/future/backports/http/client.py
@@ -75,14 +75,19 @@
from future.backports.email import parser as email_parser
from future.backports.email import message as email_message
+from future.backports.misc import create_connection as socket_create_connection
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",
@@ -553,19 +558,9 @@ def readinto(self, b):
# (for example, reading in 1k chunks)
if PY2:
- ### Python-Future:
- # TODO: debug and fix me!
data = self.fp.read(len(b))
- if data[:2] == b"b'":
- # Something has gone wrong
- import pdb
- pdb.set_trace()
- #if len(b) != len(data):
- # import pdb
- # pdb.set_trace()
n = len(data)
b[:n] = data
- ###
else:
n = self.fp.readinto(b)
@@ -705,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:]
@@ -843,7 +848,7 @@ def _tunnel(self):
def connect(self):
"""Connect to the host and port specified in __init__."""
- self.sock = socket.create_connection((self.host,self.port),
+ self.sock = socket_create_connection((self.host,self.port),
self.timeout, self.source_address)
if self._tunnel_host:
self._tunnel()
@@ -901,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:
@@ -1226,7 +1231,7 @@ def __init__(self, host, port=None, key_file=None, cert_file=None,
def connect(self):
"Connect to a host on a given (SSL) port."
- sock = socket.create_connection((self.host, self.port),
+ sock = socket_create_connection((self.host, self.port),
self.timeout, self.source_address)
if self._tunnel_host:
@@ -1266,7 +1271,7 @@ def connect(self):
# def connect(self):
# "Connect to a host on a given (SSL) port."
- # sock = socket.create_connection((self.host, self.port),
+ # sock = socket_create_connection((self.host, self.port),
# self.timeout, self.source_address)
# if self._tunnel_host:
# self.sock = sock
diff --git a/src/future/backports/http/cookiejar.py b/src/future/backports/http/cookiejar.py
index cad72f9b..a39242c0 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:
@@ -1844,7 +1851,7 @@ def lwp_cookie_str(cookie):
class LWPCookieJar(FileCookieJar):
"""
The LWPCookieJar saves a sequence of "Set-Cookie3" lines.
- "Set-Cookie3" is the format used by the libwww-perl libary, not known
+ "Set-Cookie3" is the format used by the libwww-perl library, not known
to be compatible with any browser, but which is easy to read and
doesn't lose information about RFC 2965 cookies.
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 b69547c0..992b978f 100644
--- a/src/future/backports/misc.py
+++ b/src/future/backports/misc.py
@@ -1,17 +1,35 @@
"""
-Miscellaneous function (re)definitions from the Py3.3 standard library for
-Python 2.6/2.7.
-
-math.ceil
-
-collections.OrderedDict (for Python 2.6)
-collections.Counter (for Python 2.6)
+Miscellaneous function (re)definitions from the Py3.4+ standard library
+for Python 2.6/2.7.
+
+- math.ceil (for Python 2.7)
+- collections.OrderedDict (for Python 2.6)
+- collections.Counter (for Python 2.6)
+- collections.ChainMap (for all versions prior to Python 3.3)
+- itertools.count (for Python 2.6, with step parameter)
+- subprocess.check_output (for Python 2.6)
+- reprlib.recursive_repr (for Python 2.6+)
+- functools.cmp_to_key (for Python 2.6)
"""
-from math import ceil as oldceil
+from __future__ import absolute_import
+
import subprocess
+from math import ceil as oldceil
+
+from operator import itemgetter as _itemgetter, eq as _eq
+import sys
+import heapq as _heapq
+from _weakref import proxy as _proxy
+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, PY26
+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):
@@ -22,192 +40,228 @@ def ceil(x):
return int(oldceil(x))
-# OrderedDict Shim from Raymond Hettinger, python core dev
-# http://code.activestate.com/recipes/576693-ordered-dictionary-for-py24/
-# here to support version 2.6.
+########################################################################
+### reprlib.recursive_repr decorator from Py3.4
+########################################################################
+
+from itertools import islice
if PY26:
- # don't need this except in 2.6
+ # 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
+ except ImportError:
+ from _dummy_thread import get_ident
+else:
try:
from thread import get_ident
except ImportError:
from dummy_thread import get_ident
-try:
- from _abcoll import KeysView, ValuesView, ItemsView
-except ImportError:
- pass
+def recursive_repr(fillvalue='...'):
+ 'Decorator to make a repr function return fillvalue for a recursive call'
+
+ def decorating_function(user_function):
+ repr_running = set()
+
+ def wrapper(self):
+ key = id(self), get_ident()
+ if key in repr_running:
+ return fillvalue
+ repr_running.add(key)
+ try:
+ result = user_function(self)
+ finally:
+ repr_running.discard(key)
+ return result
+
+ # Can't use functools.wraps() here because of bootstrap issues
+ wrapper.__module__ = getattr(user_function, '__module__')
+ wrapper.__doc__ = getattr(user_function, '__doc__')
+ wrapper.__name__ = getattr(user_function, '__name__')
+ wrapper.__annotations__ = getattr(user_function, '__annotations__', {})
+ return wrapper
-class _OrderedDict(dict):
+ 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
+################################################################################
+
+class _Link(object):
+ __slots__ = 'prev', 'next', 'key', '__weakref__'
+
+class OrderedDict(dict):
'Dictionary that remembers insertion order'
# An inherited dict maps keys to values.
# The inherited dict provides __getitem__, __len__, __contains__, and get.
# The remaining methods are order-aware.
- # Big-O running times for all methods are the same as for regular
- # dictionaries.
+ # Big-O running times for all methods are the same as regular dictionaries.
- # The internal self.__map dictionary maps keys to links in a doubly linked
- # list. The circular doubly linked list starts and ends with a sentinel
- # element. The sentinel element never gets deleted (this simplifies the
- # algorithm). Each link is stored as a list of length three: [PREV, NEXT,
- # KEY].
+ # The internal self.__map dict maps keys to links in a doubly linked list.
+ # The circular doubly linked list starts and ends with a sentinel element.
+ # The sentinel element never gets deleted (this simplifies the algorithm).
+ # The sentinel is in self.__hardroot with a weakref proxy in self.__root.
+ # The prev links are weakref proxies (to prevent circular references).
+ # Individual links are kept alive by the hard reference in self.__map.
+ # Those hard references disappear when a key is deleted from an OrderedDict.
- def __init__(self, *args, **kwds):
- '''Initialize an ordered dictionary. Signature is the same as for
- regular dictionaries, but keyword arguments are not recommended
- because their insertion order is arbitrary.
+ def __init__(*args, **kwds):
+ '''Initialize an ordered dictionary. The signature is the same as
+ regular dictionaries, but keyword arguments are not recommended because
+ their insertion order is arbitrary.
'''
+ if not args:
+ raise TypeError("descriptor '__init__' of 'OrderedDict' object "
+ "needs an argument")
+ self = args[0]
+ args = args[1:]
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
try:
self.__root
except AttributeError:
- self.__root = root = [] # sentinel node
- root[:] = [root, root, None]
+ self.__hardroot = _Link()
+ self.__root = root = _proxy(self.__hardroot)
+ root.prev = root.next = root
self.__map = {}
self.__update(*args, **kwds)
- def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
+ def __setitem__(self, key, value,
+ dict_setitem=dict.__setitem__, proxy=_proxy, Link=_Link):
'od.__setitem__(i, y) <==> od[i]=y'
- # Setting a new item creates a new link which goes at the end of the
- # linked list, and the inherited dictionary is updated with the new
- # key/value pair.
+ # Setting a new item creates a new link at the end of the linked list,
+ # and the inherited dictionary is updated with the new key/value pair.
if key not in self:
+ self.__map[key] = link = Link()
root = self.__root
- last = root[0]
- last[1] = root[0] = self.__map[key] = [last, root, key]
+ last = root.prev
+ link.prev, link.next, link.key = last, root, key
+ last.next = link
+ root.prev = proxy(link)
dict_setitem(self, key, value)
def __delitem__(self, key, dict_delitem=dict.__delitem__):
'od.__delitem__(y) <==> del od[y]'
- # Deleting an existing item uses self.__map to find the link which is
- # then removed by updating the links in the predecessor and successor
- # nodes.
+ # Deleting an existing item uses self.__map to find the link which gets
+ # removed by updating the links in the predecessor and successor nodes.
dict_delitem(self, key)
- link_prev, link_next, key = self.__map.pop(key)
- link_prev[1] = link_next
- link_next[0] = link_prev
+ link = self.__map.pop(key)
+ link_prev = link.prev
+ link_next = link.next
+ link_prev.next = link_next
+ link_next.prev = link_prev
def __iter__(self):
'od.__iter__() <==> iter(od)'
+ # Traverse the linked list in order.
root = self.__root
- curr = root[1]
+ curr = root.next
while curr is not root:
- yield curr[2]
- curr = curr[1]
+ yield curr.key
+ curr = curr.next
def __reversed__(self):
'od.__reversed__() <==> reversed(od)'
+ # Traverse the linked list in reverse order.
root = self.__root
- curr = root[0]
+ curr = root.prev
while curr is not root:
- yield curr[2]
- curr = curr[0]
+ yield curr.key
+ curr = curr.prev
def clear(self):
'od.clear() -> None. Remove all items from od.'
- try:
- for node in itervalues(self.__map):
- del node[:]
- root = self.__root
- root[:] = [root, root, None]
- self.__map.clear()
- except AttributeError:
- pass
+ root = self.__root
+ root.prev = root.next = root
+ self.__map.clear()
dict.clear(self)
def popitem(self, last=True):
'''od.popitem() -> (k, v), return and remove a (key, value) pair.
- Pairs are returned in LIFO order if last is true or FIFO order if
- false.
+ Pairs are returned in LIFO order if last is true or FIFO order if false.
+
'''
if not self:
raise KeyError('dictionary is empty')
root = self.__root
if last:
- link = root[0]
- link_prev = link[0]
- link_prev[1] = root
- root[0] = link_prev
+ link = root.prev
+ link_prev = link.prev
+ link_prev.next = root
+ root.prev = link_prev
else:
- link = root[1]
- link_next = link[1]
- root[1] = link_next
- link_next[0] = root
- key = link[2]
+ link = root.next
+ link_next = link.next
+ root.next = link_next
+ link_next.prev = root
+ key = link.key
del self.__map[key]
value = dict.pop(self, key)
return key, value
- # -- the following methods do not depend on the internal structure --
-
- def keys(self):
- 'od.keys() -> list of keys in od'
- return list(self)
-
- def values(self):
- 'od.values() -> list of values in od'
- return [self[key] for key in self]
-
- def items(self):
- 'od.items() -> list of (key, value) pairs in od'
- return [(key, self[key]) for key in self]
-
- def iterkeys(self):
- 'od.iterkeys() -> an iterator over the keys in od'
- return iter(self)
+ def move_to_end(self, key, last=True):
+ '''Move an existing element to the end (or beginning if last==False).
- def itervalues(self):
- 'od.itervalues -> an iterator over the values in od'
- for k in self:
- yield self[k]
+ Raises KeyError if the element does not exist.
+ When last=True, acts like a fast version of self[key]=self.pop(key).
- def iteritems(self):
- 'od.iteritems -> an iterator over the (key, value) items in od'
- for k in self:
- yield (k, self[k])
-
- def update(*args, **kwds):
- '''od.update(E, **F) -> None. Update od from dict/iterable E and F.
-
- If E is a dict instance, does: for k in E: od[k] = E[k]
- If E has a .keys() method, does: for k in E.keys(): od[k] = E[k]
- Or if E is an iterable of items, does:for k, v in E: od[k] = v
- In either case, this is followed by: for k, v in F.items(): od[k] = v
'''
- if len(args) > 2:
- raise TypeError('update() takes at most 2 positional '
- 'arguments (%d given)' % (len(args),))
- elif not args:
- raise TypeError('update() takes at least 1 argument (0 given)')
- self = args[0]
- # Make progressively weaker assumptions about "other"
- other = ()
- if len(args) == 2:
- other = args[1]
- if isinstance(other, dict):
- for key in other:
- self[key] = other[key]
- elif hasattr(other, 'keys'):
- for key in other.keys():
- self[key] = other[key]
+ link = self.__map[key]
+ link_prev = link.prev
+ link_next = link.next
+ link_prev.next = link_next
+ link_next.prev = link_prev
+ root = self.__root
+ if last:
+ last = root.prev
+ link.prev = last
+ link.next = root
+ last.next = root.prev = link
else:
- for key, value in other:
- self[key] = value
- for key, value in kwds.items():
- self[key] = value
- # let subclasses override update without breaking __init__
- __update = update
+ first = root.next
+ link.prev = root
+ link.next = first
+ root.next = first.prev = link
+
+ def __sizeof__(self):
+ sizeof = sys.getsizeof
+ n = len(self) + 1 # number of links including root
+ size = sizeof(self.__dict__) # instance dictionary
+ size += sizeof(self.__map) * 2 # internal dict and inherited dict
+ size += sizeof(self.__hardroot) * n # link objects
+ size += sizeof(self.__root) * n # proxy objects
+ return size
+
+ update = __update = MutableMapping.update
+ keys = MutableMapping.keys
+ values = MutableMapping.values
+ items = MutableMapping.items
+ __ne__ = MutableMapping.__ne__
__marker = object()
def pop(self, key, default=__marker):
- '''od.pop(k[,d]) -> v, remove specified key and return the\
- corresponding value. If key is not found, d is returned if given,
- otherwise KeyError is raised.
+ '''od.pop(k[,d]) -> v, remove specified key and return the corresponding
+ value. If key is not found, d is returned if given, otherwise KeyError
+ is raised.
+
'''
if key in self:
result = self[key]
@@ -224,28 +278,19 @@ def setdefault(self, key, default=None):
self[key] = default
return default
- def __repr__(self, _repr_running={}):
+ @recursive_repr()
+ def __repr__(self):
'od.__repr__() <==> repr(od)'
- call_key = id(self), get_ident()
- if call_key in _repr_running:
- return '...'
- _repr_running[call_key] = 1
- try:
- if not self:
- return '%s()' % (self.__class__.__name__,)
- return '%s(%r)' % (self.__class__.__name__, list(self.items()))
- finally:
- del _repr_running[call_key]
+ if not self:
+ return '%s()' % (self.__class__.__name__,)
+ return '%s(%r)' % (self.__class__.__name__, list(self.items()))
def __reduce__(self):
'Return state information for pickling'
- items = [[k, self[k]] for k in self]
inst_dict = vars(self).copy()
for k in vars(OrderedDict()):
inst_dict.pop(k, None)
- if inst_dict:
- return (self.__class__, (items,), inst_dict)
- return self.__class__, (items,)
+ return self.__class__, (), inst_dict or None, None, iter(self.items())
def copy(self):
'od.copy() -> a shallow copy of od'
@@ -253,41 +298,24 @@ def copy(self):
@classmethod
def fromkeys(cls, iterable, value=None):
- '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S and
- values equal to v (which defaults to None).
+ '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S.
+ If not specified, the value defaults to None.
+
'''
- d = cls()
+ self = cls()
for key in iterable:
- d[key] = value
- return d
+ self[key] = value
+ return self
def __eq__(self, other):
- '''od.__eq__(y) <==> od==y. Comparison to another OD is
- order-sensitive while comparison to a regular mapping is
- order-insensitive.
+ '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
+ while comparison to a regular mapping is order-insensitive.
+
'''
if isinstance(other, OrderedDict):
- return (len(self) == len(other) and
- list(self.items()) == list(other.items()))
+ return dict.__eq__(self, other) and all(map(_eq, self, other))
return dict.__eq__(self, other)
- def __ne__(self, other):
- return not self == other
-
- # -- the following methods are only used in Python 2.7 --
-
- def viewkeys(self):
- "od.viewkeys() -> a set-like object providing a view on od's keys"
- return KeysView(self)
-
- def viewvalues(self):
- "od.viewvalues() -> an object providing a view on od's values"
- return ValuesView(self)
-
- def viewitems(self):
- "od.viewitems() -> a set-like object providing a view on od's items"
- return ItemsView(self)
-
# {{{ http://code.activestate.com/recipes/576611/ (r11)
@@ -297,45 +325,105 @@ def viewitems(self):
except ImportError:
pass
+########################################################################
+### Counter
+########################################################################
-class _Counter(dict):
+def _count_elements(mapping, iterable):
+ 'Tally elements from the iterable.'
+ mapping_get = mapping.get
+ for elem in iterable:
+ mapping[elem] = mapping_get(elem, 0) + 1
- '''Dict subclass for counting hashable objects. Sometimes called a bag
+class Counter(dict):
+ '''Dict subclass for counting hashable items. Sometimes called a bag
or multiset. Elements are stored as dictionary keys and their counts
are stored as dictionary values.
- >>> Counter('zyzygy')
- Counter({'y': 3, 'z': 2, 'g': 1})
+ >>> c = Counter('abcdeabcdabcaba') # count elements from a string
+
+ >>> c.most_common(3) # three most common elements
+ [('a', 5), ('b', 4), ('c', 3)]
+ >>> sorted(c) # list all unique elements
+ ['a', 'b', 'c', 'd', 'e']
+ >>> ''.join(sorted(c.elements())) # list elements with repetitions
+ 'aaaaabbbbcccdde'
+ >>> sum(c.values()) # total of all counts
+ 15
+
+ >>> c['a'] # count of letter 'a'
+ 5
+ >>> for elem in 'shazam': # update counts from an iterable
+ ... c[elem] += 1 # by adding 1 to each element's count
+ >>> c['a'] # now there are seven 'a'
+ 7
+ >>> del c['b'] # remove all 'b'
+ >>> c['b'] # now there are zero 'b'
+ 0
+
+ >>> d = Counter('simsalabim') # make another counter
+ >>> c.update(d) # add in the second counter
+ >>> c['a'] # now there are nine 'a'
+ 9
+
+ >>> c.clear() # empty the counter
+ >>> c
+ Counter()
+
+ Note: If a count is set to zero or reduced to zero, it will remain
+ in the counter until the entry is deleted or the counter is cleared:
+
+ >>> c = Counter('aaabbc')
+ >>> c['b'] -= 2 # reduce the count of 'b' by two
+ >>> c.most_common() # 'b' is still in, but its count is zero
+ [('a', 3), ('c', 1), ('b', 0)]
'''
-
- def __init__(self, iterable=None, **kwds):
+ # References:
+ # http://en.wikipedia.org/wiki/Multiset
+ # http://www.gnu.org/software/smalltalk/manual-base/html_node/Bag.html
+ # http://www.demo2s.com/Tutorial/Cpp/0380__set-multiset/Catalog0380__set-multiset.htm
+ # http://code.activestate.com/recipes/259174/
+ # Knuth, TAOCP Vol. II section 4.6.3
+
+ def __init__(*args, **kwds):
'''Create a new, empty Counter object. And if given, count elements
from an input iterable. Or, initialize the count from another mapping
of elements to their counts.
- >>> c = Counter() # a new, empty counter
- >>> c = Counter('gallahad') # a new counter from an iterable
- >>> c = Counter({'a': 4, 'b': 2}) # a new counter from a mapping
- >>> c = Counter(a=4, b=2) # a new counter from keyword args
+ >>> c = Counter() # a new, empty counter
+ >>> c = Counter('gallahad') # a new counter from an iterable
+ >>> c = Counter({'a': 4, 'b': 2}) # a new counter from a mapping
+ >>> c = Counter(a=4, b=2) # a new counter from keyword args
'''
- self.update(iterable, **kwds)
+ if not args:
+ raise TypeError("descriptor '__init__' of 'Counter' object "
+ "needs an argument")
+ self = args[0]
+ args = args[1:]
+ if len(args) > 1:
+ raise TypeError('expected at most 1 arguments, got %d' % len(args))
+ super(Counter, self).__init__()
+ self.update(*args, **kwds)
def __missing__(self, key):
+ 'The count of elements not in the Counter is zero.'
+ # Needed so that self[missing_item] does not raise KeyError
return 0
def most_common(self, n=None):
'''List the n most common elements and their counts from the most
common to the least. If n is None, then list all element counts.
- >>> Counter('abracadabra').most_common(3)
- [('a', 5), ('r', 2), ('b', 2)]
+ >>> Counter('abcdeabcdabcaba').most_common(3)
+ [('a', 5), ('b', 4), ('c', 3)]
'''
+ # Emulate Bag.sortedByCount from Smalltalk
if n is None:
- return sorted(iteritems(self), key=itemgetter(1), reverse=True)
- return nlargest(n, iteritems(self), key=itemgetter(1))
+ return sorted(self.items(), key=_itemgetter(1), reverse=True)
+ return _heapq.nlargest(n, self.items(), key=_itemgetter(1))
def elements(self):
'''Iterator over elements repeating each as many times as its count.
@@ -344,22 +432,31 @@ def elements(self):
>>> sorted(c.elements())
['A', 'A', 'B', 'B', 'C', 'C']
- If an element's count has been set to zero or is a negative number,
- elements() will ignore it.
+ # Knuth's example for prime factors of 1836: 2**2 * 3**3 * 17**1
+ >>> prime_factors = Counter({2: 2, 3: 3, 17: 1})
+ >>> product = 1
+ >>> for factor in prime_factors.elements(): # loop over factors
+ ... product *= factor # and multiply them
+ >>> product
+ 1836
+
+ Note, if an element's count has been set to zero or is a negative
+ number, elements() will ignore it.
'''
- for elem, count in iteritems(self):
- for _ in range(count):
- yield elem
+ # Emulate Bag.do from Smalltalk and Multiset.begin from C++.
+ return _chain.from_iterable(_starmap(_repeat, self.items()))
- # Override dict methods where the meaning changes for Counter objects.
+ # Override dict methods where necessary
@classmethod
def fromkeys(cls, iterable, v=None):
+ # There is no equivalent method for counters because setting v=1
+ # means that no element can have a count greater than one.
raise NotImplementedError(
'Counter.fromkeys() is undefined. Use Counter(iterable) instead.')
- def update(self, iterable=None, **kwds):
+ def update(*args, **kwds):
'''Like dict.update() but add counts instead of replacing them.
Source can be an iterable, a dictionary, or another Counter instance.
@@ -372,37 +469,90 @@ def update(self, iterable=None, **kwds):
4
'''
+ # The regular dict.update() operation makes no sense here because the
+ # replace behavior results in the some of original untouched counts
+ # being mixed-in with all of the other counts for a mismash that
+ # doesn't have a straight-forward interpretation in most counting
+ # contexts. Instead, we implement straight-addition. Both the inputs
+ # and outputs are allowed to contain zero and negative counts.
+
+ if not args:
+ raise TypeError("descriptor 'update' of 'Counter' object "
+ "needs an argument")
+ self = args[0]
+ args = args[1:]
+ if len(args) > 1:
+ raise TypeError('expected at most 1 arguments, got %d' % len(args))
+ iterable = args[0] if args else None
if iterable is not None:
- if hasattr(iterable, 'iteritems'):
+ if isinstance(iterable, Mapping):
if self:
self_get = self.get
- for elem, count in iteritems(iterable):
- self[elem] = self_get(elem, 0) + count
+ for elem, count in iterable.items():
+ self[elem] = count + self_get(elem, 0)
else:
- dict.update(
- self, iterable) # fast path when counter is empty
+ super(Counter, self).update(iterable) # fast path when counter is empty
else:
- self_get = self.get
- for elem in iterable:
- self[elem] = self_get(elem, 0) + 1
+ _count_elements(self, iterable)
if kwds:
self.update(kwds)
+ def subtract(*args, **kwds):
+ '''Like dict.update() but subtracts counts instead of replacing them.
+ Counts can be reduced below zero. Both the inputs and outputs are
+ allowed to contain zero and negative counts.
+
+ Source can be an iterable, a dictionary, or another Counter instance.
+
+ >>> c = Counter('which')
+ >>> c.subtract('witch') # subtract elements from another iterable
+ >>> c.subtract(Counter('watch')) # subtract elements from another counter
+ >>> c['h'] # 2 in which, minus 1 in witch, minus 1 in watch
+ 0
+ >>> c['w'] # 1 in which, minus 1 in witch, minus 1 in watch
+ -1
+
+ '''
+ if not args:
+ raise TypeError("descriptor 'subtract' of 'Counter' object "
+ "needs an argument")
+ self = args[0]
+ args = args[1:]
+ if len(args) > 1:
+ raise TypeError('expected at most 1 arguments, got %d' % len(args))
+ iterable = args[0] if args else None
+ if iterable is not None:
+ self_get = self.get
+ if isinstance(iterable, Mapping):
+ for elem, count in iterable.items():
+ self[elem] = self_get(elem, 0) - count
+ else:
+ for elem in iterable:
+ self[elem] = self_get(elem, 0) - 1
+ if kwds:
+ self.subtract(kwds)
+
def copy(self):
- 'Like dict.copy() but returns a Counter instance instead of a dict.'
- return Counter(self)
+ 'Return a shallow copy.'
+ return self.__class__(self)
+
+ def __reduce__(self):
+ return self.__class__, (dict(self),)
def __delitem__(self, elem):
- '''Like dict.__delitem__() but does not raise KeyError for missing
- values.'''
+ 'Like dict.__delitem__() but does not raise KeyError for missing values.'
if elem in self:
- dict.__delitem__(self, elem)
+ super(Counter, self).__delitem__(elem)
def __repr__(self):
if not self:
return '%s()' % self.__class__.__name__
- items = ', '.join(map('%r: %r'.__mod__, self.most_common()))
- return '%s({%s})' % (self.__class__.__name__, items)
+ try:
+ items = ', '.join(map('%r: %r'.__mod__, self.most_common()))
+ return '%s({%s})' % (self.__class__.__name__, items)
+ except TypeError:
+ # handle case where values are not orderable
+ return '{0}({1!r})'.format(self.__class__.__name__, dict(self))
# Multiset-style mathematical operations discussed in:
# Knuth TAOCP Volume II section 4.6.3 exercise 19
@@ -419,15 +569,17 @@ def __add__(self, other):
>>> Counter('abbb') + Counter('bcc')
Counter({'b': 4, 'c': 2, 'a': 1})
-
'''
if not isinstance(other, Counter):
return NotImplemented
result = Counter()
- for elem in set(self) | set(other):
- newcount = self[elem] + other[elem]
+ for elem, count in self.items():
+ newcount = count + other[elem]
if newcount > 0:
result[elem] = newcount
+ for elem, count in other.items():
+ if elem not in self and count > 0:
+ result[elem] = count
return result
def __sub__(self, other):
@@ -440,10 +592,13 @@ def __sub__(self, other):
if not isinstance(other, Counter):
return NotImplemented
result = Counter()
- for elem in set(self) | set(other):
- newcount = self[elem] - other[elem]
+ for elem, count in self.items():
+ newcount = count - other[elem]
if newcount > 0:
result[elem] = newcount
+ for elem, count in other.items():
+ if elem not in self and count < 0:
+ result[elem] = 0 - count
return result
def __or__(self, other):
@@ -455,12 +610,15 @@ def __or__(self, other):
'''
if not isinstance(other, Counter):
return NotImplemented
- _max = max
result = Counter()
- for elem in set(self) | set(other):
- newcount = _max(self[elem], other[elem])
+ for elem, count in self.items():
+ other_count = other[elem]
+ newcount = other_count if count < other_count else count
if newcount > 0:
result[elem] = newcount
+ for elem, count in other.items():
+ if elem not in self and count > 0:
+ result[elem] = count
return result
def __and__(self, other):
@@ -472,26 +630,95 @@ def __and__(self, other):
'''
if not isinstance(other, Counter):
return NotImplemented
- _min = min
result = Counter()
- if len(self) < len(other):
- self, other = other, self
- for elem in filter(self.__contains__, other):
- newcount = _min(self[elem], other[elem])
+ for elem, count in self.items():
+ other_count = other[elem]
+ newcount = count if count < other_count else other_count
if newcount > 0:
result[elem] = newcount
return result
-try:
- from collections import OrderedDict, Counter
-except ImportError:
- # Python 2.6 doesn't have these:
- OrderedDict = _OrderedDict
- Counter = _Counter
+ def __pos__(self):
+ 'Adds an empty counter, effectively stripping negative and zero counts'
+ return self + Counter()
+
+ def __neg__(self):
+ '''Subtracts from an empty counter. Strips positive and zero counts,
+ and flips the sign on negative counts.
+
+ '''
+ return Counter() - self
+
+ def _keep_positive(self):
+ '''Internal method to strip elements with a negative or zero count'''
+ nonpositive = [elem for elem, count in self.items() if not count > 0]
+ for elem in nonpositive:
+ del self[elem]
+ return self
+
+ def __iadd__(self, other):
+ '''Inplace add from another counter, keeping only positive counts.
+
+ >>> c = Counter('abbb')
+ >>> c += Counter('bcc')
+ >>> c
+ Counter({'b': 4, 'c': 2, 'a': 1})
+
+ '''
+ for elem, count in other.items():
+ self[elem] += count
+ return self._keep_positive()
+
+ def __isub__(self, other):
+ '''Inplace subtract counter, but keep only results with positive counts.
+
+ >>> c = Counter('abbbc')
+ >>> c -= Counter('bccd')
+ >>> c
+ Counter({'b': 2, 'a': 1})
+
+ '''
+ for elem, count in other.items():
+ self[elem] -= count
+ return self._keep_positive()
+
+ def __ior__(self, other):
+ '''Inplace union is the maximum of value from either counter.
+
+ >>> c = Counter('abbb')
+ >>> c |= Counter('bcc')
+ >>> c
+ Counter({'b': 3, 'c': 2, 'a': 1})
+
+ '''
+ for elem, other_count in other.items():
+ count = self[elem]
+ if other_count > count:
+ self[elem] = other_count
+ return self._keep_positive()
+
+ def __iand__(self, other):
+ '''Inplace intersection is the minimum of corresponding counts.
+
+ >>> c = Counter('abbb')
+ >>> c &= Counter('bcc')
+ >>> c
+ Counter({'b': 1})
+
+ '''
+ for elem, count in self.items():
+ other_count = other[elem]
+ if other_count < count:
+ self[elem] = other_count
+ return self._keep_positive()
-# For Python 2.6 compatibility: see http://stackoverflow.com/questions/4814970/
def check_output(*popenargs, **kwargs):
+ """
+ For Python 2.6 compatibility: see
+ http://stackoverflow.com/questions/4814970/
+ """
+
if 'stdout' in kwargs:
raise ValueError('stdout argument not allowed, it will be overridden.')
process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
@@ -503,3 +730,229 @@ def check_output(*popenargs, **kwargs):
cmd = popenargs[0]
raise subprocess.CalledProcessError(retcode, cmd)
return output
+
+
+def count(start=0, step=1):
+ """
+ ``itertools.count`` in Py 2.6 doesn't accept a step
+ parameter. This is an enhanced version of ``itertools.count``
+ for Py2.6 equivalent to ``itertools.count`` in Python 2.7+.
+ """
+ while True:
+ yield start
+ start += step
+
+
+########################################################################
+### ChainMap (helper for configparser and string.Template)
+### From the Py3.4 source code. See also:
+### https://github.com/kkxue/Py2ChainMap/blob/master/py2chainmap.py
+########################################################################
+
+class ChainMap(MutableMapping):
+ ''' A ChainMap groups multiple dicts (or other mappings) together
+ to create a single, updateable view.
+
+ The underlying mappings are stored in a list. That list is public and can
+ accessed or updated using the *maps* attribute. There is no other state.
+
+ Lookups search the underlying mappings successively until a key is found.
+ In contrast, writes, updates, and deletions only operate on the first
+ mapping.
+
+ '''
+
+ def __init__(self, *maps):
+ '''Initialize a ChainMap by setting *maps* to the given mappings.
+ If no mappings are provided, a single empty dictionary is used.
+
+ '''
+ self.maps = list(maps) or [{}] # always at least one map
+
+ def __missing__(self, key):
+ raise KeyError(key)
+
+ def __getitem__(self, key):
+ for mapping in self.maps:
+ try:
+ return mapping[key] # can't use 'key in mapping' with defaultdict
+ except KeyError:
+ pass
+ return self.__missing__(key) # support subclasses that define __missing__
+
+ def get(self, key, default=None):
+ return self[key] if key in self else default
+
+ def __len__(self):
+ return len(set().union(*self.maps)) # reuses stored hash values if possible
+
+ def __iter__(self):
+ return iter(set().union(*self.maps))
+
+ def __contains__(self, key):
+ return any(key in m for m in self.maps)
+
+ def __bool__(self):
+ return any(self.maps)
+
+ # Py2 compatibility:
+ __nonzero__ = __bool__
+
+ @recursive_repr()
+ def __repr__(self):
+ return '{0.__class__.__name__}({1})'.format(
+ self, ', '.join(map(repr, self.maps)))
+
+ @classmethod
+ def fromkeys(cls, iterable, *args):
+ 'Create a ChainMap with a single dict created from the iterable.'
+ return cls(dict.fromkeys(iterable, *args))
+
+ def copy(self):
+ 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]'
+ return self.__class__(self.maps[0].copy(), *self.maps[1:])
+
+ __copy__ = copy
+
+ def new_child(self, m=None): # like Django's Context.push()
+ '''
+ New ChainMap with a new map followed by all previous maps. If no
+ map is provided, an empty dict is used.
+ '''
+ if m is None:
+ m = {}
+ return self.__class__(m, *self.maps)
+
+ @property
+ def parents(self): # like Django's Context.pop()
+ 'New ChainMap from maps[1:].'
+ return self.__class__(*self.maps[1:])
+
+ def __setitem__(self, key, value):
+ self.maps[0][key] = value
+
+ def __delitem__(self, key):
+ try:
+ del self.maps[0][key]
+ except KeyError:
+ raise KeyError('Key not found in the first mapping: {0!r}'.format(key))
+
+ def popitem(self):
+ 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.'
+ try:
+ return self.maps[0].popitem()
+ except KeyError:
+ raise KeyError('No keys found in the first mapping.')
+
+ def pop(self, key, *args):
+ 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].'
+ try:
+ return self.maps[0].pop(key, *args)
+ except KeyError:
+ raise KeyError('Key not found in the first mapping: {0!r}'.format(key))
+
+ def clear(self):
+ 'Clear maps[0], leaving maps[1:] intact.'
+ self.maps[0].clear()
+
+
+# Re-use the same sentinel as in the Python stdlib socket module:
+from socket import _GLOBAL_DEFAULT_TIMEOUT
+# Was: _GLOBAL_DEFAULT_TIMEOUT = object()
+
+
+def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
+ source_address=None):
+ """Backport of 3-argument create_connection() for Py2.6.
+
+ Connect to *address* and return the socket object.
+
+ Convenience function. Connect to *address* (a 2-tuple ``(host,
+ port)``) and return the socket object. Passing the optional
+ *timeout* parameter will set the timeout on the socket instance
+ before attempting to connect. If no *timeout* is supplied, the
+ global default timeout setting returned by :func:`getdefaulttimeout`
+ is used. If *source_address* is set it must be a tuple of (host, port)
+ for the socket to bind as a source address before making the connection.
+ An host of '' or port 0 tells the OS to use the default.
+ """
+
+ host, port = address
+ err = None
+ for res in getaddrinfo(host, port, 0, SOCK_STREAM):
+ af, socktype, proto, canonname, sa = res
+ sock = None
+ try:
+ sock = socket(af, socktype, proto)
+ if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
+ sock.settimeout(timeout)
+ if source_address:
+ sock.bind(source_address)
+ sock.connect(sa)
+ return sock
+
+ except error as _:
+ err = _
+ if sock is not None:
+ sock.close()
+
+ if err is not None:
+ raise err
+ else:
+ raise error("getaddrinfo returns an empty list")
+
+# Backport from Py2.7 for Py2.6:
+def cmp_to_key(mycmp):
+ """Convert a cmp= function into a key= function"""
+ class K(object):
+ __slots__ = ['obj']
+ def __init__(self, obj, *args):
+ self.obj = obj
+ def __lt__(self, other):
+ return mycmp(self.obj, other.obj) < 0
+ def __gt__(self, other):
+ return mycmp(self.obj, other.obj) > 0
+ def __eq__(self, other):
+ return mycmp(self.obj, other.obj) == 0
+ def __le__(self, other):
+ return mycmp(self.obj, other.obj) <= 0
+ def __ge__(self, other):
+ return mycmp(self.obj, other.obj) >= 0
+ def __ne__(self, other):
+ return mycmp(self.obj, other.obj) != 0
+ def __hash__(self):
+ raise TypeError('hash not implemented')
+ return K
+
+# Back up our definitions above in case they're useful
+_OrderedDict = OrderedDict
+_Counter = Counter
+_check_output = check_output
+_count = count
+_ceil = ceil
+__count_elements = _count_elements
+_recursive_repr = recursive_repr
+_ChainMap = ChainMap
+_create_connection = create_connection
+_cmp_to_key = cmp_to_key
+
+# Overwrite the definitions above with the usual ones
+# from the standard library:
+if sys.version_info >= (2, 7):
+ from collections import OrderedDict, Counter
+ from itertools import count
+ from functools import cmp_to_key
+ try:
+ from subprocess import check_output
+ except ImportError:
+ # Not available. This happens with Google App Engine: see issue #231
+ pass
+ from socket import create_connection
+
+if sys.version_info >= (3, 0):
+ from math import ceil
+ from collections import _count_elements
+
+if sys.version_info >= (3, 3):
+ from reprlib import recursive_repr
+ from collections import ChainMap
diff --git a/src/future/backports/socket.py b/src/future/backports/socket.py
index 84e0e9d5..930e1dae 100644
--- a/src/future/backports/socket.py
+++ b/src/future/backports/socket.py
@@ -410,7 +410,10 @@ def getfqdn(name=''):
return name
-_GLOBAL_DEFAULT_TIMEOUT = object()
+# Re-use the same sentinel as in the Python stdlib socket module:
+from socket import _GLOBAL_DEFAULT_TIMEOUT
+# Was: _GLOBAL_DEFAULT_TIMEOUT = object()
+
def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
source_address=None):
diff --git a/src/future/backports/test/support.py b/src/future/backports/test/support.py
index afe59d03..6639372b 100644
--- a/src/future/backports/test/support.py
+++ b/src/future/backports/test/support.py
@@ -9,9 +9,6 @@
from future import utils
from future.builtins import str, range, open, int, map, list
-# if __name__ != 'test.support':
-# raise ImportError('support must be imported from the test package')
-
import contextlib
import errno
import functools
@@ -31,7 +28,6 @@
# import collections.abc # not present on Py2.7
import re
import subprocess
-import imp
import time
try:
import sysconfig
@@ -344,37 +340,6 @@ def rmtree(path):
if error.errno != errno.ENOENT:
raise
-def make_legacy_pyc(source):
- """Move a PEP 3147 pyc/pyo file to its legacy pyc/pyo location.
-
- The choice of .pyc or .pyo extension is done based on the __debug__ flag
- value.
-
- :param source: The file system path to the source file. The source file
- does not need to exist, however the PEP 3147 pyc file must exist.
- :return: The file system path to the legacy pyc file.
- """
- pyc_file = imp.cache_from_source(source)
- up_one = os.path.dirname(os.path.abspath(source))
- legacy_pyc = os.path.join(up_one, source + ('c' if __debug__ else 'o'))
- os.rename(pyc_file, legacy_pyc)
- return legacy_pyc
-
-def forget(modname):
- """'Forget' a module was ever imported.
-
- This removes the module from sys.modules and deletes any PEP 3147 or
- legacy .pyc and .pyo files.
- """
- unload(modname)
- for dirname in sys.path:
- source = os.path.join(dirname, modname + '.py')
- # It doesn't matter if they exist or not, unlink all possible
- # combinations of PEP 3147 and legacy pyc and pyo files.
- unlink(source + 'c')
- unlink(source + 'o')
- unlink(imp.cache_from_source(source, debug_override=True))
- unlink(imp.cache_from_source(source, debug_override=False))
# On some platforms, should not run gui test even if it is allowed
# in `use_resources'.
@@ -669,7 +634,7 @@ def _is_ipv6_enabled():
# # First try printable and common characters to have a readable filename.
# # For each character, the encoding list are just example of encodings able
# # to encode the character (the list is not exhaustive).
-#
+#
# # U+00E6 (Latin Small Letter Ae): cp1252, iso-8859-1
# '\u00E6',
# # U+0130 (Latin Capital Letter I With Dot Above): cp1254, iso8859_3
@@ -688,11 +653,11 @@ def _is_ipv6_enabled():
# '\u062A',
# # U+0E01 (Thai Character Ko Kai): cp874
# '\u0E01',
-#
+#
# # Then try more "special" characters. "special" because they may be
# # interpreted or displayed differently depending on the exact locale
# # encoding and the font.
-#
+#
# # U+00A0 (No-Break Space)
# '\u00A0',
# # U+20AC (Euro Sign)
@@ -705,7 +670,7 @@ def _is_ipv6_enabled():
# else:
# FS_NONASCII = character
# break
-#
+#
# # TESTFN_UNICODE is a non-ascii filename
# TESTFN_UNICODE = TESTFN + "-\xe0\xf2\u0258\u0141\u011f"
# if sys.platform == 'darwin':
@@ -715,7 +680,7 @@ def _is_ipv6_enabled():
# import unicodedata
# TESTFN_UNICODE = unicodedata.normalize('NFD', TESTFN_UNICODE)
# TESTFN_ENCODING = sys.getfilesystemencoding()
-#
+#
# # TESTFN_UNENCODABLE is a filename (str type) that should *not* be able to be
# # encoded by the filesystem encoding (in strict mode). It can be None if we
# # cannot generate such filename.
@@ -748,7 +713,7 @@ def _is_ipv6_enabled():
# # File system encoding (eg. ISO-8859-* encodings) can encode
# # the byte 0xff. Skip some unicode filename tests.
# pass
-#
+#
# # TESTFN_UNDECODABLE is a filename (bytes type) that should *not* be able to be
# # decoded from the filesystem encoding (in strict mode). It can be None if we
# # cannot generate such filename (ex: the latin1 encoding can decode any byte
@@ -778,7 +743,7 @@ def _is_ipv6_enabled():
# except UnicodeDecodeError:
# TESTFN_UNDECODABLE = os.fsencode(TESTFN) + name
# break
-#
+#
# if FS_NONASCII:
# TESTFN_NONASCII = TESTFN + '-' + FS_NONASCII
# else:
@@ -959,10 +924,12 @@ def _filterwarnings(filters, quiet=False):
frame = sys._getframe(2)
registry = frame.f_globals.get('__warningregistry__')
if registry:
- # Was: registry.clear()
- # Py2-compatible:
- for i in range(len(registry)):
- registry.pop()
+ if utils.PY3:
+ registry.clear()
+ else:
+ # Py2-compatible:
+ for i in range(len(registry)):
+ registry.pop()
with warnings.catch_warnings(record=True) as w:
# Set filter "always" to record all warnings. Because
# test_warnings swap the module, we need to look up in
@@ -1668,15 +1635,15 @@ def case_pred(test):
# We don't have sysconfig on Py2.6:
# #=======================================================================
# # Check for the presence of docstrings.
-#
+#
# HAVE_DOCSTRINGS = (check_impl_detail(cpython=False) or
# sys.platform == 'win32' or
# sysconfig.get_config_var('WITH_DOC_STRINGS'))
-#
+#
# requires_docstrings = unittest.skipUnless(HAVE_DOCSTRINGS,
# "test requires docstrings")
-#
-#
+#
+#
# #=======================================================================
# doctest driver.
diff --git a/src/future/backports/urllib/parse.py b/src/future/backports/urllib/parse.py
index ada2f8bb..04e52d49 100644
--- a/src/future/backports/urllib/parse.py
+++ b/src/future/backports/urllib/parse.py
@@ -727,14 +727,14 @@ def quote_from_bytes(bs, safe='/'):
return str('')
### For Python-Future:
bs = bytes(bs)
- ###
+ ###
if isinstance(safe, str):
# Normalize 'safe' by converting to bytes and removing non-ASCII chars
safe = str(safe).encode('ascii', 'ignore')
else:
### For Python-Future:
safe = bytes(safe)
- ###
+ ###
safe = bytes([c for c in safe if c < 128])
if not bs.rstrip(_ALWAYS_SAFE_BYTES + safe):
return bs.decode()
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/backports/xmlrpc/client.py b/src/future/backports/xmlrpc/client.py
index b78e5bad..0838f61a 100644
--- a/src/future/backports/xmlrpc/client.py
+++ b/src/future/backports/xmlrpc/client.py
@@ -134,10 +134,11 @@
from future.builtins import bytes, dict, int, range, str
import base64
-# Py2.7 compatibility hack
-base64.encodebytes = base64.encodestring
-base64.decodebytes = base64.decodestring
import sys
+if sys.version_info < (3, 9):
+ # Py2.7 compatibility hack
+ base64.encodebytes = base64.encodestring
+ base64.decodebytes = base64.decodestring
import time
from datetime import datetime
from future.backports.http import client as http_client
@@ -1251,7 +1252,7 @@ def close(self):
# Send HTTP request.
#
# @param host Host descriptor (URL or (URL, x509 info) tuple).
- # @param handler Targer RPC handler (a path relative to host)
+ # @param handler Target RPC handler (a path relative to host)
# @param request_body The XML-RPC request body
# @param debug Enable debugging if debug is true.
# @return An HTTPConnection.
diff --git a/src/future/builtins/__init__.py b/src/future/builtins/__init__.py
index 94011f97..1734cd45 100644
--- a/src/future/builtins/__init__.py
+++ b/src/future/builtins/__init__.py
@@ -2,7 +2,7 @@
A module that brings in equivalents of the new and modified Python 3
builtins into Py2. Has no effect on Py3.
-See the docs `here `_
+See the docs `here `_
(``docs/what-else.rst``) for more information.
"""
@@ -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:
@@ -38,12 +38,12 @@
if not utils.PY3:
# We only import names that shadow the builtins on Py2. No other namespace
# pollution on Py2.
-
+
# Only shadow builtins on Py2; no new names
- __all__ = ['filter', 'map', 'zip',
+ __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/iterators.py b/src/future/builtins/iterators.py
index b82f29f2..dff651e0 100644
--- a/src/future/builtins/iterators.py
+++ b/src/future/builtins/iterators.py
@@ -7,7 +7,7 @@
for i in range(10**15):
pass
-
+
for (a, b) in zip(range(10**15), range(-10**15, 0)):
pass
@@ -50,4 +50,3 @@
range = builtins.range
zip = builtins.zip
__all__ = []
-
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/newnext.py b/src/future/builtins/newnext.py
index 9364023a..097638ac 100644
--- a/src/future/builtins/newnext.py
+++ b/src/future/builtins/newnext.py
@@ -43,7 +43,7 @@
def newnext(iterator, default=_SENTINEL):
"""
next(iterator[, default])
-
+
Return the next item from the iterator. If default is given and the iterator
is exhausted, it is returned instead of raising StopIteration.
"""
@@ -68,4 +68,3 @@ def newnext(iterator, default=_SENTINEL):
__all__ = ['newnext']
-
diff --git a/src/future/builtins/newround.py b/src/future/builtins/newround.py
index f59b35b3..b06c1169 100644
--- a/src/future/builtins/newround.py
+++ b/src/future/builtins/newround.py
@@ -1,7 +1,8 @@
"""
``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
@@ -12,13 +13,13 @@
def newround(number, ndigits=None):
"""
See Python 3 documentation: uses Banker's Rounding.
-
+
Delegates to the __round__ method if for some reason this exists.
-
+
If not, rounds a number to a given precision in decimal digits (default
0 digits). This returns an int when called with one argument,
otherwise the same type as the number. ndigits may be negative.
-
+
See the test_round method in future/tests/test_builtins.py for
examples.
"""
@@ -28,27 +29,32 @@ def newround(number, ndigits=None):
ndigits = 0
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:
- d = from_float_26(number).quantize(exponent, rounding=ROUND_HALF_EVEN)
+ if not PY26:
+ d = Decimal.from_float(number)
+ else:
+ d = from_float_26(number)
+
+ if ndigits < 0:
+ result = newround(d / exponent) * exponent
+ else:
+ 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 5190a2ac..3e8cc80f 100644
--- a/src/future/builtins/newsuper.py
+++ b/src/future/builtins/newsuper.py
@@ -24,7 +24,7 @@ def append(self, item):
"Of course, you can still explicitly pass in the arguments if you want
to do something strange. Sometimes you really do want that, e.g. to
skip over some classes in the method resolution order.
-
+
"How does it work? By inspecting the calling frame to determine the
function object being executed and the object on which it's being
called, and then walking the object's __mro__ chain to find out where
@@ -51,60 +51,59 @@ def newsuper(typ=_SENTINEL, type_or_obj=_SENTINEL, framedepth=1):
# Infer the correct call if used without arguments.
if typ is _SENTINEL:
# We'll need to do some frame hacking.
- f = sys._getframe(framedepth)
+ f = sys._getframe(framedepth)
try:
# Get the function's first positional argument.
type_or_obj = f.f_locals[f.f_code.co_varnames[0]]
except (IndexError, KeyError,):
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:
+ 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)
- 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:
return _builtin_super(typ, type_or_obj)
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
@@ -112,4 +111,3 @@ def superm(*args, **kwds):
__all__ = ['newsuper']
-
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/_dummy_thread.py b/src/future/moves/_dummy_thread.py
index 688d249b..6633f42e 100644
--- a/src/future/moves/_dummy_thread.py
+++ b/src/future/moves/_dummy_thread.py
@@ -1,8 +1,13 @@
from __future__ import absolute_import
-from future.utils import PY3
+from future.utils import PY3, PY39_PLUS
-if PY3:
- from _dummy_thread import *
+
+if PY39_PLUS:
+ # _dummy_thread and dummy_threading modules were both deprecated in
+ # Python 3.7 and removed in Python 3.9
+ from _thread import *
+elif PY3:
+ from _dummy_thread import *
else:
__future_module__ = True
from dummy_thread import *
diff --git a/src/future/moves/collections.py b/src/future/moves/collections.py
index f4dfb5a9..664ee6a3 100644
--- a/src/future/moves/collections.py
+++ b/src/future/moves/collections.py
@@ -1,4 +1,6 @@
from __future__ import absolute_import
+import sys
+
from future.utils import PY2, PY26
__future_module__ = True
@@ -11,3 +13,6 @@
if PY26:
from future.backports.misc import OrderedDict, Counter
+
+if sys.version_info < (3, 3):
+ from future.backports.misc import ChainMap, _count_elements
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/html/__init__.py b/src/future/moves/html/__init__.py
index 13758ef0..22ed6e7d 100644
--- a/src/future/moves/html/__init__.py
+++ b/src/future/moves/html/__init__.py
@@ -1,24 +1,18 @@
-from __future__ import absolute_import, unicode_literals
+from __future__ import absolute_import
from future.utils import PY3
__future_module__ = True
if PY3:
from html import *
else:
-
# cgi.escape isn't good enough for the single Py3.3 html test to pass.
- # Define it inline here instead. From the Py3.3 stdlib
+ # Define it inline here instead. From the Py3.4 stdlib. Note that the
+ # html.escape() function from the Py3.3 stdlib is not suitable for use on
+ # Py2.x.
"""
General functions for HTML manipulation.
"""
-
- _escape_map = {ord('&'): '&', ord('<'): '<', ord('>'): '>'}
- _escape_map_full = {ord('&'): '&', ord('<'): '<', ord('>'): '>',
- ord('"'): '"', ord('\''): '''}
-
- # NB: this is a candidate for a bytes/string polymorphic interface
-
def escape(s, quote=True):
"""
Replace special characters "&", "<" and ">" to HTML-safe sequences.
@@ -26,6 +20,12 @@ def escape(s, quote=True):
characters, both double quote (") and single quote (') characters are also
translated.
"""
+ s = s.replace("&", "&") # Must be done first!
+ s = s.replace("<", "<")
+ s = s.replace(">", ">")
if quote:
- return s.translate(_escape_map_full)
- return s.translate(_escape_map)
+ s = s.replace('"', """)
+ s = s.replace('\'', "'")
+ return s
+
+ __all__ = ['escape']
diff --git a/src/future/moves/http/client.py b/src/future/moves/http/client.py
index 1ca0e9bc..55f9c9c1 100644
--- a/src/future/moves/http/client.py
+++ b/src/future/moves/http/client.py
@@ -4,4 +4,5 @@
from http.client import *
else:
from httplib import *
+ from httplib import HTTPMessage
__future_module__ = True
diff --git a/src/future/moves/multiprocessing.py b/src/future/moves/multiprocessing.py
new file mode 100644
index 00000000..a871b676
--- /dev/null
+++ b/src/future/moves/multiprocessing.py
@@ -0,0 +1,7 @@
+from __future__ import absolute_import
+from future.utils import PY3
+
+from multiprocessing import *
+if not PY3:
+ __future_module__ = True
+ from multiprocessing.queues import SimpleQueue
diff --git a/src/future/moves/test/support.py b/src/future/moves/test/support.py
index ab189f40..f70c9d7d 100644
--- a/src/future/moves/test/support.py
+++ b/src/future/moves/test/support.py
@@ -1,11 +1,19 @@
from __future__ import absolute_import
+
+import sys
+
from future.standard_library import suspend_hooks
from future.utils import PY3
if PY3:
from test.support import *
+ if sys.version_info[:2] >= (3, 10):
+ from test.support.os_helper import (
+ EnvironmentVarGuard,
+ TESTFN,
+ )
+ from test.support.warnings_helper import check_warnings
else:
__future_module__ = True
with suspend_hooks():
from test.test_support import *
-
diff --git a/src/future/moves/tkinter/__init__.py b/src/future/moves/tkinter/__init__.py
index 09442e46..e4082966 100644
--- a/src/future/moves/tkinter/__init__.py
+++ b/src/future/moves/tkinter/__init__.py
@@ -4,5 +4,24 @@
if not PY3:
from Tkinter import *
+ from Tkinter import (_cnfmerge, _default_root, _flatten,
+ _support_default_root, _test,
+ _tkinter, _setit)
+
+ try: # >= 2.7.4
+ from Tkinter import (_join)
+ except ImportError:
+ pass
+
+ try: # >= 2.7.4
+ from Tkinter import (_stringify)
+ except ImportError:
+ pass
+
+ try: # >= 2.7.9
+ from Tkinter import (_splitdict)
+ except ImportError:
+ pass
+
else:
from tkinter import *
diff --git a/src/future/moves/tkinter/colorchooser.py b/src/future/moves/tkinter/colorchooser.py
index 5e7c97f4..6dde6e8d 100644
--- a/src/future/moves/tkinter/colorchooser.py
+++ b/src/future/moves/tkinter/colorchooser.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The tkColorChooser module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/future/moves/tkinter/commondialog.py b/src/future/moves/tkinter/commondialog.py
index 7747a0ba..eb7ae8d6 100644
--- a/src/future/moves/tkinter/commondialog.py
+++ b/src/future/moves/tkinter/commondialog.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The tkCommonDialog module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/future/moves/tkinter/constants.py b/src/future/moves/tkinter/constants.py
index 99216f33..ffe09815 100644
--- a/src/future/moves/tkinter/constants.py
+++ b/src/future/moves/tkinter/constants.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The Tkconstants module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/future/moves/tkinter/dialog.py b/src/future/moves/tkinter/dialog.py
index a5b77781..113370ca 100644
--- a/src/future/moves/tkinter/dialog.py
+++ b/src/future/moves/tkinter/dialog.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The Dialog module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/future/moves/tkinter/dnd.py b/src/future/moves/tkinter/dnd.py
index 748b111a..1ab43791 100644
--- a/src/future/moves/tkinter/dnd.py
+++ b/src/future/moves/tkinter/dnd.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The Tkdnd module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/future/moves/tkinter/filedialog.py b/src/future/moves/tkinter/filedialog.py
index 35e21ac0..6a6f03ca 100644
--- a/src/future/moves/tkinter/filedialog.py
+++ b/src/future/moves/tkinter/filedialog.py
@@ -10,4 +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/tkinter/font.py b/src/future/moves/tkinter/font.py
index 63d86dc7..628f399a 100644
--- a/src/future/moves/tkinter/font.py
+++ b/src/future/moves/tkinter/font.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The tkFont module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/future/moves/tkinter/messagebox.py b/src/future/moves/tkinter/messagebox.py
index 3ed52e1f..b43d8702 100644
--- a/src/future/moves/tkinter/messagebox.py
+++ b/src/future/moves/tkinter/messagebox.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The tkMessageBox module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/future/moves/tkinter/scrolledtext.py b/src/future/moves/tkinter/scrolledtext.py
index 13bd660d..1c69db60 100644
--- a/src/future/moves/tkinter/scrolledtext.py
+++ b/src/future/moves/tkinter/scrolledtext.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The ScrolledText module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/future/moves/tkinter/simpledialog.py b/src/future/moves/tkinter/simpledialog.py
index e952fa99..dba93fbf 100644
--- a/src/future/moves/tkinter/simpledialog.py
+++ b/src/future/moves/tkinter/simpledialog.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The SimpleDialog module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/future/moves/tkinter/tix.py b/src/future/moves/tkinter/tix.py
index 019df6f7..8d1718ad 100644
--- a/src/future/moves/tkinter/tix.py
+++ b/src/future/moves/tkinter/tix.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The Tix module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/future/moves/tkinter/ttk.py b/src/future/moves/tkinter/ttk.py
new file mode 100644
index 00000000..081c1b49
--- /dev/null
+++ b/src/future/moves/tkinter/ttk.py
@@ -0,0 +1,12 @@
+from __future__ import absolute_import
+
+from future.utils import PY3
+
+if PY3:
+ from tkinter.ttk import *
+else:
+ try:
+ from ttk import *
+ except ImportError:
+ raise ImportError('The ttk module is missing. Does your Py2 '
+ 'installation include tkinter?')
diff --git a/src/future/moves/urllib/__init__.py b/src/future/moves/urllib/__init__.py
index 8d1298c9..5cf428b6 100644
--- a/src/future/moves/urllib/__init__.py
+++ b/src/future/moves/urllib/__init__.py
@@ -3,4 +3,3 @@
if not PY3:
__future_module__ = True
-
diff --git a/src/future/moves/urllib/error.py b/src/future/moves/urllib/error.py
index ae49255f..7d8ada73 100644
--- a/src/future/moves/urllib/error.py
+++ b/src/future/moves/urllib/error.py
@@ -7,10 +7,10 @@
from urllib.error import *
else:
__future_module__ = True
-
+
# We use this method to get at the original Py2 urllib before any renaming magic
# ContentTooShortError = sys.py2_modules['urllib'].ContentTooShortError
-
+
with suspend_hooks():
from urllib import ContentTooShortError
from urllib2 import URLError, HTTPError
diff --git a/src/future/moves/urllib/parse.py b/src/future/moves/urllib/parse.py
index 832dfb51..9074b816 100644
--- a/src/future/moves/urllib/parse.py
+++ b/src/future/moves/urllib/parse.py
@@ -10,7 +10,7 @@
from urlparse import (ParseResult, SplitResult, parse_qs, parse_qsl,
urldefrag, urljoin, urlparse, urlsplit,
urlunparse, urlunsplit)
-
+
# we use this method to get at the original py2 urllib before any renaming
# quote = sys.py2_modules['urllib'].quote
# quote_plus = sys.py2_modules['urllib'].quote_plus
@@ -18,7 +18,7 @@
# unquote_plus = sys.py2_modules['urllib'].unquote_plus
# urlencode = sys.py2_modules['urllib'].urlencode
# splitquery = sys.py2_modules['urllib'].splitquery
-
+
with suspend_hooks():
from urllib import (quote,
quote_plus,
diff --git a/src/future/moves/urllib/request.py b/src/future/moves/urllib/request.py
index 375dc29f..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():
@@ -51,7 +52,7 @@
# URLopener,
# FancyURLopener,
# proxy_bypass)
-
+
# from urllib2 import (
# AbstractBasicAuthHandler,
# AbstractDigestAuthHandler,
@@ -80,7 +81,7 @@
# UnknownHandler,
# urlopen,
# )
-
+
# from urlparse import (
# urldefrag
# urljoin,
diff --git a/src/future/moves/urllib/response.py b/src/future/moves/urllib/response.py
index 120ea13e..a287ae28 100644
--- a/src/future/moves/urllib/response.py
+++ b/src/future/moves/urllib/response.py
@@ -10,4 +10,3 @@
addclosehook,
addinfo,
addinfourl)
-
diff --git a/src/future/standard_library/__init__.py b/src/future/standard_library/__init__.py
index 307df037..d467aaf4 100644
--- a/src/future/standard_library/__init__.py
+++ b/src/future/standard_library/__init__.py
@@ -11,14 +11,13 @@
And then these normal Py3 imports work on both Py3 and Py2::
import builtins
- import configparser
import copyreg
import queue
import reprlib
import socketserver
import winreg # on Windows only
import test.support
- import html, html.parser, html.entites
+ import html, html.parser, html.entities
import http, http.client, http.server
import http.cookies, http.cookiejar
import urllib.parse, urllib.request, urllib.response, urllib.error, urllib.robotparser
@@ -31,9 +30,10 @@
from itertools import filterfalse, zip_longest
from sys import intern
from collections import UserDict, UserList, UserString
- from collections import OrderedDict, Counter # even on Py2.6
+ from collections import OrderedDict, Counter, ChainMap # even on Py2.6
from subprocess import getoutput, getstatusoutput
from subprocess import check_output # even on Py2.6
+ from multiprocessing import SimpleQueue
(The renamed modules and functions are still available under their old
names on Python 2.)
@@ -55,7 +55,6 @@
import dbm.dumb
import dbm.gnu
import collections.abc # on Py33
- import tkinter
import pickle # should (optionally) bring in cPickle on Python 2
"""
@@ -64,9 +63,12 @@
import sys
import logging
-import imp
+# imp was deprecated in python 3.6
+if sys.version_info >= (3, 6):
+ import importlib as imp
+else:
+ import imp
import contextlib
-import types
import copy
import os
@@ -110,6 +112,7 @@
'future.moves.socketserver': 'socketserver',
'ConfigParser': 'configparser',
'repr': 'reprlib',
+ 'multiprocessing.queues': 'multiprocessing',
# 'FileDialog': 'tkinter.filedialog',
# 'tkFileDialog': 'tkinter.filedialog',
# 'SimpleDialog': 'tkinter.simpledialog',
@@ -127,7 +130,7 @@
# 'Tkinter': 'tkinter',
'_winreg': 'winreg',
'thread': '_thread',
- 'dummy_thread': '_dummy_thread',
+ 'dummy_thread': '_dummy_thread' if sys.version_info < (3, 9) else '_thread',
# 'anydbm': 'dbm', # causes infinite import loop
# 'whichdb': 'dbm', # causes infinite import loop
# anydbm and whichdb are handled by fix_imports2
@@ -182,9 +185,11 @@
MOVES = [('collections', 'UserList', 'UserList', 'UserList'),
('collections', 'UserDict', 'UserDict', 'UserDict'),
('collections', 'UserString','UserString', 'UserString'),
+ ('collections', 'ChainMap', 'future.backports.misc', 'ChainMap'),
('itertools', 'filterfalse','itertools', 'ifilterfalse'),
('itertools', 'zip_longest','itertools', 'izip_longest'),
('sys', 'intern','__builtin__', 'intern'),
+ ('multiprocessing', 'SimpleQueue', 'multiprocessing.queues', 'SimpleQueue'),
# The re module has no ASCII flag in Py2, but this is the default.
# Set re.ASCII to a zero constant. stat.ST_MODE just happens to be one
# (and it exists on Py2.6+).
@@ -197,6 +202,10 @@
('math', 'ceil', 'future.backports.misc', 'ceil'),
('collections', 'OrderedDict', 'future.backports.misc', 'OrderedDict'),
('collections', 'Counter', 'future.backports.misc', 'Counter'),
+ ('collections', 'ChainMap', 'future.backports.misc', 'ChainMap'),
+ ('itertools', 'count', 'future.backports.misc', 'count'),
+ ('reprlib', 'recursive_repr', 'future.backports.misc', 'recursive_repr'),
+ ('functools', 'cmp_to_key', 'future.backports.misc', 'cmp_to_key'),
# This is no use, since "import urllib.request" etc. still fails:
# ('urllib', 'error', 'future.moves.urllib', 'error'),
@@ -267,7 +276,7 @@ def load_module(self, name):
# New name. Look up the corresponding old (Py2) name:
oldname = self.new_to_old[name]
module = self._find_and_load_module(oldname)
- module.__future_module__ = True
+ # module.__future_module__ = True
else:
module = self._find_and_load_module(name)
# In any case, make it available under the requested (Py3) name
@@ -395,7 +404,7 @@ def scrub_future_sys_modules():
"""
Deprecated.
"""
- return {}
+ return {}
class suspend_hooks(object):
"""
@@ -451,7 +460,7 @@ def install_aliases():
# We look up the module in sys.modules because __import__ just returns the
# top-level package:
newmod = sys.modules[newmodname]
- newmod.__future_module__ = True
+ # newmod.__future_module__ = True
__import__(oldmodname)
oldmod = sys.modules[oldmodname]
@@ -461,11 +470,11 @@ def install_aliases():
# Hack for urllib so it appears to have the same structure on Py2 as on Py3
import urllib
- from future.moves.urllib import request
- from future.moves.urllib import response
- from future.moves.urllib import parse
- from future.moves.urllib import error
- from future.moves.urllib import robotparser
+ from future.backports.urllib import request
+ from future.backports.urllib import response
+ from future.backports.urllib import parse
+ from future.backports.urllib import error
+ from future.backports.urllib import robotparser
urllib.request = request
urllib.response = response
urllib.parse = parse
@@ -738,8 +747,9 @@ class exclude_local_folder_imports(object):
A context-manager that prevents standard library modules like configparser
from being imported from the local python-future source folder on Py3.
- (The presence of a configparser folder would otherwise prevent setuptools
- from running on Py3.)
+ (This was need prior to v0.16.0 because the presence of a configparser
+ folder would otherwise have prevented setuptools from running on Py3. Maybe
+ it's not needed any more?)
"""
def __init__(self, *args):
assert len(args) > 0
@@ -753,7 +763,9 @@ def __enter__(self):
self.old_sys_modules = copy.copy(sys.modules)
if sys.version_info[0] < 3:
return
- FUTURE_SOURCE_SUBFOLDERS = ['future', 'past', 'libfuturize', 'configparser']
+ # The presence of all these indicates we've found our source folder,
+ # because `builtins` won't have been installed in site-packages by setup.py:
+ FUTURE_SOURCE_SUBFOLDERS = ['future', 'past', 'libfuturize', 'libpasteurize', 'builtins']
# Look for the future source folder:
for folder in self.old_sys_path:
@@ -785,7 +797,6 @@ def __exit__(self, *args):
sys.modules[m] = self.old_sys_modules[m]
TOP_LEVEL_MODULES = ['builtins',
- 'configparser',
'copyreg',
'html',
'http',
diff --git a/src/future/tests/base.py b/src/future/tests/base.py
index 0386bb99..4ef437ba 100644
--- a/src/future/tests/base.py
+++ b/src/future/tests/base.py
@@ -1,4 +1,4 @@
-from __future__ import print_function
+from __future__ import print_function, absolute_import
import os
import tempfile
import unittest
@@ -6,15 +6,14 @@
import re
import warnings
import io
-import functools
from textwrap import dedent
-if not hasattr(unittest, 'skip'):
- import unittest2 as unittest
-
-from future.utils import bind_method, PY26, PY3, PY2
+from future.utils import bind_method, PY26, PY3, PY2, PY27
from future.moves.subprocess import check_output, STDOUT, CalledProcessError
+if PY26:
+ import unittest2 as unittest
+
def reformat_code(code):
"""
@@ -44,7 +43,8 @@ def order_future_lines(code):
if line.startswith('from __future__ import ')]
future_line_numbers = [i for i, line in enumerate(lines)
- if line.startswith('from future')]
+ if line.startswith('from future')
+ or line.startswith('from past')]
builtins_line_numbers = [i for i, line in enumerate(lines)
if line.startswith('from builtins')]
@@ -56,7 +56,7 @@ def mymax(numbers):
return max(numbers) if len(numbers) > 0 else 0
def mymin(numbers):
- return min(numbers) if len(numbers) > 0 else 0
+ return min(numbers) if len(numbers) > 0 else float('inf')
assert mymax(uufuture_line_numbers) <= mymin(future_line_numbers), \
'the __future__ and future imports are out of order'
@@ -163,7 +163,7 @@ def convert(self, code, stages=(1, 2), all_imports=False, from3=False,
"""
Converts the code block using ``futurize`` and returns the
resulting code.
-
+
Passing stages=[1] or stages=[2] passes the flag ``--stage1`` or
``stage2`` to ``futurize``. Passing both stages runs ``futurize``
with both stages by default.
@@ -259,10 +259,10 @@ def convert_check(self, before, expected, stages=(1, 2), all_imports=False,
If ignore_imports is True, ignores the presence of any lines
beginning:
-
+
from __future__ import ...
from future import ...
-
+
for the purpose of the comparison.
"""
output = self.convert(before, stages=stages, all_imports=all_imports,
@@ -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):
@@ -325,14 +329,23 @@ def _futurize_test_script(self, filename='mytestscript.py', stages=(1, 2),
try:
output = check_output(call_args, stderr=STDOUT, env=self.env)
except CalledProcessError as e:
- msg = ('Error running the command %s\n%s\nContents of file %s:\n\n%s' %
- (' '.join(call_args),
- 'env=%s' % self.env,
- fn,
- '----\n%s\n----' % open(fn).read(),
- )
- )
+ with open(fn) as f:
+ msg = (
+ 'Error running the command %s\n'
+ '%s\n'
+ 'Contents of file %s:\n'
+ '\n'
+ '%s') % (
+ ' '.join(call_args),
+ 'env=%s' % self.env,
+ fn,
+ '----\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
@@ -344,13 +357,21 @@ def _run_test_script(self, filename='mytestscript.py',
output = check_output([interpreter, fn],
env=self.env, stderr=STDOUT)
except CalledProcessError as e:
- msg = ('Error running the command %s\n%s\nContents of file %s:\n\n%s' %
- (' '.join([interpreter, fn]),
- 'env=%s' % self.env,
- fn,
- '----\n%s\n----' % open(fn).read(),
- )
- )
+ with open(fn) as f:
+ msg = (
+ 'Error running the command %s\n'
+ '%s\n'
+ 'Contents of file %s:\n'
+ '\n'
+ '%s') % (
+ ' '.join([interpreter, fn]),
+ 'env=%s' % self.env,
+ fn,
+ '----\n%s\n----' % f.read(),
+ )
+ if not hasattr(e, 'output'):
+ # The attribute CalledProcessError.output doesn't exist on Py2.6
+ e.output = None
raise VerboseCalledProcessError(msg, e.returncode, e.cmd, output=e.output)
return output
@@ -364,33 +385,22 @@ def expectedFailurePY3(func):
return func
return unittest.expectedFailure(func)
-
def expectedFailurePY26(func):
if not PY26:
return func
- @functools.wraps(func)
- def wrapper(*args, **kwargs):
- try:
- func(*args, **kwargs)
- except Exception:
- raise unittest.case._ExpectedFailure(sys.exc_info())
- # The following contributes to a FAILURE on Py2.6 (with
- # unittest2). Ignore it ...
- # raise unittest.case._UnexpectedSuccess
- return wrapper
+ return unittest.expectedFailure(func)
+
+
+def expectedFailurePY27(func):
+ if not PY27:
+ return func
+ return unittest.expectedFailure(func)
def expectedFailurePY2(func):
if not PY2:
return func
- @functools.wraps(func)
- def wrapper(*args, **kwargs):
- try:
- func(*args, **kwargs)
- except Exception:
- raise unittest.case._ExpectedFailure(sys.exc_info())
- raise unittest.case._UnexpectedSuccess
- return wrapper
+ return unittest.expectedFailure(func)
# Renamed in Py3.3:
diff --git a/src/future/types/__init__.py b/src/future/types/__init__.py
index 71279bbb..06250770 100644
--- a/src/future/types/__init__.py
+++ b/src/future/types/__init__.py
@@ -15,7 +15,7 @@
to bring in the new semantics for these functions from Python 3. And
then, for example::
-
+
b = bytes(b'ABCD')
assert list(b) == [65, 66, 67, 68]
assert repr(b) == "b'ABCD'"
@@ -46,7 +46,7 @@
pass
and::
-
+
class VerboseList(list):
def append(self, item):
print('Adding an item')
@@ -112,7 +112,7 @@ def f(a, b):
raises a TypeError when f is called if a unicode object is passed as
`a` or a bytes object is passed as `b`.
- This also skips over keyword arguments, so
+ This also skips over keyword arguments, so
@disallow_types([0, 1], [unicode, bytes])
def g(a, b=None):
@@ -130,7 +130,7 @@ def g(a, b=None):
... def __add__(self, other):
... pass
- >>> newbytes('1234') + u'1234' #doctest: +IGNORE_EXCEPTION_DETAIL
+ >>> newbytes('1234') + u'1234' #doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
TypeError: can't concat 'bytes' to (unicode) str
@@ -255,4 +255,3 @@ def issubset(list1, list2):
unicode: newstr}
__all__ = ['newbytes', 'newdict', 'newint', 'newlist', 'newrange', 'newstr', 'newtypes']
-
diff --git a/src/future/types/newbytes.py b/src/future/types/newbytes.py
index 2f96be6d..c9d584a7 100644
--- a/src/future/types/newbytes.py
+++ b/src/future/types/newbytes.py
@@ -5,14 +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
@@ -29,6 +34,13 @@ def __instancecheck__(cls, instance):
return issubclass(instance.__class__, cls)
+def _newchr(x):
+ if isinstance(x, str): # this happens on pypy
+ return x.encode('ascii')
+ else:
+ return chr(x)
+
+
class newbytes(with_metaclass(BaseNewBytes, _builtin_bytes)):
"""
A backport of the Python 3 bytes object to Py2
@@ -42,14 +54,14 @@ def __new__(cls, *args, **kwargs):
bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer
bytes(int) -> bytes object of size given by the parameter initialized with null bytes
bytes() -> empty bytes object
-
+
Construct an immutable array of bytes from:
- an iterable yielding integers in range(256)
- a text string encoded using the specified encoding
- any object implementing the buffer API.
- an integer
"""
-
+
encoding = None
errors = None
@@ -91,7 +103,9 @@ def __new__(cls, *args, **kwargs):
if errors is not None:
newargs.append(errors)
value = args[0].encode(*newargs)
- ###
+ ###
+ elif hasattr(args[0], '__bytes__'):
+ value = args[0].__bytes__()
elif isinstance(args[0], Iterable):
if len(args[0]) == 0:
# This could be an empty list or tuple. Return b'' as on Py3.
@@ -102,8 +116,7 @@ def __new__(cls, *args, **kwargs):
# But then we can't index into e.g. frozensets. Try to proceed
# anyway.
try:
- values = [chr(x) for x in args[0]]
- value = b''.join(values)
+ value = bytearray([_newchr(x) for x in args[0]])
except:
raise ValueError('bytes must be in range(0, 256)')
elif isinstance(args[0], Integral):
@@ -112,8 +125,16 @@ def __new__(cls, *args, **kwargs):
value = b'\x00' * args[0]
else:
value = args[0]
- return super(newbytes, cls).__new__(cls, value)
-
+ if type(value) == newbytes:
+ # Above we use type(...) rather than isinstance(...) because the
+ # newbytes metaclass overrides __instancecheck__.
+ # oldbytes(value) gives the wrong thing on Py2: the same
+ # result as str(value) on Py3, e.g. "b'abc'". (Issue #193).
+ # So we handle this case separately:
+ return copy.copy(value)
+ else:
+ return super(newbytes, cls).__new__(cls, value)
+
def __repr__(self):
return 'b' + super(newbytes, self).__repr__()
@@ -140,7 +161,7 @@ def __contains__(self, key):
else:
newbyteskey = newbytes(key)
return issubset(list(newbyteskey), list(self))
-
+
@no(unicode)
def __add__(self, other):
return newbytes(super(newbytes, self).__add__(other))
@@ -148,7 +169,7 @@ def __add__(self, other):
@no(unicode)
def __radd__(self, left):
return newbytes(left) + self
-
+
@no(unicode)
def __mul__(self, other):
return newbytes(super(newbytes, self).__mul__(other))
@@ -157,6 +178,29 @@ def __mul__(self, other):
def __rmul__(self, other):
return newbytes(super(newbytes, self).__rmul__(other))
+ def __mod__(self, vals):
+ if isinstance(vals, newbytes):
+ vals = _builtin_bytes.__str__(vals)
+
+ elif isinstance(vals, tuple):
+ newvals = []
+ for v in vals:
+ if isinstance(v, newbytes):
+ v = _builtin_bytes.__str__(v)
+ newvals.append(v)
+ vals = tuple(newvals)
+
+ elif (hasattr(vals.__class__, '__getitem__') and
+ hasattr(vals.__class__, 'iteritems')):
+ for k, v in vals.iteritems():
+ if isinstance(v, newbytes):
+ vals[k] = _builtin_bytes.__str__(v)
+
+ return _builtin_bytes.__mod__(self, vals)
+
+ def __imod__(self, other):
+ return self.__mod__(other)
+
def join(self, iterable_of_bytes):
errmsg = 'sequence item {0}: expected bytes, {1} found'
if isbytes(iterable_of_bytes) or istext(iterable_of_bytes):
@@ -201,6 +245,11 @@ def decode(self, encoding='utf-8', errors='strict'):
# not keyword arguments as in Python 3 str.
from future.types.newstr import newstr
+
+ if errors == 'surrogateescape':
+ from future.utils.surrogateescape import register_surrogateescape
+ register_surrogateescape()
+
return newstr(super(newbytes, self).decode(encoding, errors))
# This is currently broken:
@@ -328,24 +377,24 @@ def __ne__(self, other):
unorderable_err = 'unorderable types: bytes() and {0}'
def __lt__(self, other):
- if not isbytes(other):
- raise TypeError(self.unorderable_err.format(type(other)))
- return super(newbytes, self).__lt__(other)
+ if isinstance(other, _builtin_bytes):
+ return super(newbytes, self).__lt__(other)
+ raise TypeError(self.unorderable_err.format(type(other)))
def __le__(self, other):
- if not isbytes(other):
- raise TypeError(self.unorderable_err.format(type(other)))
- return super(newbytes, self).__le__(other)
+ if isinstance(other, _builtin_bytes):
+ return super(newbytes, self).__le__(other)
+ raise TypeError(self.unorderable_err.format(type(other)))
def __gt__(self, other):
- if not isbytes(other):
- raise TypeError(self.unorderable_err.format(type(other)))
- return super(newbytes, self).__gt__(other)
+ if isinstance(other, _builtin_bytes):
+ return super(newbytes, self).__gt__(other)
+ raise TypeError(self.unorderable_err.format(type(other)))
def __ge__(self, other):
- if not isbytes(other):
- raise TypeError(self.unorderable_err.format(type(other)))
- return super(newbytes, self).__ge__(other)
+ if isinstance(other, _builtin_bytes):
+ return super(newbytes, self).__ge__(other)
+ raise TypeError(self.unorderable_err.format(type(other)))
def __native__(self):
# We can't just feed a newbytes object into str(), because
@@ -366,7 +415,7 @@ def rstrip(self, bytes_to_strip=None):
"""
Strip trailing bytes contained in the argument.
If the argument is omitted, strip trailing ASCII whitespace.
- """
+ """
return newbytes(super(newbytes, self).rstrip(bytes_to_strip))
@no(unicode)
@@ -374,24 +423,24 @@ def strip(self, bytes_to_strip=None):
"""
Strip leading and trailing bytes contained in the argument.
If the argument is omitted, strip trailing ASCII whitespace.
- """
+ """
return newbytes(super(newbytes, self).strip(bytes_to_strip))
def lower(self):
"""
b.lower() -> copy of b
-
+
Return a copy of b with all ASCII characters converted to lowercase.
- """
+ """
return newbytes(super(newbytes, self).lower())
@no(unicode)
def upper(self):
"""
b.upper() -> copy of b
-
+
Return a copy of b with all ASCII characters converted to uppercase.
- """
+ """
return newbytes(super(newbytes, self).upper())
@classmethod
diff --git a/src/future/types/newdict.py b/src/future/types/newdict.py
index 5dbcc4b7..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,14 +64,8 @@ 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):
"""
Hook for the future.utils.native() function
diff --git a/src/future/types/newint.py b/src/future/types/newint.py
index 7157f95e..ebc5715e 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):
@@ -201,10 +203,16 @@ def __rmod__(self, other):
def __divmod__(self, other):
value = super(newint, self).__divmod__(other)
+ if value is NotImplemented:
+ mylong = long(self)
+ return (mylong // other, mylong % other)
return (newint(value[0]), newint(value[1]))
def __rdivmod__(self, other):
value = super(newint, self).__rdivmod__(other)
+ if value is NotImplemented:
+ mylong = long(self)
+ return (other // mylong, other % mylong)
return (newint(value[0]), newint(value[1]))
def __pow__(self, other):
@@ -215,9 +223,11 @@ def __pow__(self, other):
def __rpow__(self, other):
value = super(newint, self).__rpow__(other)
- if value is NotImplemented:
+ if isint(value):
+ return newint(value)
+ elif value is NotImplemented:
return other ** long(self)
- return newint(value)
+ return value
def __lshift__(self, other):
if not isint(other):
@@ -276,6 +286,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):
@@ -299,20 +312,29 @@ def to_bytes(self, length, byteorder='big', signed=False):
used to represent the integer. If signed is False and a negative integer
is given, an OverflowError is raised.
"""
- if signed:
- raise NotImplementedError("Not yet implemented. Please contribute a patch at http://python-future.org")
+ if length < 0:
+ raise ValueError("length argument must be non-negative")
+ if length == 0 and self == 0:
+ return newbytes()
+ if signed and self < 0:
+ bits = length * 8
+ num = (2**bits) + self
+ if num <= 0:
+ raise OverflowError("int too small to convert")
else:
if self < 0:
raise OverflowError("can't convert negative int to unsigned")
num = self
if byteorder not in ('little', 'big'):
raise ValueError("byteorder must be either 'little' or 'big'")
- if length < 0:
- raise ValueError("length argument must be non-negative")
- if length == 0 and num == 0:
- return newbytes()
h = b'%x' % num
s = newbytes((b'0'*(len(h) % 2) + h).zfill(length*2).decode('hex'))
+ if signed:
+ high_set = s[0] & 0x80
+ if self > 0 and high_set:
+ raise OverflowError("int too big to convert")
+ if self < 0 and not high_set:
+ raise OverflowError("int too small to convert")
if len(s) > length:
raise OverflowError("int too big to convert")
return s if byteorder == 'big' else s[::-1]
@@ -335,15 +357,13 @@ def from_bytes(cls, mybytes, byteorder='big', signed=False):
The signed keyword-only argument indicates whether two's complement is
used to represent the integer.
"""
- if signed:
- raise NotImplementedError("Not yet implemented. Please contribute a patch at http://python-future.org")
if byteorder not in ('little', 'big'):
raise ValueError("byteorder must be either 'little' or 'big'")
if isinstance(mybytes, unicode):
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:
@@ -351,6 +371,8 @@ def from_bytes(cls, mybytes, byteorder='big', signed=False):
# The encode() method has been disabled by newbytes, but Py2's
# str has it:
num = int(native(b).encode('hex'), 16)
+ if signed and (b[0] & 0x80):
+ num = num - (2 ** (len(b)*8))
return cls(num)
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 59c19ea5..31b84fc1 100644
--- a/src/future/types/newobject.py
+++ b/src/future/types/newobject.py
@@ -15,10 +15,10 @@ def __str__(self):
a = A()
print(str(a))
-
+
# On Python 2, these relations hold:
assert unicode(a) == my_unicode_string
- assert str(a) == my_unicode_string.encode('utf-8')
+ assert str(a) == my_unicode_string.encode('utf-8')
Another example::
@@ -32,29 +32,11 @@ def __next__(self): # note the Py3 interface
return next(self._iter).upper()
def __iter__(self):
return self
-
+
assert list(Upper('hello')) == list('HELLO')
"""
-import sys
-
-from future.utils import with_metaclass
-
-
-_builtin_object = object
-ver = sys.version_info[:2]
-
-
-# We no longer define a metaclass for newobject because this breaks multiple
-# inheritance and custom metaclass use with this exception:
-
-# TypeError: Error when calling the metaclass bases
-# metaclass conflict: the metaclass of a derived class must be a
-# (non-strict) subclass of the metaclasses of all its bases
-
-# See issues #91 and #96.
-
class newobject(object):
"""
@@ -62,7 +44,7 @@ class newobject(object):
next
__unicode__
__nonzero__
-
+
Subclasses of this class can merely define the Python 3 methods (__next__,
__str__, and __bool__).
"""
@@ -70,7 +52,7 @@ def next(self):
if hasattr(self, '__next__'):
return type(self).__next__(self)
raise TypeError('newobject is not an iterator')
-
+
def __unicode__(self):
# All subclasses of the builtin object should have __str__ defined.
# Note that old-style classes do not have __str__ defined.
@@ -86,6 +68,8 @@ def __unicode__(self):
def __nonzero__(self):
if hasattr(self, '__bool__'):
return type(self).__bool__(self)
+ if hasattr(self, '__len__'):
+ return type(self).__len__(self)
# object has no __nonzero__ method
return True
@@ -121,12 +105,13 @@ def __long__(self):
# else:
# value = args[0]
# return super(newdict, cls).__new__(cls, value)
-
+
def __native__(self):
"""
Hook for the future.utils.native() function
"""
return object(self)
+ __slots__ = []
__all__ = ['newobject']
diff --git a/src/future/types/newopen.py b/src/future/types/newopen.py
index 8da06427..b75d45af 100644
--- a/src/future/types/newopen.py
+++ b/src/future/types/newopen.py
@@ -30,4 +30,3 @@ def __enter__(self):
def __exit__(self, etype, value, traceback):
self.f.close()
-
diff --git a/src/future/types/newrange.py b/src/future/types/newrange.py
index 2438d205..dc5eb802 100644
--- a/src/future/types/newrange.py
+++ b/src/future/types/newrange.py
@@ -1,5 +1,5 @@
"""
-Nearly identical to xrange.py, by Dan Crosta, from
+Nearly identical to xrange.py, by Dan Crosta, from
https://github.com/dcrosta/xrange.git
@@ -17,11 +17,19 @@
Read more at
https://late.am/post/2012/06/18/what-the-heck-is-an-xrange
"""
+from __future__ import absolute_import
-from math import ceil
-from collections import Sequence, Iterator
+from future.utils import PY2
-from future.utils import PY3
+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
+# For backward compatibility with python-future versions < 0.14.4:
+_count = count
class newrange(Sequence):
@@ -58,16 +66,28 @@ def __init__(self, *args):
self._step = step
self._len = (stop - start) // step + bool((stop - start) % step)
+ @property
+ def start(self):
+ return self._start
+
+ @property
+ def stop(self):
+ return self._stop
+
+ @property
+ def step(self):
+ return self._step
+
def __repr__(self):
if self._step == 1:
return 'range(%d, %d)' % (self._start, self._stop)
return 'range(%d, %d, %d)' % (self._start, self._stop, self._step)
def __eq__(self, other):
- return isinstance(other, newrange) and \
- self._start == other._start and \
- self._stop == other._stop and \
- self._step == other._step
+ return (isinstance(other, newrange) and
+ (self._len == 0 == other._len or
+ (self._start, self._step, self._len) ==
+ (other._start, other._step, other._len)))
def __len__(self):
return self._len
@@ -75,14 +95,17 @@ def __len__(self):
def index(self, value):
"""Return the 0-based position of integer `value` in
the sequence this range represents."""
- diff = value - self._start
+ try:
+ diff = value - self._start
+ except TypeError:
+ raise ValueError('%r is not in range' % value)
quotient, remainder = divmod(diff, self._step)
if remainder == 0 and 0 <= quotient < self._len:
return abs(quotient)
raise ValueError('%r is not in range' % value)
def count(self, value):
- """Return the number of ocurrences of integer `value`
+ """Return the number of occurrences of integer `value`
in the sequence this range represents."""
# a value can occur exactly zero or one times
return int(value in self)
@@ -97,12 +120,7 @@ def __contains__(self, value):
return False
def __reversed__(self):
- """Return a range which represents a sequence whose
- contents are the same as the sequence this range
- represents, but in the opposite order."""
- sign = self._step / abs(self._step)
- last = self._start + ((self._len - 1) * self._step)
- return newrange(last, self._start - sign, -1 * self._step)
+ return iter(self[::-1])
def __getitem__(self, index):
"""Return the element at position ``index`` in the sequence
@@ -121,56 +139,32 @@ def __getitem_slice(self, slce):
"""Return a range which represents the requested slce
of the sequence represented by this range.
"""
- start, stop, step = slce.start, slce.stop, slce.step
- if step == 0:
- raise ValueError('slice step cannot be 0')
-
- start = start or self._start
- stop = stop or self._stop
- if start < 0:
- start = max(0, start + self._len)
- if stop < 0:
- stop = max(start, stop + self._len)
-
- if step is None or step > 0:
- return newrange(start, stop, step or 1)
- else:
- rv = reversed(self)
- rv._step = step
- return rv
+ scaled_indices = (self._step * n for n in slce.indices(self._len))
+ start_offset, stop_offset, new_step = scaled_indices
+ return newrange(self._start + start_offset,
+ self._start + stop_offset,
+ new_step)
def __iter__(self):
"""Return an iterator which enumerates the elements of the
sequence this range represents."""
- return rangeiterator(self)
+ return range_iterator(self)
-class rangeiterator(Iterator):
+class range_iterator(Iterator):
"""An iterator for a :class:`range`.
"""
-
- def __init__(self, rangeobj):
- self._range = rangeobj
-
- # Intialize the "last outputted value" to the value
- # just before the first value; this simplifies next()
- self._last = self._range._start - self._range._step
- self._count = 0
+ def __init__(self, range_):
+ self._stepper = islice(count(range_.start, range_.step), len(range_))
def __iter__(self):
- """An iterator is already an iterator, so return ``self``.
- """
return self
+ def __next__(self):
+ return next(self._stepper)
+
def next(self):
- """Return the next element in the sequence represented
- by the range we are iterating, or raise StopIteration
- if we have passed the end of the sequence."""
- self._last += self._range._step
- self._count += 1
- if self._count > self._range._len:
- raise StopIteration()
- return self._last
+ return next(self._stepper)
__all__ = ['newrange']
diff --git a/src/future/types/newstr.py b/src/future/types/newstr.py
index fd8615af..8ca191f9 100644
--- a/src/future/types/newstr.py
+++ b/src/future/types/newstr.py
@@ -37,10 +37,9 @@
``__unicode__`` method on objects in Python 2. To define string
representations of your objects portably across Py3 and Py2, use the
:func:`python_2_unicode_compatible` decorator in :mod:`future.utils`.
-
+
"""
-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):
@@ -73,7 +75,7 @@ def __new__(cls, *args, **kwargs):
str(object='') -> str
str(bytes_or_buffer[, encoding[, errors]]) -> str
-
+
Create a new string object from the given object. If encoding or
errors is specified, then the object must expose a data buffer
that will be decoded using the given encoding and error handler.
@@ -81,7 +83,7 @@ def __new__(cls, *args, **kwargs):
or repr(object).
encoding defaults to sys.getdefaultencoding().
errors defaults to 'strict'.
-
+
"""
if len(args) == 0:
return super(newstr, cls).__new__(cls)
@@ -100,11 +102,12 @@ def __new__(cls, *args, **kwargs):
else:
value = args[0]
return super(newstr, cls).__new__(cls, value)
-
+
def __repr__(self):
"""
Without the u prefix
"""
+
value = super(newstr, self).__repr__()
# assert value[0] == u'u'
return value[1:]
@@ -128,7 +131,7 @@ def __contains__(self, key):
else:
raise TypeError(errmsg.format(type(key)))
return issubset(list(newkey), list(self))
-
+
@no('newbytes')
def __add__(self, other):
return newstr(super(newstr, self).__add__(other))
@@ -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
@@ -302,24 +312,28 @@ def __ne__(self, other):
unorderable_err = 'unorderable types: str() and {0}'
def __lt__(self, other):
- if not istext(other):
- raise TypeError(self.unorderable_err.format(type(other)))
- return super(newstr, self).__lt__(other)
+ if (isinstance(other, unicode) or
+ isinstance(other, bytes) and not isnewbytes(other)):
+ return super(newstr, self).__lt__(other)
+ raise TypeError(self.unorderable_err.format(type(other)))
def __le__(self, other):
- if not istext(other):
- raise TypeError(self.unorderable_err.format(type(other)))
- return super(newstr, self).__le__(other)
+ if (isinstance(other, unicode) or
+ isinstance(other, bytes) and not isnewbytes(other)):
+ return super(newstr, self).__le__(other)
+ raise TypeError(self.unorderable_err.format(type(other)))
def __gt__(self, other):
- if not istext(other):
- raise TypeError(self.unorderable_err.format(type(other)))
- return super(newstr, self).__gt__(other)
+ if (isinstance(other, unicode) or
+ isinstance(other, bytes) and not isnewbytes(other)):
+ return super(newstr, self).__gt__(other)
+ raise TypeError(self.unorderable_err.format(type(other)))
def __ge__(self, other):
- if not istext(other):
- raise TypeError(self.unorderable_err.format(type(other)))
- return super(newstr, self).__ge__(other)
+ if (isinstance(other, unicode) or
+ isinstance(other, bytes) and not isnewbytes(other)):
+ return super(newstr, self).__ge__(other)
+ raise TypeError(self.unorderable_err.format(type(other)))
def __getattribute__(self, name):
"""
diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py
index 6466fa28..ec1b1027 100644
--- a/src/future/utils/__init__.py
+++ b/src/future/utils/__init__.py
@@ -1,23 +1,27 @@
"""
A selection of cross-compatible functions for Python 2 and 3.
-This exports useful functions for 2/3 compatible code that are not
-builtins on Python 3:
+This module exports useful functions for 2/3 compatible code:
* bind_method: binds functions to classes
* ``native_str_to_bytes`` and ``bytes_to_native_str``
* ``native_str``: always equal to the native platform string object (because
this may be shadowed by imports from future.builtins)
* lists: lrange(), lmap(), lzip(), lfilter()
- * iterable method compatibility: iteritems, iterkeys, itervalues
+ * iterable method compatibility:
+ - iteritems, iterkeys, itervalues
+ - viewitems, viewkeys, viewvalues
- * Uses the original method if available, otherwise uses items, keys, values.
+ These use the original method if available, otherwise they use items,
+ keys, values.
* types:
* text_type: unicode in Python 2, str in Python 3
- * binary_type: str in Python 2, bythes 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
@@ -27,38 +31,14 @@
Take a text string, a byte string, or a sequence of characters taken
from a byte string, and make a byte string.
-This module also defines a simple decorator called
-``python_2_unicode_compatible`` (from django.utils.encoding) which
-defines ``__unicode__`` and ``__str__`` methods consistently under Python
-3 and 2. To support Python 3 and 2 with a single code base, simply define
-a ``__str__`` method returning unicode text and apply the
-python_2_unicode_compatible decorator to the class like this::
-
- >>> from future.utils import python_2_unicode_compatible
-
- >>> @python_2_unicode_compatible
- ... class MyClass(object):
- ... def __str__(self):
- ... return u'Unicode string: \u5b54\u5b50'
-
- >>> a = MyClass()
+ * raise_from()
+ * raise_with_traceback()
-Then, after this import:
+This module also defines these decorators:
- >>> from future.builtins import str
-
-the following is ``True`` on both Python 3 and 2::
-
- >>> str(a) == a.encode('utf-8').decode('utf-8')
- True
-
-and, on a Unicode-enabled terminal with the right fonts, these both print the
-Chinese characters for Confucius::
-
- print(a)
- print(str(a))
-
-On Python 3, this decorator is a no-op.
+ * ``python_2_unicode_compatible``
+ * ``with_metaclass``
+ * ``implements_iterator``
Some of the functions in this module come from the following sources:
@@ -77,19 +57,51 @@
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)
PYPY = hasattr(sys, 'pypy_translation_info')
def python_2_unicode_compatible(cls):
"""
A decorator that defines __unicode__ and __str__ methods under Python
- 2. Under Python 3 it does nothing.
-
+ 2. Under Python 3, this decorator is a no-op.
+
To support Python 2 and 3 with a single code base, define a __str__
- method returning unicode text and apply this decorator to the class.
+ method returning unicode text and apply this decorator to the class, like
+ this::
+
+ >>> from future.utils import python_2_unicode_compatible
+
+ >>> @python_2_unicode_compatible
+ ... class MyClass(object):
+ ... def __str__(self):
+ ... return u'Unicode string: \u5b54\u5b50'
+
+ >>> a = MyClass()
+
+ Then, after this import:
+
+ >>> from future.builtins import str
+
+ the following is ``True`` on both Python 3 and 2::
+
+ >>> str(a) == a.encode('utf-8').decode('utf-8')
+ True
+
+ and, on a Unicode-enabled terminal with the right fonts, these both print the
+ Chinese characters for Confucius::
+
+ >>> print(a)
+ >>> print(str(a))
The implementation comes from django.utils.encoding.
"""
@@ -104,13 +116,13 @@ def with_metaclass(meta, *bases):
Function from jinja2/_compat.py. License: BSD.
Use it like this::
-
+
class BaseForm(object):
pass
-
+
class FormType(type):
pass
-
+
class Form(with_metaclass(FormType, BaseForm)):
pass
@@ -120,7 +132,7 @@ class Form(with_metaclass(FormType, BaseForm)):
we also need to make sure that we downgrade the custom metaclass
for one level to something closer to type (that's why __call__ and
__init__ comes back from type etc.).
-
+
This has the advantage over six.with_metaclass of not introducing
dummy classes into the final MRO.
"""
@@ -134,7 +146,7 @@ def __new__(cls, name, this_bases, d):
return metaclass('temporary_class', None, {})
-# Definitions from pandas.compat follow:
+# Definitions from pandas.compat and six.py follow:
if PY3:
def bchr(s):
return bytes([s])
@@ -145,6 +157,13 @@ def bstr(s):
return bytes(s)
def bord(s):
return s
+
+ string_types = str,
+ integer_types = int,
+ class_types = type,
+ text_type = str
+ binary_type = bytes
+
else:
# Python 2
def bchr(s):
@@ -154,6 +173,12 @@ def bstr(s):
def bord(s):
return ord(s)
+ string_types = basestring,
+ integer_types = (int, long)
+ class_types = (type, types.ClassType)
+ text_type = unicode
+ binary_type = str
+
###
if PY3:
@@ -370,15 +395,14 @@ def raise_from(exc, cause):
on Python 3. (See PEP 3134).
"""
- # Is either arg an exception class (e.g. IndexError) rather than
- # instance (e.g. IndexError('my message here')? If so, pass the
- # name of the class undisturbed through to "raise ... from ...".
- if isinstance(exc, type) and issubclass(exc, Exception):
- exc = exc.__name__
- if isinstance(cause, type) and issubclass(cause, Exception):
- cause = cause.__name__
- execstr = "raise " + _repr_strip(exc) + " from " + _repr_strip(cause)
myglobals, mylocals = _get_caller_globals_and_locals()
+
+ # We pass the exception and cause along with other globals
+ # when we exec():
+ myglobals = myglobals.copy()
+ myglobals['__python_future_raise_from_exc'] = exc
+ myglobals['__python_future_raise_from_cause'] = cause
+ execstr = "raise __python_future_raise_from_exc from __python_future_raise_from_cause"
exec(execstr, myglobals, mylocals)
def raise_(tp, value=None, tb=None):
@@ -387,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
@@ -425,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")
@@ -463,7 +511,7 @@ def implements_iterator(cls):
From jinja2/_compat.py. License: BSD.
Use as a decorator like this::
-
+
@implements_iterator
class UppercasingIterator(object):
def __init__(self, iterable):
@@ -472,7 +520,7 @@ def __iter__(self):
return self
def __next__(self):
return next(self._iter).upper()
-
+
'''
if PY3:
return cls
@@ -482,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):
@@ -503,7 +551,7 @@ def is_new_style(cls):
function to test for whether a class is new-style. (Python 3 only has
new-style classes.)
"""
- return hasattr(cls, '__class__') and ('__dict__' in dir(cls)
+ return hasattr(cls, '__class__') and ('__dict__' in dir(cls)
or hasattr(cls, '__slots__'))
# The native platform string and bytes types. Useful because ``str`` and
@@ -534,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):
@@ -570,7 +617,7 @@ def native(obj):
On Py2, returns the corresponding native Py2 types that are
superclasses for backported objects from Py3:
-
+
>>> from builtins import str, bytes, int
>>> native(str(u'ABC'))
@@ -639,7 +686,7 @@ def as_native_str(encoding='utf-8'):
unicode, into one that returns a native platform str.
Use it as a decorator like this::
-
+
from __future__ import unicode_literals
class MyClass(object):
@@ -700,7 +747,7 @@ def ensure_new_type(obj):
elif native_type == dict:
return newdict(obj)
else:
- return NotImplementedError('type %s not supported' % type(obj))
+ return obj
else:
# Already a new type
assert type(obj) in [newbytes, newstr]
@@ -708,17 +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/future/utils/surrogateescape.py b/src/future/utils/surrogateescape.py
index a0d8d44a..0dcc9fa6 100644
--- a/src/future/utils/surrogateescape.py
+++ b/src/future/utils/surrogateescape.py
@@ -83,7 +83,7 @@ def replace_surrogate_encode(mystring):
# The following magic comes from Py3.3's Python/codecs.c file:
if not 0xD800 <= code <= 0xDCFF:
# Not a surrogate. Fail with the original exception.
- raise exc
+ raise NotASurrogateError
# mybytes = [0xe0 | (code >> 12),
# 0x80 | ((code >> 6) & 0x3f),
# 0x80 | (code & 0x3f)]
@@ -186,14 +186,13 @@ def register_surrogateescape():
codecs.register_error(FS_ERRORS, surrogateescape_handler)
-if True:
- # Tests:
- register_surrogateescape()
-
- b = decodefilename(fn)
- assert b == encoded, "%r != %r" % (b, encoded)
- c = encodefilename(b)
- assert c == fn, '%r != %r' % (c, fn)
- # print("ok")
-
-
+if __name__ == '__main__':
+ pass
+ # # Tests:
+ # register_surrogateescape()
+
+ # b = decodefilename(fn)
+ # assert b == encoded, "%r != %r" % (b, encoded)
+ # c = encodefilename(b)
+ # assert c == fn, '%r != %r' % (c, fn)
+ # # print("ok")
diff --git a/src/html/__init__.py b/src/html/__init__.py
index 8250e6de..e957e745 100644
--- a/src/html/__init__.py
+++ b/src/html/__init__.py
@@ -1,31 +1,9 @@
from __future__ import absolute_import
import sys
-__future_module__ = True
-
-if sys.version_info[0] == 3:
- raise ImportError('Cannot import module from python-future source folder')
+if sys.version_info[0] < 3:
+ from future.moves.html import *
else:
- # cgi.escape isn't good enough for the single Py3.3 html test to pass.
- # Define it inline here instead. From the Py3.3 stdlib
- """
- General functions for HTML manipulation.
- """
-
-
- _escape_map = {ord('&'): '&', ord('<'): '<', ord('>'): '>'}
- _escape_map_full = {ord('&'): '&', ord('<'): '<', ord('>'): '>',
- ord('"'): '"', ord('\''): '''}
-
- # NB: this is a candidate for a bytes/string polymorphic interface
-
- def escape(s, quote=True):
- """
- Replace special characters "&", "<" and ">" to HTML-safe sequences.
- If the optional flag quote is true (the default), the quotation mark
- characters, both double quote (") and single quote (') characters are also
- translated.
- """
- if quote:
- return s.translate(_escape_map_full)
- return s.translate(_escape_map)
+ raise ImportError('This package should not be accessible on Python 3. '
+ 'Either you are trying to run from the python-future src folder '
+ 'or your installation of python-future is corrupted.')
diff --git a/src/html/entities.py b/src/html/entities.py
index 56a88609..211649e5 100644
--- a/src/html/entities.py
+++ b/src/html/entities.py
@@ -4,5 +4,4 @@
if PY3:
from html.entities import *
else:
- __future_module__ = True
- from htmlentitydefs import *
+ from future.moves.html.entities import *
diff --git a/src/html/parser.py b/src/html/parser.py
index 94d65804..e3948879 100644
--- a/src/html/parser.py
+++ b/src/html/parser.py
@@ -1,8 +1,8 @@
from __future__ import absolute_import
-from future.utils import PY3
+import sys
+__future_module__ = True
-if PY3:
- from html.parser import *
+if sys.version_info[0] >= 3:
+ raise ImportError('Cannot import module from python-future source folder')
else:
- __future_module__ = True
- from HTMLParser import *
+ from future.moves.html.parser import *
diff --git a/src/http/__init__.py b/src/http/__init__.py
index 2e225103..e4f853e5 100644
--- a/src/http/__init__.py
+++ b/src/http/__init__.py
@@ -1,8 +1,9 @@
from __future__ import absolute_import
import sys
-__future_module__ = True
if sys.version_info[0] < 3:
pass
else:
- raise ImportError('Cannot import module from python-future source folder')
+ raise ImportError('This package should not be accessible on Python 3. '
+ 'Either you are trying to run from the python-future src folder '
+ 'or your installation of python-future is corrupted.')
diff --git a/src/http/client.py b/src/http/client.py
index 24edcbaf..a6a31006 100644
--- a/src/http/client.py
+++ b/src/http/client.py
@@ -1,8 +1,90 @@
from __future__ import absolute_import
import sys
-__future_module__ = True
-if sys.version_info[0] < 3:
- from httplib import *
-else:
- raise ImportError('Cannot import module from python-future source folder')
+assert sys.version_info[0] < 3
+
+from httplib import *
+from httplib import HTTPMessage
+
+# These constants aren't included in __all__ in httplib.py:
+
+from httplib import (HTTP_PORT,
+ HTTPS_PORT,
+
+ CONTINUE,
+ SWITCHING_PROTOCOLS,
+ PROCESSING,
+
+ OK,
+ CREATED,
+ ACCEPTED,
+ NON_AUTHORITATIVE_INFORMATION,
+ NO_CONTENT,
+ RESET_CONTENT,
+ PARTIAL_CONTENT,
+ MULTI_STATUS,
+ IM_USED,
+
+ MULTIPLE_CHOICES,
+ MOVED_PERMANENTLY,
+ FOUND,
+ SEE_OTHER,
+ NOT_MODIFIED,
+ USE_PROXY,
+ TEMPORARY_REDIRECT,
+
+ BAD_REQUEST,
+ UNAUTHORIZED,
+ PAYMENT_REQUIRED,
+ FORBIDDEN,
+ NOT_FOUND,
+ METHOD_NOT_ALLOWED,
+ NOT_ACCEPTABLE,
+ PROXY_AUTHENTICATION_REQUIRED,
+ REQUEST_TIMEOUT,
+ CONFLICT,
+ GONE,
+ LENGTH_REQUIRED,
+ PRECONDITION_FAILED,
+ REQUEST_ENTITY_TOO_LARGE,
+ REQUEST_URI_TOO_LONG,
+ UNSUPPORTED_MEDIA_TYPE,
+ REQUESTED_RANGE_NOT_SATISFIABLE,
+ EXPECTATION_FAILED,
+ UNPROCESSABLE_ENTITY,
+ LOCKED,
+ FAILED_DEPENDENCY,
+ UPGRADE_REQUIRED,
+
+ INTERNAL_SERVER_ERROR,
+ NOT_IMPLEMENTED,
+ BAD_GATEWAY,
+ SERVICE_UNAVAILABLE,
+ GATEWAY_TIMEOUT,
+ HTTP_VERSION_NOT_SUPPORTED,
+ INSUFFICIENT_STORAGE,
+ NOT_EXTENDED,
+
+ MAXAMOUNT,
+ )
+
+# These are not available on Python 2.6.x:
+try:
+ from httplib import LineTooLong, LineAndFileWrapper
+except ImportError:
+ pass
+
+# 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,
+ _is_illegal_header_value,
+ _METHODS_EXPECTING_BODY
+ )
+except ImportError:
+ pass
diff --git a/src/http/cookiejar.py b/src/http/cookiejar.py
index ea00df77..d847b2bf 100644
--- a/src/http/cookiejar.py
+++ b/src/http/cookiejar.py
@@ -1,8 +1,6 @@
from __future__ import absolute_import
-from future.utils import PY3
+import sys
-if PY3:
- from http.cookiejar import *
-else:
- __future_module__ = True
- from cookielib import *
+assert sys.version_info[0] < 3
+
+from cookielib import *
diff --git a/src/http/cookies.py b/src/http/cookies.py
index 1b74fe2d..eb2a8238 100644
--- a/src/http/cookies.py
+++ b/src/http/cookies.py
@@ -1,9 +1,7 @@
from __future__ import absolute_import
-from future.utils import PY3
+import sys
-if PY3:
- from http.cookies import *
-else:
- __future_module__ = True
- from Cookie import *
- from Cookie import Morsel # left out of __all__ on Py2.7!
+assert sys.version_info[0] < 3
+
+from Cookie import *
+from Cookie import Morsel # left out of __all__ on Py2.7!
diff --git a/src/http/server.py b/src/http/server.py
index fe2b1f97..29710557 100644
--- a/src/http/server.py
+++ b/src/http/server.py
@@ -1,21 +1,18 @@
from __future__ import absolute_import
import sys
-__future_module__ = True
-if sys.version_info[0] == 3:
- raise ImportError('Cannot import module from python-future source folder')
-else:
- __future_module__ = True
- from BaseHTTPServer import *
- from CGIHTTPServer import *
- from SimpleHTTPServer import *
+assert sys.version_info[0] < 3
+
+from BaseHTTPServer import *
+from CGIHTTPServer import *
+from SimpleHTTPServer import *
+try:
+ from CGIHTTPServer import _url_collapse_path # needed for a test
+except ImportError:
try:
- from CGIHTTPServer import _url_collapse_path # needed for a test
+ # Python 2.7.0 to 2.7.3
+ from CGIHTTPServer import (
+ _url_collapse_path_split as _url_collapse_path)
except ImportError:
- try:
- # Python 2.7.0 to 2.7.3
- from CGIHTTPServer import (
- _url_collapse_path_split as _url_collapse_path)
- except ImportError:
- # Doesn't exist on Python 2.6.x. Ignore it.
- pass
+ # Doesn't exist on Python 2.6.x. Ignore it.
+ pass
diff --git a/src/libfuturize/fixer_util.py b/src/libfuturize/fixer_util.py
index f2366a6e..b5c123f6 100644
--- a/src/libfuturize/fixer_util.py
+++ b/src/libfuturize/fixer_util.py
@@ -9,14 +9,46 @@
"""
from lib2to3.fixer_util import (FromImport, Newline, is_import,
- find_root, does_tree_import, Comma)
+ find_root, does_tree_import,
+ Call, Name, Comma)
from lib2to3.pytree import Leaf, Node
-from lib2to3.pygram import python_symbols as syms, python_grammar
+from lib2to3.pygram import python_symbols as syms
from lib2to3.pygram import token
-from lib2to3.fixer_util import (Node, Call, Name, syms, Comma, Number)
import re
+def canonical_fix_name(fix, avail_fixes):
+ """
+ Examples:
+ >>> canonical_fix_name('fix_wrap_text_literals')
+ 'libfuturize.fixes.fix_wrap_text_literals'
+ >>> canonical_fix_name('wrap_text_literals')
+ 'libfuturize.fixes.fix_wrap_text_literals'
+ >>> canonical_fix_name('wrap_te')
+ ValueError("unknown fixer name")
+ >>> canonical_fix_name('wrap')
+ ValueError("ambiguous fixer name")
+ """
+ if ".fix_" in fix:
+ return fix
+ else:
+ if fix.startswith('fix_'):
+ fix = fix[4:]
+ # Infer the full module name for the fixer.
+ # First ensure that no names clash (e.g.
+ # lib2to3.fixes.fix_blah and libfuturize.fixes.fix_blah):
+ found = [f for f in avail_fixes
+ if f.endswith('fix_{0}'.format(fix))]
+ if len(found) > 1:
+ raise ValueError("Ambiguous fixer name. Choose a fully qualified "
+ "module name instead from these:\n" +
+ "\n".join(" " + myf for myf in found))
+ elif len(found) == 0:
+ raise ValueError("Unknown fixer. Use --list-fixes or -l for a list.")
+ return found[0]
+
+
+
## These functions are from 3to2 by Joe Amenta:
def Star(prefix=None):
@@ -29,8 +61,8 @@ def Minus(prefix=None):
return Leaf(token.MINUS, u'-', prefix=prefix)
def commatize(leafs):
- u"""
- Accepts/turns: (Name, Name, ..., Name, Name)
+ """
+ Accepts/turns: (Name, Name, ..., Name, Name)
Returns/into: (Name, Comma, Name, Comma, ..., Name, Comma, Name)
"""
new_leafs = []
@@ -41,7 +73,7 @@ def commatize(leafs):
return new_leafs
def indentation(node):
- u"""
+ """
Returns the indentation for this node
Iff a node is in a suite, then it has indentation.
"""
@@ -62,7 +94,7 @@ def indentation(node):
return node.prefix
def indentation_step(node):
- u"""
+ """
Dirty little trick to get the difference between each indentation level
Implemented by finding the shortest indentation string
(technically, the "least" of all of the indentation strings, but
@@ -78,13 +110,13 @@ def indentation_step(node):
return min(all_indents)
def suitify(parent):
- u"""
+ """
Turn the stuff after the first colon in parent's children
into a suite, if it wasn't already
"""
for node in parent.children:
if node.type == syms.suite:
- # already in the prefered format, do nothing
+ # already in the preferred format, do nothing
return
# One-liners have no suite node, we have to fake one up
@@ -102,7 +134,7 @@ def suitify(parent):
parent.append_child(suite)
def NameImport(package, as_name=None, prefix=None):
- u"""
+ """
Accepts a package (Name node), name to import it as (string), and
optional prefix and returns a node:
import [as ]
@@ -119,7 +151,7 @@ def NameImport(package, as_name=None, prefix=None):
_import_stmts = (syms.import_name, syms.import_from)
def import_binding_scope(node):
- u"""
+ """
Generator yields all nodes for which a node (an import_stmt) has scope
The purpose of this is for a call to _find() on each of them
"""
@@ -187,6 +219,14 @@ def ImportAsName(name, as_name, prefix=None):
return new_node
+def is_docstring(node):
+ """
+ Returns True if the node appears to be a docstring
+ """
+ return (node.type == syms.simple_stmt and
+ len(node.children) > 0 and node.children[0].type == token.STRING)
+
+
def future_import(feature, node):
"""
This seems to work
@@ -196,15 +236,14 @@ def future_import(feature, node):
if does_tree_import(u"__future__", feature, node):
return
- # Look for a shebang line
- shebang_idx = None
+ # Look for a shebang or encoding line
+ shebang_encoding_idx = None
for idx, node in enumerate(root.children):
- # If it's a shebang line, attach the prefix to
- if is_shebang_comment(node):
- shebang_idx = idx
- if node.type == syms.simple_stmt and \
- len(node.children) > 0 and node.children[0].type == token.STRING:
+ # Is it a shebang or encoding line?
+ if is_shebang_comment(node) or is_encoding_comment(node):
+ shebang_encoding_idx = idx
+ if is_docstring(node):
# skip over docstring
continue
names = check_future_import(node)
@@ -216,13 +255,15 @@ def future_import(feature, node):
return
import_ = FromImport(u'__future__', [Leaf(token.NAME, feature, prefix=" ")])
- if shebang_idx == 0 and idx == 0:
+ if shebang_encoding_idx == 0 and idx == 0:
# If this __future__ import would go on the first line,
- # detach the shebang prefix from the current first line
+ # detach the shebang / encoding prefix from the current first line.
# and attach it to our new __future__ import node.
import_.prefix = root.children[0].prefix
root.children[0].prefix = u''
- children = [import_, Newline()]
+ # End the __future__ import line with a newline and add a blank line
+ # afterwards:
+ children = [import_ , Newline()]
root.insert_child(idx, Node(syms.simple_stmt, children))
@@ -231,7 +272,7 @@ def future_import2(feature, node):
An alternative to future_import() which might not work ...
"""
root = find_root(node)
-
+
if does_tree_import(u"__future__", feature, node):
return
@@ -263,7 +304,7 @@ def parse_args(arglist, scheme):
Parse a list of arguments into a dict
"""
arglist = [i for i in arglist if i.type != token.COMMA]
-
+
ret_mapping = dict([(k, None) for k in scheme])
for i, arg in enumerate(arglist):
@@ -291,12 +332,13 @@ def is_import_stmt(node):
def touch_import_top(package, name_to_import, node):
"""Works like `does_tree_import` but adds an import statement at the
- top if it was not imported (but below any __future__ imports).
+ top if it was not imported (but below any __future__ imports) and below any
+ comments such as shebang lines).
Based on lib2to3.fixer_util.touch_import()
Calling this multiple times adds the imports in reverse order.
-
+
Also adds "standard_library.install_aliases()" after "from future import
standard_library". This should probably be factored into another function.
"""
@@ -343,11 +385,12 @@ def touch_import_top(package, name_to_import, node):
for idx, node in enumerate(root.children):
if node.type != syms.simple_stmt:
break
- if not (node.children and node.children[0].type == token.STRING):
+ if not is_docstring(node):
# This is the usual case.
break
insert_pos = idx
+ children_hooks = []
if package is None:
import_ = Node(syms.import_name, [
Leaf(token.NAME, u"import"),
@@ -371,13 +414,13 @@ def touch_import_top(package, name_to_import, node):
]
)
children_hooks = [install_hooks, Newline()]
- else:
- children_hooks = []
-
- FromImport(package, [Leaf(token.NAME, name_to_import, prefix=u" ")])
+
+ # FromImport(package, [Leaf(token.NAME, name_to_import, prefix=u" ")])
children_import = [import_, Newline()]
- root.insert_child(insert_pos, Node(syms.simple_stmt, children_import))
+ old_prefix = root.children[insert_pos].prefix
+ root.children[insert_pos].prefix = u''
+ root.insert_child(insert_pos, Node(syms.simple_stmt, children_import, prefix=old_prefix))
if len(children_hooks) > 0:
root.insert_child(insert_pos + 1, Node(syms.simple_stmt, children_hooks))
@@ -399,9 +442,11 @@ def check_future_import(node):
hasattr(node.children[1], 'value') and
node.children[1].value == u'__future__'):
return set()
- node = node.children[3]
+ if node.children[3].type == token.LPAR:
+ node = node.children[4]
+ else:
+ node = node.children[3]
# now node is the import_as_name[s]
- # print(python_grammar.number2symbol[node.type]) # breaks sometimes
if node.type == syms.import_as_names:
result = set()
for n in node.children:
@@ -424,16 +469,34 @@ def check_future_import(node):
assert False, "strange import: %s" % savenode
-SHEBANG_REGEX = r'^#!\s*.*python'
+SHEBANG_REGEX = r'^#!.*python'
+ENCODING_REGEX = r"^#.*coding[:=]\s*([-\w.]+)"
+
def is_shebang_comment(node):
"""
Comments are prefixes for Leaf nodes. Returns whether the given node has a
- prefix that looks like a shebang line.
+ prefix that looks like a shebang line or an encoding line:
+
+ #!/usr/bin/env python
+ #!/usr/bin/python3
"""
return bool(re.match(SHEBANG_REGEX, node.prefix))
+def is_encoding_comment(node):
+ """
+ Comments are prefixes for Leaf nodes. Returns whether the given node has a
+ prefix that looks like an encoding line:
+
+ # coding: utf-8
+ # encoding: utf-8
+ # -*- coding: -*-
+ # vim: set fileencoding= :
+ """
+ return bool(re.match(ENCODING_REGEX, node.prefix))
+
+
def wrap_in_fn_call(fn_name, args, prefix=None):
"""
Example:
@@ -442,15 +505,14 @@ def wrap_in_fn_call(fn_name, args, prefix=None):
>>> wrap_in_fn_call("olddiv", (arg1, arg2))
olddiv(arg1, arg2)
+
+ >>> wrap_in_fn_call("olddiv", [arg1, comma, arg2, comma, arg3])
+ olddiv(arg1, arg2, arg3)
"""
assert len(args) > 0
- if len(args) == 1:
- newargs = args
- elif len(args) == 2:
+ if len(args) == 2:
expr1, expr2 = args
newargs = [expr1, Comma(), expr2]
else:
- assert NotImplementedError('write me')
+ newargs = args
return Call(Name(fn_name), newargs, prefix=prefix)
-
-
diff --git a/src/libfuturize/fixes/__init__.py b/src/libfuturize/fixes/__init__.py
index 556abe8b..0b562501 100644
--- a/src/libfuturize/fixes/__init__.py
+++ b/src/libfuturize/fixes/__init__.py
@@ -8,6 +8,7 @@
lib2to3_fix_names_stage1 = set([
'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',
@@ -40,18 +41,16 @@
# The following fixers add a dependency on the ``future`` package on order to
# support Python 2:
lib2to3_fix_names_stage2 = set([
- 'lib2to3.fixes.fix_basestring',
# 'lib2to3.fixes.fix_buffer', # perhaps not safe. Test this.
# 'lib2to3.fixes.fix_callable', # not needed in Py3.2+
'lib2to3.fixes.fix_dict', # TODO: add support for utils.viewitems() etc. and move to stage2
- 'lib2to3.fixes.fix_exec',
# 'lib2to3.fixes.fix_execfile', # some problems: see issue #37.
# We use a custom fixer instead (see below)
# 'lib2to3.fixes.fix_future', # we don't want to remove __future__ imports
'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',
@@ -79,6 +78,7 @@
])
libfuturize_fix_names_stage2 = set([
+ 'libfuturize.fixes.fix_basestring',
# 'libfuturize.fixes.fix_add__future__imports_except_unicode_literals', # just in case
'libfuturize.fixes.fix_cmp',
'libfuturize.fixes.fix_division_safe',
@@ -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',
@@ -94,4 +95,3 @@
# 'libfuturize.fixes.fix_unicode_literals_import',
'libfuturize.fixes.fix_xrange_with_import', # custom one because of a bug with Py3.3's lib2to3
])
-
diff --git a/src/libfuturize/fixes/fix_UserDict.py b/src/libfuturize/fixes/fix_UserDict.py
index d028b316..cb0cfacc 100644
--- a/src/libfuturize/fixes/fix_UserDict.py
+++ b/src/libfuturize/fixes/fix_UserDict.py
@@ -16,12 +16,12 @@
# def alternates(members):
# return "(" + "|".join(map(repr, members)) + ")"
-#
-#
+#
+#
# def build_pattern(mapping=MAPPING):
# mod_list = ' | '.join(["module_name='%s'" % key for key in mapping])
# bare_names = alternates(mapping.keys())
-#
+#
# yield """name_import=import_name< 'import' ((%s) |
# multiple_imports=dotted_as_names< any* (%s) any* >) >
# """ % (mod_list, mod_list)
@@ -33,7 +33,7 @@
# multiple_imports=dotted_as_names<
# any* dotted_as_name< (%s) 'as' any > any* >) >
# """ % (mod_list, mod_list)
-#
+#
# # Find usages of module members in code e.g. thread.foo(bar)
# yield "power< bare_with_attr=(%s) trailer<'.' any > any* >" % bare_names
@@ -96,9 +96,7 @@ def transform(self, node, results):
self.transform(node, results)
else:
# Replace usage of the module.
- import pdb; pdb.set_trace()
bare_name = results["bare_with_attr"][0]
new_name = self.replace.get(bare_name.value)
if new_name:
bare_name.replace(Name(new_name, prefix=bare_name.prefix))
-
diff --git a/src/libfuturize/fixes/fix_absolute_import.py b/src/libfuturize/fixes/fix_absolute_import.py
index ab6a7647..eab9c527 100644
--- a/src/libfuturize/fixes/fix_absolute_import.py
+++ b/src/libfuturize/fixes/fix_absolute_import.py
@@ -89,4 +89,3 @@ def probably_a_local_import(self, imp_name):
if exists(base_path + ext):
return True
return False
-
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 1904d37b..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,7 +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_basestring.py b/src/libfuturize/fixes/fix_basestring.py
new file mode 100644
index 00000000..5676d08f
--- /dev/null
+++ b/src/libfuturize/fixes/fix_basestring.py
@@ -0,0 +1,17 @@
+"""
+Fixer that adds ``from past.builtins import basestring`` if there is a
+reference to ``basestring``
+"""
+
+from lib2to3 import fixer_base
+
+from libfuturize.fixer_util import touch_import_top
+
+
+class FixBasestring(fixer_base.BaseFix):
+ BM_compatible = True
+
+ PATTERN = "'basestring'"
+
+ def transform(self, node, results):
+ touch_import_top(u'past.builtins', 'basestring', node)
diff --git a/src/libfuturize/fixes/fix_cmp.py b/src/libfuturize/fixes/fix_cmp.py
index be56507e..762eb4b4 100644
--- a/src/libfuturize/fixes/fix_cmp.py
+++ b/src/libfuturize/fixes/fix_cmp.py
@@ -31,4 +31,3 @@ class FixCmp(fixer_base.BaseFix):
def transform(self, node, results):
name = results["name"]
touch_import_top(u'past.builtins', name.value, node)
-
diff --git a/src/libfuturize/fixes/fix_division.py b/src/libfuturize/fixes/fix_division.py
index 48745504..6975a52b 100644
--- a/src/libfuturize/fixes/fix_division.py
+++ b/src/libfuturize/fixes/fix_division.py
@@ -10,4 +10,3 @@
"""
from libpasteurize.fixes.fix_division import FixDivision
-
diff --git a/src/libfuturize/fixes/fix_division_safe.py b/src/libfuturize/fixes/fix_division_safe.py
index 5e16b0a5..65c8c1da 100644
--- a/src/libfuturize/fixes/fix_division_safe.py
+++ b/src/libfuturize/fixes/fix_division_safe.py
@@ -13,8 +13,9 @@
nothing.
"""
+import re
+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)
@@ -28,6 +29,25 @@ def match_division(node):
return node.type == slash and not node.next_sibling.type == slash and \
not node.prev_sibling.type == slash
+const_re = re.compile('^[0-9]*[.][0-9]*$')
+
+def is_floaty(node):
+ return _is_floaty(node.prev_sibling) or _is_floaty(node.next_sibling)
+
+
+def _is_floaty(expr):
+ if isinstance(expr, list):
+ expr = expr[0]
+
+ if isinstance(expr, Leaf):
+ # If it's a leaf, let's see if it's a numeric constant containing a '.'
+ return const_re.match(expr.value)
+ elif isinstance(expr, Node):
+ # If the expression is a node, let's see if it's a direct cast to float
+ if isinstance(expr.children[0], Leaf):
+ return expr.children[0].value == u'float'
+ return False
+
class FixDivisionSafe(fixer_base.BaseFix):
# BM_compatible = True
@@ -51,22 +71,39 @@ def match(self, node):
Since the tree needs to be fixed once and only once if and only if it
matches, we can start discarding matches after the first.
"""
- if (node.type == self.syms.term and
- len(node.children) == 3 and
- match_division(node.children[1])):
- expr1, expr2 = node.children[0], node.children[2]
- return expr1, expr2
- else:
- return False
+ if node.type == self.syms.term:
+ 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):
if self.skip:
return
future_import(u"division", node)
-
touch_import_top(u'past.utils', u'old_div', node)
- expr1, expr2 = results[0].clone(), results[1].clone()
- # Strip any leading space for the first number:
- expr1.prefix = u''
- return wrap_in_fn_call("old_div", (expr1, expr2), prefix=node.prefix)
-
+ return results
diff --git a/src/libfuturize/fixes/fix_execfile.py b/src/libfuturize/fixes/fix_execfile.py
index 2b794c88..cfe9d8d0 100644
--- a/src/libfuturize/fixes/fix_execfile.py
+++ b/src/libfuturize/fixes/fix_execfile.py
@@ -35,4 +35,3 @@ class FixExecfile(fixer_base.BaseFix):
def transform(self, node, results):
name = results["name"]
touch_import_top(u'past.builtins', name.value, node)
-
diff --git a/src/libfuturize/fixes/fix_future_builtins.py b/src/libfuturize/fixes/fix_future_builtins.py
index bf3aba40..eea6c6a1 100644
--- a/src/libfuturize/fixes/fix_future_builtins.py
+++ b/src/libfuturize/fixes/fix_future_builtins.py
@@ -57,4 +57,3 @@ def transform(self, node, results):
name = results["name"]
touch_import_top(u'builtins', name.value, node)
# name.replace(Name(u"input", prefix=name.prefix))
-
diff --git a/src/libfuturize/fixes/fix_future_standard_library.py b/src/libfuturize/fixes/fix_future_standard_library.py
index 501c2a94..a1c3f3d4 100644
--- a/src/libfuturize/fixes/fix_future_standard_library.py
+++ b/src/libfuturize/fixes/fix_future_standard_library.py
@@ -22,5 +22,3 @@ def transform(self, node, results):
# TODO: add a blank line between any __future__ imports and this?
touch_import_top(u'future', u'standard_library', node)
return result
-
-
diff --git a/src/libfuturize/fixes/fix_future_standard_library_urllib.py b/src/libfuturize/fixes/fix_future_standard_library_urllib.py
index 3d62959f..cf673884 100644
--- a/src/libfuturize/fixes/fix_future_standard_library_urllib.py
+++ b/src/libfuturize/fixes/fix_future_standard_library_urllib.py
@@ -26,5 +26,3 @@ def transform(self, node, results):
# TODO: add a blank line between any __future__ imports and this?
touch_import_top(u'future', u'standard_library', root)
return result
-
-
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_metaclass.py b/src/libfuturize/fixes/fix_metaclass.py
index a917341d..a7eee40d 100644
--- a/src/libfuturize/fixes/fix_metaclass.py
+++ b/src/libfuturize/fixes/fix_metaclass.py
@@ -37,7 +37,7 @@
def has_metaclass(parent):
""" we have to check the cls_node without changing it.
- There are two possiblities:
+ There are two possibilities:
1) clsdef => suite => simple_stmt => expr_stmt => Leaf('__meta')
2) clsdef => simple_stmt => expr_stmt => Leaf('__meta')
"""
@@ -63,7 +63,7 @@ def fixup_parse_tree(cls_node):
# already in the preferred format, do nothing
return
- # !%@#! oneliners have no suite node, we have to fake one up
+ # !%@#! one-liners have no suite node, we have to fake one up
for i, node in enumerate(cls_node.children):
if node.type == token.COLON:
break
@@ -201,6 +201,11 @@ def transform(self, node, results):
else:
raise ValueError("Unexpected class definition")
+ # now stick the metaclass in the arglist
+ meta_txt = last_metaclass.children[0].children[0]
+ meta_txt.value = 'metaclass'
+ orig_meta_prefix = meta_txt.prefix
+
# Was: touch_import(None, u'future.utils', node)
touch_import(u'future.utils', u'with_metaclass', node)
diff --git a/src/libfuturize/fixes/fix_oldstr_wrap.py b/src/libfuturize/fixes/fix_oldstr_wrap.py
index 4258f6aa..ad58771d 100644
--- a/src/libfuturize/fixes/fix_oldstr_wrap.py
+++ b/src/libfuturize/fixes/fix_oldstr_wrap.py
@@ -27,8 +27,6 @@ class FixOldstrWrap(fixer_base.BaseFix):
PATTERN = "STRING"
def transform(self, node, results):
- import pdb
- pdb.set_trace()
if node.type == token.STRING:
touch_import_top(u'past.types', u'oldstr', node)
if _literal_re.match(node.value):
@@ -39,42 +37,3 @@ def transform(self, node, results):
new.value = u'b' + new.value
wrapped = wrap_in_fn_call("oldstr", [new], prefix=node.prefix)
return wrapped
-
- def transform(self, node, results):
- expr1, expr2 = results[0].clone(), results[1].clone()
- # Strip any leading space for the first number:
- expr1.prefix = u''
- return wrap_in_fn_call("old_div", expr1, expr2, prefix=node.prefix)
-
-
-class FixDivisionSafe(fixer_base.BaseFix):
- # BM_compatible = True
- run_order = 4 # this seems to be ignored?
-
- _accept_type = token.SLASH
-
- PATTERN = """
- term<(not('/') any)+ '/' ((not('/') any))>
- """
-
- def match(self, node):
- u"""
- Since the tree needs to be fixed once and only once if and only if it
- matches, then we can start discarding matches after we make the first.
- """
- if (node.type == self.syms.term and
- len(node.children) == 3 and
- match_division(node.children[1])):
- expr1, expr2 = node.children[0], node.children[2]
- return expr1, expr2
- else:
- return False
-
- def transform(self, node, results):
- future_import(u"division", node)
- touch_import_top(u'past.utils', u'old_div', node)
- expr1, expr2 = results[0].clone(), results[1].clone()
- # Strip any leading space for the first number:
- expr1.prefix = u''
- return wrap_in_fn_call("old_div", expr1, expr2, prefix=node.prefix)
-
diff --git a/src/libfuturize/fixes/fix_order___future__imports.py b/src/libfuturize/fixes/fix_order___future__imports.py
index 120665a4..00d7ef60 100644
--- a/src/libfuturize/fixes/fix_order___future__imports.py
+++ b/src/libfuturize/fixes/fix_order___future__imports.py
@@ -28,12 +28,9 @@ class FixOrderFutureImports(fixer_base.BaseFix):
# Match only once per file
# """
# if hasattr(node, 'type') and node.type == syms.file_input:
- # import pdb
- # pdb.set_trace()
# return True
# return False
def transform(self, node, results):
# TODO # write me
pass
-
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_print_with_import.py b/src/libfuturize/fixes/fix_print_with_import.py
index 57986977..34490461 100644
--- a/src/libfuturize/fixes/fix_print_with_import.py
+++ b/src/libfuturize/fixes/fix_print_with_import.py
@@ -14,7 +14,9 @@
class FixPrintWithImport(FixPrint):
run_order = 7
def transform(self, node, results):
- n_stmt = super(FixPrintWithImport, self).transform(node, results)
+ # Add the __future__ import first. (Otherwise any shebang or encoding
+ # comment line attached as a prefix to the print statement will be
+ # copied twice and appear twice.)
future_import(u'print_function', node)
+ n_stmt = super(FixPrintWithImport, self).transform(node, results)
return n_stmt
-
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/libfuturize/fixes/fix_remove_old__future__imports.py b/src/libfuturize/fixes/fix_remove_old__future__imports.py
index 060eb004..9336f75f 100644
--- a/src/libfuturize/fixes/fix_remove_old__future__imports.py
+++ b/src/libfuturize/fixes/fix_remove_old__future__imports.py
@@ -24,4 +24,3 @@ def transform(self, node, results):
remove_future_import(u"with_statement", node)
remove_future_import(u"nested_scopes", node)
remove_future_import(u"generators", node)
-
diff --git a/src/libfuturize/fixes/fix_unicode_keep_u.py b/src/libfuturize/fixes/fix_unicode_keep_u.py
index a6f70f09..2e9a4e47 100644
--- a/src/libfuturize/fixes/fix_unicode_keep_u.py
+++ b/src/libfuturize/fixes/fix_unicode_keep_u.py
@@ -22,4 +22,3 @@ def transform(self, node, results):
new = node.clone()
new.value = _mapping[node.value]
return new
-
diff --git a/src/libfuturize/fixes/fix_unicode_literals_import.py b/src/libfuturize/fixes/fix_unicode_literals_import.py
index 9f21d7c6..51c50620 100644
--- a/src/libfuturize/fixes/fix_unicode_literals_import.py
+++ b/src/libfuturize/fixes/fix_unicode_literals_import.py
@@ -1,6 +1,6 @@
"""
Adds this import:
-
+
from __future__ import unicode_literals
"""
@@ -16,4 +16,3 @@ class FixUnicodeLiteralsImport(fixer_base.BaseFix):
def transform(self, node, results):
future_import(u"unicode_literals", node)
-
diff --git a/src/libfuturize/main.py b/src/libfuturize/main.py
index b0bd77bd..634c2f25 100644
--- a/src/libfuturize/main.py
+++ b/src/libfuturize/main.py
@@ -1,5 +1,5 @@
"""
-futurize: automatic conversion to clean 2&3 code using ``python-future``
+futurize: automatic conversion to clean 2/3 code using ``python-future``
======================================================================
Like Armin Ronacher's modernize.py, ``futurize`` attempts to produce clean
@@ -14,7 +14,7 @@
This will attempt to port the code to standard Py3 code that also
provides Py2 compatibility with the help of the right imports from
-``future``. To write the changes to disk, use the -w flag.
+``future``.
To write changes to the files, use the -w flag.
@@ -62,8 +62,6 @@
"""
from __future__ import (absolute_import, print_function, unicode_literals)
-from future.builtins import *
-import future
import future.utils
from future import __version__
@@ -72,7 +70,7 @@
import optparse
import os
-from lib2to3.main import main, warn, StdoutRefactoringTool
+from lib2to3.main import warn, StdoutRefactoringTool
from lib2to3 import refactor
from libfuturize.fixes import (lib2to3_fix_names_stage1,
@@ -93,7 +91,7 @@ def main(args=None):
Returns a suggested exit status (0, 1, 2).
"""
-
+
# Set up option parser
parser = optparse.OptionParser(usage="futurize [options] file|dir ...")
parser.add_option("-V", "--version", action="store_true",
@@ -207,7 +205,27 @@ def main(args=None):
print("Use --help to show usage.", file=sys.stderr)
return 2
- unwanted_fixes = set(fixer_pkg + ".fix_" + fix for fix in options.nofix)
+ unwanted_fixes = set()
+ for fix in options.nofix:
+ if ".fix_" in fix:
+ unwanted_fixes.add(fix)
+ else:
+ # Infer the full module name for the fixer.
+ # First ensure that no names clash (e.g.
+ # lib2to3.fixes.fix_blah and libfuturize.fixes.fix_blah):
+ found = [f for f in avail_fixes
+ if f.endswith('fix_{0}'.format(fix))]
+ if len(found) > 1:
+ print("Ambiguous fixer name. Choose a fully qualified "
+ "module name instead from these:\n" +
+ "\n".join(" " + myf for myf in found),
+ file=sys.stderr)
+ return 2
+ elif len(found) == 0:
+ print("Unknown fixer. Use --list-fixes or -l for a list.",
+ file=sys.stderr)
+ return 2
+ unwanted_fixes.add(found[0])
extra_fixes = set()
if options.all_imports:
diff --git a/src/libpasteurize/fixes/__init__.py b/src/libpasteurize/fixes/__init__.py
index c362ada2..905aec47 100644
--- a/src/libpasteurize/fixes/__init__.py
+++ b/src/libpasteurize/fixes/__init__.py
@@ -52,4 +52,3 @@
'libpasteurize.fixes.fix_unpacking', # yes, this is useful
# 'libpasteurize.fixes.fix_with' # way out of date
])
-
diff --git a/src/libpasteurize/fixes/feature_base.py b/src/libpasteurize/fixes/feature_base.py
index 8a264964..c36d9a95 100644
--- a/src/libpasteurize/fixes/feature_base.py
+++ b/src/libpasteurize/fixes/feature_base.py
@@ -40,7 +40,7 @@ def update_mapping(self):
Called every time we care about the mapping of names to features.
"""
self.mapping = dict([(f.name, f) for f in iter(self)])
-
+
@property
def PATTERN(self):
u"""
diff --git a/src/libpasteurize/fixes/fix_add_all__future__imports.py b/src/libpasteurize/fixes/fix_add_all__future__imports.py
index 32f89ec1..a151f9f1 100644
--- a/src/libpasteurize/fixes/fix_add_all__future__imports.py
+++ b/src/libpasteurize/fixes/fix_add_all__future__imports.py
@@ -18,8 +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/libpasteurize/fixes/fix_add_all_future_builtins.py b/src/libpasteurize/fixes/fix_add_all_future_builtins.py
index 97204b58..22911bad 100644
--- a/src/libpasteurize/fixes/fix_add_all_future_builtins.py
+++ b/src/libpasteurize/fixes/fix_add_all_future_builtins.py
@@ -35,4 +35,3 @@ def transform(self, node, results):
# range round str super zip"""
# for builtin in sorted(builtins.split(), reverse=True):
# touch_import_top(u'builtins', builtin, node)
-
diff --git a/src/libpasteurize/fixes/fix_annotations.py b/src/libpasteurize/fixes/fix_annotations.py
index 1926288c..884b6741 100644
--- a/src/libpasteurize/fixes/fix_annotations.py
+++ b/src/libpasteurize/fixes/fix_annotations.py
@@ -19,7 +19,7 @@ def warn_once(self, node, reason):
if not self.warned:
self.warned = True
self.warning(node, reason=reason)
-
+
PATTERN = u"""
funcdef< 'def' any parameters< '(' [params=any] ')' > ['->' ret=any] ':' any* >
"""
diff --git a/src/libpasteurize/fixes/fix_features.py b/src/libpasteurize/fixes/fix_features.py
index 7e5f545a..52630f98 100644
--- a/src/libpasteurize/fixes/fix_features.py
+++ b/src/libpasteurize/fixes/fix_features.py
@@ -71,7 +71,7 @@ def match(self, node):
# if it's there, so we don't care if it fails for normal reasons.
pass
return to_ret
-
+
def transform(self, node, results):
for feature_name in results:
if feature_name in self.features_warned:
diff --git a/src/libpasteurize/fixes/fix_fullargspec.py b/src/libpasteurize/fixes/fix_fullargspec.py
index 489295f7..4bd37e15 100644
--- a/src/libpasteurize/fixes/fix_fullargspec.py
+++ b/src/libpasteurize/fixes/fix_fullargspec.py
@@ -8,7 +8,7 @@
warn_msg = u"some of the values returned by getfullargspec are not valid in Python 2 and have no equivalent."
class FixFullargspec(fixer_base.BaseFix):
-
+
PATTERN = u"'getfullargspec'"
def transform(self, node, results):
diff --git a/src/libpasteurize/fixes/fix_future_builtins.py b/src/libpasteurize/fixes/fix_future_builtins.py
index 27339abc..68496799 100644
--- a/src/libpasteurize/fixes/fix_future_builtins.py
+++ b/src/libpasteurize/fixes/fix_future_builtins.py
@@ -44,4 +44,3 @@ def transform(self, node, results):
name = results["name"]
touch_import_top(u'builtins', name.value, node)
# name.replace(Name(u"input", prefix=name.prefix))
-
diff --git a/src/libpasteurize/fixes/fix_imports.py b/src/libpasteurize/fixes/fix_imports.py
index d79558ce..b18ecf3d 100644
--- a/src/libpasteurize/fixes/fix_imports.py
+++ b/src/libpasteurize/fixes/fix_imports.py
@@ -16,6 +16,7 @@
u"winreg": u"_winreg",
u"configparser": u"ConfigParser",
u"copyreg": u"copy_reg",
+ u"multiprocessing.SimpleQueue": u"multiprocessing.queues.SimpleQueue",
u"queue": u"Queue",
u"socketserver": u"SocketServer",
u"_markupbase": u"markupbase",
@@ -40,6 +41,7 @@
u"tkinter.colorchooser": u"tkColorChooser",
u"tkinter.commondialog": u"tkCommonDialog",
u"tkinter.font": u"tkFont",
+ u"tkinter.ttk": u"ttk",
u"tkinter.messagebox": u"tkMessageBox",
u"tkinter.turtle": u"turtle",
u"urllib.robotparser": u"robotparser",
@@ -109,4 +111,3 @@ class FixImports(fixer_base.BaseFix):
def transform(self, node, results):
touch_import_top(u'future', u'standard_library', node)
-
diff --git a/src/libpasteurize/fixes/fix_imports2.py b/src/libpasteurize/fixes/fix_imports2.py
index 5b30b5f5..70444e9e 100644
--- a/src/libpasteurize/fixes/fix_imports2.py
+++ b/src/libpasteurize/fixes/fix_imports2.py
@@ -18,11 +18,11 @@
u'RADIOBUTTON', u'RAISED', u'READABLE', u'RIDGE', u'RIGHT',
u'ROUND', u'S', u'SCROLL', u'SE', u'SEL', u'SEL_FIRST', u'SEL_LAST',
u'SEPARATOR', u'SINGLE', u'SOLID', u'SUNKEN', u'SW', u'StringTypes',
- u'TOP', u'TRUE', u'TclVersion', u'TkVersion', u'UNDERLINE',
+ u'TOP', u'TRUE', u'TclVersion', u'TkVersion', u'UNDERLINE',
u'UNITS', u'VERTICAL', u'W', u'WORD', u'WRITABLE', u'X', u'Y', u'YES',
u'wantobjects')
-PY2MODULES = {
+PY2MODULES = {
u'urllib2' : (
u'AbstractBasicAuthHandler', u'AbstractDigestAuthHandler',
u'AbstractHTTPHandler', u'BaseHandler', u'CacheFTPHandler',
@@ -172,4 +172,3 @@ class FixImports2(fixer_base.BaseFix):
def transform(self, node, results):
touch_import_top(u'future', u'standard_library', node)
-
diff --git a/src/libpasteurize/fixes/fix_kwargs.py b/src/libpasteurize/fixes/fix_kwargs.py
index 59a3043b..290f991e 100644
--- a/src/libpasteurize/fixes/fix_kwargs.py
+++ b/src/libpasteurize/fixes/fix_kwargs.py
@@ -61,7 +61,7 @@ def remove_params(raw_params, kwargs_default=_kwargs_default_name):
return False
else:
return True
-
+
def needs_fixing(raw_params, kwargs_default=_kwargs_default_name):
u"""
Returns string with the name of the kwargs dict if the params after the first star need fixing
@@ -145,4 +145,3 @@ def transform(self, node, results):
arglist.append_child(Comma())
arglist.append_child(DoubleStar(prefix=u" "))
arglist.append_child(Name(new_kwargs))
-
diff --git a/src/libpasteurize/fixes/fix_metaclass.py b/src/libpasteurize/fixes/fix_metaclass.py
index 5e6e64d8..52dd1d14 100644
--- a/src/libpasteurize/fixes/fix_metaclass.py
+++ b/src/libpasteurize/fixes/fix_metaclass.py
@@ -61,7 +61,7 @@ def transform(self, node, results):
name = meta
name.prefix = u" "
stmt_node = Node(syms.atom, [target, equal, name])
-
+
suitify(node)
for item in node.children:
if item.type == syms.suite:
diff --git a/src/libpasteurize/fixes/fix_unpacking.py b/src/libpasteurize/fixes/fix_unpacking.py
index 1e53a9bf..6e839e6b 100644
--- a/src/libpasteurize/fixes/fix_unpacking.py
+++ b/src/libpasteurize/fixes/fix_unpacking.py
@@ -18,8 +18,12 @@ def assignment_source(num_pre, num_post, LISTNAME, ITERNAME):
Returns a source fit for Assign() from fixer_util
"""
children = []
- pre = unicode(num_pre)
- post = unicode(num_post)
+ try:
+ pre = unicode(num_pre)
+ post = unicode(num_post)
+ except NameError:
+ pre = str(num_pre)
+ post = str(num_post)
# This code builds the assignment source from lib2to3 tree primitives.
# It's not very readable, but it seems like the most correct way to do it.
if num_pre > 0:
@@ -60,7 +64,7 @@ def fix_explicit_context(self, node, results):
setup_line = Assign(Name(self.LISTNAME), Call(Name(u"list"), [source.clone()]))
power_line = Assign(target, assignment_source(len(pre), len(post), self.LISTNAME, self.ITERNAME))
return setup_line, power_line
-
+
def fix_implicit_context(self, node, results):
u"""
Only example of the implicit context is
diff --git a/src/libpasteurize/main.py b/src/libpasteurize/main.py
index b0c2ce53..4179174b 100644
--- a/src/libpasteurize/main.py
+++ b/src/libpasteurize/main.py
@@ -1,5 +1,5 @@
"""
-pasteurize: automatic conversion of Python 3 code to clean 2&3 code
+pasteurize: automatic conversion of Python 3 code to clean 2/3 code
===================================================================
``pasteurize`` attempts to convert existing Python 3 code into source-compatible
@@ -44,9 +44,7 @@
from lib2to3.main import main, warn, StdoutRefactoringTool
from lib2to3 import refactor
-import future
from future import __version__
-from future.builtins import *
from libpasteurize.fixes import fix_names
@@ -116,8 +114,27 @@ def main(args=None):
level = logging.DEBUG if options.verbose else logging.INFO
logging.basicConfig(format='%(name)s: %(message)s', level=level)
- # Initialize the refactoring tool
- unwanted_fixes = set(fixer_pkg + ".fix_" + fix for fix in options.nofix)
+ unwanted_fixes = set()
+ for fix in options.nofix:
+ if ".fix_" in fix:
+ unwanted_fixes.add(fix)
+ else:
+ # Infer the full module name for the fixer.
+ # First ensure that no names clash (e.g.
+ # lib2to3.fixes.fix_blah and libfuturize.fixes.fix_blah):
+ found = [f for f in avail_fixes
+ if f.endswith('fix_{0}'.format(fix))]
+ if len(found) > 1:
+ print("Ambiguous fixer name. Choose a fully qualified "
+ "module name instead from these:\n" +
+ "\n".join(" " + myf for myf in found),
+ file=sys.stderr)
+ return 2
+ elif len(found) == 0:
+ print("Unknown fixer. Use --list-fixes or -l for a list.",
+ file=sys.stderr)
+ return 2
+ unwanted_fixes.add(found[0])
extra_fixes = set()
if options.all_imports:
@@ -126,8 +143,45 @@ def main(args=None):
extra_fixes.add(prefix + 'fix_add_future_standard_library_import')
extra_fixes.add(prefix + 'fix_add_all_future_builtins')
- fixer_names = avail_fixes | extra_fixes - unwanted_fixes
+ explicit = set()
+ if options.fix:
+ all_present = False
+ for fix in options.fix:
+ if fix == 'all':
+ all_present = True
+ else:
+ if ".fix_" in fix:
+ explicit.add(fix)
+ else:
+ # Infer the full module name for the fixer.
+ # First ensure that no names clash (e.g.
+ # lib2to3.fixes.fix_blah and libpasteurize.fixes.fix_blah):
+ found = [f for f in avail_fixes
+ if f.endswith('fix_{0}'.format(fix))]
+ if len(found) > 1:
+ print("Ambiguous fixer name. Choose a fully qualified "
+ "module name instead from these:\n" +
+ "\n".join(" " + myf for myf in found),
+ file=sys.stderr)
+ return 2
+ elif len(found) == 0:
+ print("Unknown fixer. Use --list-fixes or -l for a list.",
+ file=sys.stderr)
+ return 2
+ explicit.add(found[0])
+ if len(explicit & unwanted_fixes) > 0:
+ print("Conflicting usage: the following fixers have been "
+ "simultaneously requested and disallowed:\n" +
+ "\n".join(" " + myf for myf in (explicit & unwanted_fixes)),
+ file=sys.stderr)
+ return 2
+ requested = avail_fixes.union(explicit) if all_present else explicit
+ else:
+ requested = avail_fixes.union(explicit)
+
+ fixer_names = requested | extra_fixes - unwanted_fixes
+ # Initialize the refactoring tool
rt = StdoutRefactoringTool(sorted(fixer_names), flags, set(),
options.nobackups, not options.no_diffs)
@@ -148,4 +202,3 @@ def main(args=None):
# Return error status (0 if rt.errors is zero)
return int(bool(rt.errors))
-
diff --git a/src/past/__init__.py b/src/past/__init__.py
index b434acb9..54619e0a 100644
--- a/src/past/__init__.py
+++ b/src/past/__init__.py
@@ -61,35 +61,30 @@
$ python3
- >>> from past import autotranslate
+ >>> from past.translation import autotranslate
>>> authotranslate('mypy2module')
>>> import mypy2module
until the authors of the Python 2 modules have upgraded their code. Then, for
example::
-
+
>>> mypy2module.func_taking_py2_string(oldstr(b'abcd'))
Credits
-------
-:Author: Ed Schofield
-:Sponsor: Python Charmers Pty Ltd, Australia: http://pythoncharmers.com
+:Author: Ed Schofield, Jordan M. Adler, et al
+:Sponsor: Python Charmers: https://pythoncharmers.com
Licensing
---------
-Copyright 2013-2014 Python Charmers Pty Ltd, Australia.
+Copyright 2013-2024 Python Charmers, Australia.
The software is distributed under an MIT licence. See LICENSE.txt.
-
"""
-# from past.builtins import *
-
-from past.translation import install_hooks as autotranslate
from future import __version__, __copyright__, __license__
__title__ = 'past'
__author__ = 'Ed Schofield'
-
diff --git a/src/past/builtins/__init__.py b/src/past/builtins/__init__.py
index a967736d..1b19e373 100644
--- a/src/past/builtins/__init__.py
+++ b/src/past/builtins/__init__.py
@@ -59,9 +59,9 @@
if utils.PY3:
# We only import names that shadow the builtins on Py3. No other namespace
# pollution on Py3.
-
+
# Only shadow builtins on Py3; no new names
- __all__ = ['filter', 'map', 'range', 'reduce', 'zip',
+ __all__ = ['filter', 'map', 'range', 'reduce', 'zip',
'basestring', 'dict', 'str', 'long', 'unicode',
'apply', 'chr', 'cmp', 'execfile', 'intern', 'raw_input',
'reload', 'unichr', 'xrange'
diff --git a/src/past/builtins/misc.py b/src/past/builtins/misc.py
index 55dc63c6..0b8e6a98 100644
--- a/src/past/builtins/misc.py
+++ b/src/past/builtins/misc.py
@@ -1,13 +1,21 @@
from __future__ import unicode_literals
-import sys
+
import inspect
-from collections import Mapping
+import sys
+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 +33,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 +105,18 @@ def oct(number):
return '0' + builtins.oct(number)[2:]
raw_input = input
- from imp import reload
+ # imp was deprecated in python 3.6
+ if sys.version_info >= (3, 6):
+ from importlib import reload
+ else:
+ # 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 +149,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)
@@ -87,4 +160,3 @@ def execfile(filename, myglobals=None, mylocals=None):
'reload', 'unichr', 'unicode', 'xrange']
else:
__all__ = []
-
diff --git a/src/past/builtins/noniterators.py b/src/past/builtins/noniterators.py
index 66a4a8a5..183ffffd 100644
--- a/src/past/builtins/noniterators.py
+++ b/src/past/builtins/noniterators.py
@@ -6,7 +6,7 @@
And then, for example::
assert isinstance(range(5), list)
-
+
The list-producing functions this brings in are::
- ``filter``
@@ -19,7 +19,7 @@
from __future__ import division, absolute_import, print_function
-from itertools import chain, starmap
+from itertools import chain, starmap
import itertools # since zip_longest doesn't exist on Py2
from past.types import basestring
from past.utils import PY3
@@ -36,7 +36,7 @@ def flatmap(f, items):
def oldfilter(*args):
"""
filter(function or None, sequence) -> list, tuple, or string
-
+
Return those items of sequence for which function(item) is true.
If function is None, return the items that are true. If sequence
is a tuple or string, return the same type, else return a list.
@@ -56,7 +56,7 @@ def oldfilter(*args):
def oldmap(func, *iterables):
"""
map(function, sequence[, sequence, ...]) -> list
-
+
Return a list of the results of applying the function to the
items of the argument sequence(s). If more than one sequence is
given, the function is called with an argument list consisting of
@@ -64,7 +64,7 @@ def oldmap(func, *iterables):
missing values when not all sequences have the same length. If
the function is None, return a list of the items of the sequence
(or a list of tuples if more than one sequence).
-
+
Test cases:
>>> oldmap(None, 'hello world')
['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
@@ -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)
@@ -102,22 +102,22 @@ def oldmap(func, *iterables):
# PyObject *it; /* the iterator object */
# int saw_StopIteration; /* bool: did the iterator end? */
# } sequence;
- #
+ #
# PyObject *func, *result;
# sequence *seqs = NULL, *sqp;
# Py_ssize_t n, len;
# register int i, j;
- #
+ #
# n = PyTuple_Size(args);
# if (n < 2) {
# PyErr_SetString(PyExc_TypeError,
# "map() requires at least two args");
# return NULL;
# }
- #
+ #
# func = PyTuple_GetItem(args, 0);
# n--;
- #
+ #
# if (func == Py_None) {
# if (PyErr_WarnPy3k("map(None, ...) not supported in 3.x; "
# "use list(...)", 1) < 0)
@@ -127,7 +127,7 @@ def oldmap(func, *iterables):
# return PySequence_List(PyTuple_GetItem(args, 1));
# }
# }
- #
+ #
# /* Get space for sequence descriptors. Must NULL out the iterator
# * pointers so that jumping to Fail_2 later doesn't see trash.
# */
@@ -139,7 +139,7 @@ def oldmap(func, *iterables):
# seqs[i].it = (PyObject*)NULL;
# seqs[i].saw_StopIteration = 0;
# }
- #
+ #
# /* Do a first pass to obtain iterators for the arguments, and set len
# * to the largest of their lengths.
# */
@@ -147,7 +147,7 @@ def oldmap(func, *iterables):
# for (i = 0, sqp = seqs; i < n; ++i, ++sqp) {
# PyObject *curseq;
# Py_ssize_t curlen;
- #
+ #
# /* Get iterator. */
# curseq = PyTuple_GetItem(args, i+1);
# sqp->it = PyObject_GetIter(curseq);
@@ -159,27 +159,27 @@ def oldmap(func, *iterables):
# PyErr_SetString(PyExc_TypeError, errbuf);
# goto Fail_2;
# }
- #
+ #
# /* Update len. */
# curlen = _PyObject_LengthHint(curseq, 8);
# if (curlen > len)
# len = curlen;
# }
- #
+ #
# /* Get space for the result list. */
# if ((result = (PyObject *) PyList_New(len)) == NULL)
# goto Fail_2;
- #
+ #
# /* Iterate over the sequences until all have stopped. */
# for (i = 0; ; ++i) {
# PyObject *alist, *item=NULL, *value;
# int numactive = 0;
- #
+ #
# if (func == Py_None && n == 1)
# alist = NULL;
# else if ((alist = PyTuple_New(n)) == NULL)
# goto Fail_1;
- #
+ #
# for (j = 0, sqp = seqs; j < n; ++j, ++sqp) {
# if (sqp->saw_StopIteration) {
# Py_INCREF(Py_None);
@@ -204,15 +204,15 @@ def oldmap(func, *iterables):
# else
# break;
# }
- #
+ #
# if (!alist)
# alist = item;
- #
+ #
# if (numactive == 0) {
# Py_DECREF(alist);
# break;
# }
- #
+ #
# if (func == Py_None)
# value = alist;
# else {
@@ -230,12 +230,12 @@ def oldmap(func, *iterables):
# else if (PyList_SetItem(result, i, value) < 0)
# goto Fail_1;
# }
- #
+ #
# if (i < len && PyList_SetSlice(result, i, len, NULL) < 0)
# goto Fail_1;
- #
+ #
# goto Succeed;
- #
+ #
# Fail_1:
# Py_DECREF(result);
# Fail_2:
@@ -270,4 +270,3 @@ def oldzip(*args, **kwargs):
reduce = __builtin__.reduce
zip = __builtin__.zip
__all__ = []
-
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 29f8be69..ae6c0d90 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:
@@ -28,22 +28,35 @@
>>> from past.translation import remove_hooks
>>> remove_hooks()
-Author: Ed Schofield.
+Author: Ed Schofield.
Inspired by and based on ``uprefix`` by Vinay M. Sajip.
"""
-import imp
+import sys
+# imp was deprecated in python 3.6
+if sys.version_info >= (3, 6):
+ import importlib as imp
+else:
+ import imp
import logging
-import marshal
import os
-import sys
import copy
from lib2to3.pgen2.parse import ParseError
from lib2to3.refactor import RefactoringTool
from libfuturize import fixes
-__version__ = '0.1.0'
+try:
+ from importlib.machinery import (
+ PathFinder,
+ SourceFileLoader,
+ )
+except ImportError:
+ PathFinder = None
+ SourceFileLoader = object
+
+if sys.version_info[:2] < (3, 4):
+ import imp
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
@@ -54,12 +67,6 @@
list(fixes.lib2to3_fix_names_stage2))
-# There are two possible grammars: with or without the print statement.
-# Hence we have two possible refactoring tool implementations.
-_rt = RefactoringTool(myfixes)
-_rtp = RefactoringTool(myfixes, {'print_function': True})
-
-#
# We detect whether the code is Py2 or Py3 by applying certain lib2to3 fixers
# to it. If the diff is empty, it's Python 3 code.
@@ -122,12 +129,42 @@
]
-_rt_py2_detect = RefactoringTool(py2_detect_fixers)
-_rtp_py2_detect = RefactoringTool(py2_detect_fixers,
- {'print_function': True})
+class RTs:
+ """
+ A namespace for the refactoring tools. This avoids creating these at
+ the module level, which slows down the module import. (See issue #117).
+
+ There are two possible grammars: with or without the print statement.
+ Hence we have two possible refactoring tool implementations.
+ """
+ _rt = None
+ _rtp = None
+ _rt_py2_detect = None
+ _rtp_py2_detect = None
+
+ @staticmethod
+ def setup():
+ """
+ Call this before using the refactoring tools to create them on demand
+ if needed.
+ """
+ if None in [RTs._rt, RTs._rtp]:
+ RTs._rt = RefactoringTool(myfixes)
+ RTs._rtp = RefactoringTool(myfixes, {'print_function': True})
+
+
+ @staticmethod
+ def setup_detect_python2():
+ """
+ Call this before using the refactoring tools to create them on demand
+ if needed.
+ """
+ if None in [RTs._rt_py2_detect, RTs._rtp_py2_detect]:
+ RTs._rt_py2_detect = RefactoringTool(py2_detect_fixers)
+ RTs._rtp_py2_detect = RefactoringTool(py2_detect_fixers,
+ {'print_function': True})
-#
# We need to find a prefix for the standard library, as we don't want to
# process any files there (they will already be Python 3).
#
@@ -145,7 +182,6 @@
# Instead, we use the portion of the path common to both the stdlib modules
# ``math`` and ``urllib``.
-import os, sys
def splitall(path):
"""
Split a path into all components. From Python Cookbook.
@@ -179,8 +215,6 @@ def common_substring(s1, s2):
chunks.append(dir1)
return os.path.join(*chunks)
-# import math
-# import urllib
# _stdlibprefix = common_substring(math.__file__, urllib.__file__)
@@ -188,35 +222,98 @@ def detect_python2(source, pathname):
"""
Returns a bool indicating whether we think the code is Py2
"""
+ RTs.setup_detect_python2()
try:
- tree = _rt_py2_detect.refactor_string(source, pathname)
+ tree = RTs._rt_py2_detect.refactor_string(source, pathname)
except ParseError as e:
if e.msg != 'bad input' or e.value != '=':
raise
- tree = _rtp.refactor_string(source, pathname)
+ tree = RTs._rtp.refactor_string(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
+def transform(source, pathname):
+ # This implementation uses lib2to3,
+ # you can override and use something else
+ # if that's better for you
+
+ # lib2to3 likes a newline at the end
+ RTs.setup()
+ source += '\n'
+ try:
+ tree = RTs._rt.refactor_string(source, pathname)
+ except ParseError as e:
+ if e.msg != 'bad input' or e.value != '=':
+ raise
+ tree = RTs._rtp.refactor_string(source, pathname)
+ # could optimise a bit for only doing str(tree) if
+ # getattr(tree, 'was_changed', False) returns True
+ return str(tree)[:-1] # remove added newline
+
+
+class PastSourceFileLoader(SourceFileLoader):
+ exclude_paths = []
+ include_paths = []
+
+ def _convert_needed(self):
+ fullname = self.name
+ if any(fullname.startswith(path) for path in self.exclude_paths):
+ convert = False
+ elif any(fullname.startswith(path) for path in self.include_paths):
+ convert = True
+ else:
+ convert = False
+ return convert
+
+ def _exec_transformed_module(self, module):
+ source = self.get_source(self.name)
+ pathname = self.path
+ if detect_python2(source, pathname):
+ source = transform(source, pathname)
+ code = compile(source, pathname, "exec")
+ exec(code, module.__dict__)
+
+ # For Python 3.3
+ def load_module(self, fullname):
+ logger.debug("Running load_module for %s", fullname)
+ if fullname in sys.modules:
+ mod = sys.modules[fullname]
+ else:
+ if self._convert_needed():
+ logger.debug("Autoconverting %s", fullname)
+ mod = imp.new_module(fullname)
+ sys.modules[fullname] = mod
+
+ # required by PEP 302
+ mod.__file__ = self.path
+ mod.__loader__ = self
+ if self.is_package(fullname):
+ mod.__path__ = []
+ mod.__package__ = fullname
+ else:
+ mod.__package__ = fullname.rpartition('.')[0]
+ self._exec_transformed_module(mod)
+ else:
+ mod = super().load_module(fullname)
+ return mod
+
+ # For Python >=3.4
+ def exec_module(self, module):
+ logger.debug("Running exec_module for %s", module)
+ if self._convert_needed():
+ logger.debug("Autoconverting %s", self.name)
+ self._exec_transformed_module(module)
+ else:
+ super().exec_module(module)
+
+
class Py2Fixer(object):
"""
An import hook class that uses lib2to3 for source-to-source translation of
@@ -250,153 +347,30 @@ def exclude(self, paths):
"""
self.exclude_paths += paths
+ # For Python 3.3
def find_module(self, fullname, path=None):
- logger.debug('Running find_module: {0}...'.format(fullname))
- if '.' in fullname:
- parent, child = fullname.rsplit('.', 1)
- if path is None:
- loader = self.find_module(parent, path)
- mod = loader.load_module(parent)
- path = mod.__path__
- fullname = child
-
- # Perhaps we should try using the new importlib functionality in Python
- # 3.3: something like this?
- # thing = importlib.machinery.PathFinder.find_module(fullname, path)
- try:
- self.found = imp.find_module(fullname, path)
- except Exception as e:
- logger.debug('Py2Fixer could not find {0}')
- logger.debug('Exception was: {0})'.format(fullname, e))
+ logger.debug("Running find_module: (%s, %s)", fullname, path)
+ loader = PathFinder.find_module(fullname, path)
+ if not loader:
+ logger.debug("Py2Fixer could not find %s", fullname)
return None
- self.kind = self.found[-1][-1]
- if self.kind == imp.PKG_DIRECTORY:
- self.pathname = os.path.join(self.found[1], '__init__.py')
- elif self.kind == imp.PY_SOURCE:
- self.pathname = self.found[1]
- return self
-
- def transform(self, source):
- # This implementation uses lib2to3,
- # you can override and use something else
- # if that's better for you
-
- # lib2to3 likes a newline at the end
- source += '\n'
- try:
- tree = _rt.refactor_string(source, self.pathname)
- except ParseError as e:
- if e.msg != 'bad input' or e.value != '=':
- raise
- tree = _rtp.refactor_string(source, self.pathname)
- # could optimise a bit for only doing str(tree) if
- # getattr(tree, 'was_changed', False) returns True
- return str(tree)[:-1] # remove added newline
-
- def load_module(self, fullname):
- logger.debug('Running load_module for {0}...'.format(fullname))
- if fullname in sys.modules:
- mod = sys.modules[fullname]
- else:
- if self.kind in (imp.PY_COMPILED, imp.C_EXTENSION, imp.C_BUILTIN,
- imp.PY_FROZEN):
- convert = False
- # elif (self.pathname.startswith(_stdlibprefix)
- # and 'site-packages' not in self.pathname):
- # # We assume it's a stdlib package in this case. Is this too brittle?
- # # Please file a bug report at https://github.com/PythonCharmers/python-future
- # # if so.
- # convert = False
- # in theory, other paths could be configured to be excluded here too
- elif any([fullname.startswith(path) for path in self.exclude_paths]):
- convert = False
- elif any([fullname.startswith(path) for path in self.include_paths]):
- convert = True
- else:
- convert = False
- if not convert:
- logger.debug('Excluded {0} from translation'.format(fullname))
- mod = imp.load_module(fullname, *self.found)
- else:
- logger.debug('Autoconverting {0} ...'.format(fullname))
- mod = imp.new_module(fullname)
- sys.modules[fullname] = mod
+ loader.__class__ = PastSourceFileLoader
+ loader.exclude_paths = self.exclude_paths
+ loader.include_paths = self.include_paths
+ return loader
+
+ # For Python >=3.4
+ def find_spec(self, fullname, path=None, target=None):
+ logger.debug("Running find_spec: (%s, %s, %s)", fullname, path, target)
+ spec = PathFinder.find_spec(fullname, path, target)
+ if not spec:
+ logger.debug("Py2Fixer could not find %s", fullname)
+ return None
+ spec.loader.__class__ = PastSourceFileLoader
+ spec.loader.exclude_paths = self.exclude_paths
+ spec.loader.include_paths = self.include_paths
+ return spec
- # required by PEP 302
- mod.__file__ = self.pathname
- mod.__name__ = fullname
- mod.__loader__ = self
-
- # This:
- # mod.__package__ = '.'.join(fullname.split('.')[:-1])
- # seems to result in "SystemError: Parent module '' not loaded,
- # cannot perform relative import" for a package's __init__.py
- # file. We use the approach below. Another option to try is the
- # minimal load_module pattern from the PEP 302 text instead.
-
- # Is the test in the next line more or less robust than the
- # following one? Presumably less ...
- # ispkg = self.pathname.endswith('__init__.py')
-
- if self.kind == imp.PKG_DIRECTORY:
- mod.__path__ = [ os.path.dirname(self.pathname) ]
- mod.__package__ = fullname
- else:
- #else, regular module
- mod.__path__ = []
- mod.__package__ = fullname.rpartition('.')[0]
-
- try:
- cachename = imp.cache_from_source(self.pathname)
- if not os.path.exists(cachename):
- update_cache = True
- else:
- sourcetime = os.stat(self.pathname).st_mtime
- cachetime = os.stat(cachename).st_mtime
- update_cache = cachetime < sourcetime
- # # Force update_cache to work around a problem with it being treated as Py3 code???
- # update_cache = True
- if not update_cache:
- with open(cachename, 'rb') as f:
- data = f.read()
- try:
- code = marshal.loads(data)
- except Exception:
- # pyc could be corrupt. Regenerate it
- update_cache = True
- if update_cache:
- if self.found[0]:
- source = self.found[0].read()
- elif self.kind == imp.PKG_DIRECTORY:
- with open(self.pathname) as f:
- source = f.read()
-
- 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')
-
- dirname = os.path.dirname(cachename)
- if not os.path.exists(dirname):
- os.makedirs(dirname)
- try:
- with open(cachename, 'wb') as f:
- data = marshal.dumps(code)
- f.write(data)
- except Exception: # could be write-protected
- pass
- exec(code, mod.__dict__)
- except Exception as e:
- # must remove module from sys.modules
- del sys.modules[fullname]
- raise # keep it simple
-
- if self.found[0]:
- self.found[0].close()
- return mod
_hook = Py2Fixer()
@@ -410,7 +384,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
@@ -435,7 +409,7 @@ def detect_hooks():
class hooks(object):
"""
Acts as a context manager. Use like this:
-
+
>>> from past import translation
>>> with translation.hooks():
... import mypy2module
@@ -455,7 +429,7 @@ def __exit__(self, *args):
class suspend_hooks(object):
"""
Acts as a context manager. Use like this:
-
+
>>> from past import translation
>>> translation.install_hooks()
>>> import http.client
@@ -474,3 +448,6 @@ def __exit__(self, *args):
if self.hooks_were_installed:
install_hooks()
+
+# alias
+autotranslate = install_hooks
diff --git a/src/past/types/__init__.py b/src/past/types/__init__.py
index a31b2646..91dd270f 100644
--- a/src/past/types/__init__.py
+++ b/src/past/types/__init__.py
@@ -27,4 +27,3 @@
unicode = str
# from .unicode import unicode
__all__ = ['basestring', 'olddict', 'oldstr', 'long', 'unicode']
-
diff --git a/src/past/types/basestring.py b/src/past/types/basestring.py
index 15437bf7..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)):
@@ -37,4 +36,3 @@ class basestring(with_metaclass(BaseBaseString)):
__all__ = ['basestring']
-
diff --git a/src/past/types/olddict.py b/src/past/types/olddict.py
index b213e28f..f4f92a26 100644
--- a/src/past/types/olddict.py
+++ b/src/past/types/olddict.py
@@ -71,7 +71,7 @@ def has_key(self, k):
# in the keyword argument list. For example: dict(one=1, two=2)
# """
- #
+ #
# if len(args) == 0:
# return super(olddict, cls).__new__(cls)
# # Was: elif isinstance(args[0], newbytes):
@@ -85,7 +85,7 @@ def has_key(self, k):
# else:
# value = args[0]
# return super(olddict, cls).__new__(cls, value)
-
+
def __native__(self):
"""
Hook for the past.utils.native() function
@@ -94,4 +94,3 @@ def __native__(self):
__all__ = ['olddict']
-
diff --git a/src/past/types/oldstr.py b/src/past/types/oldstr.py
index 1b90e3e1..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:
@@ -32,7 +35,7 @@ def unescape(s):
def
"""
return s.encode().decode('unicode_escape')
-
+
class oldstr(with_metaclass(BaseOldStr, _builtin_bytes)):
"""
@@ -55,14 +58,14 @@ def __dir__(self):
# bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer
# bytes(int) -> bytes object of size given by the parameter initialized with null bytes
# bytes() -> empty bytes object
- #
+ #
# Construct an immutable array of bytes from:
# - an iterable yielding integers in range(256)
# - a text string encoded using the specified encoding
# - any object implementing the buffer API.
# - an integer
# """
- #
+ #
# if len(args) == 0:
# return super(newbytes, cls).__new__(cls)
# # Was: elif isinstance(args[0], newbytes):
@@ -84,7 +87,7 @@ def __dir__(self):
# if 'errors' in kwargs:
# newargs.append(kwargs['errors'])
# value = args[0].encode(*newargs)
- # ###
+ # ###
# elif isinstance(args[0], Iterable):
# if len(args[0]) == 0:
# # What is this?
@@ -101,7 +104,7 @@ def __dir__(self):
# else:
# value = args[0]
# return super(newbytes, cls).__new__(cls, value)
-
+
def __repr__(self):
s = super(oldstr, self).__repr__() # e.g. b'abc' on Py3, b'abc' on Py3
return s[1:]
@@ -124,7 +127,7 @@ def __getslice__(self, *args):
def __contains__(self, key):
if isinstance(key, int):
return False
-
+
def __native__(self):
return bytes(self)
diff --git a/src/past/utils/__init__.py b/src/past/utils/__init__.py
index 02f06d59..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')
@@ -26,13 +26,13 @@ def with_metaclass(meta, *bases):
Function from jinja2/_compat.py. License: BSD.
Use it like this::
-
+
class BaseForm(object):
pass
-
+
class FormType(type):
pass
-
+
class Form(with_metaclass(FormType, BaseForm)):
pass
@@ -42,7 +42,7 @@ class Form(with_metaclass(FormType, BaseForm)):
we also need to make sure that we downgrade the custom metaclass
for one level to something closer to type (that's why __call__ and
__init__ comes back from type etc.).
-
+
This has the advantage over six.with_metaclass of not introducing
dummy classes into the final MRO.
"""
@@ -62,7 +62,7 @@ def native(obj):
On Py3, returns the corresponding native Py3 types that are
superclasses for forward-ported objects from Py2:
-
+
>>> from past.builtins import str, dict
>>> native(str(b'ABC')) # Output on Py3 follows. On Py2, output is 'ABC'
diff --git a/src/queue/__init__.py b/src/queue/__init__.py
index 78c2b2c3..22bd296b 100644
--- a/src/queue/__init__.py
+++ b/src/queue/__init__.py
@@ -5,4 +5,6 @@
if sys.version_info[0] < 3:
from Queue import *
else:
- raise ImportError('Cannot import module from python-future source folder')
+ raise ImportError('This package should not be accessible on Python 3. '
+ 'Either you are trying to run from the python-future src folder '
+ 'or your installation of python-future is corrupted.')
diff --git a/src/reprlib/__init__.py b/src/reprlib/__init__.py
index ad7ad094..6ccf9c00 100644
--- a/src/reprlib/__init__.py
+++ b/src/reprlib/__init__.py
@@ -1,8 +1,9 @@
from __future__ import absolute_import
import sys
-__future_module__ = True
if sys.version_info[0] < 3:
from repr import *
else:
- raise ImportError('Cannot import module from python-future source folder')
+ raise ImportError('This package should not be accessible on Python 3. '
+ 'Either you are trying to run from the python-future src folder '
+ 'or your installation of python-future is corrupted.')
diff --git a/src/socketserver/__init__.py b/src/socketserver/__init__.py
index a6a09092..c5b8c9c2 100644
--- a/src/socketserver/__init__.py
+++ b/src/socketserver/__init__.py
@@ -1,8 +1,9 @@
from __future__ import absolute_import
import sys
-__future_module__ = True
if sys.version_info[0] < 3:
from SocketServer import *
else:
- raise ImportError('Cannot import module from python-future source folder')
+ raise ImportError('This package should not be accessible on Python 3. '
+ 'Either you are trying to run from the python-future src folder '
+ 'or your installation of python-future is corrupted.')
diff --git a/src/tkinter/__init__.py b/src/tkinter/__init__.py
index fc38f2df..bb730c35 100644
--- a/src/tkinter/__init__.py
+++ b/src/tkinter/__init__.py
@@ -1,8 +1,28 @@
from __future__ import absolute_import
import sys
-__future_module__ = True
if sys.version_info[0] < 3:
from Tkinter import *
+ from Tkinter import (_cnfmerge, _default_root, _flatten,
+ _support_default_root, _test,
+ _tkinter, _setit)
+
+ try: # >= 2.7.4
+ from Tkinter import (_join)
+ except ImportError:
+ pass
+
+ try: # >= 2.7.4
+ from Tkinter import (_stringify)
+ except ImportError:
+ pass
+
+ try: # >= 2.7.9
+ from Tkinter import (_splitdict)
+ except ImportError:
+ pass
+
else:
- raise ImportError('Cannot import module from python-future source folder')
+ raise ImportError('This package should not be accessible on Python 3. '
+ 'Either you are trying to run from the python-future src folder '
+ 'or your installation of python-future is corrupted.')
diff --git a/src/tkinter/colorchooser.py b/src/tkinter/colorchooser.py
index 5e7c97f4..6dde6e8d 100644
--- a/src/tkinter/colorchooser.py
+++ b/src/tkinter/colorchooser.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The tkColorChooser module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/tkinter/commondialog.py b/src/tkinter/commondialog.py
index 7747a0ba..eb7ae8d6 100644
--- a/src/tkinter/commondialog.py
+++ b/src/tkinter/commondialog.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The tkCommonDialog module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/tkinter/constants.py b/src/tkinter/constants.py
index 99216f33..ffe09815 100644
--- a/src/tkinter/constants.py
+++ b/src/tkinter/constants.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The Tkconstants module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/tkinter/dialog.py b/src/tkinter/dialog.py
index a5b77781..113370ca 100644
--- a/src/tkinter/dialog.py
+++ b/src/tkinter/dialog.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The Dialog module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/tkinter/dnd.py b/src/tkinter/dnd.py
index 748b111a..1ab43791 100644
--- a/src/tkinter/dnd.py
+++ b/src/tkinter/dnd.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The Tkdnd module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/tkinter/filedialog.py b/src/tkinter/filedialog.py
index 35e21ac0..93a15388 100644
--- a/src/tkinter/filedialog.py
+++ b/src/tkinter/filedialog.py
@@ -10,4 +10,8 @@
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/tkinter/font.py b/src/tkinter/font.py
index 63d86dc7..628f399a 100644
--- a/src/tkinter/font.py
+++ b/src/tkinter/font.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The tkFont module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/tkinter/messagebox.py b/src/tkinter/messagebox.py
index 3ed52e1f..b43d8702 100644
--- a/src/tkinter/messagebox.py
+++ b/src/tkinter/messagebox.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The tkMessageBox module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/tkinter/scrolledtext.py b/src/tkinter/scrolledtext.py
index 13bd660d..1c69db60 100644
--- a/src/tkinter/scrolledtext.py
+++ b/src/tkinter/scrolledtext.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The ScrolledText module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/tkinter/simpledialog.py b/src/tkinter/simpledialog.py
index e952fa99..dba93fbf 100644
--- a/src/tkinter/simpledialog.py
+++ b/src/tkinter/simpledialog.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The SimpleDialog module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/tkinter/tix.py b/src/tkinter/tix.py
index 019df6f7..8d1718ad 100644
--- a/src/tkinter/tix.py
+++ b/src/tkinter/tix.py
@@ -10,4 +10,3 @@
except ImportError:
raise ImportError('The Tix module is missing. Does your Py2 '
'installation include tkinter?')
-
diff --git a/src/tkinter/ttk.py b/src/tkinter/ttk.py
new file mode 100644
index 00000000..081c1b49
--- /dev/null
+++ b/src/tkinter/ttk.py
@@ -0,0 +1,12 @@
+from __future__ import absolute_import
+
+from future.utils import PY3
+
+if PY3:
+ from tkinter.ttk import *
+else:
+ try:
+ from ttk import *
+ except ImportError:
+ raise ImportError('The ttk module is missing. Does your Py2 '
+ 'installation include tkinter?')
diff --git a/src/winreg/__init__.py b/src/winreg/__init__.py
index c230237f..97243bbb 100644
--- a/src/winreg/__init__.py
+++ b/src/winreg/__init__.py
@@ -5,4 +5,6 @@
if sys.version_info[0] < 3:
from _winreg import *
else:
- raise ImportError('Cannot import module from python-future source folder')
+ raise ImportError('This package should not be accessible on Python 3. '
+ 'Either you are trying to run from the python-future src folder '
+ 'or your installation of python-future is corrupted.')
diff --git a/src/xmlrpc/__init__.py b/src/xmlrpc/__init__.py
index 2e225103..e4f853e5 100644
--- a/src/xmlrpc/__init__.py
+++ b/src/xmlrpc/__init__.py
@@ -1,8 +1,9 @@
from __future__ import absolute_import
import sys
-__future_module__ = True
if sys.version_info[0] < 3:
pass
else:
- raise ImportError('Cannot import module from python-future source folder')
+ raise ImportError('This package should not be accessible on Python 3. '
+ 'Either you are trying to run from the python-future src folder '
+ 'or your installation of python-future is corrupted.')
diff --git a/src/xmlrpc/client.py b/src/xmlrpc/client.py
index 4708cf89..a8d0827e 100644
--- a/src/xmlrpc/client.py
+++ b/src/xmlrpc/client.py
@@ -1,7 +1,5 @@
from __future__ import absolute_import
-from future.utils import PY3
+import sys
-if PY3:
- from xmlrpc.client import *
-else:
- from xmlrpclib import *
+assert sys.version_info[0] < 3
+from xmlrpclib import *
diff --git a/src/xmlrpc/server.py b/src/xmlrpc/server.py
index 1a8af345..a8d0827e 100644
--- a/src/xmlrpc/server.py
+++ b/src/xmlrpc/server.py
@@ -1,7 +1,5 @@
from __future__ import absolute_import
-from future.utils import PY3
+import sys
-if PY3:
- from xmlrpc.server import *
-else:
- from xmlrpclib import *
+assert sys.version_info[0] < 3
+from xmlrpclib import *
diff --git a/test.sh b/test.sh
new file mode 100755
index 00000000..d45e98d3
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+set -exo pipefail
+
+python --version
+
+if [ -e "/root/pip" ]
+then
+ pip install /root/pip/*.zip /root/pip/*.whl /root/pip/*tar.gz
+else
+ pip install pytest unittest2
+fi
+
+pytag="py${PYTHON_VERSION//./}"
+
+python setup.py bdist_wheel --python-tag="${pytag}"
+pip install dist/future-*-${pytag}-none-any.whl
+pytest tests/
diff --git a/tests/test_future/disabled/__init__.py b/tests/test_future/disabled/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/tests/test_future/disabled/disabled_test_http_cookies.py b/tests/test_future/disabled/disabled_test_http_cookies.py
deleted file mode 100644
index ef1a1126..00000000
--- a/tests/test_future/disabled/disabled_test_http_cookies.py
+++ /dev/null
@@ -1,249 +0,0 @@
-# Simple test suite for http/cookies.py
-
-# Python 2.7's Cookie module only accepts byte-strings, whereas Python 3.3's
-# http.cookies module expects unicode strings. Include this import with the
-# backported (Py3.3) module only:
-from __future__ import unicode_literals
-
-from __future__ import print_function
-from __future__ import division
-from __future__ import absolute_import
-
-from future.builtins import str
-from future import standard_library
-from future.utils import text_to_native_str
-
-with standard_library.hooks():
- from http import cookies
-from future.standard_library.test.support import run_unittest, run_doctest # , check_warnings
-from future.tests.base import unittest
-
-import warnings
-
-class CookieTests(unittest.TestCase):
-
- def setUp(self):
- # self._warnings_manager = check_warnings()
- # self._warnings_manager.__enter__()
- warnings.filterwarnings("ignore", ".* class is insecure.*",
- DeprecationWarning)
-
- def tearDown(self):
- # self._warnings_manager.__exit__(None, None, None)
- pass
-
- def test_basic(self):
- cases = [
- {'data': 'chips=ahoy; vienna=finger',
- 'dict': {'chips':'ahoy', 'vienna':'finger'},
- 'repr': "",
- 'output': 'Set-Cookie: chips=ahoy\nSet-Cookie: vienna=finger'},
-
- {'data': 'keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"',
- 'dict': {'keebler' : 'E=mc2; L="Loves"; fudge=\012;'},
- 'repr': '''''',
- 'output': 'Set-Cookie: keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"'},
-
- # Check illegal cookies that have an '=' char in an unquoted value
- {'data': 'keebler=E=mc2',
- 'dict': {'keebler' : 'E=mc2'},
- 'repr': "",
- 'output': 'Set-Cookie: keebler=E=mc2'},
-
- # Cookies with ':' character in their name. Though not mentioned in
- # RFC, servers / browsers allow it.
-
- {'data': 'key:term=value:term',
- 'dict': {'key:term' : 'value:term'},
- 'repr': "",
- 'output': 'Set-Cookie: key:term=value:term'},
-
- ]
-
- for case in cases:
- C = cookies.SimpleCookie()
- C.load(case['data'])
- self.assertEqual(repr(C), text_to_native_str(case['repr']))
- self.assertEqual(C.output(sep='\n'), case['output'])
- for k, v in sorted(case['dict'].items()):
- self.assertEqual(C[k].value, v)
-
- def test_load(self):
- C = cookies.SimpleCookie()
- C.load('Customer="WILE_E_COYOTE"; Version=1; Path=/acme')
-
- self.assertEqual(C['Customer'].value, 'WILE_E_COYOTE')
- self.assertEqual(C['Customer']['version'], '1')
- self.assertEqual(C['Customer']['path'], '/acme')
-
- self.assertEqual(C.output(['path']),
- 'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme')
- self.assertEqual(C.js_output(), r"""
-
- """)
- self.assertEqual(C.js_output(['path']), r"""
-
- """)
-
- def test_extended_encode(self):
- # Issue 9824: some browsers don't follow the standard; we now
- # encode , and ; to keep them from tripping up.
- C = cookies.SimpleCookie()
- C['val'] = "some,funky;stuff"
- self.assertEqual(C.output(['val']),
- 'Set-Cookie: val="some\\054funky\\073stuff"')
-
- def test_special_attrs(self):
- # 'expires'
- C = cookies.SimpleCookie('Customer="WILE_E_COYOTE"')
- C['Customer']['expires'] = 0
- # can't test exact output, it always depends on current date/time
- self.assertTrue(C.output().endswith('GMT'))
-
- # loading 'expires'
- C = cookies.SimpleCookie()
- C.load('Customer="W"; expires=Wed, 01 Jan 2010 00:00:00 GMT')
- self.assertEqual(C['Customer']['expires'],
- 'Wed, 01 Jan 2010 00:00:00 GMT')
- C = cookies.SimpleCookie()
- C.load('Customer="W"; expires=Wed, 01 Jan 98 00:00:00 GMT')
- self.assertEqual(C['Customer']['expires'],
- 'Wed, 01 Jan 98 00:00:00 GMT')
-
- # 'max-age'
- C = cookies.SimpleCookie('Customer="WILE_E_COYOTE"')
- C['Customer']['max-age'] = 10
- self.assertEqual(C.output(),
- 'Set-Cookie: Customer="WILE_E_COYOTE"; Max-Age=10')
-
- def test_set_secure_httponly_attrs(self):
- C = cookies.SimpleCookie('Customer="WILE_E_COYOTE"')
- C['Customer']['secure'] = True
- C['Customer']['httponly'] = True
- self.assertEqual(C.output(),
- 'Set-Cookie: Customer="WILE_E_COYOTE"; httponly; secure')
-
- def test_secure_httponly_false_if_not_present(self):
- C = cookies.SimpleCookie()
- C.load('eggs=scrambled; Path=/bacon')
- self.assertFalse(C['eggs']['httponly'])
- self.assertFalse(C['eggs']['secure'])
-
- def test_secure_httponly_true_if_present(self):
- # Issue 16611
- C = cookies.SimpleCookie()
- C.load('eggs=scrambled; httponly; secure; Path=/bacon')
- self.assertTrue(C['eggs']['httponly'])
- self.assertTrue(C['eggs']['secure'])
-
- def test_secure_httponly_true_if_have_value(self):
- # This isn't really valid, but demonstrates what the current code
- # is expected to do in this case.
- C = cookies.SimpleCookie()
- C.load('eggs=scrambled; httponly=foo; secure=bar; Path=/bacon')
- self.assertTrue(C['eggs']['httponly'])
- self.assertTrue(C['eggs']['secure'])
- # Here is what it actually does; don't depend on this behavior. These
- # checks are testing backward compatibility for issue 16611.
- self.assertEqual(C['eggs']['httponly'], 'foo')
- self.assertEqual(C['eggs']['secure'], 'bar')
-
- def test_bad_attrs(self):
- # issue 16611: make sure we don't break backward compatibility.
- C = cookies.SimpleCookie()
- C.load('cookie=with; invalid; version; second=cookie;')
- self.assertEqual(C.output(),
- 'Set-Cookie: cookie=with\r\nSet-Cookie: second=cookie')
-
- def test_extra_spaces(self):
- C = cookies.SimpleCookie()
- C.load('eggs = scrambled ; secure ; path = bar ; foo=foo ')
- self.assertEqual(C.output(),
- 'Set-Cookie: eggs=scrambled; Path=bar; secure\r\nSet-Cookie: foo=foo')
-
- def test_quoted_meta(self):
- # Try cookie with quoted meta-data
- C = cookies.SimpleCookie()
- C.load('Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"')
- self.assertEqual(C['Customer'].value, 'WILE_E_COYOTE')
- self.assertEqual(C['Customer']['version'], '1')
- self.assertEqual(C['Customer']['path'], '/acme')
-
- self.assertEqual(C.output(['path']),
- 'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme')
- self.assertEqual(C.js_output(), r"""
-
- """)
- self.assertEqual(C.js_output(['path']), r"""
-
- """)
-
-class MorselTests(unittest.TestCase):
- """Tests for the Morsel object."""
-
- def test_reserved_keys(self):
- M = cookies.Morsel()
- # tests valid and invalid reserved keys for Morsels
- for i in M._reserved:
- # Test that all valid keys are reported as reserved and set them
- self.assertTrue(M.isReservedKey(i))
- M[i] = '%s_value' % i
- for i in M._reserved:
- # Test that valid key values come out fine
- self.assertEqual(M[i], '%s_value' % i)
- for i in "the holy hand grenade".split():
- # Test that invalid keys raise CookieError
- self.assertRaises(cookies.CookieError,
- M.__setitem__, i, '%s_value' % i)
-
- def test_setter(self):
- M = cookies.Morsel()
- # tests the .set method to set keys and their values
- for i in M._reserved:
- # Makes sure that all reserved keys can't be set this way
- self.assertRaises(cookies.CookieError,
- M.set, i, '%s_value' % i, '%s_value' % i)
- for i in "thou cast _the- !holy! ^hand| +*grenade~".split():
- # Try typical use case. Setting decent values.
- # Check output and js_output.
- M['path'] = '/foo' # Try a reserved key as well
- M.set(i, "%s_val" % i, "%s_coded_val" % i)
- self.assertEqual(
- M.output(),
- "Set-Cookie: %s=%s; Path=/foo" % (i, "%s_coded_val" % i))
- expected_js_output = """
-
- """ % (i, "%s_coded_val" % i)
- self.assertEqual(M.js_output(), expected_js_output)
- for i in ["foo bar", "foo@bar"]:
- # Try some illegal characters
- self.assertRaises(cookies.CookieError,
- M.set, i, '%s_value' % i, '%s_value' % i)
-
-
-# def test_main():
-# run_unittest(CookieTests, MorselTests)
-# run_doctest(cookies)
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tests/test_future/disabled/disabled_test_httpservers.py b/tests/test_future/disabled/disabled_test_httpservers.py
deleted file mode 100644
index 2e4a7246..00000000
--- a/tests/test_future/disabled/disabled_test_httpservers.py
+++ /dev/null
@@ -1,718 +0,0 @@
-# coding: utf-8
-
-"""Unittests for the various HTTPServer modules.
-
-From Python 3.3
-
-Written by Cody A.W. Somerville ,
-Josip Dzolonga, and Michael Otteneder for the 2007/08 GHOP contest.
-"""
-
-from __future__ import (absolute_import, division,
- print_function, unicode_literals)
-from future import standard_library, utils
-
-with standard_library.hooks():
- from http.server import BaseHTTPRequestHandler, HTTPServer, \
- SimpleHTTPRequestHandler, CGIHTTPRequestHandler
- from http import server
- import http.client
-from future.standard_library.test import support
-from future.builtins import *
-
-import os
-import sys
-import re
-import base64
-import shutil
-import tempfile
-from io import BytesIO
-
-from future.tests.base import unittest, skip26
-TestCase = unittest.TestCase
-
-threading = support.import_module('threading')
-
-
-class NoLogRequestHandler(object):
- def log_message(self, *args):
- # don't write log messages to stderr
- pass
-
- def read(self, n=None):
- return ''
-
-
-class TestServerThread(threading.Thread):
- def __init__(self, test_object, request_handler):
- threading.Thread.__init__(self)
- self.request_handler = request_handler
- self.test_object = test_object
-
- def run(self):
- self.server = HTTPServer(('localhost', 0), self.request_handler)
- self.test_object.HOST, self.test_object.PORT = self.server.socket.getsockname()
- self.test_object.server_started.set()
- self.test_object = None
- try:
- self.server.serve_forever(0.05)
- finally:
- self.server.server_close()
-
- def stop(self):
- self.server.shutdown()
-
-
-@skip26
-class BaseTestCase(TestCase):
- def setUp(self):
- self._threads = support.threading_setup()
- os.environ = support.EnvironmentVarGuard()
- self.server_started = threading.Event()
- self.thread = TestServerThread(self, self.request_handler)
- self.thread.start()
- self.server_started.wait()
-
- def tearDown(self):
- self.thread.stop()
- self.thread = None
- os.environ.__exit__()
- support.threading_cleanup(*self._threads)
-
- def request(self, uri, method='GET', body=None, headers={}):
- self.connection = http.client.HTTPConnection(self.HOST, self.PORT)
- self.connection.request(method, uri, body, headers)
- return self.connection.getresponse()
-
-
-class BaseHTTPServerTestCase(BaseTestCase):
- class request_handler(NoLogRequestHandler, BaseHTTPRequestHandler):
- protocol_version = 'HTTP/1.1'
- default_request_version = 'HTTP/1.1'
-
- def do_TEST(self):
- self.send_response(204)
- self.send_header('Content-Type', 'text/html')
- self.send_header('Connection', 'close')
- self.end_headers()
-
- def do_KEEP(self):
- self.send_response(204)
- self.send_header('Content-Type', 'text/html')
- self.send_header('Connection', 'keep-alive')
- self.end_headers()
-
- def do_KEYERROR(self):
- self.send_error(999)
-
- def do_CUSTOM(self):
- self.send_response(999)
- self.send_header('Content-Type', 'text/html')
- self.send_header('Connection', 'close')
- self.end_headers()
-
- def do_LATINONEHEADER(self):
- self.send_response(999)
- self.send_header('X-Special', 'Dängerous Mind')
- self.send_header('Connection', 'close')
- self.end_headers()
- body = self.headers['x-special-incoming'].encode('utf-8')
- self.wfile.write(body)
-
- def setUp(self):
- BaseTestCase.setUp(self)
- self.con = http.client.HTTPConnection(self.HOST, self.PORT)
- self.con.connect()
-
- def test_command(self):
- self.con.request('GET', '/')
- res = self.con.getresponse()
- self.assertEqual(res.status, 501)
-
- def test_request_line_trimming(self):
- self.con._http_vsn_str = 'HTTP/1.1\n'
- self.con.putrequest('GET', '/')
- self.con.endheaders()
- res = self.con.getresponse()
- self.assertEqual(res.status, 501)
-
- def test_version_bogus(self):
- self.con._http_vsn_str = 'FUBAR'
- self.con.putrequest('GET', '/')
- self.con.endheaders()
- res = self.con.getresponse()
- self.assertEqual(res.status, 400)
-
- def test_version_digits(self):
- self.con._http_vsn_str = 'HTTP/9.9.9'
- self.con.putrequest('GET', '/')
- self.con.endheaders()
- res = self.con.getresponse()
- self.assertEqual(res.status, 400)
-
- def test_version_none_get(self):
- self.con._http_vsn_str = ''
- self.con.putrequest('GET', '/')
- self.con.endheaders()
- res = self.con.getresponse()
- self.assertEqual(res.status, 501)
-
- def test_version_none(self):
- self.con._http_vsn_str = ''
- self.con.putrequest('PUT', '/')
- self.con.endheaders()
- res = self.con.getresponse()
- self.assertEqual(res.status, 400)
-
- def test_version_invalid(self):
- self.con._http_vsn = 99
- self.con._http_vsn_str = 'HTTP/9.9'
- self.con.putrequest('GET', '/')
- self.con.endheaders()
- res = self.con.getresponse()
- self.assertEqual(res.status, 505)
-
- def test_send_blank(self):
- self.con._http_vsn_str = ''
- self.con.putrequest('', '')
- self.con.endheaders()
- res = self.con.getresponse()
- self.assertEqual(res.status, 400)
-
- def test_header_close(self):
- self.con.putrequest('GET', '/')
- self.con.putheader('Connection', 'close')
- self.con.endheaders()
- res = self.con.getresponse()
- self.assertEqual(res.status, 501)
-
- def test_head_keep_alive(self):
- self.con._http_vsn_str = 'HTTP/1.1'
- self.con.putrequest('GET', '/')
- self.con.putheader('Connection', 'keep-alive')
- self.con.endheaders()
- res = self.con.getresponse()
- self.assertEqual(res.status, 501)
-
- def test_handler(self):
- self.con.request('TEST', '/')
- res = self.con.getresponse()
- self.assertEqual(res.status, 204)
-
- def test_return_header_keep_alive(self):
- self.con.request('KEEP', '/')
- res = self.con.getresponse()
- self.assertEqual(res.getheader('Connection'), 'keep-alive')
- self.con.request('TEST', '/')
- self.addCleanup(self.con.close)
-
- def test_internal_key_error(self):
- self.con.request('KEYERROR', '/')
- res = self.con.getresponse()
- self.assertEqual(res.status, 999)
-
- def test_return_custom_status(self):
- self.con.request('CUSTOM', '/')
- res = self.con.getresponse()
- self.assertEqual(res.status, 999)
-
- @unittest.skip('Unicode bug in Py2.7 email.parser.parsestr ?')
- def test_latin1_header(self):
- self.con.request('LATINONEHEADER', '/', headers={
- 'X-Special-Incoming': 'Ärger mit Unicode'
- })
- res = self.con.getresponse()
- self.assertEqual(res.getheader('X-Special'), 'Dängerous Mind')
- self.assertEqual(res.read(), 'Ärger mit Unicode'.encode('utf-8'))
-
-
-class SimpleHTTPServerTestCase(BaseTestCase):
- class request_handler(NoLogRequestHandler, SimpleHTTPRequestHandler):
- pass
-
- def setUp(self):
- BaseTestCase.setUp(self)
- self.cwd = os.getcwd()
- basetempdir = tempfile.gettempdir()
- os.chdir(basetempdir)
- self.data = bytes(b'We are the knights who say Ni!')
- self.tempdir = tempfile.mkdtemp(dir=basetempdir)
- self.tempdir_name = os.path.basename(self.tempdir)
- with open(os.path.join(self.tempdir, 'test'), 'wb') as temp:
- temp.write(self.data)
-
- def tearDown(self):
- try:
- os.chdir(self.cwd)
- try:
- shutil.rmtree(self.tempdir)
- except:
- pass
- finally:
- BaseTestCase.tearDown(self)
-
- def check_status_and_reason(self, response, status, data=None):
- body = response.read()
- self.assertTrue(response)
- self.assertEqual(response.status, status)
- self.assertIsNotNone(response.reason)
- if data:
- self.assertEqual(data, body)
-
- def test_get(self):
- #constructs the path relative to the root directory of the HTTPServer
- response = self.request(self.tempdir_name + '/test')
- self.check_status_and_reason(response, 200, data=self.data)
- response = self.request(self.tempdir_name + '/')
- self.check_status_and_reason(response, 200)
- response = self.request(self.tempdir_name)
- self.check_status_and_reason(response, 301)
- response = self.request('/ThisDoesNotExist')
- self.check_status_and_reason(response, 404)
- response = self.request('/' + 'ThisDoesNotExist' + '/')
- self.check_status_and_reason(response, 404)
- with open(os.path.join(self.tempdir_name, 'index.html'), 'w') as f:
- response = self.request('/' + self.tempdir_name + '/')
- self.check_status_and_reason(response, 200)
- # chmod() doesn't work as expected on Windows, and filesystem
- # permissions are ignored by root on Unix.
- if os.name == 'posix' and os.geteuid() != 0:
- os.chmod(self.tempdir, 0)
- response = self.request(self.tempdir_name + '/')
- self.check_status_and_reason(response, 404)
- os.chmod(self.tempdir, 0o755)
-
- def test_head(self):
- response = self.request(
- self.tempdir_name + '/test', method='HEAD')
- self.check_status_and_reason(response, 200)
- self.assertEqual(response.getheader('content-length'),
- str(len(self.data)))
- self.assertEqual(response.getheader('content-type'),
- 'application/octet-stream')
-
- def test_invalid_requests(self):
- response = self.request('/', method='FOO')
- self.check_status_and_reason(response, 501)
- # requests must be case sensitive,so this should fail too
- response = self.request('/', method='get')
- self.check_status_and_reason(response, 501)
- response = self.request('/', method='GETs')
- self.check_status_and_reason(response, 501)
-
-
-cgi_file1 = """\
-#!%s
-
-print("Content-type: text/html")
-print()
-print("Hello World")
-"""
-
-cgi_file2 = """\
-#!%s
-import cgi
-
-print("Content-type: text/html")
-print()
-
-form = cgi.FieldStorage()
-print("%%s, %%s, %%s" %% (form.getfirst("spam"), form.getfirst("eggs"),
- form.getfirst("bacon")))
-"""
-
-
-@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
- "This test can't be run reliably as root (issue #13308).")
-class CGIHTTPServerTestCase(BaseTestCase):
- class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler):
- pass
-
- linesep = os.linesep.encode('ascii')
-
- def setUp(self):
- BaseTestCase.setUp(self)
- self.cwd = os.getcwd()
- self.parent_dir = tempfile.mkdtemp()
- self.cgi_dir = os.path.join(self.parent_dir, 'cgi-bin')
- os.mkdir(self.cgi_dir)
- self.file1_path = None
- self.file2_path = None
-
- # The shebang line should be pure ASCII: use symlink if possible.
- # See issue #7668.
- if support.can_symlink():
- self.pythonexe = os.path.join(self.parent_dir, 'python')
- os.symlink(sys.executable, self.pythonexe)
- else:
- self.pythonexe = sys.executable
-
- try:
- # The python executable path is written as the first line of the
- # CGI Python script. The encoding cookie cannot be used, and so the
- # path should be encodable to the default script encoding (utf-8)
- self.pythonexe.encode('utf-8')
- except UnicodeEncodeError:
- self.tearDown()
- self.skipTest("Python executable path is not encodable to utf-8")
-
- self.file1_path = os.path.join(self.cgi_dir, 'file1.py')
- with open(self.file1_path, 'w', encoding='utf-8') as file1:
- file1.write(cgi_file1 % self.pythonexe)
- os.chmod(self.file1_path, 0o777)
-
- self.file2_path = os.path.join(self.cgi_dir, 'file2.py')
- with open(self.file2_path, 'w', encoding='utf-8') as file2:
- file2.write(cgi_file2 % self.pythonexe)
- os.chmod(self.file2_path, 0o777)
-
- os.chdir(self.parent_dir)
-
- def tearDown(self):
- try:
- os.chdir(self.cwd)
- if self.pythonexe != sys.executable:
- os.remove(self.pythonexe)
- if self.file1_path:
- os.remove(self.file1_path)
- if self.file2_path:
- os.remove(self.file2_path)
- os.rmdir(self.cgi_dir)
- os.rmdir(self.parent_dir)
- finally:
- BaseTestCase.tearDown(self)
-
- def test_url_collapse_path(self):
- # verify tail is the last portion and head is the rest on proper urls
- test_vectors = {
- '': '//',
- '..': IndexError,
- '/.//..': IndexError,
- '/': '//',
- '//': '//',
- '/\\': '//\\',
- '/.//': '//',
- 'cgi-bin/file1.py': '/cgi-bin/file1.py',
- '/cgi-bin/file1.py': '/cgi-bin/file1.py',
- 'a': '//a',
- '/a': '//a',
- '//a': '//a',
- './a': '//a',
- './C:/': '/C:/',
- '/a/b': '/a/b',
- '/a/b/': '/a/b/',
- '/a/b/.': '/a/b/',
- '/a/b/c/..': '/a/b/',
- '/a/b/c/../d': '/a/b/d',
- '/a/b/c/../d/e/../f': '/a/b/d/f',
- '/a/b/c/../d/e/../../f': '/a/b/f',
- '/a/b/c/../d/e/.././././..//f': '/a/b/f',
- '../a/b/c/../d/e/.././././..//f': IndexError,
- '/a/b/c/../d/e/../../../f': '/a/f',
- '/a/b/c/../d/e/../../../../f': '//f',
- '/a/b/c/../d/e/../../../../../f': IndexError,
- '/a/b/c/../d/e/../../../../f/..': '//',
- '/a/b/c/../d/e/../../../../f/../.': '//',
- }
- for path, expected in test_vectors.items():
- if isinstance(expected, type) and issubclass(expected, Exception):
- self.assertRaises(expected,
- server._url_collapse_path, path)
- else:
- actual = server._url_collapse_path(path)
- self.assertEqual(expected, actual,
- msg='path = %r\nGot: %r\nWanted: %r' %
- (path, actual, expected))
-
- @unittest.expectedFailure
- def test_headers_and_content(self):
- res = self.request('/cgi-bin/file1.py')
- self.assertEqual((b'Hello World' + self.linesep, 'text/html', 200),
- (res.read(), res.getheader('Content-type'), res.status))
-
- @unittest.expectedFailure
- def test_post(self):
- from future.standard_library.urllib.parse import urlencode
- params = urlencode(
- {'spam' : 1, 'eggs' : 'python', 'bacon' : 123456})
- headers = {'Content-type' : 'application/x-www-form-urlencoded'}
- res = self.request('/cgi-bin/file2.py', 'POST', params, headers)
-
- self.assertEqual(res.read(), b'1, python, 123456' + self.linesep)
-
- def test_invaliduri(self):
- res = self.request('/cgi-bin/invalid')
- res.read()
- self.assertEqual(res.status, 404)
-
- @unittest.expectedFailure
- def test_authorization(self):
- headers = {bytes(b'Authorization') : bytes(b'Basic ') +
- base64.b64encode(bytes(b'username:pass'))}
- res = self.request('/cgi-bin/file1.py', 'GET', headers=headers)
- self.assertEqual((b'Hello World' + self.linesep, 'text/html', 200),
- (res.read(), res.getheader('Content-type'), res.status))
-
- @unittest.expectedFailure
- def test_no_leading_slash(self):
- # http://bugs.python.org/issue2254
- res = self.request('cgi-bin/file1.py')
- self.assertEqual((b'Hello World' + self.linesep, 'text/html', 200),
- (res.read(), res.getheader('Content-type'), res.status))
-
- @unittest.expectedFailure
- def test_os_environ_is_not_altered(self):
- signature = "Test CGI Server"
- os.environ['SERVER_SOFTWARE'] = signature
- res = self.request('/cgi-bin/file1.py')
- self.assertEqual((b'Hello World' + self.linesep, 'text/html', 200),
- (res.read(), res.getheader('Content-type'), res.status))
- self.assertEqual(os.environ['SERVER_SOFTWARE'], signature)
-
-
-class SocketlessRequestHandler(SimpleHTTPRequestHandler, object):
- def __init__(self):
- self.get_called = False
- self.protocol_version = "HTTP/1.1"
-
- def do_GET(self):
- self.get_called = True
- self.send_response(200)
- self.send_header('Content-Type', 'text/html')
- self.end_headers()
- self.wfile.write(bytes(b'Data\r\n'))
-
- def log_message(self, format, *args):
- pass
-
-
-class RejectingSocketlessRequestHandler(SocketlessRequestHandler):
- def handle_expect_100(self):
- self.send_error(417)
- return False
-
-
-class AuditableBytesIO(object):
-
- def __init__(self):
- self.datas = []
-
- def write(self, data):
- self.datas.append(data)
-
- def getData(self):
- return bytes(b'').join(self.datas)
-
- @property
- def numWrites(self):
- return len(self.datas)
-
-
-@skip26
-class BaseHTTPRequestHandlerTestCase(TestCase):
- """Test the functionality of the BaseHTTPServer.
-
- Test the support for the Expect 100-continue header.
- """
-
- HTTPResponseMatch = re.compile(b'HTTP/1.[0-9]+ 200 OK')
-
- def setUp (self):
- self.handler = SocketlessRequestHandler()
-
- def send_typical_request(self, message):
- input = BytesIO(message)
- output = BytesIO()
- self.handler.rfile = input
- self.handler.wfile = output
- self.handler.handle_one_request()
- output.seek(0)
- return output.readlines()
-
- def verify_get_called(self):
- self.assertTrue(self.handler.get_called)
-
- def verify_expected_headers(self, headers):
- for fieldName in b'Server: ', b'Date: ', b'Content-Type: ':
- self.assertEqual(sum(h.startswith(fieldName) for h in headers), 1)
-
- def verify_http_server_response(self, response):
- match = self.HTTPResponseMatch.search(response)
- self.assertTrue(match is not None)
-
- def test_http_1_1(self):
- result = self.send_typical_request(bytes(b'GET / HTTP/1.1\r\n\r\n'))
- self.verify_http_server_response(result[0])
- self.verify_expected_headers(result[1:-1])
- self.verify_get_called()
- self.assertEqual(result[-1], b'Data\r\n')
-
- def test_http_1_0(self):
- result = self.send_typical_request(bytes(b'GET / HTTP/1.0\r\n\r\n'))
- self.verify_http_server_response(result[0])
- self.verify_expected_headers(result[1:-1])
- self.verify_get_called()
- self.assertEqual(result[-1], b'Data\r\n')
-
- def test_http_0_9(self):
- result = self.send_typical_request(bytes(b'GET / HTTP/0.9\r\n\r\n'))
- self.assertEqual(len(result), 1)
- self.assertEqual(result[0], b'Data\r\n')
- self.verify_get_called()
-
- def test_with_continue_1_0(self):
- result = self.send_typical_request(bytes(b'GET / HTTP/1.0\r\nExpect: 100-continue\r\n\r\n'))
- self.verify_http_server_response(result[0])
- self.verify_expected_headers(result[1:-1])
- self.verify_get_called()
- self.assertEqual(result[-1], b'Data\r\n')
-
- @unittest.skipIf(utils.PY3, 'not working on Py3.3.4 for some reason ...')
- def test_with_continue_1_1(self):
- result = self.send_typical_request(bytes(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n'))
- self.assertEqual(result[0], b'HTTP/1.1 100 Continue\r\n')
- self.assertEqual(result[1], b'HTTP/1.1 200 OK\r\n')
- self.verify_expected_headers(result[2:-1])
- self.verify_get_called()
- self.assertEqual(result[-1], b'Data\r\n')
-
- def test_header_buffering_of_send_error(self):
-
- input = BytesIO(bytes(b'GET / HTTP/1.1\r\n\r\n'))
- output = AuditableBytesIO()
- handler = SocketlessRequestHandler()
- handler.rfile = input
- handler.wfile = output
- handler.request_version = 'HTTP/1.1'
- handler.requestline = ''
- handler.command = None
-
- handler.send_error(418)
- self.assertEqual(output.numWrites, 2)
-
- def test_header_buffering_of_send_response_only(self):
-
- input = BytesIO(bytes(b'GET / HTTP/1.1\r\n\r\n'))
- output = AuditableBytesIO()
- handler = SocketlessRequestHandler()
- handler.rfile = input
- handler.wfile = output
- handler.request_version = 'HTTP/1.1'
-
- handler.send_response_only(418)
- self.assertEqual(output.numWrites, 0)
- handler.end_headers()
- self.assertEqual(output.numWrites, 1)
-
- def test_header_buffering_of_send_header(self):
-
- input = BytesIO(bytes(b'GET / HTTP/1.1\r\n\r\n'))
- output = AuditableBytesIO()
- handler = SocketlessRequestHandler()
- handler.rfile = input
- handler.wfile = output
- handler.request_version = 'HTTP/1.1'
-
- handler.send_header('Foo', 'foo')
- handler.send_header('bar', 'bar')
- self.assertEqual(output.numWrites, 0)
- handler.end_headers()
- self.assertEqual(output.getData(), b'Foo: foo\r\nbar: bar\r\n\r\n')
- self.assertEqual(output.numWrites, 1)
-
- @unittest.skipIf(utils.PY3, 'not working on Py3.3.4 for some reason ...')
- def test_header_unbuffered_when_continue(self):
-
- def _readAndReseek(f):
- pos = f.tell()
- f.seek(0)
- data = f.read()
- f.seek(pos)
- return data
-
- input = BytesIO(bytes(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n'))
- output = BytesIO()
- self.handler.rfile = input
- self.handler.wfile = output
- self.handler.request_version = 'HTTP/1.1'
-
- self.handler.handle_one_request()
- self.assertNotEqual(_readAndReseek(output), b'')
- result = _readAndReseek(output).split(bytes(b'\r\n'))
- self.assertEqual(result[0], b'HTTP/1.1 100 Continue')
- self.assertEqual(result[1], b'HTTP/1.1 200 OK')
-
- def test_with_continue_rejected(self):
- usual_handler = self.handler # Save to avoid breaking any subsequent tests.
- self.handler = RejectingSocketlessRequestHandler()
- result = self.send_typical_request(bytes(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n'))
- self.assertEqual(result[0], b'HTTP/1.1 417 Expectation Failed\r\n')
- self.verify_expected_headers(result[1:-1])
- # The expect handler should short circuit the usual get method by
- # returning false here, so get_called should be false
- self.assertFalse(self.handler.get_called)
- self.assertEqual(sum(r == b'Connection: close\r\n' for r in result[1:-1]), 1)
- self.handler = usual_handler # Restore to avoid breaking any subsequent tests.
-
- def test_request_length(self):
- # Issue #10714: huge request lines are discarded, to avoid Denial
- # of Service attacks.
- result = self.send_typical_request(bytes(b'GET ') + bytes(b'x') * 65537)
- self.assertEqual(result[0], b'HTTP/1.1 414 Request-URI Too Long\r\n')
- self.assertFalse(self.handler.get_called)
-
- def test_header_length(self):
- # Issue #6791: same for headers
- result = self.send_typical_request(
- bytes(b'GET / HTTP/1.1\r\nX-Foo: bar') + bytes(b'r') * 65537 + bytes(b'\r\n\r\n'))
- self.assertEqual(result[0], b'HTTP/1.1 400 Line too long\r\n')
- self.assertFalse(self.handler.get_called)
-
-
-@skip26
-class SimpleHTTPRequestHandlerTestCase(TestCase):
- """ Test url parsing """
- def setUp(self):
- self.translated = os.getcwd()
- self.translated = os.path.join(self.translated, 'filename')
- self.handler = SocketlessRequestHandler()
-
- def test_query_arguments(self):
- path = self.handler.translate_path('/filename')
- self.assertEqual(path, self.translated)
- path = self.handler.translate_path('/filename?foo=bar')
- self.assertEqual(path, self.translated)
- path = self.handler.translate_path('/filename?a=b&spam=eggs#zot')
- self.assertEqual(path, self.translated)
-
- def test_start_with_double_slash(self):
- path = self.handler.translate_path('//filename')
- self.assertEqual(path, self.translated)
- path = self.handler.translate_path('//filename?foo=bar')
- self.assertEqual(path, self.translated)
-
-
-class DummyTest(TestCase):
- """
- It might help on travis-ci to have at least one test being executed
- for this module.
- """
- def test_nothing(self):
- self.assertTrue(True)
-
-
-def test_main(verbose=None):
- cwd = os.getcwd()
- try:
- support.run_unittest(
- BaseHTTPRequestHandlerTestCase,
- BaseHTTPServerTestCase,
- SimpleHTTPServerTestCase,
- CGIHTTPServerTestCase,
- SimpleHTTPRequestHandlerTestCase,
- )
- finally:
- os.chdir(cwd)
-
-if __name__ == '__main__':
- test_main()
diff --git a/tests/test_future/disabled/disabled_test_urllib2_localnet.py b/tests/test_future/disabled/disabled_test_urllib2_localnet.py
deleted file mode 100644
index 59f51a03..00000000
--- a/tests/test_future/disabled/disabled_test_urllib2_localnet.py
+++ /dev/null
@@ -1,599 +0,0 @@
-#!/usr/bin/env python3
-from __future__ import unicode_literals
-from __future__ import print_function
-from __future__ import division
-from __future__ import absolute_import
-
-import os
-import hashlib
-
-from future import standard_library
-import future.standard_library.email as email
-import future.standard_library.urllib.parse as urllib_parse
-import future.standard_library.urllib.request as urllib_request
-import future.standard_library.urllib.error as urllib_error
-from future.tests.base import unittest
-from future.builtins import bytes, int, str, super
-from future.standard_library.test import support
-threading = support.import_module('threading')
-with standard_library.hooks():
- import http.server
-
-
-here = os.path.dirname(__file__)
-# Self-signed cert file for 'localhost'
-CERT_localhost = os.path.join(here, 'keycert.pem')
-# Self-signed cert file for 'fakehostname'
-CERT_fakehostname = os.path.join(here, 'keycert2.pem')
-
-# Loopback http.server infrastructure
-
-class LoopbackHttpServer(http.server.HTTPServer):
- """HTTP server w/ a few modifications that make it useful for
- loopback testing purposes.
- """
-
- def __init__(self, server_address, RequestHandlerClass):
- http.server.HTTPServer.__init__(self,
- server_address,
- RequestHandlerClass)
-
- # Set the timeout of our listening socket really low so
- # that we can stop the server easily.
- self.socket.settimeout(0.1)
-
- def get_request(self):
- """HTTPServer method, overridden."""
-
- request, client_address = self.socket.accept()
-
- # It's a loopback connection, so setting the timeout
- # really low shouldn't affect anything, but should make
- # deadlocks less likely to occur.
- request.settimeout(10.0)
-
- return (request, client_address)
-
-class LoopbackHttpServerThread(threading.Thread):
- """Stoppable thread that runs a loopback http.server."""
-
- def __init__(self, request_handler):
- threading.Thread.__init__(self)
- self._stop_server = False
- self.ready = threading.Event()
- request_handler.protocol_version = "HTTP/1.0"
- self.httpd = LoopbackHttpServer(("127.0.0.1", 0),
- request_handler)
- #print "Serving HTTP on %s port %s" % (self.httpd.server_name,
- # self.httpd.server_port)
- self.port = self.httpd.server_port
-
- def stop(self):
- """Stops the webserver if it's currently running."""
-
- # Set the stop flag.
- self._stop_server = True
-
- self.join()
- self.httpd.server_close()
-
- def run(self):
- self.ready.set()
- while not self._stop_server:
- self.httpd.handle_request()
-
-# Authentication infrastructure
-
-class DigestAuthHandler(object):
- """Handler for performing digest authentication."""
-
- def __init__(self):
- self._request_num = 0
- self._nonces = []
- self._users = {}
- self._realm_name = "Test Realm"
- self._qop = "auth"
-
- def set_qop(self, qop):
- self._qop = qop
-
- def set_users(self, users):
- assert isinstance(users, dict)
- self._users = users
-
- def set_realm(self, realm):
- self._realm_name = realm
-
- def _generate_nonce(self):
- self._request_num += 1
- nonce = hashlib.md5(str(self._request_num).encode("ascii")).hexdigest()
- self._nonces.append(nonce)
- return nonce
-
- def _create_auth_dict(self, auth_str):
- first_space_index = auth_str.find(" ")
- auth_str = auth_str[first_space_index+1:]
-
- parts = auth_str.split(",")
-
- auth_dict = {}
- for part in parts:
- name, value = part.split("=")
- name = name.strip()
- if value[0] == '"' and value[-1] == '"':
- value = value[1:-1]
- else:
- value = value.strip()
- auth_dict[name] = value
- return auth_dict
-
- def _validate_auth(self, auth_dict, password, method, uri):
- final_dict = {}
- final_dict.update(auth_dict)
- final_dict["password"] = password
- final_dict["method"] = method
- final_dict["uri"] = uri
- HA1_str = "%(username)s:%(realm)s:%(password)s" % final_dict
- HA1 = hashlib.md5(HA1_str.encode("ascii")).hexdigest()
- HA2_str = "%(method)s:%(uri)s" % final_dict
- HA2 = hashlib.md5(HA2_str.encode("ascii")).hexdigest()
- final_dict["HA1"] = HA1
- final_dict["HA2"] = HA2
- response_str = "%(HA1)s:%(nonce)s:%(nc)s:" \
- "%(cnonce)s:%(qop)s:%(HA2)s" % final_dict
- response = hashlib.md5(response_str.encode("ascii")).hexdigest()
-
- return response == auth_dict["response"]
-
- def _return_auth_challenge(self, request_handler):
- request_handler.send_response(407, "Proxy Authentication Required")
- request_handler.send_header("Content-Type", "text/html")
- request_handler.send_header(
- 'Proxy-Authenticate', 'Digest realm="%s", '
- 'qop="%s",'
- 'nonce="%s", ' % \
- (self._realm_name, self._qop, self._generate_nonce()))
- # XXX: Not sure if we're supposed to add this next header or
- # not.
- #request_handler.send_header('Connection', 'close')
- request_handler.end_headers()
- request_handler.wfile.write(b"Proxy Authentication Required.")
- return False
-
- def handle_request(self, request_handler):
- """Performs digest authentication on the given HTTP request
- handler. Returns True if authentication was successful, False
- otherwise.
-
- If no users have been set, then digest auth is effectively
- disabled and this method will always return True.
- """
-
- if len(self._users) == 0:
- return True
-
- if "Proxy-Authorization" not in request_handler.headers:
- return self._return_auth_challenge(request_handler)
- else:
- auth_dict = self._create_auth_dict(
- request_handler.headers["Proxy-Authorization"]
- )
- if auth_dict["username"] in self._users:
- password = self._users[ auth_dict["username"] ]
- else:
- return self._return_auth_challenge(request_handler)
- if not auth_dict.get("nonce") in self._nonces:
- return self._return_auth_challenge(request_handler)
- else:
- self._nonces.remove(auth_dict["nonce"])
-
- auth_validated = False
-
- # MSIE uses short_path in its validation, but Python's
- # urllib_request uses the full path, so we're going to see if
- # either of them works here.
-
- for path in [request_handler.path, request_handler.short_path]:
- if self._validate_auth(auth_dict,
- password,
- request_handler.command,
- path):
- auth_validated = True
-
- if not auth_validated:
- return self._return_auth_challenge(request_handler)
- return True
-
-# Proxy test infrastructure
-
-class FakeProxyHandler(http.server.BaseHTTPRequestHandler):
- """This is a 'fake proxy' that makes it look like the entire
- internet has gone down due to a sudden zombie invasion. It main
- utility is in providing us with authentication support for
- testing.
- """
-
- def __init__(self, digest_auth_handler, *args, **kwargs):
- # This has to be set before calling our parent's __init__(), which will
- # try to call do_GET().
- self.digest_auth_handler = digest_auth_handler
- http.server.BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
-
- def log_message(self, format, *args):
- # Uncomment the next line for debugging.
- # sys.stderr.write(format % args)
- pass
-
- def do_GET(self):
- (scm, netloc, path, params, query, fragment) = urllib_parse.urlparse(
- self.path, "http")
- self.short_path = path
- if self.digest_auth_handler.handle_request(self):
- self.send_response(200, "OK")
- self.send_header("Content-Type", "text/html")
- self.end_headers()
- self.wfile.write(bytes("You've reached %s!
" % self.path,
- "ascii"))
- self.wfile.write(b"Our apologies, but our server is down due to "
- b"a sudden zombie invasion.")
-
-# Test cases
-
-class ProxyAuthTests(unittest.TestCase):
- URL = "http://localhost"
-
- USER = "tester"
- PASSWD = "test123"
- REALM = "TestRealm"
-
- def setUp(self):
- super(ProxyAuthTests, self).setUp()
- self.digest_auth_handler = DigestAuthHandler()
- self.digest_auth_handler.set_users({self.USER: self.PASSWD})
- self.digest_auth_handler.set_realm(self.REALM)
- def create_fake_proxy_handler(*args, **kwargs):
- return FakeProxyHandler(self.digest_auth_handler, *args, **kwargs)
-
- self.server = LoopbackHttpServerThread(create_fake_proxy_handler)
- self.server.start()
- self.server.ready.wait()
- proxy_url = "http://127.0.0.1:%d" % self.server.port
- handler = urllib_request.ProxyHandler({"http" : proxy_url})
- self.proxy_digest_handler = urllib_request.ProxyDigestAuthHandler()
- self.opener = urllib_request.build_opener(
- handler, self.proxy_digest_handler)
-
- def tearDown(self):
- self.server.stop()
- super(ProxyAuthTests, self).tearDown()
-
- def test_proxy_with_bad_password_raises_httperror(self):
- self.proxy_digest_handler.add_password(self.REALM, self.URL,
- self.USER, self.PASSWD+"bad")
- self.digest_auth_handler.set_qop("auth")
- self.assertRaises(urllib_error.HTTPError,
- self.opener.open,
- self.URL)
-
- def test_proxy_with_no_password_raises_httperror(self):
- self.digest_auth_handler.set_qop("auth")
- self.assertRaises(urllib_error.HTTPError,
- self.opener.open,
- self.URL)
-
- def test_proxy_qop_auth_works(self):
- self.proxy_digest_handler.add_password(self.REALM, self.URL,
- self.USER, self.PASSWD)
- self.digest_auth_handler.set_qop("auth")
- result = self.opener.open(self.URL)
- while result.read():
- pass
- result.close()
-
- def test_proxy_qop_auth_int_works_or_throws_urlerror(self):
- self.proxy_digest_handler.add_password(self.REALM, self.URL,
- self.USER, self.PASSWD)
- self.digest_auth_handler.set_qop("auth-int")
- try:
- result = self.opener.open(self.URL)
- except urllib_error.URLError:
- # It's okay if we don't support auth-int, but we certainly
- # shouldn't receive any kind of exception here other than
- # a URLError.
- result = None
- if result:
- while result.read():
- pass
- result.close()
-
-
-def GetRequestHandler(responses):
-
- class FakeHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
-
- server_version = "TestHTTP/"
- requests = []
- headers_received = []
- port = 80
-
- def do_GET(self):
- body = self.send_head()
- while body:
- done = self.wfile.write(body)
- body = body[done:]
-
- def do_POST(self):
- content_length = self.headers["Content-Length"]
- post_data = self.rfile.read(int(content_length))
- self.do_GET()
- self.requests.append(post_data)
-
- def send_head(self):
- FakeHTTPRequestHandler.headers_received = self.headers
- self.requests.append(self.path)
- response_code, headers, body = responses.pop(0)
-
- self.send_response(response_code)
-
- for (header, value) in headers:
- self.send_header(header, value % {'port':self.port})
- if body:
- self.send_header("Content-type", "text/plain")
- self.end_headers()
- return body
- self.end_headers()
-
- def log_message(self, *args):
- pass
-
-
- return FakeHTTPRequestHandler
-
-
-class TestUrlopen(unittest.TestCase):
- """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
- tests exercising the optional 'data' and 'proxies' arguments. No tests
- for transparent redirection have been written.
- """
-
- def setUp(self):
- super(TestUrlopen, self).setUp()
- # Ignore proxies for localhost tests.
- self.old_environ = os.environ.copy()
- os.environ['NO_PROXY'] = '*'
- self.server = None
-
- def tearDown(self):
- if self.server is not None:
- self.server.stop()
- os.environ.clear()
- os.environ.update(self.old_environ)
- super(TestUrlopen, self).tearDown()
-
- def urlopen(self, url, data=None, **kwargs):
- l = []
- self.skipTest('urlopen is waiting forever ...')
- f = urllib_request.urlopen(url, data, **kwargs)
- try:
- # Exercise various methods
- l.extend(f.readlines(200))
- l.append(f.readline())
- l.append(f.read(1024))
- l.append(f.read())
- finally:
- f.close()
- return b"".join(l)
-
- def start_server(self, responses=None):
- if responses is None:
- responses = [(200, [], b"we don't care")]
- handler = GetRequestHandler(responses)
-
- self.server = LoopbackHttpServerThread(handler)
- self.server.start()
- self.server.ready.wait()
- port = self.server.port
- handler.port = port
- return handler
-
- def start_https_server(self, responses=None, certfile=CERT_localhost):
- if not hasattr(urllib_request, 'HTTPSHandler'):
- self.skipTest('ssl support required')
- from future.standard_library.test.ssl_servers import make_https_server
- if responses is None:
- responses = [(200, [], b"we care a bit")]
- handler = GetRequestHandler(responses)
- server = make_https_server(self, certfile=certfile, handler_class=handler)
- handler.port = server.port
- return handler
-
- def test_redirection(self):
- expected_response = b"We got here..."
- responses = [
- (302, [("Location", "http://localhost:%(port)s/somewhere_else")],
- ""),
- (200, [], expected_response)
- ]
-
- handler = self.start_server(responses)
- data = self.urlopen("http://localhost:%s/" % handler.port)
- self.assertEqual(data, expected_response)
- self.assertEqual(handler.requests, ["/", "/somewhere_else"])
-
- def test_chunked(self):
- expected_response = b"hello world"
- chunked_start = (
- b'a\r\n'
- b'hello worl\r\n'
- b'1\r\n'
- b'd\r\n'
- b'0\r\n'
- )
- response = [(200, [("Transfer-Encoding", "chunked")], chunked_start)]
- handler = self.start_server(response)
- data = self.urlopen("http://localhost:%s/" % handler.port)
- self.assertEqual(data, expected_response)
-
- def test_404(self):
- expected_response = b"Bad bad bad..."
- handler = self.start_server([(404, [], expected_response)])
-
- try:
- self.urlopen("http://localhost:%s/weeble" % handler.port)
- except urllib_error.URLError as f:
- data = f.read()
- f.close()
- else:
- self.fail("404 should raise URLError")
-
- self.assertEqual(data, expected_response)
- self.assertEqual(handler.requests, ["/weeble"])
-
- def test_200(self):
- expected_response = b"pycon 2008..."
- handler = self.start_server([(200, [], expected_response)])
- data = self.urlopen("http://localhost:%s/bizarre" % handler.port)
- self.assertEqual(data, expected_response)
- self.assertEqual(handler.requests, ["/bizarre"])
-
- def test_200_with_parameters(self):
- expected_response = b"pycon 2008..."
- handler = self.start_server([(200, [], expected_response)])
- data = self.urlopen("http://localhost:%s/bizarre" % handler.port,
- b"get=with_feeling")
- self.assertEqual(data, expected_response)
- self.assertEqual(handler.requests, ["/bizarre", b"get=with_feeling"])
-
- @unittest.skip('Py3.3 ssl module not yet backported')
- def test_https(self):
- handler = self.start_https_server()
- data = self.urlopen("https://localhost:%s/bizarre" % handler.port)
- self.assertEqual(data, b"we care a bit")
-
- @unittest.skip('Py3.3 ssl module not yet backported')
- def test_https_with_cafile(self):
- handler = self.start_https_server(certfile=CERT_localhost)
- import ssl
- # Good cert
- data = self.urlopen("https://localhost:%s/bizarre" % handler.port,
- cafile=CERT_localhost)
- self.assertEqual(data, b"we care a bit")
- # Bad cert
- with self.assertRaises(urllib_error.URLError) as cm:
- self.urlopen("https://localhost:%s/bizarre" % handler.port,
- cafile=CERT_fakehostname)
- # Good cert, but mismatching hostname
- handler = self.start_https_server(certfile=CERT_fakehostname)
- with self.assertRaises(ssl.CertificateError) as cm:
- self.urlopen("https://localhost:%s/bizarre" % handler.port,
- cafile=CERT_fakehostname)
-
- @unittest.skip('Py3.3 ssl module not yet backported')
- def test_https_with_cadefault(self):
- handler = self.start_https_server(certfile=CERT_localhost)
- # Self-signed cert should fail verification with system certificate store
- with self.assertRaises(urllib_error.URLError) as cm:
- self.urlopen("https://localhost:%s/bizarre" % handler.port,
- cadefault=True)
-
- def test_sending_headers(self):
- handler = self.start_server()
- req = urllib_request.Request("http://localhost:%s/" % handler.port,
- headers={"Range": "bytes=20-39"})
- urllib_request.urlopen(req)
- self.assertEqual(handler.headers_received["Range"], "bytes=20-39")
-
- @unittest.skip('urlopen is waiting forever')
- def test_basic(self):
- handler = self.start_server()
- open_url = urllib_request.urlopen("http://localhost:%s" % handler.port)
- for attr in ("read", "close", "info", "geturl"):
- self.assertTrue(hasattr(open_url, attr), "object returned from "
- "urlopen lacks the %s attribute" % attr)
- try:
- self.assertTrue(open_url.read(), "calling 'read' failed")
- finally:
- open_url.close()
-
- @unittest.skip('urlopen is waiting forever')
- def test_info(self):
- handler = self.start_server()
- try:
- open_url = urllib_request.urlopen(
- "http://localhost:%s" % handler.port)
- info_obj = open_url.info()
- self.assertIsInstance(info_obj, email_message.Message,
- "object returned by 'info' is not an "
- "instance of email_message.Message")
- self.assertEqual(info_obj.get_content_subtype(), "plain")
- finally:
- self.server.stop()
-
- @unittest.skip('urlopen is waiting forever')
- def test_geturl(self):
- # Make sure same URL as opened is returned by geturl.
- handler = self.start_server()
- open_url = urllib_request.urlopen("http://localhost:%s" % handler.port)
- url = open_url.geturl()
- self.assertEqual(url, "http://localhost:%s" % handler.port)
-
- def test_bad_address(self):
- # Make sure proper exception is raised when connecting to a bogus
- # address.
-
- # as indicated by the comment below, this might fail with some ISP,
- # so we run the test only when -unetwork/-uall is specified to
- # mitigate the problem a bit (see #17564)
- support.requires('network')
- self.assertRaises(IOError,
- # Given that both VeriSign and various ISPs have in
- # the past or are presently hijacking various invalid
- # domain name requests in an attempt to boost traffic
- # to their own sites, finding a domain name to use
- # for this test is difficult. RFC2606 leads one to
- # believe that '.invalid' should work, but experience
- # seemed to indicate otherwise. Single character
- # TLDs are likely to remain invalid, so this seems to
- # be the best choice. The trailing '.' prevents a
- # related problem: The normal DNS resolver appends
- # the domain names from the search path if there is
- # no '.' the end and, and if one of those domains
- # implements a '*' rule a result is returned.
- # However, none of this will prevent the test from
- # failing if the ISP hijacks all invalid domain
- # requests. The real solution would be to be able to
- # parameterize the framework with a mock resolver.
- urllib_request.urlopen,
- "http://sadflkjsasf.i.nvali.d./")
-
- @unittest.skip('urlopen is waiting forever')
- def test_iteration(self):
- expected_response = b"pycon 2008..."
- handler = self.start_server([(200, [], expected_response)])
- data = urllib_request.urlopen("http://localhost:%s" % handler.port)
- for line in data:
- self.assertEqual(line, expected_response)
-
- @unittest.skip('urlopen is waiting forever')
- def test_line_iteration(self):
- lines = [b"We\n", b"got\n", b"here\n", b"verylong " * 8192 + b"\n"]
- expected_response = b"".join(lines)
- handler = self.start_server([(200, [], expected_response)])
- data = urllib_request.urlopen("http://localhost:%s" % handler.port)
- for index, line in enumerate(data):
- self.assertEqual(line, lines[index],
- "Fetched line number %s doesn't match expected:\n"
- " Expected length was %s, got %s" %
- (index, len(lines[index]), len(line)))
- self.assertEqual(index + 1, len(lines))
-
-
-@support.reap_threads
-def test_main():
- support.run_unittest(ProxyAuthTests, TestUrlopen)
-
-if __name__ == "__main__":
- test_main()
diff --git a/tests/test_future/disabled/disabled_test_urllib2net.py b/tests/test_future/disabled/disabled_test_urllib2net.py
deleted file mode 100644
index ac6b55c5..00000000
--- a/tests/test_future/disabled/disabled_test_urllib2net.py
+++ /dev/null
@@ -1,402 +0,0 @@
-#!/usr/bin/env python3
-from __future__ import (absolute_import, division, print_function,
- unicode_literals)
-
-import os
-import socket
-import sys
-
-from future.standard_library.test import support
-import future.standard_library.urllib.error as urllib_error
-import future.standard_library.urllib.request as urllib_request
-from future.tests.base import unittest
-from future.builtins import open, range
-from future import utils
-
-from .test_urllib2 import sanepathname2url
-
-try:
- import ssl
-except ImportError:
- ssl = None
-
-TIMEOUT = 60 # seconds
-
-
-def _retry_thrice(func, exc, *args, **kwargs):
- for i in range(3):
- try:
- return func(*args, **kwargs)
- except exc as e:
- last_exc = e
- continue
- except:
- raise
- raise last_exc
-
-def _wrap_with_retry_thrice(func, exc):
- def wrapped(*args, **kwargs):
- return _retry_thrice(func, exc, *args, **kwargs)
- return wrapped
-
-# Connecting to remote hosts is flaky. Make it more robust by retrying
-# the connection several times.
-_urlopen_with_retry = _wrap_with_retry_thrice(urllib_request.urlopen,
- urllib_error.URLError)
-
-
-class AuthTests(unittest.TestCase):
- """Tests urllib2 authentication features."""
-
-## Disabled at the moment since there is no page under python.org which
-## could be used to HTTP authentication.
-#
-# def test_basic_auth(self):
-# import http.client
-#
-# test_url = "http://www.python.org/test/test_urllib2/basic_auth"
-# test_hostport = "www.python.org"
-# test_realm = 'Test Realm'
-# test_user = 'test.test_urllib2net'
-# test_password = 'blah'
-#
-# # failure
-# try:
-# _urlopen_with_retry(test_url)
-# except urllib2.HTTPError, exc:
-# self.assertEqual(exc.code, 401)
-# else:
-# self.fail("urlopen() should have failed with 401")
-#
-# # success
-# auth_handler = urllib2.HTTPBasicAuthHandler()
-# auth_handler.add_password(test_realm, test_hostport,
-# test_user, test_password)
-# opener = urllib2.build_opener(auth_handler)
-# f = opener.open('http://localhost/')
-# response = _urlopen_with_retry("http://www.python.org/")
-#
-# # The 'userinfo' URL component is deprecated by RFC 3986 for security
-# # reasons, let's not implement it! (it's already implemented for proxy
-# # specification strings (that is, URLs or authorities specifying a
-# # proxy), so we must keep that)
-# self.assertRaises(http.client.InvalidURL,
-# urllib2.urlopen, "http://evil:thing@example.com")
-
-
-class CloseSocketTest(unittest.TestCase):
-
- def test_close(self):
- # calling .close() on urllib2's response objects should close the
- # underlying socket
- url = "http://www.python.org/"
- with support.transient_internet(url):
- response = _urlopen_with_retry(url)
- sock = response.fp
- self.assertFalse(sock.closed)
- response.close()
- self.assertTrue(sock.closed)
-
-class OtherNetworkTests(unittest.TestCase):
- def setUp(self):
- if 0: # for debugging
- import logging
- logger = logging.getLogger("test_urllib2net")
- logger.addHandler(logging.StreamHandler())
-
- # XXX The rest of these tests aren't very good -- they don't check much.
- # They do sometimes catch some major disasters, though.
-
- def test_ftp(self):
- urls = [
- 'ftp://ftp.kernel.org/pub/linux/kernel/README',
- 'ftp://ftp.kernel.org/pub/linux/kernel/non-existent-file',
- #'ftp://ftp.kernel.org/pub/leenox/kernel/test',
- 'ftp://gatekeeper.research.compaq.com/pub/DEC/SRC'
- '/research-reports/00README-Legal-Rules-Regs',
- ]
- self._test_urls(urls, self._extra_handlers())
-
- def test_file(self):
- TESTFN = support.TESTFN
- f = open(TESTFN, 'w')
- try:
- f.write('hi there\n')
- f.close()
- urls = [
- 'file:' + sanepathname2url(os.path.abspath(TESTFN)),
- ('file:///nonsensename/etc/passwd', None,
- urllib_error.URLError),
- ]
- self._test_urls(urls, self._extra_handlers(), retry=True)
- finally:
- os.remove(TESTFN)
-
- self.assertRaises(ValueError, urllib_request.urlopen,'./relative_path/to/file')
-
- # XXX Following test depends on machine configurations that are internal
- # to CNRI. Need to set up a public server with the right authentication
- # configuration for test purposes.
-
-## def test_cnri(self):
-## if socket.gethostname() == 'bitdiddle':
-## localhost = 'bitdiddle.cnri.reston.va.us'
-## elif socket.gethostname() == 'bitdiddle.concentric.net':
-## localhost = 'localhost'
-## else:
-## localhost = None
-## if localhost is not None:
-## urls = [
-## 'file://%s/etc/passwd' % localhost,
-## 'http://%s/simple/' % localhost,
-## 'http://%s/digest/' % localhost,
-## 'http://%s/not/found.h' % localhost,
-## ]
-
-## bauth = HTTPBasicAuthHandler()
-## bauth.add_password('basic_test_realm', localhost, 'jhylton',
-## 'password')
-## dauth = HTTPDigestAuthHandler()
-## dauth.add_password('digest_test_realm', localhost, 'jhylton',
-## 'password')
-
-## self._test_urls(urls, self._extra_handlers()+[bauth, dauth])
-
- def test_urlwithfrag(self):
- urlwith_frag = "http://docs.python.org/2/glossary.html#glossary"
- with support.transient_internet(urlwith_frag):
- req = urllib_request.Request(urlwith_frag)
- res = urllib_request.urlopen(req)
- self.assertEqual(res.geturl(),
- "http://docs.python.org/2/glossary.html#glossary")
-
- def test_custom_headers(self):
- url = "http://www.example.com"
- with support.transient_internet(url):
- opener = urllib_request.build_opener()
- request = urllib_request.Request(url)
- self.assertFalse(request.header_items())
- opener.open(request)
- self.assertTrue(request.header_items())
- self.assertTrue(request.has_header('User-agent'))
- request.add_header('User-Agent','Test-Agent')
- opener.open(request)
- self.assertEqual(request.get_header('User-agent'),'Test-Agent')
-
- def test_sites_no_connection_close(self):
- # Some sites do not send Connection: close header.
- # Verify that those work properly. (#issue12576)
-
- URL = 'http://www.imdb.com' # mangles Connection:close
-
- with support.transient_internet(URL):
- try:
- with urllib_request.urlopen(URL) as res:
- pass
- except ValueError as e:
- self.fail("urlopen failed for site not sending \
- Connection:close")
- else:
- self.assertTrue(res)
-
- req = urllib_request.urlopen(URL)
- res = req.read()
- self.assertTrue(res)
-
- def _test_urls(self, urls, handlers, retry=True):
- import time
- import logging
- debug = logging.getLogger("test_urllib2").debug
-
- urlopen = urllib_request.build_opener(*handlers).open
- if retry:
- urlopen = _wrap_with_retry_thrice(urlopen, urllib_error.URLError)
-
- for url in urls:
- if isinstance(url, tuple):
- url, req, expected_err = url
- else:
- req = expected_err = None
-
- with support.transient_internet(url):
- debug(url)
- try:
- f = urlopen(url, req, TIMEOUT)
- except EnvironmentError as err:
- debug(err)
- if expected_err:
- msg = ("Didn't get expected error(s) %s for %s %s, got %s: %s" %
- (expected_err, url, req, type(err), err))
- self.assertIsInstance(err, expected_err, msg)
- except urllib_error.URLError as err:
- if isinstance(err[0], socket.timeout):
- print("" % url, file=sys.stderr)
- continue
- else:
- raise
- else:
- try:
- with support.time_out:
- with support.socket_peer_reset:
- with support.ioerror_peer_reset:
- buf = f.read()
- debug("read %d bytes" % len(buf))
- except socket.timeout:
- print("" % url, file=sys.stderr)
- f.close()
- debug("******** next url coming up...")
- time.sleep(0.1)
-
- def _extra_handlers(self):
- handlers = []
-
- cfh = urllib_request.CacheFTPHandler()
- self.addCleanup(cfh.clear_cache)
- cfh.setTimeout(1)
- handlers.append(cfh)
-
- return handlers
-
-
-class TimeoutTest(unittest.TestCase):
- def test_http_basic(self):
- self.assertIsNone(socket.getdefaulttimeout())
- url = "http://www.python.org"
- with support.transient_internet(url, timeout=None):
- u = _urlopen_with_retry(url)
- self.addCleanup(u.close)
- if utils.PY2:
- sock = u.fp._sock
- else:
- sock = u.fp.raw._sock
- self.assertIsNone(sock.gettimeout())
-
- def test_http_default_timeout(self):
- self.assertIsNone(socket.getdefaulttimeout())
- url = "http://www.python.org"
- with support.transient_internet(url):
- socket.setdefaulttimeout(60)
- try:
- u = _urlopen_with_retry(url)
- self.addCleanup(u.close)
- finally:
- socket.setdefaulttimeout(None)
- if utils.PY2:
- sock = u.fp._sock
- else:
- sock = u.fp.raw._sock
- self.assertEqual(sock.gettimeout(), 60)
-
- def test_http_no_timeout(self):
- self.assertIsNone(socket.getdefaulttimeout())
- url = "http://www.python.org"
- with support.transient_internet(url):
- socket.setdefaulttimeout(60)
- try:
- u = _urlopen_with_retry(url, timeout=None)
- self.addCleanup(u.close)
- finally:
- socket.setdefaulttimeout(None)
- if utils.PY2:
- sock = u.fp._sock
- else:
- sock = u.fp.raw._sock
- self.assertIsNone(sock.gettimeout())
-
- def test_http_timeout(self):
- url = "http://www.python.org"
- with support.transient_internet(url):
- u = _urlopen_with_retry(url, timeout=120)
- self.addCleanup(u.close)
- if utils.PY2:
- sock = u.fp._sock
- else:
- sock = u.fp.raw._sock
- self.assertEqual(sock.gettimeout(), 120)
-
- FTP_HOST = "ftp://ftp.mirror.nl/pub/gnu/"
-
- def test_ftp_basic(self):
- self.assertIsNone(socket.getdefaulttimeout())
- with support.transient_internet(self.FTP_HOST, timeout=None):
- u = _urlopen_with_retry(self.FTP_HOST)
- self.addCleanup(u.close)
- if utils.PY2:
- sock = u.fp.fp._sock
- else:
- sock = u.fp.fp.raw._sock
- self.assertIsNone(sock.gettimeout())
-
- def test_ftp_default_timeout(self):
- self.assertIsNone(socket.getdefaulttimeout())
- with support.transient_internet(self.FTP_HOST):
- socket.setdefaulttimeout(60)
- try:
- u = _urlopen_with_retry(self.FTP_HOST)
- self.addCleanup(u.close)
- finally:
- socket.setdefaulttimeout(None)
- if utils.PY2:
- sock = u.fp.fp._sock
- else:
- sock = u.fp.fp.raw._sock
- self.assertEqual(sock.gettimeout(), 60)
-
- def test_ftp_no_timeout(self):
- self.assertIsNone(socket.getdefaulttimeout())
- with support.transient_internet(self.FTP_HOST):
- socket.setdefaulttimeout(60)
- try:
- u = _urlopen_with_retry(self.FTP_HOST, timeout=None)
- self.addCleanup(u.close)
- finally:
- socket.setdefaulttimeout(None)
- if utils.PY2:
- sock = u.fp.fp._sock
- else:
- sock = u.fp.fp.raw._sock
- self.assertIsNone(sock.gettimeout())
-
- def test_ftp_timeout(self):
- with support.transient_internet(self.FTP_HOST):
- u = _urlopen_with_retry(self.FTP_HOST, timeout=60)
- self.addCleanup(u.close)
- if utils.PY2:
- sock = u.fp.fp._sock
- else:
- sock = u.fp.fp.raw._sock
- self.assertEqual(sock.gettimeout(), 60)
-
-
-@unittest.skipUnless(ssl, "requires SSL support")
-class HTTPSTests(unittest.TestCase):
-
- def test_sni(self):
- self.skipTest("test disabled - test server needed")
- # Checks that Server Name Indication works, if supported by the
- # OpenSSL linked to.
- # The ssl module itself doesn't have server-side support for SNI,
- # so we rely on a third-party test site.
- expect_sni = ssl.HAS_SNI
- with support.transient_internet("XXX"):
- u = urllib_request.urlopen("XXX")
- contents = u.readall()
- if expect_sni:
- self.assertIn(b"Great", contents)
- self.assertNotIn(b"Unfortunately", contents)
- else:
- self.assertNotIn(b"Great", contents)
- self.assertIn(b"Unfortunately", contents)
-
-
-def test_main():
- support.requires("network")
- support.run_unittest(AuthTests,
- HTTPSTests,
- OtherNetworkTests,
- CloseSocketTest,
- TimeoutTest,
- )
-
-if __name__ == "__main__":
- test_main()
diff --git a/tests/test_future/disabled/disabled_test_xmlrpc.py b/tests/test_future/disabled/disabled_test_xmlrpc.py
deleted file mode 100644
index 38c8354e..00000000
--- a/tests/test_future/disabled/disabled_test_xmlrpc.py
+++ /dev/null
@@ -1,1130 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-from future.builtins import int, str, super
-from future import standard_library
-import base64
-import datetime
-import sys
-import time
-from future.tests.base import unittest
-from future.standard_library.test import support
-with standard_library.hooks():
- import http.client
- import xmlrpc.client as xmlrpclib
- import xmlrpc.client # this crazy module refers to it under both names
- import xmlrpc.server
-import socket
-import os
-import re
-import io
-import contextlib
-from future.utils import PY3
-
-try:
- import threading
-except ImportError:
- threading = None
-
-try:
- import mock
-except ImportError:
- mock = None
-
-alist = [{'astring': 'foo@bar.baz.spam',
- 'afloat': 7283.43,
- 'anint': 2**20,
- 'ashortlong': 2,
- 'anotherlist': ['.zyx.41'],
- 'abase64': xmlrpclib.Binary(b"my dog has fleas"),
- 'b64bytes': b"my dog has fleas",
- 'b64bytearray': bytearray(b"my dog has fleas"),
- 'boolean': False,
- 'unicode': '\u4000\u6000\u8000',
- 'ukey\u4000': 'regular value',
- 'datetime1': xmlrpclib.DateTime('20050210T11:41:23'),
- 'datetime2': xmlrpclib.DateTime(
- (2005, 2, 10, 11, 41, 23, 0, 1, -1)),
- 'datetime3': xmlrpclib.DateTime(
- datetime.datetime(2005, 2, 10, 11, 41, 23)),
- }]
-
-class XMLRPCTestCase(unittest.TestCase):
-
- def test_dump_load(self):
- dump = xmlrpclib.dumps((alist,))
- load = xmlrpclib.loads(dump)
- self.assertEqual(alist, load[0][0])
-
- def test_dump_bare_datetime(self):
- # This checks that an unwrapped datetime.date object can be handled
- # by the marshalling code. This can't be done via test_dump_load()
- # since with use_builtin_types set to 1 the unmarshaller would create
- # datetime objects for the 'datetime[123]' keys as well
- dt = datetime.datetime(2005, 2, 10, 11, 41, 23)
- self.assertEqual(dt, xmlrpclib.DateTime('20050210T11:41:23'))
- s = xmlrpclib.dumps((dt,))
-
- result, m = xmlrpclib.loads(s, use_builtin_types=True)
- (newdt,) = result
- self.assertEqual(newdt, dt)
- self.assertIs(type(newdt), datetime.datetime)
- self.assertIsNone(m)
-
- result, m = xmlrpclib.loads(s, use_builtin_types=False)
- (newdt,) = result
- self.assertEqual(newdt, dt)
- self.assertIs(type(newdt), xmlrpclib.DateTime)
- self.assertIsNone(m)
-
- result, m = xmlrpclib.loads(s, use_datetime=True)
- (newdt,) = result
- self.assertEqual(newdt, dt)
- self.assertIs(type(newdt), datetime.datetime)
- self.assertIsNone(m)
-
- result, m = xmlrpclib.loads(s, use_datetime=False)
- (newdt,) = result
- self.assertEqual(newdt, dt)
- self.assertIs(type(newdt), xmlrpclib.DateTime)
- self.assertIsNone(m)
-
-
- def test_datetime_before_1900(self):
- # same as before but with a date before 1900
- dt = datetime.datetime(1, 2, 10, 11, 41, 23)
- self.assertEqual(dt, xmlrpclib.DateTime('00010210T11:41:23'))
- s = xmlrpclib.dumps((dt,))
-
- result, m = xmlrpclib.loads(s, use_builtin_types=True)
- (newdt,) = result
- self.assertEqual(newdt, dt)
- self.assertIs(type(newdt), datetime.datetime)
- self.assertIsNone(m)
-
- result, m = xmlrpclib.loads(s, use_builtin_types=False)
- (newdt,) = result
- self.assertEqual(newdt, dt)
- self.assertIs(type(newdt), xmlrpclib.DateTime)
- self.assertIsNone(m)
-
- def test_bug_1164912 (self):
- d = xmlrpclib.DateTime()
- ((new_d,), dummy) = xmlrpclib.loads(xmlrpclib.dumps((d,),
- methodresponse=True))
- self.assertIsInstance(new_d.value, str)
-
- # Check that the output of dumps() is still an 8-bit string
- s = xmlrpclib.dumps((new_d,), methodresponse=True)
- self.assertIsInstance(s, str)
-
- def test_newstyle_class(self):
- class T(object):
- pass
- t = T()
- t.x = 100
- t.y = "Hello"
- ((t2,), dummy) = xmlrpclib.loads(xmlrpclib.dumps((t,)))
- self.assertEqual(t2, t.__dict__)
-
- def test_dump_big_long(self):
- self.assertRaises(OverflowError, xmlrpclib.dumps, (2**99,))
-
- def test_dump_bad_dict(self):
- self.assertRaises(TypeError, xmlrpclib.dumps, ({(1,2,3): 1},))
-
- def test_dump_recursive_seq(self):
- l = [1,2,3]
- t = [3,4,5,l]
- l.append(t)
- self.assertRaises(TypeError, xmlrpclib.dumps, (l,))
-
- def test_dump_recursive_dict(self):
- d = {'1':1, '2':1}
- t = {'3':3, 'd':d}
- d['t'] = t
- self.assertRaises(TypeError, xmlrpclib.dumps, (d,))
-
- def test_dump_big_int(self):
- if sys.maxsize > 2**31-1:
- self.assertRaises(OverflowError, xmlrpclib.dumps,
- (int(2**34),))
-
- xmlrpclib.dumps((xmlrpclib.MAXINT, xmlrpclib.MININT))
- self.assertRaises(OverflowError, xmlrpclib.dumps,
- (xmlrpclib.MAXINT+1,))
- self.assertRaises(OverflowError, xmlrpclib.dumps,
- (xmlrpclib.MININT-1,))
-
- def dummy_write(s):
- pass
-
- m = xmlrpclib.Marshaller()
- m.dump_int(xmlrpclib.MAXINT, dummy_write)
- m.dump_int(xmlrpclib.MININT, dummy_write)
- self.assertRaises(OverflowError, m.dump_int,
- xmlrpclib.MAXINT+1, dummy_write)
- self.assertRaises(OverflowError, m.dump_int,
- xmlrpclib.MININT-1, dummy_write)
-
- def test_dump_double(self):
- xmlrpclib.dumps((float(2 ** 34),))
- xmlrpclib.dumps((float(xmlrpclib.MAXINT),
- float(xmlrpclib.MININT)))
- xmlrpclib.dumps((float(xmlrpclib.MAXINT + 42),
- float(xmlrpclib.MININT - 42)))
-
- def dummy_write(s):
- pass
-
- m = xmlrpclib.Marshaller()
- m.dump_double(xmlrpclib.MAXINT, dummy_write)
- m.dump_double(xmlrpclib.MININT, dummy_write)
- m.dump_double(xmlrpclib.MAXINT + 42, dummy_write)
- m.dump_double(xmlrpclib.MININT - 42, dummy_write)
-
- def test_dump_none(self):
- value = alist + [None]
- arg1 = (alist + [None],)
- strg = xmlrpclib.dumps(arg1, allow_none=True)
- self.assertEqual(value,
- xmlrpclib.loads(strg)[0][0])
- self.assertRaises(TypeError, xmlrpclib.dumps, (arg1,))
-
- def test_dump_bytes(self):
- sample = b"my dog has fleas"
- self.assertEqual(sample, xmlrpclib.Binary(sample))
- for type_ in bytes, bytearray, xmlrpclib.Binary:
- value = type_(sample)
- s = xmlrpclib.dumps((value,))
-
- result, m = xmlrpclib.loads(s, use_builtin_types=True)
- (newvalue,) = result
- self.assertEqual(newvalue, sample)
- self.assertIs(type(newvalue), bytes)
- self.assertIsNone(m)
-
- result, m = xmlrpclib.loads(s, use_builtin_types=False)
- (newvalue,) = result
- self.assertEqual(newvalue, sample)
- self.assertIs(type(newvalue), xmlrpclib.Binary)
- self.assertIsNone(m)
-
- def test_get_host_info(self):
- # see bug #3613, this raised a TypeError
- transp = xmlrpc.client.Transport()
- self.assertEqual(transp.get_host_info("user@host.tld"),
- ('host.tld',
- [('Authorization', 'Basic dXNlcg==')], {}))
-
- def test_ssl_presence(self):
- try:
- import ssl
- except ImportError:
- has_ssl = False
- else:
- has_ssl = True
- try:
- xmlrpc.client.ServerProxy('https://localhost:9999').bad_function()
- except NotImplementedError:
- self.assertFalse(has_ssl, "xmlrpc client's error with SSL support")
- except socket.error:
- self.assertTrue(has_ssl)
-
-class HelperTestCase(unittest.TestCase):
- def test_escape(self):
- self.assertEqual(xmlrpclib.escape("a&b"), "a&b")
- self.assertEqual(xmlrpclib.escape("ab"), "a>b")
-
-class FaultTestCase(unittest.TestCase):
- def test_repr(self):
- f = xmlrpclib.Fault(42, 'Test Fault')
- self.assertEqual(repr(f), "")
- self.assertEqual(repr(f), str(f))
-
- def test_dump_fault(self):
- f = xmlrpclib.Fault(42, 'Test Fault')
- s = xmlrpclib.dumps((f,))
- (newf,), m = xmlrpclib.loads(s)
- self.assertEqual(newf, {'faultCode': 42, 'faultString': 'Test Fault'})
- self.assertEqual(m, None)
-
- s = xmlrpclib.Marshaller().dumps(f)
- self.assertRaises(xmlrpclib.Fault, xmlrpclib.loads, s)
-
- def test_dotted_attribute(self):
- # this will raise AttributeError because code don't want us to use
- # private methods
- self.assertRaises(AttributeError,
- xmlrpc.server.resolve_dotted_attribute, str, '__add')
- self.assertTrue(xmlrpc.server.resolve_dotted_attribute(str, 'title'))
-
-class DateTimeTestCase(unittest.TestCase):
- @unittest.skipIf(mock is None, "this test requires the mock library")
- def test_default(self):
- with mock.patch('time.localtime') as localtime_mock:
- time_struct = time.struct_time(
- [2013, 7, 15, 0, 24, 49, 0, 196, 0])
- localtime_mock.return_value = time_struct
- localtime = time.localtime()
- t = xmlrpclib.DateTime()
- self.assertEqual(str(t),
- time.strftime("%Y%m%dT%H:%M:%S", localtime))
-
- def test_time(self):
- d = 1181399930.036952
- t = xmlrpclib.DateTime(d)
- self.assertEqual(str(t),
- time.strftime("%Y%m%dT%H:%M:%S", time.localtime(d)))
-
- def test_time_tuple(self):
- d = (2007,6,9,10,38,50,5,160,0)
- t = xmlrpclib.DateTime(d)
- self.assertEqual(str(t), '20070609T10:38:50')
-
- def test_time_struct(self):
- d = time.localtime(1181399930.036952)
- t = xmlrpclib.DateTime(d)
- self.assertEqual(str(t), time.strftime("%Y%m%dT%H:%M:%S", d))
-
- def test_datetime_datetime(self):
- d = datetime.datetime(2007,1,2,3,4,5)
- t = xmlrpclib.DateTime(d)
- self.assertEqual(str(t), '20070102T03:04:05')
-
- def test_repr(self):
- d = datetime.datetime(2007,1,2,3,4,5)
- t = xmlrpclib.DateTime(d)
- val ="" % id(t)
- self.assertEqual(repr(t), val)
-
- def test_decode(self):
- d = ' 20070908T07:11:13 '
- t1 = xmlrpclib.DateTime()
- t1.decode(d)
- tref = xmlrpclib.DateTime(datetime.datetime(2007,9,8,7,11,13))
- self.assertEqual(t1, tref)
-
- t2 = xmlrpclib._datetime(d)
- self.assertEqual(t2, tref)
-
- def test_comparison(self):
- now = datetime.datetime.now()
- dtime = xmlrpclib.DateTime(now.timetuple())
-
- # datetime vs. DateTime
- self.assertTrue(dtime == now)
- self.assertTrue(now == dtime)
- then = now + datetime.timedelta(seconds=4)
- self.assertTrue(then >= dtime)
- self.assertTrue(dtime < then)
-
- # str vs. DateTime
- dstr = now.strftime("%Y%m%dT%H:%M:%S")
- self.assertTrue(dtime == dstr)
- self.assertTrue(dstr == dtime)
- dtime_then = xmlrpclib.DateTime(then.timetuple())
- self.assertTrue(dtime_then >= dstr)
- self.assertTrue(dstr < dtime_then)
-
- # some other types
- dbytes = dstr.encode('ascii')
- dtuple = now.timetuple()
- with self.assertRaises(TypeError):
- dtime == 1970
- with self.assertRaises(TypeError):
- dtime != dbytes
- with self.assertRaises(TypeError):
- dtime == bytearray(dbytes)
- with self.assertRaises(TypeError):
- dtime != dtuple
- with self.assertRaises(TypeError):
- dtime < float(1970)
- with self.assertRaises(TypeError):
- dtime > dbytes
- with self.assertRaises(TypeError):
- dtime <= bytearray(dbytes)
- with self.assertRaises(TypeError):
- dtime >= dtuple
-
-class BinaryTestCase(unittest.TestCase):
-
- # XXX What should str(Binary(b"\xff")) return? I'm chosing "\xff"
- # for now (i.e. interpreting the binary data as Latin-1-encoded
- # text). But this feels very unsatisfactory. Perhaps we should
- # only define repr(), and return r"Binary(b'\xff')" instead?
-
- def test_default(self):
- t = xmlrpclib.Binary()
- self.assertEqual(str(t), '')
-
- def test_string(self):
- d = b'\x01\x02\x03abc123\xff\xfe'
- t = xmlrpclib.Binary(d)
- self.assertEqual(str(t), str(d, "latin-1"))
-
- def test_decode(self):
- d = b'\x01\x02\x03abc123\xff\xfe'
- de = base64.encodebytes(d)
- t1 = xmlrpclib.Binary()
- t1.decode(de)
- self.assertEqual(str(t1), str(d, "latin-1"))
-
- t2 = xmlrpclib._binary(de)
- self.assertEqual(str(t2), str(d, "latin-1"))
-
-
-ADDR = PORT = URL = None
-
-# The evt is set twice. First when the server is ready to serve.
-# Second when the server has been shutdown. The user must clear
-# the event after it has been set the first time to catch the second set.
-def http_server(evt, numrequests, requestHandler=None):
- class TestInstanceClass(object):
- def div(self, x, y):
- return x // y
-
- def _methodHelp(self, name):
- if name == 'div':
- return 'This is the div function'
-
- def my_function():
- '''This is my function'''
- return True
-
- class MyXMLRPCServer(xmlrpc.server.SimpleXMLRPCServer):
- def get_request(self):
- # Ensure the socket is always non-blocking. On Linux, socket
- # attributes are not inherited like they are on *BSD and Windows.
- s, port = self.socket.accept()
- s.setblocking(True)
- return s, port
-
- if not requestHandler:
- requestHandler = xmlrpc.server.SimpleXMLRPCRequestHandler
- serv = MyXMLRPCServer(("localhost", 0), requestHandler,
- logRequests=False, bind_and_activate=False)
- try:
- serv.server_bind()
- global ADDR, PORT, URL
- ADDR, PORT = serv.socket.getsockname()
- #connect to IP address directly. This avoids socket.create_connection()
- #trying to connect to "localhost" using all address families, which
- #causes slowdown e.g. on vista which supports AF_INET6. The server listens
- #on AF_INET only.
- URL = "http://%s:%d"%(ADDR, PORT)
- serv.server_activate()
- serv.register_introspection_functions()
- serv.register_multicall_functions()
- serv.register_function(pow)
- serv.register_function(lambda x,y: x+y, 'add')
- serv.register_function(my_function)
- serv.register_instance(TestInstanceClass())
- evt.set()
-
- # handle up to 'numrequests' requests
- while numrequests > 0:
- serv.handle_request()
- numrequests -= 1
-
- except socket.timeout:
- pass
- finally:
- serv.socket.close()
- PORT = None
- evt.set()
-
-def http_multi_server(evt, numrequests, requestHandler=None):
- class TestInstanceClass(object):
- def div(self, x, y):
- return x // y
-
- def _methodHelp(self, name):
- if name == 'div':
- return 'This is the div function'
-
- def my_function():
- '''This is my function'''
- return True
-
- class MyXMLRPCServer(xmlrpc.server.MultiPathXMLRPCServer):
- def get_request(self):
- # Ensure the socket is always non-blocking. On Linux, socket
- # attributes are not inherited like they are on *BSD and Windows.
- s, port = self.socket.accept()
- s.setblocking(True)
- return s, port
-
- if not requestHandler:
- requestHandler = xmlrpc.server.SimpleXMLRPCRequestHandler
- class MyRequestHandler(requestHandler):
- rpc_paths = []
-
- class BrokenDispatcher(object):
- def _marshaled_dispatch(self, data, dispatch_method=None, path=None):
- raise RuntimeError("broken dispatcher")
-
- serv = MyXMLRPCServer(("localhost", 0), MyRequestHandler,
- logRequests=False, bind_and_activate=False)
- serv.socket.settimeout(3)
- serv.server_bind()
- try:
- global ADDR, PORT, URL
- ADDR, PORT = serv.socket.getsockname()
- #connect to IP address directly. This avoids socket.create_connection()
- #trying to connect to "localhost" using all address families, which
- #causes slowdown e.g. on vista which supports AF_INET6. The server listens
- #on AF_INET only.
- URL = "http://%s:%d"%(ADDR, PORT)
- serv.server_activate()
- paths = ["/foo", "/foo/bar"]
- for path in paths:
- d = serv.add_dispatcher(path, xmlrpc.server.SimpleXMLRPCDispatcher())
- d.register_introspection_functions()
- d.register_multicall_functions()
- serv.get_dispatcher(paths[0]).register_function(pow)
- serv.get_dispatcher(paths[1]).register_function(lambda x,y: x+y, 'add')
- serv.add_dispatcher("/is/broken", BrokenDispatcher())
- evt.set()
-
- # handle up to 'numrequests' requests
- while numrequests > 0:
- serv.handle_request()
- numrequests -= 1
-
- except socket.timeout:
- pass
- finally:
- serv.socket.close()
- PORT = None
- evt.set()
-
-# This function prevents errors like:
-#
-def is_unavailable_exception(e):
- '''Returns True if the given ProtocolError is the product of a server-side
- exception caused by the 'temporarily unavailable' response sometimes
- given by operations on non-blocking sockets.'''
-
- # sometimes we get a -1 error code and/or empty headers
- try:
- if e.errcode == -1 or e.headers is None:
- return True
- exc_mess = e.headers.get('X-exception')
- except AttributeError:
- # Ignore socket.errors here.
- exc_mess = str(e)
-
- if exc_mess and 'temporarily unavailable' in exc_mess.lower():
- return True
-
-def make_request_and_skipIf(condition, reason):
- # If we skip the test, we have to make a request because the
- # the server created in setUp blocks expecting one to come in.
- if not condition:
- return lambda func: func
- def decorator(func):
- def make_request_and_skip(self):
- try:
- xmlrpclib.ServerProxy(URL).my_function()
- except (xmlrpclib.ProtocolError, socket.error) as e:
- if not is_unavailable_exception(e):
- raise
- raise unittest.SkipTest(reason)
- return make_request_and_skip
- return decorator
-
-@unittest.skipUnless(threading, 'Threading required for this test.')
-class BaseServerTestCase(unittest.TestCase):
- requestHandler = None
- request_count = 1
- threadFunc = staticmethod(http_server)
-
- def setUp(self):
- # enable traceback reporting
- xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = True
-
- self.evt = threading.Event()
- # start server thread to handle requests
- serv_args = (self.evt, self.request_count, self.requestHandler)
- threading.Thread(target=self.threadFunc, args=serv_args).start()
-
- # wait for the server to be ready
- self.evt.wait()
- self.evt.clear()
-
- def tearDown(self):
- # wait on the server thread to terminate
- self.evt.wait()
-
- # disable traceback reporting
- xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = False
-
-@unittest.skipIf(sys.version_info[:2] == (2, 6),
- 'test seems to hang on Py2.6')
-class SimpleServerTestCase(BaseServerTestCase):
- def test_simple1(self):
- try:
- p = xmlrpclib.ServerProxy(URL)
- self.assertEqual(p.pow(6,8), 6**8)
- except (xmlrpclib.ProtocolError, socket.error) as e:
- # ignore failures due to non-blocking socket 'unavailable' errors
- if not is_unavailable_exception(e):
- # protocol error; provide additional information in test output
- self.fail("%s\n%s" % (e, getattr(e, "headers", "")))
-
- def test_nonascii(self):
- start_string = 'P\N{LATIN SMALL LETTER Y WITH CIRCUMFLEX}t'
- end_string = 'h\N{LATIN SMALL LETTER O WITH HORN}n'
- try:
- p = xmlrpclib.ServerProxy(URL)
- self.assertEqual(p.add(start_string, end_string),
- start_string + end_string)
- except (xmlrpclib.ProtocolError, socket.error) as e:
- # ignore failures due to non-blocking socket 'unavailable' errors
- if not is_unavailable_exception(e):
- # protocol error; provide additional information in test output
- self.fail("%s\n%s" % (e, getattr(e, "headers", "")))
-
- # [ch] The test 404 is causing lots of false alarms.
- def XXXtest_404(self):
- # send POST with http.client, it should return 404 header and
- # 'Not Found' message.
- conn = http.client.HTTPConnection(ADDR, PORT)
- conn.request('POST', '/this-is-not-valid')
- response = conn.getresponse()
- conn.close()
-
- self.assertEqual(response.status, 404)
- self.assertEqual(response.reason, 'Not Found')
-
- def test_introspection1(self):
- expected_methods = set(['pow', 'div', 'my_function', 'add',
- 'system.listMethods', 'system.methodHelp',
- 'system.methodSignature', 'system.multicall'])
- try:
- p = xmlrpclib.ServerProxy(URL)
- meth = p.system.listMethods()
- self.assertEqual(set(meth), expected_methods)
- except (xmlrpclib.ProtocolError, socket.error) as e:
- # ignore failures due to non-blocking socket 'unavailable' errors
- if not is_unavailable_exception(e):
- # protocol error; provide additional information in test output
- self.fail("%s\n%s" % (e, getattr(e, "headers", "")))
-
-
- def test_introspection2(self):
- try:
- # test _methodHelp()
- p = xmlrpclib.ServerProxy(URL)
- divhelp = p.system.methodHelp('div')
- self.assertEqual(divhelp, 'This is the div function')
- except (xmlrpclib.ProtocolError, socket.error) as e:
- # ignore failures due to non-blocking socket 'unavailable' errors
- if not is_unavailable_exception(e):
- # protocol error; provide additional information in test output
- self.fail("%s\n%s" % (e, getattr(e, "headers", "")))
-
- @make_request_and_skipIf(sys.flags.optimize >= 2,
- "Docstrings are omitted with -O2 and above")
- def test_introspection3(self):
- try:
- # test native doc
- p = xmlrpclib.ServerProxy(URL)
- myfunction = p.system.methodHelp('my_function')
- self.assertEqual(myfunction, 'This is my function')
- except (xmlrpclib.ProtocolError, socket.error) as e:
- # ignore failures due to non-blocking socket 'unavailable' errors
- if not is_unavailable_exception(e):
- # protocol error; provide additional information in test output
- self.fail("%s\n%s" % (e, getattr(e, "headers", "")))
-
- def test_introspection4(self):
- # the SimpleXMLRPCServer doesn't support signatures, but
- # at least check that we can try making the call
- try:
- p = xmlrpclib.ServerProxy(URL)
- divsig = p.system.methodSignature('div')
- self.assertEqual(divsig, 'signatures not supported')
- except (xmlrpclib.ProtocolError, socket.error) as e:
- # ignore failures due to non-blocking socket 'unavailable' errors
- if not is_unavailable_exception(e):
- # protocol error; provide additional information in test output
- self.fail("%s\n%s" % (e, getattr(e, "headers", "")))
-
- def test_multicall(self):
- try:
- p = xmlrpclib.ServerProxy(URL)
- multicall = xmlrpclib.MultiCall(p)
- multicall.add(2,3)
- multicall.pow(6,8)
- multicall.div(127,42)
- add_result, pow_result, div_result = multicall()
- self.assertEqual(add_result, 2+3)
- self.assertEqual(pow_result, 6**8)
- self.assertEqual(div_result, 127//42)
- except (xmlrpclib.ProtocolError, socket.error) as e:
- # ignore failures due to non-blocking socket 'unavailable' errors
- if not is_unavailable_exception(e):
- # protocol error; provide additional information in test output
- self.fail("%s\n%s" % (e, getattr(e, "headers", "")))
-
- def test_non_existing_multicall(self):
- try:
- p = xmlrpclib.ServerProxy(URL)
- multicall = xmlrpclib.MultiCall(p)
- multicall.this_is_not_exists()
- result = multicall()
-
- # result.results contains;
- # [{'faultCode': 1, 'faultString': ':'
- # 'method "this_is_not_exists" is not supported'>}]
-
- self.assertEqual(result.results[0]['faultCode'], 1)
- self.assertEqual(result.results[0]['faultString'],
- ':method "this_is_not_exists" '
- 'is not supported')
- except (xmlrpclib.ProtocolError, socket.error) as e:
- # ignore failures due to non-blocking socket 'unavailable' errors
- if not is_unavailable_exception(e):
- # protocol error; provide additional information in test output
- self.fail("%s\n%s" % (e, getattr(e, "headers", "")))
-
- def test_dotted_attribute(self):
- # Raises an AttributeError because private methods are not allowed.
- self.assertRaises(AttributeError,
- xmlrpc.server.resolve_dotted_attribute, str, '__add')
-
- self.assertTrue(xmlrpc.server.resolve_dotted_attribute(str, 'title'))
- # Get the test to run faster by sending a request with test_simple1.
- # This avoids waiting for the socket timeout.
- self.test_simple1()
-
- def test_unicode_host(self):
- server = xmlrpclib.ServerProxy("http://%s:%d/RPC2" % (ADDR, PORT))
- self.assertEqual(server.add("a", "\xe9"), "a\xe9")
-
- def test_partial_post(self):
- # Check that a partial POST doesn't make the server loop: issue #14001.
- conn = http.client.HTTPConnection(ADDR, PORT)
- conn.request('POST', '/RPC2 HTTP/1.0\r\nContent-Length: 100\r\n\r\nbye')
- conn.close()
-
-
-@unittest.skipIf(sys.version_info[:2] == (2, 6),
- 'test seems to hang on Py2.6')
-class MultiPathServerTestCase(BaseServerTestCase):
- threadFunc = staticmethod(http_multi_server)
- request_count = 2
- def test_path1(self):
- p = xmlrpclib.ServerProxy(URL+"/foo")
- self.assertEqual(p.pow(6,8), 6**8)
- self.assertRaises(xmlrpclib.Fault, p.add, 6, 8)
-
- def test_path2(self):
- p = xmlrpclib.ServerProxy(URL+"/foo/bar")
- self.assertEqual(p.add(6,8), 6+8)
- self.assertRaises(xmlrpclib.Fault, p.pow, 6, 8)
-
- def test_path3(self):
- p = xmlrpclib.ServerProxy(URL+"/is/broken")
- self.assertRaises(xmlrpclib.Fault, p.add, 6, 8)
-
-#A test case that verifies that a server using the HTTP/1.1 keep-alive mechanism
-#does indeed serve subsequent requests on the same connection
-class BaseKeepaliveServerTestCase(BaseServerTestCase):
- #a request handler that supports keep-alive and logs requests into a
- #class variable
- class RequestHandler(xmlrpc.server.SimpleXMLRPCRequestHandler):
- parentClass = xmlrpc.server.SimpleXMLRPCRequestHandler
- protocol_version = 'HTTP/1.1'
- myRequests = []
- def handle(self):
- self.myRequests.append([])
- self.reqidx = len(self.myRequests)-1
- return self.parentClass.handle(self)
- def handle_one_request(self):
- result = self.parentClass.handle_one_request(self)
- self.myRequests[self.reqidx].append(self.raw_requestline)
- return result
-
- requestHandler = RequestHandler
- def setUp(self):
- #clear request log
- self.RequestHandler.myRequests = []
- return BaseServerTestCase.setUp(self)
-
-#A test case that verifies that a server using the HTTP/1.1 keep-alive mechanism
-#does indeed serve subsequent requests on the same connection
-@unittest.skipIf(sys.version_info[:2] == (2, 6),
- 'test seems to hang on Py2.6')
-class KeepaliveServerTestCase1(BaseKeepaliveServerTestCase):
- def test_two(self):
- p = xmlrpclib.ServerProxy(URL)
- #do three requests.
- self.assertEqual(p.pow(6,8), 6**8)
- self.assertEqual(p.pow(6,8), 6**8)
- self.assertEqual(p.pow(6,8), 6**8)
- p("close")()
-
- #they should have all been handled by a single request handler
- self.assertEqual(len(self.RequestHandler.myRequests), 1)
-
- #check that we did at least two (the third may be pending append
- #due to thread scheduling)
- self.assertGreaterEqual(len(self.RequestHandler.myRequests[-1]), 2)
-
-
-#test special attribute access on the serverproxy, through the __call__
-#function.
-class KeepaliveServerTestCase2(BaseKeepaliveServerTestCase):
- #ask for two keepalive requests to be handled.
- request_count=2
-
- @unittest.skip('Waiting forever?')
- def test_close(self):
- p = xmlrpclib.ServerProxy(URL)
- #do some requests with close.
- self.assertEqual(p.pow(6,8), 6**8)
- self.assertEqual(p.pow(6,8), 6**8)
- self.assertEqual(p.pow(6,8), 6**8)
- p("close")() #this should trigger a new keep-alive request
- self.assertEqual(p.pow(6,8), 6**8)
- self.assertEqual(p.pow(6,8), 6**8)
- self.assertEqual(p.pow(6,8), 6**8)
- p("close")()
-
- #they should have all been two request handlers, each having logged at least
- #two complete requests
- self.assertEqual(len(self.RequestHandler.myRequests), 2)
- self.assertGreaterEqual(len(self.RequestHandler.myRequests[-1]), 2)
- self.assertGreaterEqual(len(self.RequestHandler.myRequests[-2]), 2)
-
-
- @unittest.skip('Waiting forever?')
- def test_transport(self):
- p = xmlrpclib.ServerProxy(URL)
- #do some requests with close.
- self.assertEqual(p.pow(6,8), 6**8)
- p("transport").close() #same as above, really.
- self.assertEqual(p.pow(6,8), 6**8)
- p("close")()
- self.assertEqual(len(self.RequestHandler.myRequests), 2)
-
-#A test case that verifies that gzip encoding works in both directions
-#(for a request and the response)
-@unittest.skipIf(sys.version_info[:2] == (2, 6),
- 'test seems to hang on Py2.6')
-class GzipServerTestCase(BaseServerTestCase):
- #a request handler that supports keep-alive and logs requests into a
- #class variable
- class RequestHandler(xmlrpc.server.SimpleXMLRPCRequestHandler):
- parentClass = xmlrpc.server.SimpleXMLRPCRequestHandler
- protocol_version = 'HTTP/1.1'
-
- def do_POST(self):
- #store content of last request in class
- self.__class__.content_length = int(self.headers["content-length"])
- return self.parentClass.do_POST(self)
- requestHandler = RequestHandler
-
- class Transport(xmlrpclib.Transport):
- #custom transport, stores the response length for our perusal
- fake_gzip = False
- def parse_response(self, response):
- self.response_length=int(response.getheader("content-length", 0))
- return xmlrpclib.Transport.parse_response(self, response)
-
- def send_content(self, connection, body):
- if self.fake_gzip:
- #add a lone gzip header to induce decode error remotely
- connection.putheader("Content-Encoding", "gzip")
- return xmlrpclib.Transport.send_content(self, connection, body)
-
- def setUp(self):
- BaseServerTestCase.setUp(self)
-
- def test_gzip_request(self):
- t = self.Transport()
- t.encode_threshold = None
- p = xmlrpclib.ServerProxy(URL, transport=t)
- self.assertEqual(p.pow(6,8), 6**8)
- a = self.RequestHandler.content_length
- t.encode_threshold = 0 #turn on request encoding
- self.assertEqual(p.pow(6,8), 6**8)
- b = self.RequestHandler.content_length
- self.assertTrue(a>b)
- p("close")()
-
- def test_bad_gzip_request(self):
- t = self.Transport()
- t.encode_threshold = None
- t.fake_gzip = True
- p = xmlrpclib.ServerProxy(URL, transport=t)
- cm = self.assertRaisesRegex(xmlrpclib.ProtocolError,
- re.compile(r"\b400\b"))
- with cm:
- p.pow(6, 8)
- p("close")()
-
- def test_gsip_response(self):
- t = self.Transport()
- p = xmlrpclib.ServerProxy(URL, transport=t)
- old = self.requestHandler.encode_threshold
- self.requestHandler.encode_threshold = None #no encoding
- self.assertEqual(p.pow(6,8), 6**8)
- a = t.response_length
- self.requestHandler.encode_threshold = 0 #always encode
- self.assertEqual(p.pow(6,8), 6**8)
- p("close")()
- b = t.response_length
- self.requestHandler.encode_threshold = old
- self.assertTrue(a>b)
-
-#Test special attributes of the ServerProxy object
-class ServerProxyTestCase(unittest.TestCase):
- def setUp(self):
- unittest.TestCase.setUp(self)
- if threading:
- self.url = URL
- else:
- # Without threading, http_server() and http_multi_server() will not
- # be executed and URL is still equal to None. 'http://' is a just
- # enough to choose the scheme (HTTP)
- self.url = 'http://'
-
- def test_close(self):
- p = xmlrpclib.ServerProxy(self.url)
- self.assertEqual(p('close')(), None)
-
- def test_transport(self):
- t = xmlrpclib.Transport()
- p = xmlrpclib.ServerProxy(self.url, transport=t)
- self.assertEqual(p('transport'), t)
-
-# This is a contrived way to make a failure occur on the server side
-# in order to test the _send_traceback_header flag on the server
-class FailingMessageClass(http.client.HTTPMessage):
- def get(self, key, failobj=None):
- key = key.lower()
- if key == 'content-length':
- return 'I am broken'
- return super().get(key, failobj)
-
-
-@unittest.skipIf(sys.version_info[:2] == (2, 6),
- 'test seems to hang on Py2.6')
-@unittest.skipUnless(threading, 'Threading required for this test.')
-class FailingServerTestCase(unittest.TestCase):
- def setUp(self):
- self.evt = threading.Event()
- # start server thread to handle requests
- serv_args = (self.evt, 1)
- threading.Thread(target=http_server, args=serv_args).start()
-
- # wait for the server to be ready
- self.evt.wait()
- self.evt.clear()
-
- def tearDown(self):
- # wait on the server thread to terminate
- self.evt.wait()
- # reset flag
- xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = False
- # reset message class
- default_class = http.client.HTTPMessage
- xmlrpc.server.SimpleXMLRPCRequestHandler.MessageClass = default_class
-
- def test_basic(self):
- # check that flag is false by default
- flagval = xmlrpc.server.SimpleXMLRPCServer._send_traceback_header
- self.assertEqual(flagval, False)
-
- # enable traceback reporting
- xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = True
-
- # test a call that shouldn't fail just as a smoke test
- try:
- p = xmlrpclib.ServerProxy(URL)
- self.assertEqual(p.pow(6,8), 6**8)
- except (xmlrpclib.ProtocolError, socket.error) as e:
- # ignore failures due to non-blocking socket 'unavailable' errors
- if not is_unavailable_exception(e):
- # protocol error; provide additional information in test output
- self.fail("%s\n%s" % (e, getattr(e, "headers", "")))
-
- def test_fail_no_info(self):
- # use the broken message class
- xmlrpc.server.SimpleXMLRPCRequestHandler.MessageClass = FailingMessageClass
-
- try:
- p = xmlrpclib.ServerProxy(URL)
- p.pow(6,8)
- except (xmlrpclib.ProtocolError, socket.error) as e:
- # ignore failures due to non-blocking socket 'unavailable' errors
- if not is_unavailable_exception(e) and hasattr(e, "headers"):
- # The two server-side error headers shouldn't be sent back in this case
- self.assertTrue(e.headers.get("X-exception") is None)
- self.assertTrue(e.headers.get("X-traceback") is None)
- else:
- self.fail('ProtocolError not raised')
-
- def test_fail_with_info(self):
- # use the broken message class
- xmlrpc.server.SimpleXMLRPCRequestHandler.MessageClass = FailingMessageClass
-
- # Check that errors in the server send back exception/traceback
- # info when flag is set
- xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = True
-
- try:
- p = xmlrpclib.ServerProxy(URL)
- p.pow(6,8)
- except (xmlrpclib.ProtocolError, socket.error) as e:
- # ignore failures due to non-blocking socket 'unavailable' errors
- if not is_unavailable_exception(e) and hasattr(e, "headers"):
- # We should get error info in the response
- expected_err = ("invalid literal for %s() with base 10: 'I am broken'" %
- ('int' if PY3 else 'long'))
- self.assertEqual(e.headers.get("X-exception"), expected_err)
- self.assertTrue(e.headers.get("X-traceback") is not None)
- else:
- self.fail('ProtocolError not raised')
-
-
-@contextlib.contextmanager
-def captured_stdout(encoding='utf-8'):
- """A variation on support.captured_stdout() which gives a text stream
- having a `buffer` attribute.
- """
- import io
- orig_stdout = sys.stdout
- sys.stdout = io.TextIOWrapper(io.BytesIO(), encoding=encoding)
- try:
- yield sys.stdout
- finally:
- sys.stdout = orig_stdout
-
-
-class CGIHandlerTestCase(unittest.TestCase):
- def setUp(self):
- self.cgi = xmlrpc.server.CGIXMLRPCRequestHandler()
-
- def tearDown(self):
- self.cgi = None
-
- def test_cgi_get(self):
- with support.EnvironmentVarGuard() as env:
- env['REQUEST_METHOD'] = 'GET'
- # if the method is GET and no request_text is given, it runs handle_get
- # get sysout output
- with captured_stdout(encoding=self.cgi.encoding) as data_out:
- self.cgi.handle_request()
-
- # parse Status header
- data_out.seek(0)
- handle = data_out.read()
- status = handle.split()[1]
- message = ' '.join(handle.split()[2:4])
-
- self.assertEqual(status, '400')
- self.assertEqual(message, 'Bad Request')
-
-
- def test_cgi_xmlrpc_response(self):
- data = """
-
- test_method
-
-
- foo
-
-
- bar
-
-
-
- """
-
- with support.EnvironmentVarGuard() as env:
- with captured_stdout(encoding=self.cgi.encoding) as data_out:
- with support.captured_stdin() as data_in:
- data_in.write(data)
- data_in.seek(0)
- env['CONTENT_LENGTH'] = str(len(data))
- self.cgi.handle_request()
- data_out.seek(0)
-
- # will respond exception, if so, our goal is achieved ;)
- handle = data_out.read()
-
- # start with 44th char so as not to get http header, we just
- # need only xml
- self.assertRaises(xmlrpclib.Fault, xmlrpclib.loads, handle[44:])
-
- # Also test the content-length returned by handle_request
- # Using the same test method inorder to avoid all the datapassing
- # boilerplate code.
- # Test for bug: http://bugs.python.org/issue5040
-
- content = handle[handle.find(" dt1:
- delta = dt0 - dt1
- else:
- delta = dt1 - dt0
- # The difference between the system time here and the system
- # time on the server should not be too big.
- self.assertTrue(delta.days <= 1)
-
- def test_python_builders(self):
- # Get the list of builders from the XMLRPC buildbot interface at
- # python.org.
- server = xmlrpclib.ServerProxy("http://buildbot.python.org/all/xmlrpc/")
- try:
- builders = server.getAllBuilders()
- except socket.error as e:
- self.skipTest("network error: %s" % e)
- return
- self.addCleanup(lambda: server('close')())
-
- # Perform a minimal sanity check on the result, just to be sure
- # the request means what we think it means.
- self.assertIsInstance(builders, collections.Sequence)
- self.assertTrue([x for x in builders if "3.x" in x], builders)
-
-
-def test_main():
- support.requires("network")
- support.run_unittest(CurrentTimeTest)
-
-if __name__ == "__main__":
- test_main()
diff --git a/tests/test_future/disabled/test_email/__init__.py b/tests/test_future/disabled/test_email/__init__.py
deleted file mode 100644
index 43e21c9a..00000000
--- a/tests/test_future/disabled/test_email/__init__.py
+++ /dev/null
@@ -1,164 +0,0 @@
-from __future__ import unicode_literals
-from __future__ import print_function
-from __future__ import division
-from __future__ import absolute_import
-from future.builtins import open
-from future.builtins import range
-from future.builtins import super
-from future.builtins import str
-from future.builtins import bytes
-from future import utils
-import os
-import sys
-from future.standard_library.test import support as test_support
-
-from future.standard_library import email
-from future.standard_library.email.message import Message
-from future.standard_library.email._policybase import compat32
-from future.tests.base import unittest
-from future.tests.test_email import __file__ as landmark
-
-# Run all tests in package for '-m unittest test.test_email'
-def load_tests(loader, standard_tests, pattern):
- this_dir = os.path.dirname(__file__)
- if pattern is None:
- pattern = "test*"
- package_tests = loader.discover(start_dir=this_dir, pattern=pattern)
- standard_tests.addTests(package_tests)
- return standard_tests
-
-
-# used by regrtest and __main__.
-def test_main():
- here = os.path.dirname(__file__)
- # Unittest mucks with the path, so we have to save and restore
- # it to keep regrtest happy.
- savepath = sys.path[:]
- test_support._run_suite(unittest.defaultTestLoader.discover(here))
- sys.path[:] = savepath
-
-
-# helper code used by a number of test modules.
-
-def openfile(filename, *args, **kws):
- path = os.path.join(os.path.dirname(landmark), 'data', filename)
- return open(path, *args, **kws)
-
-
-# Base test class
-class TestEmailBase(unittest.TestCase):
-
- maxDiff = None
- # Currently the default policy is compat32. By setting that as the default
- # here we make minimal changes in the test_email tests compared to their
- # pre-3.3 state.
- policy = compat32
-
- def __init__(self, *args, **kw):
- super().__init__(*args, **kw)
- self.addTypeEqualityFunc(bytes, self.assertBytesEqual)
-
- # Backward compatibility to minimize test_email test changes.
- ndiffAssertEqual = unittest.TestCase.assertEqual
-
- def _msgobj(self, filename):
- with openfile(filename) as fp:
- return email.message_from_file(fp, policy=self.policy)
-
- def _str_msg(self, string, message=Message, policy=None):
- if policy is None:
- policy = self.policy
- return email.message_from_string(string, message, policy=policy)
-
- def _bytes_repr(self, b):
- return [repr(x) for x in bytes(b).splitlines(keepends=True)]
-
- def assertBytesEqual(self, first, second, msg):
- """Our byte strings are really encoded strings; improve diff output"""
- self.assertEqual(self._bytes_repr(first), self._bytes_repr(second))
-
- def assertDefectsEqual(self, actual, expected):
- self.assertEqual(len(actual), len(expected), actual)
- for i in range(len(actual)):
- self.assertIsInstance(actual[i], expected[i],
- 'item {}'.format(i))
-
-
-def parameterize(cls):
- """A test method parameterization class decorator.
-
- Parameters are specified as the value of a class attribute that ends with
- the string '_params'. Call the portion before '_params' the prefix. Then
- a method to be parameterized must have the same prefix, the string
- '_as_', and an arbitrary suffix.
-
- The value of the _params attribute may be either a dictionary or a list.
- The values in the dictionary and the elements of the list may either be
- single values, or a list. If single values, they are turned into single
- element tuples. However derived, the resulting sequence is passed via
- *args to the parameterized test function.
-
- In a _params dictioanry, the keys become part of the name of the generated
- tests. In a _params list, the values in the list are converted into a
- string by joining the string values of the elements of the tuple by '_' and
- converting any blanks into '_'s, and this become part of the name.
- The full name of a generated test is a 'test_' prefix, the portion of the
- test function name after the '_as_' separator, plus an '_', plus the name
- derived as explained above.
-
- For example, if we have:
-
- count_params = range(2)
-
- def count_as_foo_arg(self, foo):
- self.assertEqual(foo+1, myfunc(foo))
-
- we will get parameterized test methods named:
- test_foo_arg_0
- test_foo_arg_1
- test_foo_arg_2
-
- Or we could have:
-
- example_params = {'foo': ('bar', 1), 'bing': ('bang', 2)}
-
- def example_as_myfunc_input(self, name, count):
- self.assertEqual(name+str(count), myfunc(name, count))
-
- and get:
- test_myfunc_input_foo
- test_myfunc_input_bing
-
- Note: if and only if the generated test name is a valid identifier can it
- be used to select the test individually from the unittest command line.
-
- """
- paramdicts = {}
- for name, attr in cls.__dict__.items():
- if name.endswith('_params'):
- if not hasattr(attr, 'keys'):
- d = {}
- for x in attr:
- if not hasattr(x, '__iter__'):
- x = (x,)
- n = '_'.join(str(v) for v in x).replace(' ', '_')
- d[n] = x
- attr = d
- paramdicts[name[:-7] + '_as_'] = attr
- testfuncs = {}
- for name, attr in cls.__dict__.items():
- for paramsname, paramsdict in paramdicts.items():
- if name.startswith(paramsname):
- testnameroot = 'test_' + name[len(paramsname):]
- for paramname, params in paramsdict.items():
- test = (lambda self, name=name, params=params:
- getattr(self, name)(*params))
- testname = testnameroot + '_' + paramname
- if utils.PY2:
- test.__name__ = utils.text_to_native_str(testname)
- else:
- test.__name__ = testname
- testfuncs[testname] = test
- for key, value in testfuncs.items():
- setattr(cls, key, value)
- return cls
diff --git a/tests/test_future/disabled/test_email/__main__.py b/tests/test_future/disabled/test_email/__main__.py
deleted file mode 100644
index 98c1cf54..00000000
--- a/tests/test_future/disabled/test_email/__main__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from future.tests.test_email import test_main
-
-test_main()
diff --git a/tests/test_future/disabled/test_email/data/PyBanner048.gif b/tests/test_future/disabled/test_email/data/PyBanner048.gif
deleted file mode 100644
index 1a5c87f6..00000000
Binary files a/tests/test_future/disabled/test_email/data/PyBanner048.gif and /dev/null differ
diff --git a/tests/test_future/disabled/test_email/data/audiotest.au b/tests/test_future/disabled/test_email/data/audiotest.au
deleted file mode 100644
index f76b0501..00000000
Binary files a/tests/test_future/disabled/test_email/data/audiotest.au and /dev/null differ
diff --git a/tests/test_future/disabled/test_email/data/msg_01.txt b/tests/test_future/disabled/test_email/data/msg_01.txt
deleted file mode 100644
index 7e33bcf9..00000000
--- a/tests/test_future/disabled/test_email/data/msg_01.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-Return-Path:
-Delivered-To: bbb@zzz.org
-Received: by mail.zzz.org (Postfix, from userid 889)
- id 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
-MIME-Version: 1.0
-Content-Type: text/plain; charset=us-ascii
-Content-Transfer-Encoding: 7bit
-Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
-From: bbb@ddd.com (John X. Doe)
-To: bbb@zzz.org
-Subject: This is a test message
-Date: Fri, 4 May 2001 14:05:44 -0400
-
-
-Hi,
-
-Do you like this message?
-
--Me
diff --git a/tests/test_future/disabled/test_email/data/msg_02.txt b/tests/test_future/disabled/test_email/data/msg_02.txt
deleted file mode 100644
index 43f24803..00000000
--- a/tests/test_future/disabled/test_email/data/msg_02.txt
+++ /dev/null
@@ -1,135 +0,0 @@
-MIME-version: 1.0
-From: ppp-request@zzz.org
-Sender: ppp-admin@zzz.org
-To: ppp@zzz.org
-Subject: Ppp digest, Vol 1 #2 - 5 msgs
-Date: Fri, 20 Apr 2001 20:18:00 -0400 (EDT)
-X-Mailer: Mailman v2.0.4
-X-Mailman-Version: 2.0.4
-Content-Type: multipart/mixed; boundary="192.168.1.2.889.32614.987812255.500.21814"
-
---192.168.1.2.889.32614.987812255.500.21814
-Content-type: text/plain; charset=us-ascii
-Content-description: Masthead (Ppp digest, Vol 1 #2)
-
-Send Ppp mailing list submissions to
- ppp@zzz.org
-
-To subscribe or unsubscribe via the World Wide Web, visit
- http://www.zzz.org/mailman/listinfo/ppp
-or, via email, send a message with subject or body 'help' to
- ppp-request@zzz.org
-
-You can reach the person managing the list at
- ppp-admin@zzz.org
-
-When replying, please edit your Subject line so it is more specific
-than "Re: Contents of Ppp digest..."
-
-
---192.168.1.2.889.32614.987812255.500.21814
-Content-type: text/plain; charset=us-ascii
-Content-description: Today's Topics (5 msgs)
-
-Today's Topics:
-
- 1. testing #1 (Barry A. Warsaw)
- 2. testing #2 (Barry A. Warsaw)
- 3. testing #3 (Barry A. Warsaw)
- 4. testing #4 (Barry A. Warsaw)
- 5. testing #5 (Barry A. Warsaw)
-
---192.168.1.2.889.32614.987812255.500.21814
-Content-Type: multipart/digest; boundary="__--__--"
-
---__--__--
-
-Message: 1
-Content-Type: text/plain; charset=us-ascii
-Content-Transfer-Encoding: 7bit
-Date: Fri, 20 Apr 2001 20:16:13 -0400
-To: ppp@zzz.org
-From: barry@digicool.com (Barry A. Warsaw)
-Subject: [Ppp] testing #1
-Precedence: bulk
-
-
-hello
-
-
---__--__--
-
-Message: 2
-Date: Fri, 20 Apr 2001 20:16:21 -0400
-Content-Type: text/plain; charset=us-ascii
-Content-Transfer-Encoding: 7bit
-To: ppp@zzz.org
-From: barry@digicool.com (Barry A. Warsaw)
-Precedence: bulk
-
-
-hello
-
-
---__--__--
-
-Message: 3
-Date: Fri, 20 Apr 2001 20:16:25 -0400
-Content-Type: text/plain; charset=us-ascii
-Content-Transfer-Encoding: 7bit
-To: ppp@zzz.org
-From: barry@digicool.com (Barry A. Warsaw)
-Subject: [Ppp] testing #3
-Precedence: bulk
-
-
-hello
-
-
---__--__--
-
-Message: 4
-Date: Fri, 20 Apr 2001 20:16:28 -0400
-Content-Type: text/plain; charset=us-ascii
-Content-Transfer-Encoding: 7bit
-To: ppp@zzz.org
-From: barry@digicool.com (Barry A. Warsaw)
-Subject: [Ppp] testing #4
-Precedence: bulk
-
-
-hello
-
-
---__--__--
-
-Message: 5
-Date: Fri, 20 Apr 2001 20:16:32 -0400
-Content-Type: text/plain; charset=us-ascii
-Content-Transfer-Encoding: 7bit
-To: ppp@zzz.org
-From: barry@digicool.com (Barry A. Warsaw)
-Subject: [Ppp] testing #5
-Precedence: bulk
-
-
-hello
-
-
-
-
---__--__----
---192.168.1.2.889.32614.987812255.500.21814
-Content-type: text/plain; charset=us-ascii
-Content-description: Digest Footer
-
-_______________________________________________
-Ppp mailing list
-Ppp@zzz.org
-http://www.zzz.org/mailman/listinfo/ppp
-
-
---192.168.1.2.889.32614.987812255.500.21814--
-
-End of Ppp Digest
-
diff --git a/tests/test_future/disabled/test_email/data/msg_03.txt b/tests/test_future/disabled/test_email/data/msg_03.txt
deleted file mode 100644
index c748ebf1..00000000
--- a/tests/test_future/disabled/test_email/data/msg_03.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-Return-Path:
-Delivered-To: bbb@zzz.org
-Received: by mail.zzz.org (Postfix, from userid 889)
- id 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
-Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
-From: bbb@ddd.com (John X. Doe)
-To: bbb@zzz.org
-Subject: This is a test message
-Date: Fri, 4 May 2001 14:05:44 -0400
-
-
-Hi,
-
-Do you like this message?
-
--Me
diff --git a/tests/test_future/disabled/test_email/data/msg_04.txt b/tests/test_future/disabled/test_email/data/msg_04.txt
deleted file mode 100644
index 1f633c44..00000000
--- a/tests/test_future/disabled/test_email/data/msg_04.txt
+++ /dev/null
@@ -1,37 +0,0 @@
-Return-Path:
-Delivered-To: barry@python.org
-Received: by mail.python.org (Postfix, from userid 889)
- id C2BF0D37C6; Tue, 11 Sep 2001 00:05:05 -0400 (EDT)
-MIME-Version: 1.0
-Content-Type: multipart/mixed; boundary="h90VIIIKmx"
-Content-Transfer-Encoding: 7bit
-Message-ID: <15261.36209.358846.118674@anthem.python.org>
-From: barry@python.org (Barry A. Warsaw)
-To: barry@python.org
-Subject: a simple multipart
-Date: Tue, 11 Sep 2001 00:05:05 -0400
-X-Mailer: VM 6.95 under 21.4 (patch 4) "Artificial Intelligence" XEmacs Lucid
-X-Attribution: BAW
-X-Oblique-Strategy: Make a door into a window
-
-
---h90VIIIKmx
-Content-Type: text/plain
-Content-Disposition: inline;
- filename="msg.txt"
-Content-Transfer-Encoding: 7bit
-
-a simple kind of mirror
-to reflect upon our own
-
---h90VIIIKmx
-Content-Type: text/plain
-Content-Disposition: inline;
- filename="msg.txt"
-Content-Transfer-Encoding: 7bit
-
-a simple kind of mirror
-to reflect upon our own
-
---h90VIIIKmx--
-
diff --git a/tests/test_future/disabled/test_email/data/msg_05.txt b/tests/test_future/disabled/test_email/data/msg_05.txt
deleted file mode 100644
index 87d5e9cb..00000000
--- a/tests/test_future/disabled/test_email/data/msg_05.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-From: foo
-Subject: bar
-To: baz
-MIME-Version: 1.0
-Content-Type: multipart/report; report-type=delivery-status;
- boundary="D1690A7AC1.996856090/mail.example.com"
-Message-Id: <20010803162810.0CA8AA7ACC@mail.example.com>
-
-This is a MIME-encapsulated message.
-
---D1690A7AC1.996856090/mail.example.com
-Content-Type: text/plain
-
-Yadda yadda yadda
-
---D1690A7AC1.996856090/mail.example.com
-
-Yadda yadda yadda
-
---D1690A7AC1.996856090/mail.example.com
-Content-Type: message/rfc822
-
-From: nobody@python.org
-
-Yadda yadda yadda
-
---D1690A7AC1.996856090/mail.example.com--
-
diff --git a/tests/test_future/disabled/test_email/data/msg_06.txt b/tests/test_future/disabled/test_email/data/msg_06.txt
deleted file mode 100644
index 69f3a47f..00000000
--- a/tests/test_future/disabled/test_email/data/msg_06.txt
+++ /dev/null
@@ -1,33 +0,0 @@
-Return-Path:
-Delivered-To: barry@python.org
-MIME-Version: 1.0
-Content-Type: message/rfc822
-Content-Description: forwarded message
-Content-Transfer-Encoding: 7bit
-Message-ID: <15265.9482.641338.555352@python.org>
-From: barry@zope.com (Barry A. Warsaw)
-Sender: barry@python.org
-To: barry@python.org
-Subject: forwarded message from Barry A. Warsaw
-Date: Thu, 13 Sep 2001 17:28:42 -0400
-X-Mailer: VM 6.95 under 21.4 (patch 4) "Artificial Intelligence" XEmacs Lucid
-X-Attribution: BAW
-X-Oblique-Strategy: Be dirty
-X-Url: http://barry.wooz.org
-
-MIME-Version: 1.0
-Content-Type: text/plain; charset=us-ascii
-Return-Path:
-Delivered-To: barry@python.org
-Message-ID: <15265.9468.713530.98441@python.org>
-From: barry@zope.com (Barry A. Warsaw)
-Sender: barry@python.org
-To: barry@python.org
-Subject: testing
-Date: Thu, 13 Sep 2001 17:28:28 -0400
-X-Mailer: VM 6.95 under 21.4 (patch 4) "Artificial Intelligence" XEmacs Lucid
-X-Attribution: BAW
-X-Oblique-Strategy: Spectrum analysis
-X-Url: http://barry.wooz.org
-
-
diff --git a/tests/test_future/disabled/test_email/data/msg_07.txt b/tests/test_future/disabled/test_email/data/msg_07.txt
deleted file mode 100644
index 721f3a0d..00000000
--- a/tests/test_future/disabled/test_email/data/msg_07.txt
+++ /dev/null
@@ -1,83 +0,0 @@
-MIME-Version: 1.0
-From: Barry
-To: Dingus Lovers
-Subject: Here is your dingus fish
-Date: Fri, 20 Apr 2001 19:35:02 -0400
-Content-Type: multipart/mixed; boundary="BOUNDARY"
-
---BOUNDARY
-Content-Type: text/plain; charset="us-ascii"
-
-Hi there,
-
-This is the dingus fish.
-
---BOUNDARY
-Content-Type: image/gif; name="dingusfish.gif"
-Content-Transfer-Encoding: base64
-content-disposition: attachment; filename="dingusfish.gif"
-
-R0lGODdhAAEAAfAAAP///wAAACwAAAAAAAEAAQAC/oSPqcvtD6OctNqLs968+w+G4kiW5omm6sq2
-7gvH8kzX9o3n+s73/g8MCofEovGITGICTKbyCV0FDNOo9SqpQqpOrJfXzTQj2vD3TGtqL+NtGQ2f
-qTXmxzuOd7WXdcc9DyjU53ewFni4s0fGhdiYaEhGBelICTNoV1j5NUnFcrmUqemjNifJVWpaOqaI
-oFq3SspZsSraE7sHq3jr1MZqWvi662vxV4tD+pvKW6aLDOCLyur8PDwbanyDeq0N3DctbQYeLDvR
-RY6t95m6UB0d3mwIrV7e2VGNvjjffukeJp4w7F65KecGFsTHQGAygOrgrWs1jt28Rc88KESYcGLA
-/obvTkH6p+CinWJiJmIMqXGQwH/y4qk0SYjgQTczT3ajKZGfuI0uJ4kkVI/DT5s3/ejkxI0aT4Y+
-YTYgWbImUaXk9nlLmnSh1qJiJFl0OpUqRK4oOy7NyRQtHWofhoYVxkwWXKUSn0YsS+fUV6lhqfYb
-6ayd3Z5qQdG1B7bvQzaJjwUV2lixMUZ7JVsOlfjWVr/3NB/uFvnySBN6Dcb6rGwaRM3wsormw5cC
-M9NxWy/bWdufudCvy8bOAjXjVVwta/uO21sE5RHBCzNFXtgq9ORtH4eYjVP4Yryo026nvkFmCeyA
-B29efV6ravCMK5JwWd5897Qrx7ll38o6iHDZ/rXPR//feevhF4l7wjUGX3xq1eeRfM4RSJGBIV1D
-z1gKPkfWag3mVBVvva1RlX5bAJTPR/2YqNtw/FkIYYEi/pIZiAdpcxpoHtmnYYoZtvhUftzdx5ZX
-JSKDW405zkGcZzzGZ6KEv4FI224oDmijlEf+xp6MJK5ojY/ASeVUR+wsKRuJ+XFZ5o7ZeEime8t1
-ouUsU6YjF5ZtUihhkGfCdFQLWQFJ3UXxmElfhQnR+eCdcDbkFZp6vTRmj56ApCihn5QGpaToNZmR
-n3NVSpZcQpZ2KEONusaiCsKAug0wkQbJSFO+PTSjneGxOuFjPlUk3ovWvdIerjUg9ZGIOtGq/qeX
-eCYrrCX+1UPsgTKGGRSbzd5q156d/gpfbJxe66eD5iQKrXj7RGgruGxs62qebBHUKS32CKluCiqZ
-qh+pmehmEb71noAUoe5e9Zm17S7773V10pjrtG4CmuurCV/n6zLK5turWNhqOvFXbjhZrMD0YhKe
-wR0zOyuvsh6MWrGoIuzvyWu5y1WIFAqmJselypxXh6dKLNOKEB98L88bS2rkNqqlKzCNJp9c0G0j
-Gzh0iRrCbHSXmPR643QS+4rWhgFmnSbSuXCjS0xAOWkU2UdLqyuUNfHSFdUouy3bm5i5GnDM3tG8
-doJ4r5tqu3pPbRSVfvs8uJzeNXhp3n4j/tZ42SwH7eaWUUOjc3qFV9453UHTXZfcLH+OeNs5g36x
-lBnHvTm7EbMbLeuaLncao8vWCXimfo1o+843Ak6y4ChNeGntvAYvfLK4ezmoyNIbNCLTCXO9ZV3A
-E8/s88RczPzDwI4Ob7XZyl7+9Miban29h+tJZPrE21wgvBphDfrrfPdCTPKJD/y98L1rZwHcV6Jq
-Zab0metpuNIX/qAFPoz171WUaUb4HAhBSzHuHfjzHb3kha/2Cctis/ORArVHNYfFyYRH2pYIRzic
-isVOfPWD1b6mRTqpCRBozzof6UZVvFXRxWIr3GGrEviGYgyPMfahheiSaLs/9QeFu7oZ/ndSY8DD
-ya9x+uPed+7mxN2IzIISBOMLFYWVqC3Pew1T2nFuuCiwZS5/v6II10i4t1OJcUH2U9zxKodHsGGv
-Oa+zkvNUYUOa/TCCRutF9MzDwdlUMJADTCGSbDQ5OV4PTamDoPEi6Ecc/RF5RWwkcdSXvSOaDWSn
-I9LlvubFTQpuc6JKXLcKeb+xdbKRBnwREemXyjg6ME65aJiOuBgrktzykfPLJBKR9ClMavJ62/Ff
-BlNIyod9yX9wcSXexnXFpvkrbXk64xsx5Db7wXKP5fSgsvwIMM/9631VLBfkmtbHRXpqmtei52hG
-pUwSlo+BASQoeILDOBgREECxBBh5/iYmNsQ9dIv5+OI++QkqdsJPc3uykz5fkM+OraeekcQF7X4n
-B5S67za5U967PmooGQhUXfF7afXyCD7ONdRe17QogYjVx38uLwtrS6nhTnm15LQUnu9E2uK6CNI/
-1HOABj0ESwOjut4FEpFQpdNAm4K2LHnDWHNcmKB2ioKBogysVZtMO2nSxUdZ8Yk2kJc7URioLVI0
-YgmtIwZj4LoeKemgnOnbUdGnzZ4Oa6scqiolBGqS6RgWNLu0RMhcaE6rhhU4hiuqFXPAG8fGwTPW
-FKeLMtdVmXLSs5YJGF/YeVm7rREMlY3UYE+yCxbaMXX8y15m5zVHq6GOKDMynzII/jdUHdyVqIy0
-ifX2+r/EgtZcvRzSb72gU9ui87M2VecjKildW/aFqaYhKoryUjfB/g4qtyVuc60xFDGmCxwjW+qu
-zjuwl2GkOWn66+3QiiEctvd04OVvcCVzjgT7lrkvjVGKKHmmlDUKowSeikb5kK/mJReuWOxONx+s
-ULsl+Lqb0CVn0SrVyJ6wt4t6yTeSCafhPhAf0OXn6L60UMxiLolFAtmN35S2Ob1lZpQ1r/n0Qb5D
-oQ1zJiRVDgF8N3Q8TYfbi3DyWCy3lT1nxyBs6FT3S2GOzWRlxwKvlRP0RPJA9SjxEy0UoEnkA+M4
-cnzLMJrBGWLFEaaUb5lvpqbq/loOaU5+DFuHPxo82/OZuM8FXG3oVNZhtWpMpb/0Xu5m/LfLhHZQ
-7yuVI0MqZ7NE43imC8jH3IwGZlbPm0xkJYs7+2U48hXTsFSMqgGDvai0kLxyynKNT/waj+q1c1tz
-GjOpPBgdCSq3UKZxCSsqFIY+O6JbAWGWcV1pwqLyj5sGqCF1xb1F3varUWqrJv6cN3PrUXzijtfZ
-FshpBL3Xwr4GIPvU2N8EjrJgS1zl21rbXQMXeXc5jjFyrhpCzijSv/RQtyPSzHCFMhlME95fHglt
-pRsX+dfSQjUeHAlpWzJ5iOo79Ldnaxai6bXTcGO3fp07ri7HLEmXXPlYi8bv/qVxvNcdra6m7Rlb
-6JBTb5fd66VhFRjGArh2n7R1rDW4P5NOT9K0I183T2scYkeZ3q/VFyLb09U9ajzXBS8Kgkhc4mBS
-kYY9cy3Vy9lUnuNJH8HGIclUilwnBtjUOH0gteGOZ4c/XNrhXLSYDyxfnD8z1pDy7rYRvDolhnbe
-UMzxCZUs40s6s7UIvBnLgc0+vKuOkIXeOrDymlp+Zxra4MZLBbVrqD/jTJ597pDmnw5c4+DbyB88
-9Cg9DodYcSuMZT/114pptqc/EuTjRPvH/z5slzI3tluOEBBLqOXLOX+0I5929tO97wkvl/atCz+y
-xJrdwteW2FNW/NSmBP+f/maYtVs/bYyBC7Ox3jsYZHL05CIrBa/nS+b3bHfiYm4Ueil1YZZSgAUI
-fFZ1dxUmeA2oQRQ3RuGXNGLFV9/XbGFGPV6kfzk1TBBCd+izc7q1H+OHMJwmaBX2IQNYVAKHYepV
-SSGCe6CnbYHHETKGNe43EDvFgZr0gB/nVHPHZ80VV1ojOiI3XDvYIkl4ayo4bxQIgrFXWTvBI0nH
-VElWMuw2aLUWCRHHf8ymVCHjFlJnOSojfevCYyyyZDH0IcvHhrsnQ5O1OsWzONuVVKIxSxiFZ/tR
-fKDAf6xFTnw4O9Qig2VCfW2hJQrmMOuHW0W3dLQmCMO2ccdUd/xyfflH/olTiHZVdGwb8nIwRzSE
-J15jFlOJuBZBZ4CiyHyd2IFylFlB+HgHhYabhWOGwYO1ZH/Og1dtQlFMk352CGRSIFTapnWQEUtN
-l4zv8S0aaCFDyGCBqDUxZYpxGHX01y/JuH1xhn7TOCnNCI4eKDs5WGX4R425F4vF1o3BJ4vO0otq
-I3rimI7jJY1jISqnBxknCIvruF83mF5wN4X7qGLIhR8A2Vg0yFERSIXn9Vv3GHy3Vj/WIkKddlYi
-yIMv2I/VMjTLpW7pt05SWIZR0RPyxpB4SIUM9lBPGBl0GC7oSEEwRYLe4pJpZY2P0zbI1n+Oc44w
-qY3PUnmF0ixjVpDD/mJ9wpOBGTVgXlaCaZiPcIWK5NiKBIiPdGaQ0TWGvAiG7nMchdZb7Vgf8zNi
-MuMyzRdy/lePe9iC4TRx7WhhOQI/QiSVNAmAa2lT/piFbuh7ofJoYSZzrSZ1bvmWw3eN2nKUPVky
-uPN5/VRfohRd0VYZoqhKIlU6TXYhJxmPUIloAwc1bPmHEpaZYZORHNlXUJM07hATwHR8MJYqkwWR
-WaIezFhxSFlc8/Fq82hEnpeRozg3ULhhr9lAGtVEkCg5ZNRuuVleBPaZadhG0ZgkyPmDOTOKzViM
-YgOcpukKqQcbjAWS0IleQ2ROjdh6A+md1qWdBRSX7iSYgFRTtRmBpJioieXJiHfJiMGIR9fJOn8I
-MSfXYhspn4ooSa2mSAj4n+8Bmg03fBJZoPOJgsVZRxu1oOMRPXYYjdqjihFaEoZpXBREanuJoRI6
-cibFinq4ngUKh/wQd/H5ofYCZ0HJXR62opZFaAT0iFIZo4DIiUojkjeqKiuoZirKo5Y1a7AWckGa
-BkuYoD5lpDK6eUs6CkDqpETwl1EqpfhJpVeKpVl6EgUAADs=
-
---BOUNDARY--
diff --git a/tests/test_future/disabled/test_email/data/msg_08.txt b/tests/test_future/disabled/test_email/data/msg_08.txt
deleted file mode 100644
index b5630836..00000000
--- a/tests/test_future/disabled/test_email/data/msg_08.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-MIME-Version: 1.0
-From: Barry Warsaw
-To: Dingus Lovers
-Subject: Lyrics
-Date: Fri, 20 Apr 2001 19:35:02 -0400
-Content-Type: multipart/mixed; boundary="BOUNDARY"
-
---BOUNDARY
-Content-Type: text/plain; charset="us-ascii"
-
-
---BOUNDARY
-Content-Type: text/html; charset="iso-8859-1"
-
-
---BOUNDARY
-Content-Type: text/plain; charset="iso-8859-2"
-
-
---BOUNDARY
-Content-Type: text/plain; charset="koi8-r"
-
-
---BOUNDARY--
diff --git a/tests/test_future/disabled/test_email/data/msg_09.txt b/tests/test_future/disabled/test_email/data/msg_09.txt
deleted file mode 100644
index 575c4c20..00000000
--- a/tests/test_future/disabled/test_email/data/msg_09.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-MIME-Version: 1.0
-From: Barry Warsaw
-To: Dingus Lovers
-Subject: Lyrics
-Date: Fri, 20 Apr 2001 19:35:02 -0400
-Content-Type: multipart/mixed; boundary="BOUNDARY"
-
---BOUNDARY
-Content-Type: text/plain; charset="us-ascii"
-
-
---BOUNDARY
-Content-Type: text/html; charset="iso-8859-1"
-
-
---BOUNDARY
-Content-Type: text/plain
-
-
---BOUNDARY
-Content-Type: text/plain; charset="koi8-r"
-
-
---BOUNDARY--
diff --git a/tests/test_future/disabled/test_email/data/msg_10.txt b/tests/test_future/disabled/test_email/data/msg_10.txt
deleted file mode 100644
index 07903960..00000000
--- a/tests/test_future/disabled/test_email/data/msg_10.txt
+++ /dev/null
@@ -1,39 +0,0 @@
-MIME-Version: 1.0
-From: Barry Warsaw
-To: Dingus Lovers
-Subject: Lyrics
-Date: Fri, 20 Apr 2001 19:35:02 -0400
-Content-Type: multipart/mixed; boundary="BOUNDARY"
-
---BOUNDARY
-Content-Type: text/plain; charset="us-ascii"
-Content-Transfer-Encoding: 7bit
-
-This is a 7bit encoded message.
-
---BOUNDARY
-Content-Type: text/html; charset="iso-8859-1"
-Content-Transfer-Encoding: Quoted-Printable
-
-=A1This is a Quoted Printable encoded message!
-
---BOUNDARY
-Content-Type: text/plain; charset="iso-8859-1"
-Content-Transfer-Encoding: Base64
-
-VGhpcyBpcyBhIEJhc2U2NCBlbmNvZGVkIG1lc3NhZ2Uu
-
-
---BOUNDARY
-Content-Type: text/plain; charset="iso-8859-1"
-Content-Transfer-Encoding: Base64
-
-VGhpcyBpcyBhIEJhc2U2NCBlbmNvZGVkIG1lc3NhZ2UuCg==
-
-
---BOUNDARY
-Content-Type: text/plain; charset="iso-8859-1"
-
-This has no Content-Transfer-Encoding: header.
-
---BOUNDARY--
diff --git a/tests/test_future/disabled/test_email/data/msg_11.txt b/tests/test_future/disabled/test_email/data/msg_11.txt
deleted file mode 100644
index 8f7f1991..00000000
--- a/tests/test_future/disabled/test_email/data/msg_11.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-Content-Type: message/rfc822
-MIME-Version: 1.0
-Subject: The enclosing message
-
-Subject: An enclosed message
-
-Here is the body of the message.
diff --git a/tests/test_future/disabled/test_email/data/msg_12.txt b/tests/test_future/disabled/test_email/data/msg_12.txt
deleted file mode 100644
index 4bec8d94..00000000
--- a/tests/test_future/disabled/test_email/data/msg_12.txt
+++ /dev/null
@@ -1,36 +0,0 @@
-MIME-Version: 1.0
-From: Barry Warsaw
-To: Dingus Lovers
-Subject: Lyrics
-Date: Fri, 20 Apr 2001 19:35:02 -0400
-Content-Type: multipart/mixed; boundary="BOUNDARY"
-
---BOUNDARY
-Content-Type: text/plain; charset="us-ascii"
-
-
---BOUNDARY
-Content-Type: text/html; charset="iso-8859-1"
-
-
---BOUNDARY
-Content-Type: multipart/mixed; boundary="ANOTHER"
-
---ANOTHER
-Content-Type: text/plain; charset="iso-8859-2"
-
-
---ANOTHER
-Content-Type: text/plain; charset="iso-8859-3"
-
---ANOTHER--
-
---BOUNDARY
-Content-Type: text/plain; charset="us-ascii"
-
-
---BOUNDARY
-Content-Type: text/plain; charset="koi8-r"
-
-
---BOUNDARY--
diff --git a/tests/test_future/disabled/test_email/data/msg_12a.txt b/tests/test_future/disabled/test_email/data/msg_12a.txt
deleted file mode 100644
index e94224ec..00000000
--- a/tests/test_future/disabled/test_email/data/msg_12a.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-MIME-Version: 1.0
-From: Barry Warsaw
-To: Dingus Lovers
-Subject: Lyrics
-Date: Fri, 20 Apr 2001 19:35:02 -0400
-Content-Type: multipart/mixed; boundary="BOUNDARY"
-
---BOUNDARY
-Content-Type: text/plain; charset="us-ascii"
-
-
---BOUNDARY
-Content-Type: text/html; charset="iso-8859-1"
-
-
---BOUNDARY
-Content-Type: multipart/mixed; boundary="ANOTHER"
-
---ANOTHER
-Content-Type: text/plain; charset="iso-8859-2"
-
-
---ANOTHER
-Content-Type: text/plain; charset="iso-8859-3"
-
-
---ANOTHER--
-
-
---BOUNDARY
-Content-Type: text/plain; charset="us-ascii"
-
-
---BOUNDARY
-Content-Type: text/plain; charset="koi8-r"
-
-
---BOUNDARY--
diff --git a/tests/test_future/disabled/test_email/data/msg_13.txt b/tests/test_future/disabled/test_email/data/msg_13.txt
deleted file mode 100644
index 8e6d52d5..00000000
--- a/tests/test_future/disabled/test_email/data/msg_13.txt
+++ /dev/null
@@ -1,94 +0,0 @@
-MIME-Version: 1.0
-From: Barry
-To: Dingus Lovers
-Subject: Here is your dingus fish
-Date: Fri, 20 Apr 2001 19:35:02 -0400
-Content-Type: multipart/mixed; boundary="OUTER"
-
---OUTER
-Content-Type: text/plain; charset="us-ascii"
-
-A text/plain part
-
---OUTER
-Content-Type: multipart/mixed; boundary=BOUNDARY
-
-
---BOUNDARY
-Content-Type: text/plain; charset="us-ascii"
-
-Hi there,
-
-This is the dingus fish.
-
---BOUNDARY
-Content-Type: image/gif; name="dingusfish.gif"
-Content-Transfer-Encoding: base64
-content-disposition: attachment; filename="dingusfish.gif"
-
-R0lGODdhAAEAAfAAAP///wAAACwAAAAAAAEAAQAC/oSPqcvtD6OctNqLs968+w+G4kiW5omm6sq2
-7gvH8kzX9o3n+s73/g8MCofEovGITGICTKbyCV0FDNOo9SqpQqpOrJfXzTQj2vD3TGtqL+NtGQ2f
-qTXmxzuOd7WXdcc9DyjU53ewFni4s0fGhdiYaEhGBelICTNoV1j5NUnFcrmUqemjNifJVWpaOqaI
-oFq3SspZsSraE7sHq3jr1MZqWvi662vxV4tD+pvKW6aLDOCLyur8PDwbanyDeq0N3DctbQYeLDvR
-RY6t95m6UB0d3mwIrV7e2VGNvjjffukeJp4w7F65KecGFsTHQGAygOrgrWs1jt28Rc88KESYcGLA
-/obvTkH6p+CinWJiJmIMqXGQwH/y4qk0SYjgQTczT3ajKZGfuI0uJ4kkVI/DT5s3/ejkxI0aT4Y+
-YTYgWbImUaXk9nlLmnSh1qJiJFl0OpUqRK4oOy7NyRQtHWofhoYVxkwWXKUSn0YsS+fUV6lhqfYb
-6ayd3Z5qQdG1B7bvQzaJjwUV2lixMUZ7JVsOlfjWVr/3NB/uFvnySBN6Dcb6rGwaRM3wsormw5cC
-M9NxWy/bWdufudCvy8bOAjXjVVwta/uO21sE5RHBCzNFXtgq9ORtH4eYjVP4Yryo026nvkFmCeyA
-B29efV6ravCMK5JwWd5897Qrx7ll38o6iHDZ/rXPR//feevhF4l7wjUGX3xq1eeRfM4RSJGBIV1D
-z1gKPkfWag3mVBVvva1RlX5bAJTPR/2YqNtw/FkIYYEi/pIZiAdpcxpoHtmnYYoZtvhUftzdx5ZX
-JSKDW405zkGcZzzGZ6KEv4FI224oDmijlEf+xp6MJK5ojY/ASeVUR+wsKRuJ+XFZ5o7ZeEime8t1
-ouUsU6YjF5ZtUihhkGfCdFQLWQFJ3UXxmElfhQnR+eCdcDbkFZp6vTRmj56ApCihn5QGpaToNZmR
-n3NVSpZcQpZ2KEONusaiCsKAug0wkQbJSFO+PTSjneGxOuFjPlUk3ovWvdIerjUg9ZGIOtGq/qeX
-eCYrrCX+1UPsgTKGGRSbzd5q156d/gpfbJxe66eD5iQKrXj7RGgruGxs62qebBHUKS32CKluCiqZ
-qh+pmehmEb71noAUoe5e9Zm17S7773V10pjrtG4CmuurCV/n6zLK5turWNhqOvFXbjhZrMD0YhKe
-wR0zOyuvsh6MWrGoIuzvyWu5y1WIFAqmJselypxXh6dKLNOKEB98L88bS2rkNqqlKzCNJp9c0G0j
-Gzh0iRrCbHSXmPR643QS+4rWhgFmnSbSuXCjS0xAOWkU2UdLqyuUNfHSFdUouy3bm5i5GnDM3tG8
-doJ4r5tqu3pPbRSVfvs8uJzeNXhp3n4j/tZ42SwH7eaWUUOjc3qFV9453UHTXZfcLH+OeNs5g36x
-lBnHvTm7EbMbLeuaLncao8vWCXimfo1o+843Ak6y4ChNeGntvAYvfLK4ezmoyNIbNCLTCXO9ZV3A
-E8/s88RczPzDwI4Ob7XZyl7+9Miban29h+tJZPrE21wgvBphDfrrfPdCTPKJD/y98L1rZwHcV6Jq
-Zab0metpuNIX/qAFPoz171WUaUb4HAhBSzHuHfjzHb3kha/2Cctis/ORArVHNYfFyYRH2pYIRzic
-isVOfPWD1b6mRTqpCRBozzof6UZVvFXRxWIr3GGrEviGYgyPMfahheiSaLs/9QeFu7oZ/ndSY8DD
-ya9x+uPed+7mxN2IzIISBOMLFYWVqC3Pew1T2nFuuCiwZS5/v6II10i4t1OJcUH2U9zxKodHsGGv
-Oa+zkvNUYUOa/TCCRutF9MzDwdlUMJADTCGSbDQ5OV4PTamDoPEi6Ecc/RF5RWwkcdSXvSOaDWSn
-I9LlvubFTQpuc6JKXLcKeb+xdbKRBnwREemXyjg6ME65aJiOuBgrktzykfPLJBKR9ClMavJ62/Ff
-BlNIyod9yX9wcSXexnXFpvkrbXk64xsx5Db7wXKP5fSgsvwIMM/9631VLBfkmtbHRXpqmtei52hG
-pUwSlo+BASQoeILDOBgREECxBBh5/iYmNsQ9dIv5+OI++QkqdsJPc3uykz5fkM+OraeekcQF7X4n
-B5S67za5U967PmooGQhUXfF7afXyCD7ONdRe17QogYjVx38uLwtrS6nhTnm15LQUnu9E2uK6CNI/
-1HOABj0ESwOjut4FEpFQpdNAm4K2LHnDWHNcmKB2ioKBogysVZtMO2nSxUdZ8Yk2kJc7URioLVI0
-YgmtIwZj4LoeKemgnOnbUdGnzZ4Oa6scqiolBGqS6RgWNLu0RMhcaE6rhhU4hiuqFXPAG8fGwTPW
-FKeLMtdVmXLSs5YJGF/YeVm7rREMlY3UYE+yCxbaMXX8y15m5zVHq6GOKDMynzII/jdUHdyVqIy0
-ifX2+r/EgtZcvRzSb72gU9ui87M2VecjKildW/aFqaYhKoryUjfB/g4qtyVuc60xFDGmCxwjW+qu
-zjuwl2GkOWn66+3QiiEctvd04OVvcCVzjgT7lrkvjVGKKHmmlDUKowSeikb5kK/mJReuWOxONx+s
-ULsl+Lqb0CVn0SrVyJ6wt4t6yTeSCafhPhAf0OXn6L60UMxiLolFAtmN35S2Ob1lZpQ1r/n0Qb5D
-oQ1zJiRVDgF8N3Q8TYfbi3DyWCy3lT1nxyBs6FT3S2GOzWRlxwKvlRP0RPJA9SjxEy0UoEnkA+M4
-cnzLMJrBGWLFEaaUb5lvpqbq/loOaU5+DFuHPxo82/OZuM8FXG3oVNZhtWpMpb/0Xu5m/LfLhHZQ
-7yuVI0MqZ7NE43imC8jH3IwGZlbPm0xkJYs7+2U48hXTsFSMqgGDvai0kLxyynKNT/waj+q1c1tz
-GjOpPBgdCSq3UKZxCSsqFIY+O6JbAWGWcV1pwqLyj5sGqCF1xb1F3varUWqrJv6cN3PrUXzijtfZ
-FshpBL3Xwr4GIPvU2N8EjrJgS1zl21rbXQMXeXc5jjFyrhpCzijSv/RQtyPSzHCFMhlME95fHglt
-pRsX+dfSQjUeHAlpWzJ5iOo79Ldnaxai6bXTcGO3fp07ri7HLEmXXPlYi8bv/qVxvNcdra6m7Rlb
-6JBTb5fd66VhFRjGArh2n7R1rDW4P5NOT9K0I183T2scYkeZ3q/VFyLb09U9ajzXBS8Kgkhc4mBS
-kYY9cy3Vy9lUnuNJH8HGIclUilwnBtjUOH0gteGOZ4c/XNrhXLSYDyxfnD8z1pDy7rYRvDolhnbe
-UMzxCZUs40s6s7UIvBnLgc0+vKuOkIXeOrDymlp+Zxra4MZLBbVrqD/jTJ597pDmnw5c4+DbyB88
-9Cg9DodYcSuMZT/114pptqc/EuTjRPvH/z5slzI3tluOEBBLqOXLOX+0I5929tO97wkvl/atCz+y
-xJrdwteW2FNW/NSmBP+f/maYtVs/bYyBC7Ox3jsYZHL05CIrBa/nS+b3bHfiYm4Ueil1YZZSgAUI
-fFZ1dxUmeA2oQRQ3RuGXNGLFV9/XbGFGPV6kfzk1TBBCd+izc7q1H+OHMJwmaBX2IQNYVAKHYepV
-SSGCe6CnbYHHETKGNe43EDvFgZr0gB/nVHPHZ80VV1ojOiI3XDvYIkl4ayo4bxQIgrFXWTvBI0nH
-VElWMuw2aLUWCRHHf8ymVCHjFlJnOSojfevCYyyyZDH0IcvHhrsnQ5O1OsWzONuVVKIxSxiFZ/tR
-fKDAf6xFTnw4O9Qig2VCfW2hJQrmMOuHW0W3dLQmCMO2ccdUd/xyfflH/olTiHZVdGwb8nIwRzSE
-J15jFlOJuBZBZ4CiyHyd2IFylFlB+HgHhYabhWOGwYO1ZH/Og1dtQlFMk352CGRSIFTapnWQEUtN
-l4zv8S0aaCFDyGCBqDUxZYpxGHX01y/JuH1xhn7TOCnNCI4eKDs5WGX4R425F4vF1o3BJ4vO0otq
-I3rimI7jJY1jISqnBxknCIvruF83mF5wN4X7qGLIhR8A2Vg0yFERSIXn9Vv3GHy3Vj/WIkKddlYi
-yIMv2I/VMjTLpW7pt05SWIZR0RPyxpB4SIUM9lBPGBl0GC7oSEEwRYLe4pJpZY2P0zbI1n+Oc44w
-qY3PUnmF0ixjVpDD/mJ9wpOBGTVgXlaCaZiPcIWK5NiKBIiPdGaQ0TWGvAiG7nMchdZb7Vgf8zNi
-MuMyzRdy/lePe9iC4TRx7WhhOQI/QiSVNAmAa2lT/piFbuh7ofJoYSZzrSZ1bvmWw3eN2nKUPVky
-uPN5/VRfohRd0VYZoqhKIlU6TXYhJxmPUIloAwc1bPmHEpaZYZORHNlXUJM07hATwHR8MJYqkwWR
-WaIezFhxSFlc8/Fq82hEnpeRozg3ULhhr9lAGtVEkCg5ZNRuuVleBPaZadhG0ZgkyPmDOTOKzViM
-YgOcpukKqQcbjAWS0IleQ2ROjdh6A+md1qWdBRSX7iSYgFRTtRmBpJioieXJiHfJiMGIR9fJOn8I
-MSfXYhspn4ooSa2mSAj4n+8Bmg03fBJZoPOJgsVZRxu1oOMRPXYYjdqjihFaEoZpXBREanuJoRI6
-cibFinq4ngUKh/wQd/H5ofYCZ0HJXR62opZFaAT0iFIZo4DIiUojkjeqKiuoZirKo5Y1a7AWckGa
-BkuYoD5lpDK6eUs6CkDqpETwl1EqpfhJpVeKpVl6EgUAADs=
-
---BOUNDARY--
-
---OUTER--
diff --git a/tests/test_future/disabled/test_email/data/msg_14.txt b/tests/test_future/disabled/test_email/data/msg_14.txt
deleted file mode 100644
index 5d98d2fd..00000000
--- a/tests/test_future/disabled/test_email/data/msg_14.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-Return-Path:
-Delivered-To: bbb@zzz.org
-Received: by mail.zzz.org (Postfix, from userid 889)
- id 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
-MIME-Version: 1.0
-Content-Type: text; charset=us-ascii
-Content-Transfer-Encoding: 7bit
-Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
-From: bbb@ddd.com (John X. Doe)
-To: bbb@zzz.org
-Subject: This is a test message
-Date: Fri, 4 May 2001 14:05:44 -0400
-
-
-Hi,
-
-I'm sorry but I'm using a drainbread ISP, which although big and
-wealthy can't seem to generate standard compliant email. :(
-
-This message has a Content-Type: header with no subtype. I hope you
-can still read it.
-
--Me
diff --git a/tests/test_future/disabled/test_email/data/msg_15.txt b/tests/test_future/disabled/test_email/data/msg_15.txt
deleted file mode 100644
index 0025624e..00000000
--- a/tests/test_future/disabled/test_email/data/msg_15.txt
+++ /dev/null
@@ -1,52 +0,0 @@
-Return-Path:
-Received: from fepD.post.tele.dk (195.41.46.149) by mail.groupcare.dk (LSMTP for Windows NT v1.1b) with SMTP id <0.0014F8A2@mail.groupcare.dk>; Mon, 30 Apr 2001 12:17:50 +0200
-User-Agent: Microsoft-Outlook-Express-Macintosh-Edition/5.02.2106
-Subject: XX
-From: xx@xx.dk
-To: XX
-Message-ID:
-Mime-version: 1.0
-Content-type: multipart/mixed;
- boundary="MS_Mac_OE_3071477847_720252_MIME_Part"
-
-> Denne meddelelse er i MIME-format. Da dit postl
-
---MS_Mac_OE_3071477847_720252_MIME_Part
-Content-type: multipart/alternative;
- boundary="MS_Mac_OE_3071477847_720252_MIME_Part"
-
-
---MS_Mac_OE_3071477847_720252_MIME_Part
-Content-type: text/plain; charset="ISO-8859-1"
-Content-transfer-encoding: quoted-printable
-
-Some removed test.
-
---MS_Mac_OE_3071477847_720252_MIME_Part
-Content-type: text/html; charset="ISO-8859-1"
-Content-transfer-encoding: quoted-printable
-
-
-
-Some removed HTML
-
-
-Some removed text.
-
-
-
-
---MS_Mac_OE_3071477847_720252_MIME_Part--
-
-
---MS_Mac_OE_3071477847_720252_MIME_Part
-Content-type: image/gif; name="xx.gif";
- x-mac-creator="6F676C65";
- x-mac-type="47494666"
-Content-disposition: attachment
-Content-transfer-encoding: base64
-
-Some removed base64 encoded chars.
-
---MS_Mac_OE_3071477847_720252_MIME_Part--
-
diff --git a/tests/test_future/disabled/test_email/data/msg_16.txt b/tests/test_future/disabled/test_email/data/msg_16.txt
deleted file mode 100644
index 56167e9f..00000000
--- a/tests/test_future/disabled/test_email/data/msg_16.txt
+++ /dev/null
@@ -1,123 +0,0 @@
-Return-Path: <>
-Delivered-To: scr-admin@socal-raves.org
-Received: from cougar.noc.ucla.edu (cougar.noc.ucla.edu [169.232.10.18])
- by babylon.socal-raves.org (Postfix) with ESMTP id CCC2C51B84
- for ; Sun, 23 Sep 2001 20:13:54 -0700 (PDT)
-Received: from sims-ms-daemon by cougar.noc.ucla.edu
- (Sun Internet Mail Server sims.3.5.2000.03.23.18.03.p10)
- id <0GK500B01D0B8Y@cougar.noc.ucla.edu> for scr-admin@socal-raves.org; Sun,
- 23 Sep 2001 20:14:35 -0700 (PDT)
-Received: from cougar.noc.ucla.edu
- (Sun Internet Mail Server sims.3.5.2000.03.23.18.03.p10)
- id <0GK500B01D0B8X@cougar.noc.ucla.edu>; Sun, 23 Sep 2001 20:14:35 -0700 (PDT)
-Date: Sun, 23 Sep 2001 20:14:35 -0700 (PDT)
-From: Internet Mail Delivery
-Subject: Delivery Notification: Delivery has failed
-To: scr-admin@socal-raves.org
-Message-id: <0GK500B04D0B8X@cougar.noc.ucla.edu>
-MIME-version: 1.0
-Sender: scr-owner@socal-raves.org
-Errors-To: scr-owner@socal-raves.org
-X-BeenThere: scr@socal-raves.org
-X-Mailman-Version: 2.1a3
-Precedence: bulk
-List-Help:
-List-Post:
-List-Subscribe: ,
-
-List-Id: SoCal-Raves
-List-Unsubscribe: ,
-
-List-Archive:
-Content-Type: multipart/report; boundary="Boundary_(ID_PGS2F2a+z+/jL7hupKgRhA)"
-
-
---Boundary_(ID_PGS2F2a+z+/jL7hupKgRhA)
-Content-type: text/plain; charset=ISO-8859-1
-
-This report relates to a message you sent with the following header fields:
-
- Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
- Date: Sun, 23 Sep 2001 20:10:55 -0700
- From: "Ian T. Henry"
- To: SoCal Raves
- Subject: [scr] yeah for Ians!!
-
-Your message cannot be delivered to the following recipients:
-
- Recipient address: jangel1@cougar.noc.ucla.edu
- Reason: recipient reached disk quota
-
-
---Boundary_(ID_PGS2F2a+z+/jL7hupKgRhA)
-Content-type: message/DELIVERY-STATUS
-
-Original-envelope-id: 0GK500B4HD0888@cougar.noc.ucla.edu
-Reporting-MTA: dns; cougar.noc.ucla.edu
-
-Action: failed
-Status: 5.0.0 (recipient reached disk quota)
-Original-recipient: rfc822;jangel1@cougar.noc.ucla.edu
-Final-recipient: rfc822;jangel1@cougar.noc.ucla.edu
-
---Boundary_(ID_PGS2F2a+z+/jL7hupKgRhA)
-Content-type: MESSAGE/RFC822
-
-Return-path: scr-admin@socal-raves.org
-Received: from sims-ms-daemon by cougar.noc.ucla.edu
- (Sun Internet Mail Server sims.3.5.2000.03.23.18.03.p10)
- id <0GK500B01D0B8X@cougar.noc.ucla.edu>; Sun, 23 Sep 2001 20:14:35 -0700 (PDT)
-Received: from panther.noc.ucla.edu by cougar.noc.ucla.edu
- (Sun Internet Mail Server sims.3.5.2000.03.23.18.03.p10)
- with ESMTP id <0GK500B4GD0888@cougar.noc.ucla.edu> for jangel1@sims-ms-daemon;
- Sun, 23 Sep 2001 20:14:33 -0700 (PDT)
-Received: from babylon.socal-raves.org
- (ip-209-85-222-117.dreamhost.com [209.85.222.117])
- by panther.noc.ucla.edu (8.9.1a/8.9.1) with ESMTP id UAA09793 for
- ; Sun, 23 Sep 2001 20:14:32 -0700 (PDT)
-Received: from babylon (localhost [127.0.0.1]) by babylon.socal-raves.org
- (Postfix) with ESMTP id D3B2951B70; Sun, 23 Sep 2001 20:13:47 -0700 (PDT)
-Received: by babylon.socal-raves.org (Postfix, from userid 60001)
- id A611F51B82; Sun, 23 Sep 2001 20:13:46 -0700 (PDT)
-Received: from tiger.cc.oxy.edu (tiger.cc.oxy.edu [134.69.3.112])
- by babylon.socal-raves.org (Postfix) with ESMTP id ADA7351B70 for
- ; Sun, 23 Sep 2001 20:13:44 -0700 (PDT)
-Received: from ent (n16h86.dhcp.oxy.edu [134.69.16.86])
- by tiger.cc.oxy.edu (8.8.8/8.8.8) with SMTP id UAA08100 for
- ; Sun, 23 Sep 2001 20:14:24 -0700 (PDT)
-Date: Sun, 23 Sep 2001 20:10:55 -0700
-From: "Ian T. Henry"
-Subject: [scr] yeah for Ians!!
-Sender: scr-admin@socal-raves.org
-To: SoCal Raves
-Errors-to: scr-admin@socal-raves.org
-Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
-MIME-version: 1.0
-X-Mailer: Microsoft Outlook Express 5.50.4522.1200
-Content-type: text/plain; charset=us-ascii
-Precedence: bulk
-Delivered-to: scr-post@babylon.socal-raves.org
-Delivered-to: scr@socal-raves.org
-X-Converted-To-Plain-Text: from multipart/alternative by demime 0.98e
-X-Converted-To-Plain-Text: Alternative section used was text/plain
-X-BeenThere: scr@socal-raves.org
-X-Mailman-Version: 2.1a3
-List-Help:
-List-Post:
-List-Subscribe: ,
-
-List-Id: SoCal-Raves
-List-Unsubscribe: ,
-
-List-Archive:
-
-I always love to find more Ian's that are over 3 years old!!
-
-Ian
-_______________________________________________
-For event info, list questions, or to unsubscribe, see http://www.socal-raves.org/
-
-
-
---Boundary_(ID_PGS2F2a+z+/jL7hupKgRhA)--
-
diff --git a/tests/test_future/disabled/test_email/data/msg_17.txt b/tests/test_future/disabled/test_email/data/msg_17.txt
deleted file mode 100644
index 8d86e418..00000000
--- a/tests/test_future/disabled/test_email/data/msg_17.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-MIME-Version: 1.0
-From: Barry
-To: Dingus Lovers
-Subject: Here is your dingus fish
-Date: Fri, 20 Apr 2001 19:35:02 -0400
-Content-Type: multipart/mixed; boundary="BOUNDARY"
-
-Hi there,
-
-This is the dingus fish.
-
-[Non-text (image/gif) part of message omitted, filename dingusfish.gif]
diff --git a/tests/test_future/disabled/test_email/data/msg_18.txt b/tests/test_future/disabled/test_email/data/msg_18.txt
deleted file mode 100644
index f9f4904d..00000000
--- a/tests/test_future/disabled/test_email/data/msg_18.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-Content-Type: text/plain; charset="us-ascii"
-MIME-Version: 1.0
-Content-Transfer-Encoding: 7bit
-X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
- spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
-
diff --git a/tests/test_future/disabled/test_email/data/msg_19.txt b/tests/test_future/disabled/test_email/data/msg_19.txt
deleted file mode 100644
index 49bf7fcc..00000000
--- a/tests/test_future/disabled/test_email/data/msg_19.txt
+++ /dev/null
@@ -1,43 +0,0 @@
-Send Ppp mailing list submissions to
- ppp@zzz.org
-
-To subscribe or unsubscribe via the World Wide Web, visit
- http://www.zzz.org/mailman/listinfo/ppp
-or, via email, send a message with subject or body 'help' to
- ppp-request@zzz.org
-
-You can reach the person managing the list at
- ppp-admin@zzz.org
-
-When replying, please edit your Subject line so it is more specific
-than "Re: Contents of Ppp digest..."
-
-Today's Topics:
-
- 1. testing #1 (Barry A. Warsaw)
- 2. testing #2 (Barry A. Warsaw)
- 3. testing #3 (Barry A. Warsaw)
- 4. testing #4 (Barry A. Warsaw)
- 5. testing #5 (Barry A. Warsaw)
-
-hello
-
-
-hello
-
-
-hello
-
-
-hello
-
-
-hello
-
-
-
-_______________________________________________
-Ppp mailing list
-Ppp@zzz.org
-http://www.zzz.org/mailman/listinfo/ppp
-
diff --git a/tests/test_future/disabled/test_email/data/msg_20.txt b/tests/test_future/disabled/test_email/data/msg_20.txt
deleted file mode 100644
index 1a6a8878..00000000
--- a/tests/test_future/disabled/test_email/data/msg_20.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-Return-Path:
-Delivered-To: bbb@zzz.org
-Received: by mail.zzz.org (Postfix, from userid 889)
- id 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
-MIME-Version: 1.0
-Content-Type: text/plain; charset=us-ascii
-Content-Transfer-Encoding: 7bit
-Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
-From: bbb@ddd.com (John X. Doe)
-To: bbb@zzz.org
-Cc: ccc@zzz.org
-CC: ddd@zzz.org
-cc: eee@zzz.org
-Subject: This is a test message
-Date: Fri, 4 May 2001 14:05:44 -0400
-
-
-Hi,
-
-Do you like this message?
-
--Me
diff --git a/tests/test_future/disabled/test_email/data/msg_21.txt b/tests/test_future/disabled/test_email/data/msg_21.txt
deleted file mode 100644
index 23590b25..00000000
--- a/tests/test_future/disabled/test_email/data/msg_21.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-From: aperson@dom.ain
-To: bperson@dom.ain
-Subject: Test
-Content-Type: multipart/mixed; boundary="BOUNDARY"
-
-MIME message
---BOUNDARY
-Content-Type: text/plain; charset="us-ascii"
-MIME-Version: 1.0
-Content-Transfer-Encoding: 7bit
-
-One
---BOUNDARY
-Content-Type: text/plain; charset="us-ascii"
-MIME-Version: 1.0
-Content-Transfer-Encoding: 7bit
-
-Two
---BOUNDARY--
-End of MIME message
diff --git a/tests/test_future/disabled/test_email/data/msg_22.txt b/tests/test_future/disabled/test_email/data/msg_22.txt
deleted file mode 100644
index af9de5fa..00000000
--- a/tests/test_future/disabled/test_email/data/msg_22.txt
+++ /dev/null
@@ -1,46 +0,0 @@
-Mime-Version: 1.0
-Message-Id:
-Date: Tue, 16 Oct 2001 13:59:25 +0300
-To: a@example.com
-From: b@example.com
-Content-Type: multipart/mixed; boundary="============_-1208892523==_============"
-
---============_-1208892523==_============
-Content-Type: text/plain; charset="us-ascii" ; format="flowed"
-
-Text text text.
---============_-1208892523==_============
-Content-Id:
-Content-Type: image/jpeg; name="wibble.JPG"
- ; x-mac-type="4A504547"
- ; x-mac-creator="474B4F4E"
-Content-Disposition: attachment; filename="wibble.JPG"
-Content-Transfer-Encoding: base64
-
-/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB
-AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAALCAXABIEBAREA
-g6bCjjw/pIZSjO6FWFpldjySOmCNrO7DBZibUXhTwtCixw+GtAijVdqxxaPp0aKvmGXa
-qrbBQvms0mAMeYS/3iTV1dG0hHaRNK01XblnWxtVdjkHLMIgTyqnk9VB7CrP2KzIINpa
-4O7I+zxYO9WV8jZg71Zlb+8rMDkEirAVQFAUAKAFAAAUAYAUDgADgY6DjpRtXj5RxjHA
-4wQRj0wQCMdCAewpaKKK/9k=
---============_-1208892523==_============
-Content-Id:
-Content-Type: image/jpeg; name="wibble2.JPG"
- ; x-mac-type="4A504547"
- ; x-mac-creator="474B4F4E"
-Content-Disposition: attachment; filename="wibble2.JPG"
-Content-Transfer-Encoding: base64
-
-/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB
-AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAALCAXABJ0BAREA
-/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA
-W6NFJJBEkU10kKGTcWMDwxuU+0JHvk8qAtOpNwqSR0n8c3BlDyXHlqsUltHEiTvdXLxR
-7vMiGDNJAJWkAMk8ZkCFp5G2oo5W++INrbQtNfTQxJAuXlupz9oS4d5Y1W+E2XlWZJJE
-Y7LWYQxTLE1zuMbfBPxw8X2fibVdIbSbI6nLZxX635t9TjtYreWR7WGKJTLJFFKSlozO
-0ShxIXM43uC3/9k=
---============_-1208892523==_============
-Content-Type: text/plain; charset="us-ascii" ; format="flowed"
-
-Text text text.
---============_-1208892523==_============--
-
diff --git a/tests/test_future/disabled/test_email/data/msg_23.txt b/tests/test_future/disabled/test_email/data/msg_23.txt
deleted file mode 100644
index bb2e8ec3..00000000
--- a/tests/test_future/disabled/test_email/data/msg_23.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-From: aperson@dom.ain
-Content-Type: multipart/mixed; boundary="BOUNDARY"
-
---BOUNDARY
-Content-Type: text/plain
-
-A message part
---BOUNDARY--
diff --git a/tests/test_future/disabled/test_email/data/msg_24.txt b/tests/test_future/disabled/test_email/data/msg_24.txt
deleted file mode 100644
index 4e52339e..00000000
--- a/tests/test_future/disabled/test_email/data/msg_24.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-Content-Type: multipart/mixed; boundary="BOUNDARY"
-MIME-Version: 1.0
-Subject: A subject
-To: aperson@dom.ain
-From: bperson@dom.ain
-
---BOUNDARY
-
-
---BOUNDARY--
diff --git a/tests/test_future/disabled/test_email/data/msg_25.txt b/tests/test_future/disabled/test_email/data/msg_25.txt
deleted file mode 100644
index 9e35275f..00000000
--- a/tests/test_future/disabled/test_email/data/msg_25.txt
+++ /dev/null
@@ -1,117 +0,0 @@
-From MAILER-DAEMON Fri Apr 06 16:46:09 2001
-Received: from [204.245.199.98] (helo=zinfandel.lacita.com)
- by www.linux.org.uk with esmtp (Exim 3.13 #1)
- id 14lYR6-0008Iv-00
- for linuxuser-admin@www.linux.org.uk; Fri, 06 Apr 2001 16:46:09 +0100
-Received: from localhost (localhost) by zinfandel.lacita.com (8.7.3/8.6.10-MT4.00) with internal id JAB03225; Fri, 6 Apr 2001 09:23:06 -0800 (GMT-0800)
-Date: Fri, 6 Apr 2001 09:23:06 -0800 (GMT-0800)
-From: Mail Delivery Subsystem
-Subject: Returned mail: Too many hops 19 (17 max): from via [199.164.235.226], to
-Message-Id: <200104061723.JAB03225@zinfandel.lacita.com>
-To:
-To: postmaster@zinfandel.lacita.com
-MIME-Version: 1.0
-Content-Type: multipart/report; report-type=delivery-status;
- bo
-Auto-Submitted: auto-generated (failure)
-
-This is a MIME-encapsulated message
-
---JAB03225.986577786/zinfandel.lacita.com
-
-The original message was received at Fri, 6 Apr 2001 09:23:03 -0800 (GMT-0800)
-from [199.164.235.226]
-
- ----- The following addresses have delivery notifications -----
- (unrecoverable error)
-
- ----- Transcript of session follows -----
-554 Too many hops 19 (17 max): from via [199.164.235.226], to
-
---JAB03225.986577786/zinfandel.lacita.com
-Content-Type: message/delivery-status
-
-Reporting-MTA: dns; zinfandel.lacita.com
-Received-From-MTA: dns; [199.164.235.226]
-Arrival-Date: Fri, 6 Apr 2001 09:23:03 -0800 (GMT-0800)
-
-Final-Recipient: rfc822; scoffman@wellpartner.com
-Action: failed
-Status: 5.4.6
-Last-Attempt-Date: Fri, 6 Apr 2001 09:23:06 -0800 (GMT-0800)
-
---JAB03225.986577786/zinfandel.lacita.com
-Content-Type: text/rfc822-headers
-
-Return-Path: linuxuser-admin@www.linux.org.uk
-Received: from ns1.wellpartner.net ([199.164.235.226]) by zinfandel.lacita.com (8.7.3/8.6.10-MT4.00) with ESMTP id JAA03225 for ; Fri, 6 Apr 2001 09:23:03 -0800 (GMT-0800)
-Received: from zinfandel.lacita.com ([204.245.199.98])
- by
- fo
-Received: from ns1.wellpartner.net ([199.164.235.226]) by zinfandel.lacita.com (8.7.3/8.6.10-MT4.00) with ESMTP id JAA03221 for ; Fri, 6 Apr 2001 09:22:18 -0800 (GMT-0800)
-Received: from zinfandel.lacita.com ([204.245.199.98])
- by
- fo
-Received: from ns1.wellpartner.net ([199.164.235.226]) by zinfandel.lacita.com (8.7.3/8.6.10-MT4.00) with ESMTP id JAA03217 for ; Fri, 6 Apr 2001 09:21:37 -0800 (GMT-0800)
-Received: from zinfandel.lacita.com ([204.245.199.98])
- by
- fo
-Received: from ns1.wellpartner.net ([199.164.235.226]) by zinfandel.lacita.com (8.7.3/8.6.10-MT4.00) with ESMTP id JAA03213 for ; Fri, 6 Apr 2001 09:20:56 -0800 (GMT-0800)
-Received: from zinfandel.lacita.com ([204.245.199.98])
- by
- fo
-Received: from ns1.wellpartner.net ([199.164.235.226]) by zinfandel.lacita.com (8.7.3/8.6.10-MT4.00) with ESMTP id JAA03209 for ; Fri, 6 Apr 2001 09:20:15 -0800 (GMT-0800)
-Received: from zinfandel.lacita.com ([204.245.199.98])
- by
- fo
-Received: from ns1.wellpartner.net ([199.164.235.226]) by zinfandel.lacita.com (8.7.3/8.6.10-MT4.00) with ESMTP id JAA03205 for ; Fri, 6 Apr 2001 09:19:33 -0800 (GMT-0800)
-Received: from zinfandel.lacita.com ([204.245.199.98])
- by
- fo
-Received: from ns1.wellpartner.net ([199.164.235.226]) by zinfandel.lacita.com (8.7.3/8.6.10-MT4.00) with ESMTP id JAA03201 for ; Fri, 6 Apr 2001 09:18:52 -0800 (GMT-0800)
-Received: from zinfandel.lacita.com ([204.245.199.98])
- by
- fo
-Received: from ns1.wellpartner.net ([199.164.235.226]) by zinfandel.lacita.com (8.7.3/8.6.10-MT4.00) with ESMTP id JAA03197 for ; Fri, 6 Apr 2001 09:17:54 -0800 (GMT-0800)
-Received: from www.linux.org.uk (parcelfarce.linux.theplanet.co.uk [195.92.249.252])
- by
- fo
-Received: from localhost.localdomain
- ([
- by
- id
-Received: from [212.1.130.11] (helo=s1.uklinux.net ident=root)
- by
- id
- fo
-Received: from server (ppp-2-22.cvx4.telinco.net [212.1.149.22])
- by
- fo
-From: Daniel James
-Organization: LinuxUser
-To: linuxuser@www.linux.org.uk
-X-Mailer: KMail [version 1.1.99]
-Content-Type: text/plain;
- c
-MIME-Version: 1.0
-Message-Id: <01040616033903.00962@server>
-Content-Transfer-Encoding: 8bit
-Subject: [LinuxUser] bulletin no. 45
-Sender: linuxuser-admin@www.linux.org.uk
-Errors-To: linuxuser-admin@www.linux.org.uk
-X-BeenThere: linuxuser@www.linux.org.uk
-X-Mailman-Version: 2.0.3
-Precedence: bulk
-List-Help:
-List-Post:
-List-Subscribe: ,
-
-List-Unsubscribe: ,
-
-Date: Fri, 6 Apr 2001 16:03:39 +0100
-
---JAB03225.986577786/zinfandel.lacita.com--
-
-
diff --git a/tests/test_future/disabled/test_email/data/msg_26.txt b/tests/test_future/disabled/test_email/data/msg_26.txt
deleted file mode 100644
index 58efaa9c..00000000
--- a/tests/test_future/disabled/test_email/data/msg_26.txt
+++ /dev/null
@@ -1,46 +0,0 @@
-Received: from xcar [192.168.0.2] by jeeves.wooster.local
- (SMTPD32-7.07 EVAL) id AFF92F0214; Sun, 12 May 2002 08:55:37 +0100
-Date: Sun, 12 May 2002 08:56:15 +0100
-From: Father Time
-To: timbo@jeeves.wooster.local
-Subject: IMAP file test
-Message-ID: <6df65d354b.father.time@rpc.wooster.local>
-X-Organization: Home
-User-Agent: Messenger-Pro/2.50a (MsgServe/1.50) (RISC-OS/4.02) POPstar/2.03
-MIME-Version: 1.0
-Content-Type: multipart/mixed; boundary="1618492860--2051301190--113853680"
-Status: R
-X-UIDL: 319998302
-
-This message is in MIME format which your mailer apparently does not support.
-You either require a newer version of your software which supports MIME, or
-a separate MIME decoding utility. Alternatively, ask the sender of this
-message to resend it in a different format.
-
---1618492860--2051301190--113853680
-Content-Type: text/plain; charset=us-ascii
-
-Simple email with attachment.
-
-
---1618492860--2051301190--113853680
-Content-Type: application/riscos; name="clock.bmp,69c"; type=BMP;
- load=&fff69c4b; exec=&355dd4d1; access=&03
-Content-Disposition: attachment; filename="clock.bmp"
-Content-Transfer-Encoding: base64
-
-Qk12AgAAAAAAAHYAAAAoAAAAIAAAACAAAAABAAQAAAAAAAAAAADXDQAA1w0AAAAAAAAA
-AAAAAAAAAAAAiAAAiAAAAIiIAIgAAACIAIgAiIgAALu7uwCIiIgAERHdACLuIgAz//8A
-zAAAAN0R3QDu7iIA////AAAAAAAAAAAAAAAAAAAAAAAAAAi3AAAAAAAAADeAAAAAAAAA
-C3ADMzMzMANwAAAAAAAAAAAHMAAAAANwAAAAAAAAAACAMAd3zPfwAwgAAAAAAAAIAwd/
-f8x/f3AwgAAAAAAAgDB0x/f3//zPAwgAAAAAAAcHfM9////8z/AwAAAAAAiwd/f3////
-////A4AAAAAAcEx/f///////zAMAAAAAiwfM9////3///8zwOAAAAAcHf3////B/////
-8DAAAAALB/f3///wd3d3//AwAAAABwTPf//wCQAAD/zAMAAAAAsEx/f///B////8wDAA
-AAAHB39////wf/////AwAAAACwf39///8H/////wMAAAAIcHfM9///B////M8DgAAAAA
-sHTH///wf///xAMAAAAACHB3f3//8H////cDgAAAAAALB3zH//D//M9wMAAAAAAAgLB0
-z39///xHAwgAAAAAAAgLB3d3RHd3cDCAAAAAAAAAgLAHd0R3cAMIAAAAAAAAgAgLcAAA
-AAMwgAgAAAAACDAAAAu7t7cwAAgDgAAAAABzcIAAAAAAAAgDMwAAAAAAN7uwgAAAAAgH
-MzMAAAAACH97tzAAAAALu3c3gAAAAAAL+7tzDABAu7f7cAAAAAAACA+3MA7EQAv/sIAA
-AAAAAAAIAAAAAAAAAIAAAAAA
-
---1618492860--2051301190--113853680--
diff --git a/tests/test_future/disabled/test_email/data/msg_27.txt b/tests/test_future/disabled/test_email/data/msg_27.txt
deleted file mode 100644
index d0191769..00000000
--- a/tests/test_future/disabled/test_email/data/msg_27.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-Return-Path:
-Received: by mail.dom.ain (Postfix, from userid 889)
- id B9D0AD35DB; Tue, 4 Jun 2002 21:46:59 -0400 (EDT)
-Message-ID: <15613.28051.707126.569693@dom.ain>
-Date: Tue, 4 Jun 2002 21:46:59 -0400
-MIME-Version: 1.0
-Content-Type: text/plain; charset=us-ascii
-Content-Transfer-Encoding: 7bit
-Subject: bug demonstration
- 12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
- more text
-From: aperson@dom.ain (Anne P. Erson)
-To: bperson@dom.ain (Barney P. Erson)
-
-test
diff --git a/tests/test_future/disabled/test_email/data/msg_28.txt b/tests/test_future/disabled/test_email/data/msg_28.txt
deleted file mode 100644
index 1e4824ca..00000000
--- a/tests/test_future/disabled/test_email/data/msg_28.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-From: aperson@dom.ain
-MIME-Version: 1.0
-Content-Type: multipart/digest; boundary=BOUNDARY
-
---BOUNDARY
-Content-Type: message/rfc822
-
-Content-Type: text/plain; charset=us-ascii
-To: aa@bb.org
-From: cc@dd.org
-Subject: ee
-
-message 1
-
---BOUNDARY
-Content-Type: message/rfc822
-
-Content-Type: text/plain; charset=us-ascii
-To: aa@bb.org
-From: cc@dd.org
-Subject: ee
-
-message 2
-
---BOUNDARY--
diff --git a/tests/test_future/disabled/test_email/data/msg_29.txt b/tests/test_future/disabled/test_email/data/msg_29.txt
deleted file mode 100644
index 1fab5616..00000000
--- a/tests/test_future/disabled/test_email/data/msg_29.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-Return-Path:
-Delivered-To: bbb@zzz.org
-Received: by mail.zzz.org (Postfix, from userid 889)
- id 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
-MIME-Version: 1.0
-Content-Type: text/plain; charset=us-ascii;
- title*0*="us-ascii'en'This%20is%20even%20more%20";
- title*1*="%2A%2A%2Afun%2A%2A%2A%20";
- title*2="isn't it!"
-Content-Transfer-Encoding: 7bit
-Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
-From: bbb@ddd.com (John X. Doe)
-To: bbb@zzz.org
-Subject: This is a test message
-Date: Fri, 4 May 2001 14:05:44 -0400
-
-
-Hi,
-
-Do you like this message?
-
--Me
diff --git a/tests/test_future/disabled/test_email/data/msg_30.txt b/tests/test_future/disabled/test_email/data/msg_30.txt
deleted file mode 100644
index 4334bb6e..00000000
--- a/tests/test_future/disabled/test_email/data/msg_30.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-From: aperson@dom.ain
-MIME-Version: 1.0
-Content-Type: multipart/digest; boundary=BOUNDARY
-
---BOUNDARY
-
-Content-Type: text/plain; charset=us-ascii
-To: aa@bb.org
-From: cc@dd.org
-Subject: ee
-
-message 1
-
---BOUNDARY
-
-Content-Type: text/plain; charset=us-ascii
-To: aa@bb.org
-From: cc@dd.org
-Subject: ee
-
-message 2
-
---BOUNDARY--
diff --git a/tests/test_future/disabled/test_email/data/msg_31.txt b/tests/test_future/disabled/test_email/data/msg_31.txt
deleted file mode 100644
index 1e58e56c..00000000
--- a/tests/test_future/disabled/test_email/data/msg_31.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-From: aperson@dom.ain
-MIME-Version: 1.0
-Content-Type: multipart/mixed; boundary=BOUNDARY_
-
---BOUNDARY
-Content-Type: text/plain
-
-message 1
-
---BOUNDARY
-Content-Type: text/plain
-
-message 2
-
---BOUNDARY--
diff --git a/tests/test_future/disabled/test_email/data/msg_32.txt b/tests/test_future/disabled/test_email/data/msg_32.txt
deleted file mode 100644
index 07ec5af9..00000000
--- a/tests/test_future/disabled/test_email/data/msg_32.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-Delivered-To: freebsd-isp@freebsd.org
-Date: Tue, 26 Sep 2000 12:23:03 -0500
-From: Anne Person
-To: Barney Dude
-Subject: Re: Limiting Perl CPU Utilization...
-Mime-Version: 1.0
-Content-Type: text/plain; charset*=ansi-x3.4-1968''us-ascii
-Content-Disposition: inline
-User-Agent: Mutt/1.3.8i
-Sender: owner-freebsd-isp@FreeBSD.ORG
-Precedence: bulk
-X-Loop: FreeBSD.org
-
-Some message.
diff --git a/tests/test_future/disabled/test_email/data/msg_33.txt b/tests/test_future/disabled/test_email/data/msg_33.txt
deleted file mode 100644
index 042787a4..00000000
--- a/tests/test_future/disabled/test_email/data/msg_33.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-Delivered-To: freebsd-isp@freebsd.org
-Date: Wed, 27 Sep 2000 11:11:09 -0500
-From: Anne Person
-To: Barney Dude
-Subject: Re: Limiting Perl CPU Utilization...
-Mime-Version: 1.0
-Content-Type: multipart/signed; micalg*=ansi-x3.4-1968''pgp-md5;
- protocol*=ansi-x3.4-1968''application%2Fpgp-signature;
- boundary*="ansi-x3.4-1968''EeQfGwPcQSOJBaQU"
-Content-Disposition: inline
-Sender: owner-freebsd-isp@FreeBSD.ORG
-Precedence: bulk
-X-Loop: FreeBSD.org
-
-
---EeQfGwPcQSOJBaQU
-Content-Type: text/plain; charset*=ansi-x3.4-1968''us-ascii
-Content-Disposition: inline
-Content-Transfer-Encoding: quoted-printable
-
-part 1
-
---EeQfGwPcQSOJBaQU
-Content-Type: text/plain
-Content-Disposition: inline
-
-part 2
-
---EeQfGwPcQSOJBaQU--
diff --git a/tests/test_future/disabled/test_email/data/msg_34.txt b/tests/test_future/disabled/test_email/data/msg_34.txt
deleted file mode 100644
index 055dfea5..00000000
--- a/tests/test_future/disabled/test_email/data/msg_34.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-From: aperson@dom.ain
-To: bperson@dom.ain
-Content-Type: multipart/digest; boundary=XYZ
-
---XYZ
-Content-Type: text/plain
-
-
-This is a text plain part that is counter to recommended practice in
-RFC 2046, $5.1.5, but is not illegal
-
---XYZ
-
-From: cperson@dom.ain
-To: dperson@dom.ain
-
-A submessage
-
---XYZ--
diff --git a/tests/test_future/disabled/test_email/data/msg_35.txt b/tests/test_future/disabled/test_email/data/msg_35.txt
deleted file mode 100644
index be7d5a2f..00000000
--- a/tests/test_future/disabled/test_email/data/msg_35.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-From: aperson@dom.ain
-To: bperson@dom.ain
-Subject: here's something interesting
-counter to RFC 2822, there's no separating newline here
diff --git a/tests/test_future/disabled/test_email/data/msg_36.txt b/tests/test_future/disabled/test_email/data/msg_36.txt
deleted file mode 100644
index 5632c306..00000000
--- a/tests/test_future/disabled/test_email/data/msg_36.txt
+++ /dev/null
@@ -1,40 +0,0 @@
-Mime-Version: 1.0
-Content-Type: Multipart/Mixed; Boundary="NextPart"
-To: IETF-Announce:;
-From: Internet-Drafts@ietf.org
-Subject: I-D ACTION:draft-ietf-mboned-mix-00.txt
-Date: Tue, 22 Dec 1998 16:55:06 -0500
-
---NextPart
-
-Blah blah blah
-
---NextPart
-Content-Type: Multipart/Alternative; Boundary="OtherAccess"
-
---OtherAccess
-Content-Type: Message/External-body;
- access-type="mail-server";
- server="mailserv@ietf.org"
-
-Content-Type: text/plain
-Content-ID: <19981222151406.I-D@ietf.org>
-
-ENCODING mime
-FILE /internet-drafts/draft-ietf-mboned-mix-00.txt
-
---OtherAccess
-Content-Type: Message/External-body;
- name="draft-ietf-mboned-mix-00.txt";
- site="ftp.ietf.org";
- access-type="anon-ftp";
- directory="internet-drafts"
-
-Content-Type: text/plain
-Content-ID: <19981222151406.I-D@ietf.org>
-
-
---OtherAccess--
-
---NextPart--
-
diff --git a/tests/test_future/disabled/test_email/data/msg_37.txt b/tests/test_future/disabled/test_email/data/msg_37.txt
deleted file mode 100644
index 038d34a1..00000000
--- a/tests/test_future/disabled/test_email/data/msg_37.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-Content-Type: multipart/mixed; boundary=ABCDE
-
---ABCDE
-Content-Type: text/x-one
-
-Blah
-
---ABCDE
---ABCDE
-Content-Type: text/x-two
-
-Blah
-
---ABCDE
---ABCDE
---ABCDE
---ABCDE
-Content-Type: text/x-two
-
-Blah
-
---ABCDE--
diff --git a/tests/test_future/disabled/test_email/data/msg_38.txt b/tests/test_future/disabled/test_email/data/msg_38.txt
deleted file mode 100644
index 006df81c..00000000
--- a/tests/test_future/disabled/test_email/data/msg_38.txt
+++ /dev/null
@@ -1,101 +0,0 @@
-MIME-Version: 1.0
-Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
-
-------- =_aaaaaaaaaa0
-Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa1"
-Content-ID: <20592.1022586929.1@example.com>
-
-------- =_aaaaaaaaaa1
-Content-Type: multipart/alternative; boundary="----- =_aaaaaaaaaa2"
-Content-ID: <20592.1022586929.2@example.com>
-
-------- =_aaaaaaaaaa2
-Content-Type: text/plain
-Content-ID: <20592.1022586929.3@example.com>
-Content-Description: very tricky
-Content-Transfer-Encoding: 7bit
-
-
-Unlike the test test_nested-multiples-with-internal-boundary, this
-piece of text not only contains the outer boundary tags
-------- =_aaaaaaaaaa1
-and
-------- =_aaaaaaaaaa0
-but puts them at the start of a line! And, to be even nastier, it
-even includes a couple of end tags, such as this one:
-
-------- =_aaaaaaaaaa1--
-
-and this one, which is from a multipart we haven't even seen yet!
-
-------- =_aaaaaaaaaa4--
-
-This will, I'm sure, cause much breakage of MIME parsers. But, as
-far as I can tell, it's perfectly legal. I have not yet ever seen
-a case of this in the wild, but I've seen *similar* things.
-
-
-------- =_aaaaaaaaaa2
-Content-Type: application/octet-stream
-Content-ID: <20592.1022586929.4@example.com>
-Content-Description: patch2
-Content-Transfer-Encoding: base64
-
-XXX
-
-------- =_aaaaaaaaaa2--
-
-------- =_aaaaaaaaaa1
-Content-Type: multipart/alternative; boundary="----- =_aaaaaaaaaa3"
-Content-ID: <20592.1022586929.6@example.com>
-
-------- =_aaaaaaaaaa3
-Content-Type: application/octet-stream
-Content-ID: <20592.1022586929.7@example.com>
-Content-Description: patch3
-Content-Transfer-Encoding: base64
-
-XXX
-
-------- =_aaaaaaaaaa3
-Content-Type: application/octet-stream
-Content-ID: <20592.1022586929.8@example.com>
-Content-Description: patch4
-Content-Transfer-Encoding: base64
-
-XXX
-
-------- =_aaaaaaaaaa3--
-
-------- =_aaaaaaaaaa1
-Content-Type: multipart/alternative; boundary="----- =_aaaaaaaaaa4"
-Content-ID: <20592.1022586929.10@example.com>
-
-------- =_aaaaaaaaaa4
-Content-Type: application/octet-stream
-Content-ID: <20592.1022586929.11@example.com>
-Content-Description: patch5
-Content-Transfer-Encoding: base64
-
-XXX
-
-------- =_aaaaaaaaaa4
-Content-Type: application/octet-stream
-Content-ID: <20592.1022586929.12@example.com>
-Content-Description: patch6
-Content-Transfer-Encoding: base64
-
-XXX
-
-------- =_aaaaaaaaaa4--
-
-------- =_aaaaaaaaaa1--
-
-------- =_aaaaaaaaaa0
-Content-Type: text/plain; charset="us-ascii"
-Content-ID: <20592.1022586929.15@example.com>
-
---
-It's never too late to have a happy childhood.
-
-------- =_aaaaaaaaaa0--
diff --git a/tests/test_future/disabled/test_email/data/msg_39.txt b/tests/test_future/disabled/test_email/data/msg_39.txt
deleted file mode 100644
index 124b2691..00000000
--- a/tests/test_future/disabled/test_email/data/msg_39.txt
+++ /dev/null
@@ -1,83 +0,0 @@
-MIME-Version: 1.0
-Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
-
-------- =_aaaaaaaaaa0
-Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa1"
-Content-ID: <20592.1022586929.1@example.com>
-
-------- =_aaaaaaaaaa1
-Content-Type: multipart/alternative; boundary="----- =_aaaaaaaaaa1"
-Content-ID: <20592.1022586929.2@example.com>
-
-------- =_aaaaaaaaaa1
-Content-Type: application/octet-stream
-Content-ID: <20592.1022586929.3@example.com>
-Content-Description: patch1
-Content-Transfer-Encoding: base64
-
-XXX
-
-------- =_aaaaaaaaaa1
-Content-Type: application/octet-stream
-Content-ID: <20592.1022586929.4@example.com>
-Content-Description: patch2
-Content-Transfer-Encoding: base64
-
-XXX
-
-------- =_aaaaaaaaaa1--
-
-------- =_aaaaaaaaaa1
-Content-Type: multipart/alternative; boundary="----- =_aaaaaaaaaa1"
-Content-ID: <20592.1022586929.6@example.com>
-
-------- =_aaaaaaaaaa1
-Content-Type: application/octet-stream
-Content-ID: <20592.1022586929.7@example.com>
-Content-Description: patch3
-Content-Transfer-Encoding: base64
-
-XXX
-
-------- =_aaaaaaaaaa1
-Content-Type: application/octet-stream
-Content-ID: <20592.1022586929.8@example.com>
-Content-Description: patch4
-Content-Transfer-Encoding: base64
-
-XXX
-
-------- =_aaaaaaaaaa1--
-
-------- =_aaaaaaaaaa1
-Content-Type: multipart/alternative; boundary="----- =_aaaaaaaaaa1"
-Content-ID: <20592.1022586929.10@example.com>
-
-------- =_aaaaaaaaaa1
-Content-Type: application/octet-stream
-Content-ID: <20592.1022586929.11@example.com>
-Content-Description: patch5
-Content-Transfer-Encoding: base64
-
-XXX
-
-------- =_aaaaaaaaaa1
-Content-Type: application/octet-stream
-Content-ID: <20592.1022586929.12@example.com>
-Content-Description: patch6
-Content-Transfer-Encoding: base64
-
-XXX
-
-------- =_aaaaaaaaaa1--
-
-------- =_aaaaaaaaaa1--
-
-------- =_aaaaaaaaaa0
-Content-Type: text/plain; charset="us-ascii"
-Content-ID: <20592.1022586929.15@example.com>
-
---
-It's never too late to have a happy childhood.
-
-------- =_aaaaaaaaaa0--
diff --git a/tests/test_future/disabled/test_email/data/msg_40.txt b/tests/test_future/disabled/test_email/data/msg_40.txt
deleted file mode 100644
index 1435fa1e..00000000
--- a/tests/test_future/disabled/test_email/data/msg_40.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-MIME-Version: 1.0
-Content-Type: text/html; boundary="--961284236552522269"
-
-----961284236552522269
-Content-Type: text/html;
-Content-Transfer-Encoding: 7Bit
-
-
-
-----961284236552522269--
diff --git a/tests/test_future/disabled/test_email/data/msg_41.txt b/tests/test_future/disabled/test_email/data/msg_41.txt
deleted file mode 100644
index 76cdd1cb..00000000
--- a/tests/test_future/disabled/test_email/data/msg_41.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-From: "Allison Dunlap"
-To: yyy@example.com
-Subject: 64423
-Date: Sun, 11 Jul 2004 16:09:27 -0300
-MIME-Version: 1.0
-Content-Type: multipart/alternative;
-
-Blah blah blah
diff --git a/tests/test_future/disabled/test_email/data/msg_42.txt b/tests/test_future/disabled/test_email/data/msg_42.txt
deleted file mode 100644
index a75f8f4a..00000000
--- a/tests/test_future/disabled/test_email/data/msg_42.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-Content-Type: multipart/mixed; boundary="AAA"
-From: Mail Delivery Subsystem
-To: yyy@example.com
-
-This is a MIME-encapsulated message
-
---AAA
-
-Stuff
-
---AAA
-Content-Type: message/rfc822
-
-From: webmaster@python.org
-To: zzz@example.com
-Content-Type: multipart/mixed; boundary="BBB"
-
---BBB--
-
---AAA--
diff --git a/tests/test_future/disabled/test_email/data/msg_43.txt b/tests/test_future/disabled/test_email/data/msg_43.txt
deleted file mode 100644
index 797d12c5..00000000
--- a/tests/test_future/disabled/test_email/data/msg_43.txt
+++ /dev/null
@@ -1,217 +0,0 @@
-From SRS0=aO/p=ON=bag.python.org=None@bounce2.pobox.com Fri Nov 26 21:40:36 2004
-X-VM-v5-Data: ([nil nil nil nil nil nil nil nil nil]
- [nil nil nil nil nil nil nil "MAILER DAEMON <>" "MAILER DAEMON <>" nil nil "Banned file: auto__mail.python.bat in mail from you" "^From:" nil nil nil nil "Banned file: auto__mail.python.bat in mail from you" nil nil nil nil nil nil nil]
- nil)
-MIME-Version: 1.0
-Message-Id:
-Content-Type: multipart/report; report-type=delivery-status;
- charset=utf-8;
- boundary="----------=_1101526904-1956-5"
-X-Virus-Scanned: by XS4ALL Virus Scanner
-X-UIDL: 4\G!!!
-To:
-Subject: Banned file: auto__mail.python.bat in mail from you
-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-This is a multi-part message in MIME format...
-
-------------=_1101526904-1956-5
-Content-Type: text/plain; charset="utf-8"
-Content-Disposition: inline
-Content-Transfer-Encoding: 7bit
-
-BANNED FILENAME ALERT
-
-Your message to: xxxxxxx@dot.ca.gov, xxxxxxxxxxxxx@dot.ca.gov, xxxxxxxxxx@dot.ca.gov, xxxxxxxx@dot.ca.gov, xxxxxxxxxx@dot.ca.gov, xxxxxx@dot.ca.gov, xxxxxxxxxx@dot.ca.gov, xxxxxx@dot.ca.gov, xxxxxx@dot.ca.gov, xxxxxxxxxxxxxxxx@dot.ca.gov, xxxxxxxxxxx@dot.ca.gov, xxxxxxxxxx@dot.ca.gov, xxxxxxxxxx@dot.ca.gov, xxxxxxxxxxxx@dot.ca.gov, xxxxxxxxxxxx@dot.ca.gov, xxxxxxx@dot.ca.gov, xxxxxxxxx@dot.ca.gov, xxxxxxxxxx@dot.ca.gov, xxxxxx@dot.ca.gov, xxx@dot.ca.gov, xxxxxxx@dot.ca.gov, xxxxxxx@dot.ca.gov, xxxxxxxxxxxxxxx@dot.ca.gov, xxxxxxxxxx@dot.ca.gov, xxxxxxx@dot.ca.gov, xxx@dot.ca.gov, xxxxxxxx@dot.ca.gov, xxxxxxxxxxxxx@dot.ca.gov, xxxxxxxxxxxxx@dot.ca.gov, xxxxxxxxxxx@dot.ca.gov, xxxxxxxxx@dot.ca.gov, xxxxxxxxxx@dot.ca.gov, xxxxxxxxxxxx@dot.ca.gov, xxxxxxx@dot.ca.gov, xxxxxxxxxxxxxxx@dot.ca.gov, xxxxxxxxxxxxx@dot.ca.gov, xxxx@dot.ca.gov, xxxxxxxx@dot.ca.gov, xxxxxxxxxx@dot.ca.gov, xxxxxxxxxxxxxxxxxx@dot.ca.gov
-was blocked by our Spam Firewall. The email you sent with the following subject has NOT BEEN DELIVERED:
-
-Subject: Delivery_failure_notice
-
-An attachment in that mail was of a file type that the Spam Firewall is set to block.
-
-
-
-------------=_1101526904-1956-5
-Content-Type: message/delivery-status
-Content-Disposition: inline
-Content-Transfer-Encoding: 7bit
-Content-Description: Delivery error report
-
-Reporting-MTA: dns; sacspam01.dot.ca.gov
-Received-From-MTA: smtp; sacspam01.dot.ca.gov ([127.0.0.1])
-Arrival-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxxxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxxxxxxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxxxxxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-Final-Recipient: rfc822; xxxxxxx@dot.ca.gov
-Action: failed
-Status: 5.7.1
-Diagnostic-Code: smtp; 550 5.7.1 Message content rejected, id=01956-02-2 - BANNED: auto__mail.python.bat
-Last-Attempt-Date: Fri, 26 Nov 2004 19:41:44 -0800 (PST)
-
-------------=_1101526904-1956-5
-Content-Type: text/rfc822-headers
-Content-Disposition: inline
-Content-Transfer-Encoding: 7bit
-Content-Description: Undelivered-message headers
-
-Received: from kgsav.org (ppp-70-242-162-63.dsl.spfdmo.swbell.net [70.242.162.63])
- by sacspam01.dot.ca.gov (Spam Firewall) with SMTP
- id A232AD03DE3A; Fri, 26 Nov 2004 19:41:35 -0800 (PST)
-From: webmaster@python.org
-To: xxxxx@dot.ca.gov
-Date: Sat, 27 Nov 2004 03:35:30 UTC
-Subject: Delivery_failure_notice
-Importance: Normal
-X-Priority: 3 (Normal)
-X-MSMail-Priority: Normal
-Message-ID:
-MIME-Version: 1.0
-Content-Type: multipart/mixed; boundary="====67bd2b7a5.f99f7"
-Content-Transfer-Encoding: 7bit
-
-------------=_1101526904-1956-5--
-
diff --git a/tests/test_future/disabled/test_email/data/msg_44.txt b/tests/test_future/disabled/test_email/data/msg_44.txt
deleted file mode 100644
index 15a22528..00000000
--- a/tests/test_future/disabled/test_email/data/msg_44.txt
+++ /dev/null
@@ -1,33 +0,0 @@
-Return-Path:
-Delivered-To: barry@python.org
-Received: by mail.python.org (Postfix, from userid 889)
- id C2BF0D37C6; Tue, 11 Sep 2001 00:05:05 -0400 (EDT)
-MIME-Version: 1.0
-Content-Type: multipart/mixed; boundary="h90VIIIKmx"
-Content-Transfer-Encoding: 7bit
-Message-ID: <15261.36209.358846.118674@anthem.python.org>
-From: barry@python.org (Barry A. Warsaw)
-To: barry@python.org
-Subject: a simple multipart
-Date: Tue, 11 Sep 2001 00:05:05 -0400
-X-Mailer: VM 6.95 under 21.4 (patch 4) "Artificial Intelligence" XEmacs Lucid
-X-Attribution: BAW
-X-Oblique-Strategy: Make a door into a window
-
-
---h90VIIIKmx
-Content-Type: text/plain; name="msg.txt"
-Content-Transfer-Encoding: 7bit
-
-a simple kind of mirror
-to reflect upon our own
-
---h90VIIIKmx
-Content-Type: text/plain; name="msg.txt"
-Content-Transfer-Encoding: 7bit
-
-a simple kind of mirror
-to reflect upon our own
-
---h90VIIIKmx--
-
diff --git a/tests/test_future/disabled/test_email/data/msg_45.txt b/tests/test_future/disabled/test_email/data/msg_45.txt
deleted file mode 100644
index 58fde956..00000000
--- a/tests/test_future/disabled/test_email/data/msg_45.txt
+++ /dev/null
@@ -1,33 +0,0 @@
-From:
-To:
-Subject: test
-X-Long-Line: Some really long line contains a lot of text and thus has to be rewrapped because it is some
- really long
- line
-MIME-Version: 1.0
-Content-Type: multipart/signed; boundary="borderline";
- protocol="application/pgp-signature"; micalg=pgp-sha1
-
-This is an OpenPGP/MIME signed message (RFC 2440 and 3156)
---borderline
-Content-Type: text/plain
-X-Long-Line: Another really long line contains a lot of text and thus has to be rewrapped because it is another
- really long
- line
-
-This is the signed contents.
-
---borderline
-Content-Type: application/pgp-signature; name="signature.asc"
-Content-Description: OpenPGP digital signature
-Content-Disposition: attachment; filename="signature.asc"
-
------BEGIN PGP SIGNATURE-----
-Version: GnuPG v2.0.6 (GNU/Linux)
-
-iD8DBQFG03voRhp6o4m9dFsRApSZAKCCAN3IkJlVRg6NvAiMHlvvIuMGPQCeLZtj
-FGwfnRHFBFO/S4/DKysm0lI=
-=t7+s
------END PGP SIGNATURE-----
-
---borderline--
diff --git a/tests/test_future/disabled/test_email/data/msg_46.txt b/tests/test_future/disabled/test_email/data/msg_46.txt
deleted file mode 100644
index 1e22c4f6..00000000
--- a/tests/test_future/disabled/test_email/data/msg_46.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-Return-Path:
-Delivery-Date: Mon, 08 Feb 2010 14:05:16 +0100
-Received: from example.org (example.org [64.5.53.58])
- by example.net (node=mxbap2) with ESMTP (Nemesis)
- id UNIQUE for someone@example.com; Mon, 08 Feb 2010 14:05:16 +0100
-Date: Mon, 01 Feb 2010 12:21:16 +0100
-From: "Sender"
-To:
-Subject: GroupwiseForwardingTest
-Mime-Version: 1.0
-Content-Type: message/rfc822
-
-Return-path:
-Message-ID: <4B66B890.4070408@teconcept.de>
-Date: Mon, 01 Feb 2010 12:18:40 +0100
-From: "Dr. Sender"
-MIME-Version: 1.0
-To: "Recipient"
-Subject: GroupwiseForwardingTest
-Content-Type: text/plain; charset=ISO-8859-15
-Content-Transfer-Encoding: 7bit
-
-Testing email forwarding with Groupwise 1.2.2010
diff --git a/tests/test_future/disabled/test_email/disabled_test__encoded_words.py b/tests/test_future/disabled/test_email/disabled_test__encoded_words.py
deleted file mode 100644
index 08aecac9..00000000
--- a/tests/test_future/disabled/test_email/disabled_test__encoded_words.py
+++ /dev/null
@@ -1,190 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, division, unicode_literals
-
-from future.standard_library.email import _encoded_words as _ew
-from future.standard_library.email import errors
-from future.tests.test_email import TestEmailBase
-from future.tests.base import unittest
-
-
-class TestDecodeQ(TestEmailBase):
-
- def _test(self, source, ex_result, ex_defects=[]):
- result, defects = _ew.decode_q(source)
- self.assertEqual(result, ex_result)
- self.assertDefectsEqual(defects, ex_defects)
-
- def test_no_encoded(self):
- self._test(b'foobar', b'foobar')
-
- def test_spaces(self):
- self._test(b'foo=20bar=20', b'foo bar ')
- self._test(b'foo_bar_', b'foo bar ')
-
- def test_run_of_encoded(self):
- self._test(b'foo=20=20=21=2Cbar', b'foo !,bar')
-
-
-class TestDecodeB(TestEmailBase):
-
- def _test(self, source, ex_result, ex_defects=[]):
- result, defects = _ew.decode_b(source)
- self.assertEqual(result, ex_result)
- self.assertDefectsEqual(defects, ex_defects)
-
- def test_simple(self):
- self._test(b'Zm9v', b'foo')
-
- def test_missing_padding(self):
- self._test(b'dmk', b'vi', [errors.InvalidBase64PaddingDefect])
-
- def test_invalid_character(self):
- self._test(b'dm\x01k===', b'vi', [errors.InvalidBase64CharactersDefect])
-
- def test_invalid_character_and_bad_padding(self):
- self._test(b'dm\x01k', b'vi', [errors.InvalidBase64CharactersDefect,
- errors.InvalidBase64PaddingDefect])
-
-
-class TestDecode(TestEmailBase):
-
- def test_wrong_format_input_raises(self):
- with self.assertRaises(ValueError):
- _ew.decode('=?badone?=')
- with self.assertRaises(ValueError):
- _ew.decode('=?')
- with self.assertRaises(ValueError):
- _ew.decode('')
-
- def _test(self, source, result, charset='us-ascii', lang='', defects=[]):
- res, char, l, d = _ew.decode(source)
- self.assertEqual(res, result)
- self.assertEqual(char, charset)
- self.assertEqual(l, lang)
- self.assertDefectsEqual(d, defects)
-
- def test_simple_q(self):
- self._test('=?us-ascii?q?foo?=', 'foo')
-
- def test_simple_b(self):
- self._test('=?us-ascii?b?dmk=?=', 'vi')
-
- def test_q_case_ignored(self):
- self._test('=?us-ascii?Q?foo?=', 'foo')
-
- def test_b_case_ignored(self):
- self._test('=?us-ascii?B?dmk=?=', 'vi')
-
- def test_non_trivial_q(self):
- self._test('=?latin-1?q?=20F=fcr=20Elise=20?=', ' Für Elise ', 'latin-1')
-
- def test_q_escpaed_bytes_preserved(self):
- self._test(b'=?us-ascii?q?=20\xACfoo?='.decode('us-ascii',
- 'surrogateescape'),
- ' \uDCACfoo',
- defects = [errors.UndecodableBytesDefect])
-
- def test_b_undecodable_bytes_ignored_with_defect(self):
- self._test(b'=?us-ascii?b?dm\xACk?='.decode('us-ascii',
- 'surrogateescape'),
- 'vi',
- defects = [
- errors.InvalidBase64CharactersDefect,
- errors.InvalidBase64PaddingDefect])
-
- def test_b_invalid_bytes_ignored_with_defect(self):
- self._test('=?us-ascii?b?dm\x01k===?=',
- 'vi',
- defects = [errors.InvalidBase64CharactersDefect])
-
- def test_b_invalid_bytes_incorrect_padding(self):
- self._test('=?us-ascii?b?dm\x01k?=',
- 'vi',
- defects = [
- errors.InvalidBase64CharactersDefect,
- errors.InvalidBase64PaddingDefect])
-
- def test_b_padding_defect(self):
- self._test('=?us-ascii?b?dmk?=',
- 'vi',
- defects = [errors.InvalidBase64PaddingDefect])
-
- def test_nonnull_lang(self):
- self._test('=?us-ascii*jive?q?test?=', 'test', lang='jive')
-
- def test_unknown_8bit_charset(self):
- self._test('=?unknown-8bit?q?foo=ACbar?=',
- b'foo\xacbar'.decode('ascii', 'surrogateescape'),
- charset = 'unknown-8bit',
- defects = [])
-
- def test_unknown_charset(self):
- self._test('=?foobar?q?foo=ACbar?=',
- b'foo\xacbar'.decode('ascii', 'surrogateescape'),
- charset = 'foobar',
- # XXX Should this be a new Defect instead?
- defects = [errors.CharsetError])
-
-
-class TestEncodeQ(TestEmailBase):
-
- def _test(self, src, expected):
- self.assertEqual(_ew.encode_q(src), expected)
-
- def test_all_safe(self):
- self._test(b'foobar', 'foobar')
-
- def test_spaces(self):
- self._test(b'foo bar ', 'foo_bar_')
-
- def test_run_of_encodables(self):
- self._test(b'foo ,,bar', 'foo__=2C=2Cbar')
-
-
-class TestEncodeB(TestEmailBase):
-
- def test_simple(self):
- self.assertEqual(_ew.encode_b(b'foo'), 'Zm9v')
-
- def test_padding(self):
- self.assertEqual(_ew.encode_b(b'vi'), 'dmk=')
-
-
-class TestEncode(TestEmailBase):
-
- def test_q(self):
- self.assertEqual(_ew.encode('foo', 'utf-8', 'q'), '=?utf-8?q?foo?=')
-
- def test_b(self):
- self.assertEqual(_ew.encode('foo', 'utf-8', 'b'), '=?utf-8?b?Zm9v?=')
-
- def test_auto_q(self):
- self.assertEqual(_ew.encode('foo', 'utf-8'), '=?utf-8?q?foo?=')
-
- def test_auto_q_if_short_mostly_safe(self):
- self.assertEqual(_ew.encode('vi.', 'utf-8'), '=?utf-8?q?vi=2E?=')
-
- def test_auto_b_if_enough_unsafe(self):
- self.assertEqual(_ew.encode('.....', 'utf-8'), '=?utf-8?b?Li4uLi4=?=')
-
- def test_auto_b_if_long_unsafe(self):
- self.assertEqual(_ew.encode('vi.vi.vi.vi.vi.', 'utf-8'),
- '=?utf-8?b?dmkudmkudmkudmkudmku?=')
-
- def test_auto_q_if_long_mostly_safe(self):
- self.assertEqual(_ew.encode('vi vi vi.vi ', 'utf-8'),
- '=?utf-8?q?vi_vi_vi=2Evi_?=')
-
- def test_utf8_default(self):
- self.assertEqual(_ew.encode('foo'), '=?utf-8?q?foo?=')
-
- def test_lang(self):
- self.assertEqual(_ew.encode('foo', lang='jive'), '=?utf-8*jive?q?foo?=')
-
- def test_unknown_8bit(self):
- self.assertEqual(_ew.encode('foo\uDCACbar', charset='unknown-8bit'),
- '=?unknown-8bit?q?foo=ACbar?=')
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tests/test_future/disabled/test_email/disabled_test__header_value_parser.py b/tests/test_future/disabled/test_email/disabled_test__header_value_parser.py
deleted file mode 100644
index a5b95f23..00000000
--- a/tests/test_future/disabled/test_email/disabled_test__header_value_parser.py
+++ /dev/null
@@ -1,2556 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, division, unicode_literals
-import string
-from future.standard_library.email import _header_value_parser as parser
-from future.standard_library.email import errors
-from future.standard_library.email import policy
-from future.tests.base import unittest
-from future.tests.test_email import TestEmailBase, parameterize
-from future.builtins import bytes, range, str
-
-
-class TestTokens(TestEmailBase):
-
- # EWWhiteSpaceTerminal
-
- def test_EWWhiteSpaceTerminal(self):
- x = parser.EWWhiteSpaceTerminal(' \t', 'fws')
- self.assertEqual(x, ' \t')
- self.assertEqual(str(x), '')
- self.assertEqual(x.value, '')
- self.assertEqual(x.encoded, ' \t')
-
- # UnstructuredTokenList
-
- def test_undecodable_bytes_error_preserved(self):
- badstr = b"le pouf c\xaflebre".decode('ascii', 'surrogateescape')
- unst = parser.get_unstructured(badstr)
- self.assertDefectsEqual(unst.all_defects, [errors.UndecodableBytesDefect])
- parts = list(unst.parts)
- self.assertDefectsEqual(parts[0].all_defects, [])
- self.assertDefectsEqual(parts[1].all_defects, [])
- self.assertDefectsEqual(parts[2].all_defects, [errors.UndecodableBytesDefect])
-
-
-class TestParserMixin(object):
-
- def _assert_results(self, tl, rest, string, value, defects, remainder,
- comments=None):
- self.assertEqual(str(tl), string)
- self.assertEqual(tl.value, value)
- self.assertDefectsEqual(tl.all_defects, defects)
- self.assertEqual(rest, remainder)
- if comments is not None:
- self.assertEqual(tl.comments, comments)
-
- def _test_get_x(self, method, source, string, value, defects,
- remainder, comments=None):
- tl, rest = method(source)
- self._assert_results(tl, rest, string, value, defects, remainder,
- comments=None)
- return tl
-
- def _test_parse_x(self, method, input, string, value, defects,
- comments=None):
- tl = method(input)
- self._assert_results(tl, '', string, value, defects, '', comments)
- return tl
-
-
-class TestParser(TestParserMixin, TestEmailBase):
-
- # _wsp_splitter
-
- rfc_printable_ascii = bytes(range(33, 127)).decode('ascii')
- rfc_atext_chars = (string.ascii_letters + string.digits +
- "!#$%&\'*+-/=?^_`{}|~")
- rfc_dtext_chars = rfc_printable_ascii.translate(str.maketrans('','',r'\[]'))
-
- def test__wsp_splitter_one_word(self):
- self.assertEqual(parser._wsp_splitter('foo', 1), ['foo'])
-
- def test__wsp_splitter_two_words(self):
- self.assertEqual(parser._wsp_splitter('foo def', 1),
- ['foo', ' ', 'def'])
-
- def test__wsp_splitter_ws_runs(self):
- self.assertEqual(parser._wsp_splitter('foo \t def jik', 1),
- ['foo', ' \t ', 'def jik'])
-
-
- # get_fws
-
- def test_get_fws_only(self):
- fws = self._test_get_x(parser.get_fws, ' \t ', ' \t ', ' ', [], '')
- self.assertEqual(fws.token_type, 'fws')
-
- def test_get_fws_space(self):
- self._test_get_x(parser.get_fws, ' foo', ' ', ' ', [], 'foo')
-
- def test_get_fws_ws_run(self):
- self._test_get_x(parser.get_fws, ' \t foo ', ' \t ', ' ', [], 'foo ')
-
- # get_encoded_word
-
- def test_get_encoded_word_missing_start_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_encoded_word('abc')
-
- def test_get_encoded_word_missing_end_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_encoded_word('=?abc')
-
- def test_get_encoded_word_missing_middle_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_encoded_word('=?abc?=')
-
- def test_get_encoded_word_valid_ew(self):
- self._test_get_x(parser.get_encoded_word,
- '=?us-ascii?q?this_is_a_test?= bird',
- 'this is a test',
- 'this is a test',
- [],
- ' bird')
-
- def test_get_encoded_word_internal_spaces(self):
- self._test_get_x(parser.get_encoded_word,
- '=?us-ascii?q?this is a test?= bird',
- 'this is a test',
- 'this is a test',
- [errors.InvalidHeaderDefect],
- ' bird')
-
- def test_get_encoded_word_gets_first(self):
- self._test_get_x(parser.get_encoded_word,
- '=?us-ascii?q?first?= =?utf-8?q?second?=',
- 'first',
- 'first',
- [],
- ' =?utf-8?q?second?=')
-
- def test_get_encoded_word_gets_first_even_if_no_space(self):
- self._test_get_x(parser.get_encoded_word,
- '=?us-ascii?q?first?==?utf-8?q?second?=',
- 'first',
- 'first',
- [],
- '=?utf-8?q?second?=')
-
- def test_get_encoded_word_sets_extra_attributes(self):
- ew = self._test_get_x(parser.get_encoded_word,
- '=?us-ascii*jive?q?first_second?=',
- 'first second',
- 'first second',
- [],
- '')
- self.assertEqual(ew.encoded, '=?us-ascii*jive?q?first_second?=')
- self.assertEqual(ew.charset, 'us-ascii')
- self.assertEqual(ew.lang, 'jive')
-
- def test_get_encoded_word_lang_default_is_blank(self):
- ew = self._test_get_x(parser.get_encoded_word,
- '=?us-ascii?q?first_second?=',
- 'first second',
- 'first second',
- [],
- '')
- self.assertEqual(ew.encoded, '=?us-ascii?q?first_second?=')
- self.assertEqual(ew.charset, 'us-ascii')
- self.assertEqual(ew.lang, '')
-
- def test_get_encoded_word_non_printable_defect(self):
- self._test_get_x(parser.get_encoded_word,
- '=?us-ascii?q?first\x02second?=',
- 'first\x02second',
- 'first\x02second',
- [errors.NonPrintableDefect],
- '')
-
- def test_get_encoded_word_leading_internal_space(self):
- self._test_get_x(parser.get_encoded_word,
- '=?us-ascii?q?=20foo?=',
- ' foo',
- ' foo',
- [],
- '')
-
- # get_unstructured
-
- def _get_unst(self, value):
- token = parser.get_unstructured(value)
- return token, ''
-
- def test_get_unstructured_null(self):
- self._test_get_x(self._get_unst, '', '', '', [], '')
-
- def test_get_unstructured_one_word(self):
- self._test_get_x(self._get_unst, 'foo', 'foo', 'foo', [], '')
-
- def test_get_unstructured_normal_phrase(self):
- self._test_get_x(self._get_unst, 'foo bar bird',
- 'foo bar bird',
- 'foo bar bird',
- [],
- '')
-
- def test_get_unstructured_normal_phrase_with_whitespace(self):
- self._test_get_x(self._get_unst, 'foo \t bar bird',
- 'foo \t bar bird',
- 'foo bar bird',
- [],
- '')
-
- def test_get_unstructured_leading_whitespace(self):
- self._test_get_x(self._get_unst, ' foo bar',
- ' foo bar',
- ' foo bar',
- [],
- '')
-
- def test_get_unstructured_trailing_whitespace(self):
- self._test_get_x(self._get_unst, 'foo bar ',
- 'foo bar ',
- 'foo bar ',
- [],
- '')
-
- def test_get_unstructured_leading_and_trailing_whitespace(self):
- self._test_get_x(self._get_unst, ' foo bar ',
- ' foo bar ',
- ' foo bar ',
- [],
- '')
-
- def test_get_unstructured_one_valid_ew_no_ws(self):
- self._test_get_x(self._get_unst, '=?us-ascii?q?bar?=',
- 'bar',
- 'bar',
- [],
- '')
-
- def test_get_unstructured_one_ew_trailing_ws(self):
- self._test_get_x(self._get_unst, '=?us-ascii?q?bar?= ',
- 'bar ',
- 'bar ',
- [],
- '')
-
- def test_get_unstructured_one_valid_ew_trailing_text(self):
- self._test_get_x(self._get_unst, '=?us-ascii?q?bar?= bird',
- 'bar bird',
- 'bar bird',
- [],
- '')
-
- def test_get_unstructured_phrase_with_ew_in_middle_of_text(self):
- self._test_get_x(self._get_unst, 'foo =?us-ascii?q?bar?= bird',
- 'foo bar bird',
- 'foo bar bird',
- [],
- '')
-
- def test_get_unstructured_phrase_with_two_ew(self):
- self._test_get_x(self._get_unst,
- 'foo =?us-ascii?q?bar?= =?us-ascii?q?bird?=',
- 'foo barbird',
- 'foo barbird',
- [],
- '')
-
- def test_get_unstructured_phrase_with_two_ew_trailing_ws(self):
- self._test_get_x(self._get_unst,
- 'foo =?us-ascii?q?bar?= =?us-ascii?q?bird?= ',
- 'foo barbird ',
- 'foo barbird ',
- [],
- '')
-
- def test_get_unstructured_phrase_with_ew_with_leading_ws(self):
- self._test_get_x(self._get_unst,
- ' =?us-ascii?q?bar?=',
- ' bar',
- ' bar',
- [],
- '')
-
- def test_get_unstructured_phrase_with_two_ew_extra_ws(self):
- self._test_get_x(self._get_unst,
- 'foo =?us-ascii?q?bar?= \t =?us-ascii?q?bird?=',
- 'foo barbird',
- 'foo barbird',
- [],
- '')
-
- def test_get_unstructured_two_ew_extra_ws_trailing_text(self):
- self._test_get_x(self._get_unst,
- '=?us-ascii?q?test?= =?us-ascii?q?foo?= val',
- 'testfoo val',
- 'testfoo val',
- [],
- '')
-
- def test_get_unstructured_ew_with_internal_ws(self):
- self._test_get_x(self._get_unst,
- '=?iso-8859-1?q?hello=20world?=',
- 'hello world',
- 'hello world',
- [],
- '')
-
- def test_get_unstructured_ew_with_internal_leading_ws(self):
- self._test_get_x(self._get_unst,
- ' =?us-ascii?q?=20test?= =?us-ascii?q?=20foo?= val',
- ' test foo val',
- ' test foo val',
- [],
- '')
-
- def test_get_unstructured_invaild_ew(self):
- self._test_get_x(self._get_unst,
- '=?test val',
- '=?test val',
- '=?test val',
- [],
- '')
-
- def test_get_unstructured_undecodable_bytes(self):
- self._test_get_x(self._get_unst,
- b'test \xACfoo val'.decode('ascii', 'surrogateescape'),
- 'test \uDCACfoo val',
- 'test \uDCACfoo val',
- [errors.UndecodableBytesDefect],
- '')
-
- def test_get_unstructured_undecodable_bytes_in_EW(self):
- self._test_get_x(self._get_unst,
- (b'=?us-ascii?q?=20test?= =?us-ascii?q?=20\xACfoo?='
- b' val').decode('ascii', 'surrogateescape'),
- ' test \uDCACfoo val',
- ' test \uDCACfoo val',
- [errors.UndecodableBytesDefect]*2,
- '')
-
- def test_get_unstructured_missing_base64_padding(self):
- self._test_get_x(self._get_unst,
- '=?utf-8?b?dmk?=',
- 'vi',
- 'vi',
- [errors.InvalidBase64PaddingDefect],
- '')
-
- def test_get_unstructured_invalid_base64_character(self):
- self._test_get_x(self._get_unst,
- '=?utf-8?b?dm\x01k===?=',
- 'vi',
- 'vi',
- [errors.InvalidBase64CharactersDefect],
- '')
-
- def test_get_unstructured_invalid_base64_character_and_bad_padding(self):
- self._test_get_x(self._get_unst,
- '=?utf-8?b?dm\x01k?=',
- 'vi',
- 'vi',
- [errors.InvalidBase64CharactersDefect,
- errors.InvalidBase64PaddingDefect],
- '')
-
- def test_get_unstructured_no_whitespace_between_ews(self):
- self._test_get_x(self._get_unst,
- '=?utf-8?q?foo?==?utf-8?q?bar?=',
- 'foobar',
- 'foobar',
- [errors.InvalidHeaderDefect],
- '')
-
- # get_qp_ctext
-
- def test_get_qp_ctext_only(self):
- ptext = self._test_get_x(parser.get_qp_ctext,
- 'foobar', 'foobar', ' ', [], '')
- self.assertEqual(ptext.token_type, 'ptext')
-
- def test_get_qp_ctext_all_printables(self):
- with_qp = self.rfc_printable_ascii.replace('\\', '\\\\')
- with_qp = with_qp. replace('(', r'\(')
- with_qp = with_qp.replace(')', r'\)')
- ptext = self._test_get_x(parser.get_qp_ctext,
- with_qp, self.rfc_printable_ascii, ' ', [], '')
-
- def test_get_qp_ctext_two_words_gets_first(self):
- self._test_get_x(parser.get_qp_ctext,
- 'foo de', 'foo', ' ', [], ' de')
-
- def test_get_qp_ctext_following_wsp_preserved(self):
- self._test_get_x(parser.get_qp_ctext,
- 'foo \t\tde', 'foo', ' ', [], ' \t\tde')
-
- def test_get_qp_ctext_up_to_close_paren_only(self):
- self._test_get_x(parser.get_qp_ctext,
- 'foo)', 'foo', ' ', [], ')')
-
- def test_get_qp_ctext_wsp_before_close_paren_preserved(self):
- self._test_get_x(parser.get_qp_ctext,
- 'foo )', 'foo', ' ', [], ' )')
-
- def test_get_qp_ctext_close_paren_mid_word(self):
- self._test_get_x(parser.get_qp_ctext,
- 'foo)bar', 'foo', ' ', [], ')bar')
-
- def test_get_qp_ctext_up_to_open_paren_only(self):
- self._test_get_x(parser.get_qp_ctext,
- 'foo(', 'foo', ' ', [], '(')
-
- def test_get_qp_ctext_wsp_before_open_paren_preserved(self):
- self._test_get_x(parser.get_qp_ctext,
- 'foo (', 'foo', ' ', [], ' (')
-
- def test_get_qp_ctext_open_paren_mid_word(self):
- self._test_get_x(parser.get_qp_ctext,
- 'foo(bar', 'foo', ' ', [], '(bar')
-
- def test_get_qp_ctext_non_printables(self):
- ptext = self._test_get_x(parser.get_qp_ctext,
- 'foo\x00bar)', 'foo\x00bar', ' ',
- [errors.NonPrintableDefect], ')')
- self.assertEqual(ptext.defects[0].non_printables[0], '\x00')
-
- # get_qcontent
-
- def test_get_qcontent_only(self):
- ptext = self._test_get_x(parser.get_qcontent,
- 'foobar', 'foobar', 'foobar', [], '')
- self.assertEqual(ptext.token_type, 'ptext')
-
- def test_get_qcontent_all_printables(self):
- with_qp = self.rfc_printable_ascii.replace('\\', '\\\\')
- with_qp = with_qp. replace('"', r'\"')
- ptext = self._test_get_x(parser.get_qcontent, with_qp,
- self.rfc_printable_ascii,
- self.rfc_printable_ascii, [], '')
-
- def test_get_qcontent_two_words_gets_first(self):
- self._test_get_x(parser.get_qcontent,
- 'foo de', 'foo', 'foo', [], ' de')
-
- def test_get_qcontent_following_wsp_preserved(self):
- self._test_get_x(parser.get_qcontent,
- 'foo \t\tde', 'foo', 'foo', [], ' \t\tde')
-
- def test_get_qcontent_up_to_dquote_only(self):
- self._test_get_x(parser.get_qcontent,
- 'foo"', 'foo', 'foo', [], '"')
-
- def test_get_qcontent_wsp_before_close_paren_preserved(self):
- self._test_get_x(parser.get_qcontent,
- 'foo "', 'foo', 'foo', [], ' "')
-
- def test_get_qcontent_close_paren_mid_word(self):
- self._test_get_x(parser.get_qcontent,
- 'foo"bar', 'foo', 'foo', [], '"bar')
-
- def test_get_qcontent_non_printables(self):
- ptext = self._test_get_x(parser.get_qcontent,
- 'foo\x00fg"', 'foo\x00fg', 'foo\x00fg',
- [errors.NonPrintableDefect], '"')
- self.assertEqual(ptext.defects[0].non_printables[0], '\x00')
-
- # get_atext
-
- def test_get_atext_only(self):
- atext = self._test_get_x(parser.get_atext,
- 'foobar', 'foobar', 'foobar', [], '')
- self.assertEqual(atext.token_type, 'atext')
-
- def test_get_atext_all_atext(self):
- atext = self._test_get_x(parser.get_atext, self.rfc_atext_chars,
- self.rfc_atext_chars,
- self.rfc_atext_chars, [], '')
-
- def test_get_atext_two_words_gets_first(self):
- self._test_get_x(parser.get_atext,
- 'foo bar', 'foo', 'foo', [], ' bar')
-
- def test_get_atext_following_wsp_preserved(self):
- self._test_get_x(parser.get_atext,
- 'foo \t\tbar', 'foo', 'foo', [], ' \t\tbar')
-
- def test_get_atext_up_to_special(self):
- self._test_get_x(parser.get_atext,
- 'foo@bar', 'foo', 'foo', [], '@bar')
-
- def test_get_atext_non_printables(self):
- atext = self._test_get_x(parser.get_atext,
- 'foo\x00bar(', 'foo\x00bar', 'foo\x00bar',
- [errors.NonPrintableDefect], '(')
- self.assertEqual(atext.defects[0].non_printables[0], '\x00')
-
- # get_bare_quoted_string
-
- def test_get_bare_quoted_string_only(self):
- bqs = self._test_get_x(parser.get_bare_quoted_string,
- '"foo"', '"foo"', 'foo', [], '')
- self.assertEqual(bqs.token_type, 'bare-quoted-string')
-
- def test_get_bare_quoted_string_must_start_with_dquote(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_bare_quoted_string('foo"')
- with self.assertRaises(errors.HeaderParseError):
- parser.get_bare_quoted_string(' "foo"')
-
- def test_get_bare_quoted_string_following_wsp_preserved(self):
- self._test_get_x(parser.get_bare_quoted_string,
- '"foo"\t bar', '"foo"', 'foo', [], '\t bar')
-
- def test_get_bare_quoted_string_multiple_words(self):
- self._test_get_x(parser.get_bare_quoted_string,
- '"foo bar moo"', '"foo bar moo"', 'foo bar moo', [], '')
-
- def test_get_bare_quoted_string_multiple_words_wsp_preserved(self):
- self._test_get_x(parser.get_bare_quoted_string,
- '" foo moo\t"', '" foo moo\t"', ' foo moo\t', [], '')
-
- def test_get_bare_quoted_string_end_dquote_mid_word(self):
- self._test_get_x(parser.get_bare_quoted_string,
- '"foo"bar', '"foo"', 'foo', [], 'bar')
-
- def test_get_bare_quoted_string_quoted_dquote(self):
- self._test_get_x(parser.get_bare_quoted_string,
- r'"foo\"in"a', r'"foo\"in"', 'foo"in', [], 'a')
-
- def test_get_bare_quoted_string_non_printables(self):
- self._test_get_x(parser.get_bare_quoted_string,
- '"a\x01a"', '"a\x01a"', 'a\x01a',
- [errors.NonPrintableDefect], '')
-
- def test_get_bare_quoted_string_no_end_dquote(self):
- self._test_get_x(parser.get_bare_quoted_string,
- '"foo', '"foo"', 'foo',
- [errors.InvalidHeaderDefect], '')
- self._test_get_x(parser.get_bare_quoted_string,
- '"foo ', '"foo "', 'foo ',
- [errors.InvalidHeaderDefect], '')
-
- def test_get_bare_quoted_string_empty_quotes(self):
- self._test_get_x(parser.get_bare_quoted_string,
- '""', '""', '', [], '')
-
- # get_comment
-
- def test_get_comment_only(self):
- comment = self._test_get_x(parser.get_comment,
- '(comment)', '(comment)', ' ', [], '', ['comment'])
- self.assertEqual(comment.token_type, 'comment')
-
- def test_get_comment_must_start_with_paren(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_comment('foo"')
- with self.assertRaises(errors.HeaderParseError):
- parser.get_comment(' (foo"')
-
- def test_get_comment_following_wsp_preserved(self):
- self._test_get_x(parser.get_comment,
- '(comment) \t', '(comment)', ' ', [], ' \t', ['comment'])
-
- def test_get_comment_multiple_words(self):
- self._test_get_x(parser.get_comment,
- '(foo bar) \t', '(foo bar)', ' ', [], ' \t', ['foo bar'])
-
- def test_get_comment_multiple_words_wsp_preserved(self):
- self._test_get_x(parser.get_comment,
- '( foo bar\t ) \t', '( foo bar\t )', ' ', [], ' \t',
- [' foo bar\t '])
-
- def test_get_comment_end_paren_mid_word(self):
- self._test_get_x(parser.get_comment,
- '(foo)bar', '(foo)', ' ', [], 'bar', ['foo'])
-
- def test_get_comment_quoted_parens(self):
- self._test_get_x(parser.get_comment,
- '(foo\) \(\)bar)', '(foo\) \(\)bar)', ' ', [], '', ['foo) ()bar'])
-
- def test_get_comment_non_printable(self):
- self._test_get_x(parser.get_comment,
- '(foo\x7Fbar)', '(foo\x7Fbar)', ' ',
- [errors.NonPrintableDefect], '', ['foo\x7Fbar'])
-
- def test_get_comment_no_end_paren(self):
- self._test_get_x(parser.get_comment,
- '(foo bar', '(foo bar)', ' ',
- [errors.InvalidHeaderDefect], '', ['foo bar'])
- self._test_get_x(parser.get_comment,
- '(foo bar ', '(foo bar )', ' ',
- [errors.InvalidHeaderDefect], '', ['foo bar '])
-
- def test_get_comment_nested_comment(self):
- comment = self._test_get_x(parser.get_comment,
- '(foo(bar))', '(foo(bar))', ' ', [], '', ['foo(bar)'])
- self.assertEqual(comment[1].content, 'bar')
-
- def test_get_comment_nested_comment_wsp(self):
- comment = self._test_get_x(parser.get_comment,
- '(foo ( bar ) )', '(foo ( bar ) )', ' ', [], '', ['foo ( bar ) '])
- self.assertEqual(comment[2].content, ' bar ')
-
- def test_get_comment_empty_comment(self):
- self._test_get_x(parser.get_comment,
- '()', '()', ' ', [], '', [''])
-
- def test_get_comment_multiple_nesting(self):
- comment = self._test_get_x(parser.get_comment,
- '(((((foo)))))', '(((((foo)))))', ' ', [], '', ['((((foo))))'])
- for i in range(4, 0, -1):
- self.assertEqual(comment[0].content, '('*(i-1)+'foo'+')'*(i-1))
- comment = comment[0]
- self.assertEqual(comment.content, 'foo')
-
- def test_get_comment_missing_end_of_nesting(self):
- self._test_get_x(parser.get_comment,
- '(((((foo)))', '(((((foo)))))', ' ',
- [errors.InvalidHeaderDefect]*2, '', ['((((foo))))'])
-
- def test_get_comment_qs_in_nested_comment(self):
- comment = self._test_get_x(parser.get_comment,
- '(foo (b\)))', '(foo (b\)))', ' ', [], '', ['foo (b\))'])
- self.assertEqual(comment[2].content, 'b)')
-
- # get_cfws
-
- def test_get_cfws_only_ws(self):
- cfws = self._test_get_x(parser.get_cfws,
- ' \t \t', ' \t \t', ' ', [], '', [])
- self.assertEqual(cfws.token_type, 'cfws')
-
- def test_get_cfws_only_comment(self):
- cfws = self._test_get_x(parser.get_cfws,
- '(foo)', '(foo)', ' ', [], '', ['foo'])
- self.assertEqual(cfws[0].content, 'foo')
-
- def test_get_cfws_only_mixed(self):
- cfws = self._test_get_x(parser.get_cfws,
- ' (foo ) ( bar) ', ' (foo ) ( bar) ', ' ', [], '',
- ['foo ', ' bar'])
- self.assertEqual(cfws[1].content, 'foo ')
- self.assertEqual(cfws[3].content, ' bar')
-
- def test_get_cfws_ends_at_non_leader(self):
- cfws = self._test_get_x(parser.get_cfws,
- '(foo) bar', '(foo) ', ' ', [], 'bar', ['foo'])
- self.assertEqual(cfws[0].content, 'foo')
-
- def test_get_cfws_ends_at_non_printable(self):
- cfws = self._test_get_x(parser.get_cfws,
- '(foo) \x07', '(foo) ', ' ', [], '\x07', ['foo'])
- self.assertEqual(cfws[0].content, 'foo')
-
- def test_get_cfws_non_printable_in_comment(self):
- cfws = self._test_get_x(parser.get_cfws,
- '(foo \x07) "test"', '(foo \x07) ', ' ',
- [errors.NonPrintableDefect], '"test"', ['foo \x07'])
- self.assertEqual(cfws[0].content, 'foo \x07')
-
- def test_get_cfws_header_ends_in_comment(self):
- cfws = self._test_get_x(parser.get_cfws,
- ' (foo ', ' (foo )', ' ',
- [errors.InvalidHeaderDefect], '', ['foo '])
- self.assertEqual(cfws[1].content, 'foo ')
-
- def test_get_cfws_multiple_nested_comments(self):
- cfws = self._test_get_x(parser.get_cfws,
- '(foo (bar)) ((a)(a))', '(foo (bar)) ((a)(a))', ' ', [],
- '', ['foo (bar)', '(a)(a)'])
- self.assertEqual(cfws[0].comments, ['foo (bar)'])
- self.assertEqual(cfws[2].comments, ['(a)(a)'])
-
- # get_quoted_string
-
- def test_get_quoted_string_only(self):
- qs = self._test_get_x(parser.get_quoted_string,
- '"bob"', '"bob"', 'bob', [], '')
- self.assertEqual(qs.token_type, 'quoted-string')
- self.assertEqual(qs.quoted_value, '"bob"')
- self.assertEqual(qs.content, 'bob')
-
- def test_get_quoted_string_with_wsp(self):
- qs = self._test_get_x(parser.get_quoted_string,
- '\t "bob" ', '\t "bob" ', ' bob ', [], '')
- self.assertEqual(qs.quoted_value, ' "bob" ')
- self.assertEqual(qs.content, 'bob')
-
- def test_get_quoted_string_with_comments_and_wsp(self):
- qs = self._test_get_x(parser.get_quoted_string,
- ' (foo) "bob"(bar)', ' (foo) "bob"(bar)', ' bob ', [], '')
- self.assertEqual(qs[0][1].content, 'foo')
- self.assertEqual(qs[2][0].content, 'bar')
- self.assertEqual(qs.content, 'bob')
- self.assertEqual(qs.quoted_value, ' "bob" ')
-
- def test_get_quoted_string_with_multiple_comments(self):
- qs = self._test_get_x(parser.get_quoted_string,
- ' (foo) (bar) "bob"(bird)', ' (foo) (bar) "bob"(bird)', ' bob ',
- [], '')
- self.assertEqual(qs[0].comments, ['foo', 'bar'])
- self.assertEqual(qs[2].comments, ['bird'])
- self.assertEqual(qs.content, 'bob')
- self.assertEqual(qs.quoted_value, ' "bob" ')
-
- def test_get_quoted_string_non_printable_in_comment(self):
- qs = self._test_get_x(parser.get_quoted_string,
- ' (\x0A) "bob"', ' (\x0A) "bob"', ' bob',
- [errors.NonPrintableDefect], '')
- self.assertEqual(qs[0].comments, ['\x0A'])
- self.assertEqual(qs.content, 'bob')
- self.assertEqual(qs.quoted_value, ' "bob"')
-
- def test_get_quoted_string_non_printable_in_qcontent(self):
- qs = self._test_get_x(parser.get_quoted_string,
- ' (a) "a\x0B"', ' (a) "a\x0B"', ' a\x0B',
- [errors.NonPrintableDefect], '')
- self.assertEqual(qs[0].comments, ['a'])
- self.assertEqual(qs.content, 'a\x0B')
- self.assertEqual(qs.quoted_value, ' "a\x0B"')
-
- def test_get_quoted_string_internal_ws(self):
- qs = self._test_get_x(parser.get_quoted_string,
- ' (a) "foo bar "', ' (a) "foo bar "', ' foo bar ',
- [], '')
- self.assertEqual(qs[0].comments, ['a'])
- self.assertEqual(qs.content, 'foo bar ')
- self.assertEqual(qs.quoted_value, ' "foo bar "')
-
- def test_get_quoted_string_header_ends_in_comment(self):
- qs = self._test_get_x(parser.get_quoted_string,
- ' (a) "bob" (a', ' (a) "bob" (a)', ' bob ',
- [errors.InvalidHeaderDefect], '')
- self.assertEqual(qs[0].comments, ['a'])
- self.assertEqual(qs[2].comments, ['a'])
- self.assertEqual(qs.content, 'bob')
- self.assertEqual(qs.quoted_value, ' "bob" ')
-
- def test_get_quoted_string_header_ends_in_qcontent(self):
- qs = self._test_get_x(parser.get_quoted_string,
- ' (a) "bob', ' (a) "bob"', ' bob',
- [errors.InvalidHeaderDefect], '')
- self.assertEqual(qs[0].comments, ['a'])
- self.assertEqual(qs.content, 'bob')
- self.assertEqual(qs.quoted_value, ' "bob"')
-
- def test_get_quoted_string_no_quoted_string(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_quoted_string(' (ab) xyz')
-
- def test_get_quoted_string_qs_ends_at_noncfws(self):
- qs = self._test_get_x(parser.get_quoted_string,
- '\t "bob" fee', '\t "bob" ', ' bob ', [], 'fee')
- self.assertEqual(qs.content, 'bob')
- self.assertEqual(qs.quoted_value, ' "bob" ')
-
- # get_atom
-
- def test_get_atom_only(self):
- atom = self._test_get_x(parser.get_atom,
- 'bob', 'bob', 'bob', [], '')
- self.assertEqual(atom.token_type, 'atom')
-
- def test_get_atom_with_wsp(self):
- self._test_get_x(parser.get_atom,
- '\t bob ', '\t bob ', ' bob ', [], '')
-
- def test_get_atom_with_comments_and_wsp(self):
- atom = self._test_get_x(parser.get_atom,
- ' (foo) bob(bar)', ' (foo) bob(bar)', ' bob ', [], '')
- self.assertEqual(atom[0][1].content, 'foo')
- self.assertEqual(atom[2][0].content, 'bar')
-
- def test_get_atom_with_multiple_comments(self):
- atom = self._test_get_x(parser.get_atom,
- ' (foo) (bar) bob(bird)', ' (foo) (bar) bob(bird)', ' bob ',
- [], '')
- self.assertEqual(atom[0].comments, ['foo', 'bar'])
- self.assertEqual(atom[2].comments, ['bird'])
-
- def test_get_atom_non_printable_in_comment(self):
- atom = self._test_get_x(parser.get_atom,
- ' (\x0A) bob', ' (\x0A) bob', ' bob',
- [errors.NonPrintableDefect], '')
- self.assertEqual(atom[0].comments, ['\x0A'])
-
- def test_get_atom_non_printable_in_atext(self):
- atom = self._test_get_x(parser.get_atom,
- ' (a) a\x0B', ' (a) a\x0B', ' a\x0B',
- [errors.NonPrintableDefect], '')
- self.assertEqual(atom[0].comments, ['a'])
-
- def test_get_atom_header_ends_in_comment(self):
- atom = self._test_get_x(parser.get_atom,
- ' (a) bob (a', ' (a) bob (a)', ' bob ',
- [errors.InvalidHeaderDefect], '')
- self.assertEqual(atom[0].comments, ['a'])
- self.assertEqual(atom[2].comments, ['a'])
-
- def test_get_atom_no_atom(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_atom(' (ab) ')
-
- def test_get_atom_no_atom_before_special(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_atom(' (ab) @')
-
- def test_get_atom_atom_ends_at_special(self):
- atom = self._test_get_x(parser.get_atom,
- ' (foo) bob(bar) @bang', ' (foo) bob(bar) ', ' bob ', [], '@bang')
- self.assertEqual(atom[0].comments, ['foo'])
- self.assertEqual(atom[2].comments, ['bar'])
-
- def test_get_atom_atom_ends_at_noncfws(self):
- atom = self._test_get_x(parser.get_atom,
- 'bob fred', 'bob ', 'bob ', [], 'fred')
-
- # get_dot_atom_text
-
- def test_get_dot_atom_text(self):
- dot_atom_text = self._test_get_x(parser.get_dot_atom_text,
- 'foo.bar.bang', 'foo.bar.bang', 'foo.bar.bang', [], '')
- self.assertEqual(dot_atom_text.token_type, 'dot-atom-text')
- self.assertEqual(len(dot_atom_text), 5)
-
- def test_get_dot_atom_text_lone_atom_is_valid(self):
- dot_atom_text = self._test_get_x(parser.get_dot_atom_text,
- 'foo', 'foo', 'foo', [], '')
-
- def test_get_dot_atom_text_raises_on_leading_dot(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_dot_atom_text('.foo.bar')
-
- def test_get_dot_atom_text_raises_on_trailing_dot(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_dot_atom_text('foo.bar.')
-
- def test_get_dot_atom_text_raises_on_leading_non_atext(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_dot_atom_text(' foo.bar')
- with self.assertRaises(errors.HeaderParseError):
- parser.get_dot_atom_text('@foo.bar')
- with self.assertRaises(errors.HeaderParseError):
- parser.get_dot_atom_text('"foo.bar"')
-
- def test_get_dot_atom_text_trailing_text_preserved(self):
- dot_atom_text = self._test_get_x(parser.get_dot_atom_text,
- 'foo@bar', 'foo', 'foo', [], '@bar')
-
- def test_get_dot_atom_text_trailing_ws_preserved(self):
- dot_atom_text = self._test_get_x(parser.get_dot_atom_text,
- 'foo .bar', 'foo', 'foo', [], ' .bar')
-
- # get_dot_atom
-
- def test_get_dot_atom_only(self):
- dot_atom = self._test_get_x(parser.get_dot_atom,
- 'foo.bar.bing', 'foo.bar.bing', 'foo.bar.bing', [], '')
- self.assertEqual(dot_atom.token_type, 'dot-atom')
- self.assertEqual(len(dot_atom), 1)
-
- def test_get_dot_atom_with_wsp(self):
- self._test_get_x(parser.get_dot_atom,
- '\t foo.bar.bing ', '\t foo.bar.bing ', ' foo.bar.bing ', [], '')
-
- def test_get_dot_atom_with_comments_and_wsp(self):
- self._test_get_x(parser.get_dot_atom,
- ' (sing) foo.bar.bing (here) ', ' (sing) foo.bar.bing (here) ',
- ' foo.bar.bing ', [], '')
-
- def test_get_dot_atom_space_ends_dot_atom(self):
- self._test_get_x(parser.get_dot_atom,
- ' (sing) foo.bar .bing (here) ', ' (sing) foo.bar ',
- ' foo.bar ', [], '.bing (here) ')
-
- def test_get_dot_atom_no_atom_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_dot_atom(' (foo) ')
-
- def test_get_dot_atom_leading_dot_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_dot_atom(' (foo) .bar')
-
- def test_get_dot_atom_two_dots_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_dot_atom('bar..bang')
-
- def test_get_dot_atom_trailing_dot_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_dot_atom(' (foo) bar.bang. foo')
-
- # get_word (if this were black box we'd repeat all the qs/atom tests)
-
- def test_get_word_atom_yields_atom(self):
- word = self._test_get_x(parser.get_word,
- ' (foo) bar (bang) :ah', ' (foo) bar (bang) ', ' bar ', [], ':ah')
- self.assertEqual(word.token_type, 'atom')
- self.assertEqual(word[0].token_type, 'cfws')
-
- def test_get_word_qs_yields_qs(self):
- word = self._test_get_x(parser.get_word,
- '"bar " (bang) ah', '"bar " (bang) ', 'bar ', [], 'ah')
- self.assertEqual(word.token_type, 'quoted-string')
- self.assertEqual(word[0].token_type, 'bare-quoted-string')
- self.assertEqual(word[0].value, 'bar ')
- self.assertEqual(word.content, 'bar ')
-
- def test_get_word_ends_at_dot(self):
- self._test_get_x(parser.get_word,
- 'foo.', 'foo', 'foo', [], '.')
-
- # get_phrase
-
- def test_get_phrase_simple(self):
- phrase = self._test_get_x(parser.get_phrase,
- '"Fred A. Johnson" is his name, oh.',
- '"Fred A. Johnson" is his name',
- 'Fred A. Johnson is his name',
- [],
- ', oh.')
- self.assertEqual(phrase.token_type, 'phrase')
-
- def test_get_phrase_complex(self):
- phrase = self._test_get_x(parser.get_phrase,
- ' (A) bird (in (my|your)) "hand " is messy\t<>\t',
- ' (A) bird (in (my|your)) "hand " is messy\t',
- ' bird hand is messy ',
- [],
- '<>\t')
- self.assertEqual(phrase[0][0].comments, ['A'])
- self.assertEqual(phrase[0][2].comments, ['in (my|your)'])
-
- def test_get_phrase_obsolete(self):
- phrase = self._test_get_x(parser.get_phrase,
- 'Fred A.(weird).O Johnson',
- 'Fred A.(weird).O Johnson',
- 'Fred A. .O Johnson',
- [errors.ObsoleteHeaderDefect]*3,
- '')
- self.assertEqual(len(phrase), 7)
- self.assertEqual(phrase[3].comments, ['weird'])
-
- def test_get_phrase_pharse_must_start_with_word(self):
- phrase = self._test_get_x(parser.get_phrase,
- '(even weirder).name',
- '(even weirder).name',
- ' .name',
- [errors.InvalidHeaderDefect] + [errors.ObsoleteHeaderDefect]*2,
- '')
- self.assertEqual(len(phrase), 3)
- self.assertEqual(phrase[0].comments, ['even weirder'])
-
- def test_get_phrase_ending_with_obsolete(self):
- phrase = self._test_get_x(parser.get_phrase,
- 'simple phrase.(with trailing comment):boo',
- 'simple phrase.(with trailing comment)',
- 'simple phrase. ',
- [errors.ObsoleteHeaderDefect]*2,
- ':boo')
- self.assertEqual(len(phrase), 4)
- self.assertEqual(phrase[3].comments, ['with trailing comment'])
-
- def get_phrase_cfws_only_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_phrase(' (foo) ')
-
- # get_local_part
-
- def test_get_local_part_simple(self):
- local_part = self._test_get_x(parser.get_local_part,
- 'dinsdale@python.org', 'dinsdale', 'dinsdale', [], '@python.org')
- self.assertEqual(local_part.token_type, 'local-part')
- self.assertEqual(local_part.local_part, 'dinsdale')
-
- def test_get_local_part_with_dot(self):
- local_part = self._test_get_x(parser.get_local_part,
- 'Fred.A.Johnson@python.org',
- 'Fred.A.Johnson',
- 'Fred.A.Johnson',
- [],
- '@python.org')
- self.assertEqual(local_part.local_part, 'Fred.A.Johnson')
-
- def test_get_local_part_with_whitespace(self):
- local_part = self._test_get_x(parser.get_local_part,
- ' Fred.A.Johnson @python.org',
- ' Fred.A.Johnson ',
- ' Fred.A.Johnson ',
- [],
- '@python.org')
- self.assertEqual(local_part.local_part, 'Fred.A.Johnson')
-
- def test_get_local_part_with_cfws(self):
- local_part = self._test_get_x(parser.get_local_part,
- ' (foo) Fred.A.Johnson (bar (bird)) @python.org',
- ' (foo) Fred.A.Johnson (bar (bird)) ',
- ' Fred.A.Johnson ',
- [],
- '@python.org')
- self.assertEqual(local_part.local_part, 'Fred.A.Johnson')
- self.assertEqual(local_part[0][0].comments, ['foo'])
- self.assertEqual(local_part[0][2].comments, ['bar (bird)'])
-
- def test_get_local_part_simple_quoted(self):
- local_part = self._test_get_x(parser.get_local_part,
- '"dinsdale"@python.org', '"dinsdale"', '"dinsdale"', [], '@python.org')
- self.assertEqual(local_part.token_type, 'local-part')
- self.assertEqual(local_part.local_part, 'dinsdale')
-
- def test_get_local_part_with_quoted_dot(self):
- local_part = self._test_get_x(parser.get_local_part,
- '"Fred.A.Johnson"@python.org',
- '"Fred.A.Johnson"',
- '"Fred.A.Johnson"',
- [],
- '@python.org')
- self.assertEqual(local_part.local_part, 'Fred.A.Johnson')
-
- def test_get_local_part_quoted_with_whitespace(self):
- local_part = self._test_get_x(parser.get_local_part,
- ' "Fred A. Johnson" @python.org',
- ' "Fred A. Johnson" ',
- ' "Fred A. Johnson" ',
- [],
- '@python.org')
- self.assertEqual(local_part.local_part, 'Fred A. Johnson')
-
- def test_get_local_part_quoted_with_cfws(self):
- local_part = self._test_get_x(parser.get_local_part,
- ' (foo) " Fred A. Johnson " (bar (bird)) @python.org',
- ' (foo) " Fred A. Johnson " (bar (bird)) ',
- ' " Fred A. Johnson " ',
- [],
- '@python.org')
- self.assertEqual(local_part.local_part, ' Fred A. Johnson ')
- self.assertEqual(local_part[0][0].comments, ['foo'])
- self.assertEqual(local_part[0][2].comments, ['bar (bird)'])
-
-
- def test_get_local_part_simple_obsolete(self):
- local_part = self._test_get_x(parser.get_local_part,
- 'Fred. A.Johnson@python.org',
- 'Fred. A.Johnson',
- 'Fred. A.Johnson',
- [errors.ObsoleteHeaderDefect],
- '@python.org')
- self.assertEqual(local_part.local_part, 'Fred.A.Johnson')
-
- def test_get_local_part_complex_obsolete_1(self):
- local_part = self._test_get_x(parser.get_local_part,
- ' (foo )Fred (bar).(bird) A.(sheep)Johnson."and dogs "@python.org',
- ' (foo )Fred (bar).(bird) A.(sheep)Johnson."and dogs "',
- ' Fred . A. Johnson.and dogs ',
- [errors.ObsoleteHeaderDefect],
- '@python.org')
- self.assertEqual(local_part.local_part, 'Fred.A.Johnson.and dogs ')
-
- def test_get_local_part_complex_obsolete_invalid(self):
- local_part = self._test_get_x(parser.get_local_part,
- ' (foo )Fred (bar).(bird) A.(sheep)Johnson "and dogs"@python.org',
- ' (foo )Fred (bar).(bird) A.(sheep)Johnson "and dogs"',
- ' Fred . A. Johnson and dogs',
- [errors.InvalidHeaderDefect]*2,
- '@python.org')
- self.assertEqual(local_part.local_part, 'Fred.A.Johnson and dogs')
-
- def test_get_local_part_no_part_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_local_part(' (foo) ')
-
- def test_get_local_part_special_instead_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_local_part(' (foo) @python.org')
-
- def test_get_local_part_trailing_dot(self):
- local_part = self._test_get_x(parser.get_local_part,
- ' borris.@python.org',
- ' borris.',
- ' borris.',
- [errors.InvalidHeaderDefect]*2,
- '@python.org')
- self.assertEqual(local_part.local_part, 'borris.')
-
- def test_get_local_part_trailing_dot_with_ws(self):
- local_part = self._test_get_x(parser.get_local_part,
- ' borris. @python.org',
- ' borris. ',
- ' borris. ',
- [errors.InvalidHeaderDefect]*2,
- '@python.org')
- self.assertEqual(local_part.local_part, 'borris.')
-
- def test_get_local_part_leading_dot(self):
- local_part = self._test_get_x(parser.get_local_part,
- '.borris@python.org',
- '.borris',
- '.borris',
- [errors.InvalidHeaderDefect]*2,
- '@python.org')
- self.assertEqual(local_part.local_part, '.borris')
-
- def test_get_local_part_leading_dot_after_ws(self):
- local_part = self._test_get_x(parser.get_local_part,
- ' .borris@python.org',
- ' .borris',
- ' .borris',
- [errors.InvalidHeaderDefect]*2,
- '@python.org')
- self.assertEqual(local_part.local_part, '.borris')
-
- def test_get_local_part_double_dot_raises(self):
- local_part = self._test_get_x(parser.get_local_part,
- ' borris.(foo).natasha@python.org',
- ' borris.(foo).natasha',
- ' borris. .natasha',
- [errors.InvalidHeaderDefect]*2,
- '@python.org')
- self.assertEqual(local_part.local_part, 'borris..natasha')
-
- def test_get_local_part_quoted_strings_in_atom_list(self):
- local_part = self._test_get_x(parser.get_local_part,
- '""example" example"@example.com',
- '""example" example"',
- 'example example',
- [errors.InvalidHeaderDefect]*3,
- '@example.com')
- self.assertEqual(local_part.local_part, 'example example')
-
- def test_get_local_part_valid_and_invalid_qp_in_atom_list(self):
- local_part = self._test_get_x(parser.get_local_part,
- r'"\\"example\\" example"@example.com',
- r'"\\"example\\" example"',
- r'\example\\ example',
- [errors.InvalidHeaderDefect]*5,
- '@example.com')
- self.assertEqual(local_part.local_part, r'\example\\ example')
-
- def test_get_local_part_unicode_defect(self):
- # Currently this only happens when parsing unicode, not when parsing
- # stuff that was originally binary.
- local_part = self._test_get_x(parser.get_local_part,
- 'exámple@example.com',
- 'exámple',
- 'exámple',
- [errors.NonASCIILocalPartDefect],
- '@example.com')
- self.assertEqual(local_part.local_part, 'exámple')
-
- # get_dtext
-
- def test_get_dtext_only(self):
- dtext = self._test_get_x(parser.get_dtext,
- 'foobar', 'foobar', 'foobar', [], '')
- self.assertEqual(dtext.token_type, 'ptext')
-
- def test_get_dtext_all_dtext(self):
- dtext = self._test_get_x(parser.get_dtext, self.rfc_dtext_chars,
- self.rfc_dtext_chars,
- self.rfc_dtext_chars, [], '')
-
- def test_get_dtext_two_words_gets_first(self):
- self._test_get_x(parser.get_dtext,
- 'foo bar', 'foo', 'foo', [], ' bar')
-
- def test_get_dtext_following_wsp_preserved(self):
- self._test_get_x(parser.get_dtext,
- 'foo \t\tbar', 'foo', 'foo', [], ' \t\tbar')
-
- def test_get_dtext_non_printables(self):
- dtext = self._test_get_x(parser.get_dtext,
- 'foo\x00bar]', 'foo\x00bar', 'foo\x00bar',
- [errors.NonPrintableDefect], ']')
- self.assertEqual(dtext.defects[0].non_printables[0], '\x00')
-
- def test_get_dtext_with_qp(self):
- ptext = self._test_get_x(parser.get_dtext,
- r'foo\]\[\\bar\b\e\l\l',
- r'foo][\barbell',
- r'foo][\barbell',
- [errors.ObsoleteHeaderDefect],
- '')
-
- def test_get_dtext_up_to_close_bracket_only(self):
- self._test_get_x(parser.get_dtext,
- 'foo]', 'foo', 'foo', [], ']')
-
- def test_get_dtext_wsp_before_close_bracket_preserved(self):
- self._test_get_x(parser.get_dtext,
- 'foo ]', 'foo', 'foo', [], ' ]')
-
- def test_get_dtext_close_bracket_mid_word(self):
- self._test_get_x(parser.get_dtext,
- 'foo]bar', 'foo', 'foo', [], ']bar')
-
- def test_get_dtext_up_to_open_bracket_only(self):
- self._test_get_x(parser.get_dtext,
- 'foo[', 'foo', 'foo', [], '[')
-
- def test_get_dtext_wsp_before_open_bracket_preserved(self):
- self._test_get_x(parser.get_dtext,
- 'foo [', 'foo', 'foo', [], ' [')
-
- def test_get_dtext_open_bracket_mid_word(self):
- self._test_get_x(parser.get_dtext,
- 'foo[bar', 'foo', 'foo', [], '[bar')
-
- # get_domain_literal
-
- def test_get_domain_literal_only(self):
- domain_literal = domain_literal = self._test_get_x(parser.get_domain_literal,
- '[127.0.0.1]',
- '[127.0.0.1]',
- '[127.0.0.1]',
- [],
- '')
- self.assertEqual(domain_literal.token_type, 'domain-literal')
- self.assertEqual(domain_literal.domain, '[127.0.0.1]')
- self.assertEqual(domain_literal.ip, '127.0.0.1')
-
- def test_get_domain_literal_with_internal_ws(self):
- domain_literal = self._test_get_x(parser.get_domain_literal,
- '[ 127.0.0.1\t ]',
- '[ 127.0.0.1\t ]',
- '[ 127.0.0.1 ]',
- [],
- '')
- self.assertEqual(domain_literal.domain, '[127.0.0.1]')
- self.assertEqual(domain_literal.ip, '127.0.0.1')
-
- def test_get_domain_literal_with_surrounding_cfws(self):
- domain_literal = self._test_get_x(parser.get_domain_literal,
- '(foo)[ 127.0.0.1] (bar)',
- '(foo)[ 127.0.0.1] (bar)',
- ' [ 127.0.0.1] ',
- [],
- '')
- self.assertEqual(domain_literal.domain, '[127.0.0.1]')
- self.assertEqual(domain_literal.ip, '127.0.0.1')
-
- def test_get_domain_literal_no_start_char_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_domain_literal('(foo) ')
-
- def test_get_domain_literal_no_start_char_before_special_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_domain_literal('(foo) @')
-
- def test_get_domain_literal_bad_dtext_char_before_special_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_domain_literal('(foo) [abc[@')
-
- # get_domain
-
- def test_get_domain_regular_domain_only(self):
- domain = self._test_get_x(parser.get_domain,
- 'example.com',
- 'example.com',
- 'example.com',
- [],
- '')
- self.assertEqual(domain.token_type, 'domain')
- self.assertEqual(domain.domain, 'example.com')
-
- def test_get_domain_domain_literal_only(self):
- domain = self._test_get_x(parser.get_domain,
- '[127.0.0.1]',
- '[127.0.0.1]',
- '[127.0.0.1]',
- [],
- '')
- self.assertEqual(domain.token_type, 'domain')
- self.assertEqual(domain.domain, '[127.0.0.1]')
-
- def test_get_domain_with_cfws(self):
- domain = self._test_get_x(parser.get_domain,
- '(foo) example.com(bar)\t',
- '(foo) example.com(bar)\t',
- ' example.com ',
- [],
- '')
- self.assertEqual(domain.domain, 'example.com')
-
- def test_get_domain_domain_literal_with_cfws(self):
- domain = self._test_get_x(parser.get_domain,
- '(foo)[127.0.0.1]\t(bar)',
- '(foo)[127.0.0.1]\t(bar)',
- ' [127.0.0.1] ',
- [],
- '')
- self.assertEqual(domain.domain, '[127.0.0.1]')
-
- def test_get_domain_domain_with_cfws_ends_at_special(self):
- domain = self._test_get_x(parser.get_domain,
- '(foo)example.com\t(bar), next',
- '(foo)example.com\t(bar)',
- ' example.com ',
- [],
- ', next')
- self.assertEqual(domain.domain, 'example.com')
-
- def test_get_domain_domain_literal_with_cfws_ends_at_special(self):
- domain = self._test_get_x(parser.get_domain,
- '(foo)[127.0.0.1]\t(bar), next',
- '(foo)[127.0.0.1]\t(bar)',
- ' [127.0.0.1] ',
- [],
- ', next')
- self.assertEqual(domain.domain, '[127.0.0.1]')
-
- def test_get_domain_obsolete(self):
- domain = self._test_get_x(parser.get_domain,
- '(foo) example . (bird)com(bar)\t',
- '(foo) example . (bird)com(bar)\t',
- ' example . com ',
- [errors.ObsoleteHeaderDefect],
- '')
- self.assertEqual(domain.domain, 'example.com')
-
- def test_get_domain_no_non_cfws_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_domain(" (foo)\t")
-
- def test_get_domain_no_atom_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_domain(" (foo)\t, broken")
-
-
- # get_addr_spec
-
- def test_get_addr_spec_normal(self):
- addr_spec = self._test_get_x(parser.get_addr_spec,
- 'dinsdale@example.com',
- 'dinsdale@example.com',
- 'dinsdale@example.com',
- [],
- '')
- self.assertEqual(addr_spec.token_type, 'addr-spec')
- self.assertEqual(addr_spec.local_part, 'dinsdale')
- self.assertEqual(addr_spec.domain, 'example.com')
- self.assertEqual(addr_spec.addr_spec, 'dinsdale@example.com')
-
- def test_get_addr_spec_with_doamin_literal(self):
- addr_spec = self._test_get_x(parser.get_addr_spec,
- 'dinsdale@[127.0.0.1]',
- 'dinsdale@[127.0.0.1]',
- 'dinsdale@[127.0.0.1]',
- [],
- '')
- self.assertEqual(addr_spec.local_part, 'dinsdale')
- self.assertEqual(addr_spec.domain, '[127.0.0.1]')
- self.assertEqual(addr_spec.addr_spec, 'dinsdale@[127.0.0.1]')
-
- def test_get_addr_spec_with_cfws(self):
- addr_spec = self._test_get_x(parser.get_addr_spec,
- '(foo) dinsdale(bar)@ (bird) example.com (bog)',
- '(foo) dinsdale(bar)@ (bird) example.com (bog)',
- ' dinsdale@example.com ',
- [],
- '')
- self.assertEqual(addr_spec.local_part, 'dinsdale')
- self.assertEqual(addr_spec.domain, 'example.com')
- self.assertEqual(addr_spec.addr_spec, 'dinsdale@example.com')
-
- def test_get_addr_spec_with_qouoted_string_and_cfws(self):
- addr_spec = self._test_get_x(parser.get_addr_spec,
- '(foo) "roy a bug"(bar)@ (bird) example.com (bog)',
- '(foo) "roy a bug"(bar)@ (bird) example.com (bog)',
- ' "roy a bug"@example.com ',
- [],
- '')
- self.assertEqual(addr_spec.local_part, 'roy a bug')
- self.assertEqual(addr_spec.domain, 'example.com')
- self.assertEqual(addr_spec.addr_spec, '"roy a bug"@example.com')
-
- def test_get_addr_spec_ends_at_special(self):
- addr_spec = self._test_get_x(parser.get_addr_spec,
- '(foo) "roy a bug"(bar)@ (bird) example.com (bog) , next',
- '(foo) "roy a bug"(bar)@ (bird) example.com (bog) ',
- ' "roy a bug"@example.com ',
- [],
- ', next')
- self.assertEqual(addr_spec.local_part, 'roy a bug')
- self.assertEqual(addr_spec.domain, 'example.com')
- self.assertEqual(addr_spec.addr_spec, '"roy a bug"@example.com')
-
- def test_get_addr_spec_quoted_strings_in_atom_list(self):
- addr_spec = self._test_get_x(parser.get_addr_spec,
- '""example" example"@example.com',
- '""example" example"@example.com',
- 'example example@example.com',
- [errors.InvalidHeaderDefect]*3,
- '')
- self.assertEqual(addr_spec.local_part, 'example example')
- self.assertEqual(addr_spec.domain, 'example.com')
- self.assertEqual(addr_spec.addr_spec, '"example example"@example.com')
-
- def test_get_addr_spec_dot_atom(self):
- addr_spec = self._test_get_x(parser.get_addr_spec,
- 'star.a.star@example.com',
- 'star.a.star@example.com',
- 'star.a.star@example.com',
- [],
- '')
- self.assertEqual(addr_spec.local_part, 'star.a.star')
- self.assertEqual(addr_spec.domain, 'example.com')
- self.assertEqual(addr_spec.addr_spec, 'star.a.star@example.com')
-
- # get_obs_route
-
- def test_get_obs_route_simple(self):
- obs_route = self._test_get_x(parser.get_obs_route,
- '@example.com, @two.example.com:',
- '@example.com, @two.example.com:',
- '@example.com, @two.example.com:',
- [],
- '')
- self.assertEqual(obs_route.token_type, 'obs-route')
- self.assertEqual(obs_route.domains, ['example.com', 'two.example.com'])
-
- def test_get_obs_route_complex(self):
- obs_route = self._test_get_x(parser.get_obs_route,
- '(foo),, (blue)@example.com (bar),@two.(foo) example.com (bird):',
- '(foo),, (blue)@example.com (bar),@two.(foo) example.com (bird):',
- ' ,, @example.com ,@two. example.com :',
- [errors.ObsoleteHeaderDefect], # This is the obs-domain
- '')
- self.assertEqual(obs_route.token_type, 'obs-route')
- self.assertEqual(obs_route.domains, ['example.com', 'two.example.com'])
-
- def test_get_obs_route_no_route_before_end_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_obs_route('(foo) @example.com,')
-
- def test_get_obs_route_no_route_before_special_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_obs_route('(foo) [abc],')
-
- def test_get_obs_route_no_route_before_special_raises2(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_obs_route('(foo) @example.com [abc],')
-
- # get_angle_addr
-
- def test_get_angle_addr_simple(self):
- angle_addr = self._test_get_x(parser.get_angle_addr,
- '',
- '',
- '',
- [],
- '')
- self.assertEqual(angle_addr.token_type, 'angle-addr')
- self.assertEqual(angle_addr.local_part, 'dinsdale')
- self.assertEqual(angle_addr.domain, 'example.com')
- self.assertIsNone(angle_addr.route)
- self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')
-
- def test_get_angle_addr_empty(self):
- angle_addr = self._test_get_x(parser.get_angle_addr,
- '<>',
- '<>',
- '<>',
- [errors.InvalidHeaderDefect],
- '')
- self.assertEqual(angle_addr.token_type, 'angle-addr')
- self.assertIsNone(angle_addr.local_part)
- self.assertIsNone(angle_addr.domain)
- self.assertIsNone(angle_addr.route)
- self.assertEqual(angle_addr.addr_spec, '<>')
-
- def test_get_angle_addr_with_cfws(self):
- angle_addr = self._test_get_x(parser.get_angle_addr,
- ' (foo) (bar)',
- ' (foo) (bar)',
- ' ',
- [],
- '')
- self.assertEqual(angle_addr.token_type, 'angle-addr')
- self.assertEqual(angle_addr.local_part, 'dinsdale')
- self.assertEqual(angle_addr.domain, 'example.com')
- self.assertIsNone(angle_addr.route)
- self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')
-
- def test_get_angle_addr_qs_and_domain_literal(self):
- angle_addr = self._test_get_x(parser.get_angle_addr,
- '<"Fred Perfect"@[127.0.0.1]>',
- '<"Fred Perfect"@[127.0.0.1]>',
- '<"Fred Perfect"@[127.0.0.1]>',
- [],
- '')
- self.assertEqual(angle_addr.local_part, 'Fred Perfect')
- self.assertEqual(angle_addr.domain, '[127.0.0.1]')
- self.assertIsNone(angle_addr.route)
- self.assertEqual(angle_addr.addr_spec, '"Fred Perfect"@[127.0.0.1]')
-
- def test_get_angle_addr_internal_cfws(self):
- angle_addr = self._test_get_x(parser.get_angle_addr,
- '<(foo) dinsdale@example.com(bar)>',
- '<(foo) dinsdale@example.com(bar)>',
- '< dinsdale@example.com >',
- [],
- '')
- self.assertEqual(angle_addr.local_part, 'dinsdale')
- self.assertEqual(angle_addr.domain, 'example.com')
- self.assertIsNone(angle_addr.route)
- self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')
-
- def test_get_angle_addr_obs_route(self):
- angle_addr = self._test_get_x(parser.get_angle_addr,
- '(foo)<@example.com, (bird) @two.example.com: dinsdale@example.com> (bar) ',
- '(foo)<@example.com, (bird) @two.example.com: dinsdale@example.com> (bar) ',
- ' <@example.com, @two.example.com: dinsdale@example.com> ',
- [errors.ObsoleteHeaderDefect],
- '')
- self.assertEqual(angle_addr.local_part, 'dinsdale')
- self.assertEqual(angle_addr.domain, 'example.com')
- self.assertEqual(angle_addr.route, ['example.com', 'two.example.com'])
- self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')
-
- def test_get_angle_addr_missing_closing_angle(self):
- angle_addr = self._test_get_x(parser.get_angle_addr,
- '',
- '',
- [errors.InvalidHeaderDefect],
- '')
- self.assertEqual(angle_addr.local_part, 'dinsdale')
- self.assertEqual(angle_addr.domain, 'example.com')
- self.assertIsNone(angle_addr.route)
- self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')
-
- def test_get_angle_addr_missing_closing_angle_with_cfws(self):
- angle_addr = self._test_get_x(parser.get_angle_addr,
- '',
- '',
- [errors.InvalidHeaderDefect],
- '')
- self.assertEqual(angle_addr.local_part, 'dinsdale')
- self.assertEqual(angle_addr.domain, 'example.com')
- self.assertIsNone(angle_addr.route)
- self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')
-
- def test_get_angle_addr_ends_at_special(self):
- angle_addr = self._test_get_x(parser.get_angle_addr,
- ' (foo), next',
- ' (foo)',
- ' ',
- [],
- ', next')
- self.assertEqual(angle_addr.local_part, 'dinsdale')
- self.assertEqual(angle_addr.domain, 'example.com')
- self.assertIsNone(angle_addr.route)
- self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')
-
- def test_get_angle_addr_no_angle_raise(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_angle_addr('(foo) ')
-
- def test_get_angle_addr_no_angle_before_special_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_angle_addr('(foo) , next')
-
- def test_get_angle_addr_no_angle_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_angle_addr('bar')
-
- def test_get_angle_addr_special_after_angle_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_angle_addr('(foo) <, bar')
-
- # get_display_name This is phrase but with a different value.
-
- def test_get_display_name_simple(self):
- display_name = self._test_get_x(parser.get_display_name,
- 'Fred A Johnson',
- 'Fred A Johnson',
- 'Fred A Johnson',
- [],
- '')
- self.assertEqual(display_name.token_type, 'display-name')
- self.assertEqual(display_name.display_name, 'Fred A Johnson')
-
- def test_get_display_name_complex1(self):
- display_name = self._test_get_x(parser.get_display_name,
- '"Fred A. Johnson" is his name, oh.',
- '"Fred A. Johnson" is his name',
- '"Fred A. Johnson is his name"',
- [],
- ', oh.')
- self.assertEqual(display_name.token_type, 'display-name')
- self.assertEqual(display_name.display_name, 'Fred A. Johnson is his name')
-
- def test_get_display_name_complex2(self):
- display_name = self._test_get_x(parser.get_display_name,
- ' (A) bird (in (my|your)) "hand " is messy\t<>\t',
- ' (A) bird (in (my|your)) "hand " is messy\t',
- ' "bird hand is messy" ',
- [],
- '<>\t')
- self.assertEqual(display_name[0][0].comments, ['A'])
- self.assertEqual(display_name[0][2].comments, ['in (my|your)'])
- self.assertEqual(display_name.display_name, 'bird hand is messy')
-
- def test_get_display_name_obsolete(self):
- display_name = self._test_get_x(parser.get_display_name,
- 'Fred A.(weird).O Johnson',
- 'Fred A.(weird).O Johnson',
- '"Fred A. .O Johnson"',
- [errors.ObsoleteHeaderDefect]*3,
- '')
- self.assertEqual(len(display_name), 7)
- self.assertEqual(display_name[3].comments, ['weird'])
- self.assertEqual(display_name.display_name, 'Fred A. .O Johnson')
-
- def test_get_display_name_pharse_must_start_with_word(self):
- display_name = self._test_get_x(parser.get_display_name,
- '(even weirder).name',
- '(even weirder).name',
- ' ".name"',
- [errors.InvalidHeaderDefect] + [errors.ObsoleteHeaderDefect]*2,
- '')
- self.assertEqual(len(display_name), 3)
- self.assertEqual(display_name[0].comments, ['even weirder'])
- self.assertEqual(display_name.display_name, '.name')
-
- def test_get_display_name_ending_with_obsolete(self):
- display_name = self._test_get_x(parser.get_display_name,
- 'simple phrase.(with trailing comment):boo',
- 'simple phrase.(with trailing comment)',
- '"simple phrase." ',
- [errors.ObsoleteHeaderDefect]*2,
- ':boo')
- self.assertEqual(len(display_name), 4)
- self.assertEqual(display_name[3].comments, ['with trailing comment'])
- self.assertEqual(display_name.display_name, 'simple phrase.')
-
- # get_name_addr
-
- def test_get_name_addr_angle_addr_only(self):
- name_addr = self._test_get_x(parser.get_name_addr,
- '',
- '',
- '',
- [],
- '')
- self.assertEqual(name_addr.token_type, 'name-addr')
- self.assertIsNone(name_addr.display_name)
- self.assertEqual(name_addr.local_part, 'dinsdale')
- self.assertEqual(name_addr.domain, 'example.com')
- self.assertIsNone(name_addr.route)
- self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')
-
- def test_get_name_addr_atom_name(self):
- name_addr = self._test_get_x(parser.get_name_addr,
- 'Dinsdale ',
- 'Dinsdale ',
- 'Dinsdale ',
- [],
- '')
- self.assertEqual(name_addr.token_type, 'name-addr')
- self.assertEqual(name_addr.display_name, 'Dinsdale')
- self.assertEqual(name_addr.local_part, 'dinsdale')
- self.assertEqual(name_addr.domain, 'example.com')
- self.assertIsNone(name_addr.route)
- self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')
-
- def test_get_name_addr_atom_name_with_cfws(self):
- name_addr = self._test_get_x(parser.get_name_addr,
- '(foo) Dinsdale (bar) (bird)',
- '(foo) Dinsdale (bar) (bird)',
- ' Dinsdale ',
- [],
- '')
- self.assertEqual(name_addr.display_name, 'Dinsdale')
- self.assertEqual(name_addr.local_part, 'dinsdale')
- self.assertEqual(name_addr.domain, 'example.com')
- self.assertIsNone(name_addr.route)
- self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')
-
- def test_get_name_addr_name_with_cfws_and_dots(self):
- name_addr = self._test_get_x(parser.get_name_addr,
- '(foo) Roy.A.Bear (bar) (bird)',
- '(foo) Roy.A.Bear (bar) (bird)',
- ' "Roy.A.Bear" ',
- [errors.ObsoleteHeaderDefect]*2,
- '')
- self.assertEqual(name_addr.display_name, 'Roy.A.Bear')
- self.assertEqual(name_addr.local_part, 'dinsdale')
- self.assertEqual(name_addr.domain, 'example.com')
- self.assertIsNone(name_addr.route)
- self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')
-
- def test_get_name_addr_qs_name(self):
- name_addr = self._test_get_x(parser.get_name_addr,
- '"Roy.A.Bear" ',
- '"Roy.A.Bear" ',
- '"Roy.A.Bear" ',
- [],
- '')
- self.assertEqual(name_addr.display_name, 'Roy.A.Bear')
- self.assertEqual(name_addr.local_part, 'dinsdale')
- self.assertEqual(name_addr.domain, 'example.com')
- self.assertIsNone(name_addr.route)
- self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')
-
- def test_get_name_addr_with_route(self):
- name_addr = self._test_get_x(parser.get_name_addr,
- '"Roy.A.Bear" <@two.example.com: dinsdale@example.com>',
- '"Roy.A.Bear" <@two.example.com: dinsdale@example.com>',
- '"Roy.A.Bear" <@two.example.com: dinsdale@example.com>',
- [errors.ObsoleteHeaderDefect],
- '')
- self.assertEqual(name_addr.display_name, 'Roy.A.Bear')
- self.assertEqual(name_addr.local_part, 'dinsdale')
- self.assertEqual(name_addr.domain, 'example.com')
- self.assertEqual(name_addr.route, ['two.example.com'])
- self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')
-
- def test_get_name_addr_ends_at_special(self):
- name_addr = self._test_get_x(parser.get_name_addr,
- '"Roy.A.Bear" , next',
- '"Roy.A.Bear" ',
- '"Roy.A.Bear" ',
- [],
- ', next')
- self.assertEqual(name_addr.display_name, 'Roy.A.Bear')
- self.assertEqual(name_addr.local_part, 'dinsdale')
- self.assertEqual(name_addr.domain, 'example.com')
- self.assertIsNone(name_addr.route)
- self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')
-
- def test_get_name_addr_no_content_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_name_addr(' (foo) ')
-
- def test_get_name_addr_no_content_before_special_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_name_addr(' (foo) ,')
-
- def test_get_name_addr_no_angle_after_display_name_raises(self):
- with self.assertRaises(errors.HeaderParseError):
- parser.get_name_addr('foo bar')
-
- # get_mailbox
-
- def test_get_mailbox_addr_spec_only(self):
- mailbox = self._test_get_x(parser.get_mailbox,
- 'dinsdale@example.com',
- 'dinsdale@example.com',
- 'dinsdale@example.com',
- [],
- '')
- self.assertEqual(mailbox.token_type, 'mailbox')
- self.assertIsNone(mailbox.display_name)
- self.assertEqual(mailbox.local_part, 'dinsdale')
- self.assertEqual(mailbox.domain, 'example.com')
- self.assertIsNone(mailbox.route)
- self.assertEqual(mailbox.addr_spec, 'dinsdale@example.com')
-
- def test_get_mailbox_angle_addr_only(self):
- mailbox = self._test_get_x(parser.get_mailbox,
- '',
- '',
- '',
- [],
- '')
- self.assertEqual(mailbox.token_type, 'mailbox')
- self.assertIsNone(mailbox.display_name)
- self.assertEqual(mailbox.local_part, 'dinsdale')
- self.assertEqual(mailbox.domain, 'example.com')
- self.assertIsNone(mailbox.route)
- self.assertEqual(mailbox.addr_spec, 'dinsdale@example.com')
-
- def test_get_mailbox_name_addr(self):
- mailbox = self._test_get_x(parser.get_mailbox,
- '"Roy A. Bear" ',
- '"Roy A. Bear" ',
- '"Roy A. Bear" ',
- [],
- '')
- self.assertEqual(mailbox.token_type, 'mailbox')
- self.assertEqual(mailbox.display_name, 'Roy A. Bear')
- self.assertEqual(mailbox.local_part, 'dinsdale')
- self.assertEqual(mailbox.domain, 'example.com')
- self.assertIsNone(mailbox.route)
- self.assertEqual(mailbox.addr_spec, 'dinsdale@example.com')
-
- def test_get_mailbox_ends_at_special(self):
- mailbox = self._test_get_x(parser.get_mailbox,
- '"Roy A. Bear" , rest',
- '"Roy A. Bear" ',
- '"Roy A. Bear" ',
- [],
- ', rest')
- self.assertEqual(mailbox.token_type, 'mailbox')
- self.assertEqual(mailbox.display_name, 'Roy A. Bear')
- self.assertEqual(mailbox.local_part, 'dinsdale')
- self.assertEqual(mailbox.domain, 'example.com')
- self.assertIsNone(mailbox.route)
- self.assertEqual(mailbox.addr_spec, 'dinsdale@example.com')
-
- def test_get_mailbox_quoted_strings_in_atom_list(self):
- mailbox = self._test_get_x(parser.get_mailbox,
- '""example" example"@example.com',
- '""example" example"@example.com',
- 'example example@example.com',
- [errors.InvalidHeaderDefect]*3,
- '')
- self.assertEqual(mailbox.local_part, 'example example')
- self.assertEqual(mailbox.domain, 'example.com')
- self.assertEqual(mailbox.addr_spec, '"example example"@example.com')
-
- # get_mailbox_list
-
- def test_get_mailbox_list_single_addr(self):
- mailbox_list = self._test_get_x(parser.get_mailbox_list,
- 'dinsdale@example.com',
- 'dinsdale@example.com',
- 'dinsdale@example.com',
- [],
- '')
- self.assertEqual(mailbox_list.token_type, 'mailbox-list')
- self.assertEqual(len(mailbox_list.mailboxes), 1)
- mailbox = mailbox_list.mailboxes[0]
- self.assertIsNone(mailbox.display_name)
- self.assertEqual(mailbox.local_part, 'dinsdale')
- self.assertEqual(mailbox.domain, 'example.com')
- self.assertIsNone(mailbox.route)
- self.assertEqual(mailbox.addr_spec, 'dinsdale@example.com')
- self.assertEqual(mailbox_list.mailboxes,
- mailbox_list.all_mailboxes)
-
- def test_get_mailbox_list_two_simple_addr(self):
- mailbox_list = self._test_get_x(parser.get_mailbox_list,
- 'dinsdale@example.com, dinsdale@test.example.com',
- 'dinsdale@example.com, dinsdale@test.example.com',
- 'dinsdale@example.com, dinsdale@test.example.com',
- [],
- '')
- self.assertEqual(mailbox_list.token_type, 'mailbox-list')
- self.assertEqual(len(mailbox_list.mailboxes), 2)
- self.assertEqual(mailbox_list.mailboxes[0].addr_spec,
- 'dinsdale@example.com')
- self.assertEqual(mailbox_list.mailboxes[1].addr_spec,
- 'dinsdale@test.example.com')
- self.assertEqual(mailbox_list.mailboxes,
- mailbox_list.all_mailboxes)
-
- def test_get_mailbox_list_two_name_addr(self):
- mailbox_list = self._test_get_x(parser.get_mailbox_list,
- ('"Roy A. Bear" ,'
- ' "Fred Flintstone" '),
- ('"Roy A. Bear" ,'
- ' "Fred Flintstone" '),
- ('"Roy A. Bear" ,'
- ' "Fred Flintstone" '),
- [],
- '')
- self.assertEqual(len(mailbox_list.mailboxes), 2)
- self.assertEqual(mailbox_list.mailboxes[0].addr_spec,
- 'dinsdale@example.com')
- self.assertEqual(mailbox_list.mailboxes[0].display_name,
- 'Roy A. Bear')
- self.assertEqual(mailbox_list.mailboxes[1].addr_spec,
- 'dinsdale@test.example.com')
- self.assertEqual(mailbox_list.mailboxes[1].display_name,
- 'Fred Flintstone')
- self.assertEqual(mailbox_list.mailboxes,
- mailbox_list.all_mailboxes)
-
- def test_get_mailbox_list_two_complex(self):
- mailbox_list = self._test_get_x(parser.get_mailbox_list,
- ('(foo) "Roy A. Bear" (bar),'
- ' "Fred Flintstone" '),
- ('(foo) "Roy A. Bear" (bar),'
- ' "Fred Flintstone" '),
- (' "Roy A. Bear" ,'
- ' "Fred Flintstone" '),
- [errors.ObsoleteHeaderDefect],
- '')
- self.assertEqual(len(mailbox_list.mailboxes), 2)
- self.assertEqual(mailbox_list.mailboxes[0].addr_spec,
- 'dinsdale@example.com')
- self.assertEqual(mailbox_list.mailboxes[0].display_name,
- 'Roy A. Bear')
- self.assertEqual(mailbox_list.mailboxes[1].addr_spec,
- 'dinsdale@test.example.com')
- self.assertEqual(mailbox_list.mailboxes[1].display_name,
- 'Fred Flintstone')
- self.assertEqual(mailbox_list.mailboxes,
- mailbox_list.all_mailboxes)
-
- def test_get_mailbox_list_unparseable_mailbox_null(self):
- mailbox_list = self._test_get_x(parser.get_mailbox_list,
- ('"Roy A. Bear"[] dinsdale@example.com,'
- ' "Fred Flintstone" '),
- ('"Roy A. Bear"[] dinsdale@example.com,'
- ' "Fred Flintstone" '),
- ('"Roy A. Bear"[] dinsdale@example.com,'
- ' "Fred Flintstone" '),
- [errors.InvalidHeaderDefect, # the 'extra' text after the local part
- errors.InvalidHeaderDefect, # the local part with no angle-addr
- errors.ObsoleteHeaderDefect, # period in extra text (example.com)
- errors.ObsoleteHeaderDefect], # (bird) in valid address.
- '')
- self.assertEqual(len(mailbox_list.mailboxes), 1)
- self.assertEqual(len(mailbox_list.all_mailboxes), 2)
- self.assertEqual(mailbox_list.all_mailboxes[0].token_type,
- 'invalid-mailbox')
- self.assertIsNone(mailbox_list.all_mailboxes[0].display_name)
- self.assertEqual(mailbox_list.all_mailboxes[0].local_part,
- 'Roy A. Bear')
- self.assertIsNone(mailbox_list.all_mailboxes[0].domain)
- self.assertEqual(mailbox_list.all_mailboxes[0].addr_spec,
- '"Roy A. Bear"')
- self.assertIs(mailbox_list.all_mailboxes[1],
- mailbox_list.mailboxes[0])
- self.assertEqual(mailbox_list.mailboxes[0].addr_spec,
- 'dinsdale@test.example.com')
- self.assertEqual(mailbox_list.mailboxes[0].display_name,
- 'Fred Flintstone')
-
- def test_get_mailbox_list_junk_after_valid_address(self):
- mailbox_list = self._test_get_x(parser.get_mailbox_list,
- ('"Roy A. Bear" @@,'
- ' "Fred Flintstone" '),
- ('"Roy A. Bear" @@,'
- ' "Fred Flintstone" '),
- ('"Roy A. Bear" @@,'
- ' "Fred Flintstone" '),
- [errors.InvalidHeaderDefect],
- '')
- self.assertEqual(len(mailbox_list.mailboxes), 1)
- self.assertEqual(len(mailbox_list.all_mailboxes), 2)
- self.assertEqual(mailbox_list.all_mailboxes[0].addr_spec,
- 'dinsdale@example.com')
- self.assertEqual(mailbox_list.all_mailboxes[0].display_name,
- 'Roy A. Bear')
- self.assertEqual(mailbox_list.all_mailboxes[0].token_type,
- 'invalid-mailbox')
- self.assertIs(mailbox_list.all_mailboxes[1],
- mailbox_list.mailboxes[0])
- self.assertEqual(mailbox_list.mailboxes[0].addr_spec,
- 'dinsdale@test.example.com')
- self.assertEqual(mailbox_list.mailboxes[0].display_name,
- 'Fred Flintstone')
-
- def test_get_mailbox_list_empty_list_element(self):
- mailbox_list = self._test_get_x(parser.get_mailbox_list,
- ('"Roy A. Bear" , (bird),,'
- ' "Fred Flintstone" '),
- ('"Roy A. Bear" , (bird),,'
- ' "Fred Flintstone" '),
- ('"Roy A. Bear" , ,,'
- ' "Fred Flintstone" '),
- [errors.ObsoleteHeaderDefect]*2,
- '')
- self.assertEqual(len(mailbox_list.mailboxes), 2)
- self.assertEqual(mailbox_list.all_mailboxes,
- mailbox_list.mailboxes)
- self.assertEqual(mailbox_list.all_mailboxes[0].addr_spec,
- 'dinsdale@example.com')
- self.assertEqual(mailbox_list.all_mailboxes[0].display_name,
- 'Roy A. Bear')
- self.assertEqual(mailbox_list.mailboxes[1].addr_spec,
- 'dinsdale@test.example.com')
- self.assertEqual(mailbox_list.mailboxes[1].display_name,
- 'Fred Flintstone')
-
- def test_get_mailbox_list_only_empty_elements(self):
- mailbox_list = self._test_get_x(parser.get_mailbox_list,
- '(foo),, (bar)',
- '(foo),, (bar)',
- ' ,, ',
- [errors.ObsoleteHeaderDefect]*3,
- '')
- self.assertEqual(len(mailbox_list.mailboxes), 0)
- self.assertEqual(mailbox_list.all_mailboxes,
- mailbox_list.mailboxes)
-
- # get_group_list
-
- def test_get_group_list_cfws_only(self):
- group_list = self._test_get_x(parser.get_group_list,
- '(hidden);',
- '(hidden)',
- ' ',
- [],
- ';')
- self.assertEqual(group_list.token_type, 'group-list')
- self.assertEqual(len(group_list.mailboxes), 0)
- self.assertEqual(group_list.mailboxes,
- group_list.all_mailboxes)
-
- def test_get_group_list_mailbox_list(self):
- group_list = self._test_get_x(parser.get_group_list,
- 'dinsdale@example.org, "Fred A. Bear" ',
- 'dinsdale@example.org, "Fred A. Bear" ',
- 'dinsdale@example.org, "Fred A. Bear" ',
- [],
- '')
- self.assertEqual(group_list.token_type, 'group-list')
- self.assertEqual(len(group_list.mailboxes), 2)
- self.assertEqual(group_list.mailboxes,
- group_list.all_mailboxes)
- self.assertEqual(group_list.mailboxes[1].display_name,
- 'Fred A. Bear')
-
- def test_get_group_list_obs_group_list(self):
- group_list = self._test_get_x(parser.get_group_list,
- ', (foo),,(bar)',
- ', (foo),,(bar)',
- ', ,, ',
- [errors.ObsoleteHeaderDefect],
- '')
- self.assertEqual(group_list.token_type, 'group-list')
- self.assertEqual(len(group_list.mailboxes), 0)
- self.assertEqual(group_list.mailboxes,
- group_list.all_mailboxes)
-
- def test_get_group_list_comment_only_invalid(self):
- group_list = self._test_get_x(parser.get_group_list,
- '(bar)',
- '(bar)',
- ' ',
- [errors.InvalidHeaderDefect],
- '')
- self.assertEqual(group_list.token_type, 'group-list')
- self.assertEqual(len(group_list.mailboxes), 0)
- self.assertEqual(group_list.mailboxes,
- group_list.all_mailboxes)
-
- # get_group
-
- def test_get_group_empty(self):
- group = self._test_get_x(parser.get_group,
- 'Monty Python:;',
- 'Monty Python:;',
- 'Monty Python:;',
- [],
- '')
- self.assertEqual(group.token_type, 'group')
- self.assertEqual(group.display_name, 'Monty Python')
- self.assertEqual(len(group.mailboxes), 0)
- self.assertEqual(group.mailboxes,
- group.all_mailboxes)
-
- def test_get_group_null_addr_spec(self):
- group = self._test_get_x(parser.get_group,
- 'foo: <>;',
- 'foo: <>;',
- 'foo: <>;',
- [errors.InvalidHeaderDefect],
- '')
- self.assertEqual(group.display_name, 'foo')
- self.assertEqual(len(group.mailboxes), 0)
- self.assertEqual(len(group.all_mailboxes), 1)
- self.assertEqual(group.all_mailboxes[0].value, '<>')
-
- def test_get_group_cfws_only(self):
- group = self._test_get_x(parser.get_group,
- 'Monty Python: (hidden);',
- 'Monty Python: (hidden);',
- 'Monty Python: ;',
- [],
- '')
- self.assertEqual(group.token_type, 'group')
- self.assertEqual(group.display_name, 'Monty Python')
- self.assertEqual(len(group.mailboxes), 0)
- self.assertEqual(group.mailboxes,
- group.all_mailboxes)
-
- def test_get_group_single_mailbox(self):
- group = self._test_get_x(parser.get_group,
- 'Monty Python: "Fred A. Bear" ;',
- 'Monty Python: "Fred A. Bear" ;',
- 'Monty Python: "Fred A. Bear" ;',
- [],
- '')
- self.assertEqual(group.token_type, 'group')
- self.assertEqual(group.display_name, 'Monty Python')
- self.assertEqual(len(group.mailboxes), 1)
- self.assertEqual(group.mailboxes,
- group.all_mailboxes)
- self.assertEqual(group.mailboxes[0].addr_spec,
- 'dinsdale@example.com')
-
- def test_get_group_mixed_list(self):
- group = self._test_get_x(parser.get_group,
- ('Monty Python: "Fred A. Bear" ,'
- '(foo) Roger , x@test.example.com;'),
- ('Monty Python: "Fred A. Bear" ,'
- '(foo) Roger , x@test.example.com;'),
- ('Monty Python: "Fred A. Bear" ,'
- ' Roger , x@test.example.com;'),
- [],
- '')
- self.assertEqual(group.token_type, 'group')
- self.assertEqual(group.display_name, 'Monty Python')
- self.assertEqual(len(group.mailboxes), 3)
- self.assertEqual(group.mailboxes,
- group.all_mailboxes)
- self.assertEqual(group.mailboxes[0].display_name,
- 'Fred A. Bear')
- self.assertEqual(group.mailboxes[1].display_name,
- 'Roger')
- self.assertEqual(group.mailboxes[2].local_part, 'x')
-
- def test_get_group_one_invalid(self):
- group = self._test_get_x(parser.get_group,
- ('Monty Python: "Fred A. Bear" ,'
- '(foo) Roger ping@exampele.com, x@test.example.com;'),
- ('Monty Python: "Fred A. Bear" ,'
- '(foo) Roger ping@exampele.com, x@test.example.com;'),
- ('Monty Python: "Fred A. Bear" ,'
- ' Roger ping@exampele.com, x@test.example.com;'),
- [errors.InvalidHeaderDefect, # non-angle addr makes local part invalid
- errors.InvalidHeaderDefect], # and its not obs-local either: no dots.
- '')
- self.assertEqual(group.token_type, 'group')
- self.assertEqual(group.display_name, 'Monty Python')
- self.assertEqual(len(group.mailboxes), 2)
- self.assertEqual(len(group.all_mailboxes), 3)
- self.assertEqual(group.mailboxes[0].display_name,
- 'Fred A. Bear')
- self.assertEqual(group.mailboxes[1].local_part, 'x')
- self.assertIsNone(group.all_mailboxes[1].display_name)
-
- # get_address
-
- def test_get_address_simple(self):
- address = self._test_get_x(parser.get_address,
- 'dinsdale@example.com',
- 'dinsdale@example.com',
- 'dinsdale@example.com',
- [],
- '')
- self.assertEqual(address.token_type, 'address')
- self.assertEqual(len(address.mailboxes), 1)
- self.assertEqual(address.mailboxes,
- address.all_mailboxes)
- self.assertEqual(address.mailboxes[0].domain,
- 'example.com')
- self.assertEqual(address[0].token_type,
- 'mailbox')
-
- def test_get_address_complex(self):
- address = self._test_get_x(parser.get_address,
- '(foo) "Fred A. Bear" <(bird)dinsdale@example.com>',
- '(foo) "Fred A. Bear" <(bird)dinsdale@example.com>',
- ' "Fred A. Bear" < dinsdale@example.com>',
- [],
- '')
- self.assertEqual(address.token_type, 'address')
- self.assertEqual(len(address.mailboxes), 1)
- self.assertEqual(address.mailboxes,
- address.all_mailboxes)
- self.assertEqual(address.mailboxes[0].display_name,
- 'Fred A. Bear')
- self.assertEqual(address[0].token_type,
- 'mailbox')
-
- def test_get_address_empty_group(self):
- address = self._test_get_x(parser.get_address,
- 'Monty Python:;',
- 'Monty Python:;',
- 'Monty Python:;',
- [],
- '')
- self.assertEqual(address.token_type, 'address')
- self.assertEqual(len(address.mailboxes), 0)
- self.assertEqual(address.mailboxes,
- address.all_mailboxes)
- self.assertEqual(address[0].token_type,
- 'group')
- self.assertEqual(address[0].display_name,
- 'Monty Python')
-
- def test_get_address_group(self):
- address = self._test_get_x(parser.get_address,
- 'Monty Python: x@example.com, y@example.com;',
- 'Monty Python: x@example.com, y@example.com;',
- 'Monty Python: x@example.com, y@example.com;',
- [],
- '')
- self.assertEqual(address.token_type, 'address')
- self.assertEqual(len(address.mailboxes), 2)
- self.assertEqual(address.mailboxes,
- address.all_mailboxes)
- self.assertEqual(address[0].token_type,
- 'group')
- self.assertEqual(address[0].display_name,
- 'Monty Python')
- self.assertEqual(address.mailboxes[0].local_part, 'x')
-
- def test_get_address_quoted_local_part(self):
- address = self._test_get_x(parser.get_address,
- '"foo bar"@example.com',
- '"foo bar"@example.com',
- '"foo bar"@example.com',
- [],
- '')
- self.assertEqual(address.token_type, 'address')
- self.assertEqual(len(address.mailboxes), 1)
- self.assertEqual(address.mailboxes,
- address.all_mailboxes)
- self.assertEqual(address.mailboxes[0].domain,
- 'example.com')
- self.assertEqual(address.mailboxes[0].local_part,
- 'foo bar')
- self.assertEqual(address[0].token_type, 'mailbox')
-
- def test_get_address_ends_at_special(self):
- address = self._test_get_x(parser.get_address,
- 'dinsdale@example.com, next',
- 'dinsdale@example.com',
- 'dinsdale@example.com',
- [],
- ', next')
- self.assertEqual(address.token_type, 'address')
- self.assertEqual(len(address.mailboxes), 1)
- self.assertEqual(address.mailboxes,
- address.all_mailboxes)
- self.assertEqual(address.mailboxes[0].domain,
- 'example.com')
- self.assertEqual(address[0].token_type, 'mailbox')
-
- def test_get_address_invalid_mailbox_invalid(self):
- address = self._test_get_x(parser.get_address,
- 'ping example.com, next',
- 'ping example.com',
- 'ping example.com',
- [errors.InvalidHeaderDefect, # addr-spec with no domain
- errors.InvalidHeaderDefect, # invalid local-part
- errors.InvalidHeaderDefect, # missing .s in local-part
- ],
- ', next')
- self.assertEqual(address.token_type, 'address')
- self.assertEqual(len(address.mailboxes), 0)
- self.assertEqual(len(address.all_mailboxes), 1)
- self.assertIsNone(address.all_mailboxes[0].domain)
- self.assertEqual(address.all_mailboxes[0].local_part, 'ping example.com')
- self.assertEqual(address[0].token_type, 'invalid-mailbox')
-
- def test_get_address_quoted_strings_in_atom_list(self):
- address = self._test_get_x(parser.get_address,
- '""example" example"@example.com',
- '""example" example"@example.com',
- 'example example@example.com',
- [errors.InvalidHeaderDefect]*3,
- '')
- self.assertEqual(address.all_mailboxes[0].local_part, 'example example')
- self.assertEqual(address.all_mailboxes[0].domain, 'example.com')
- self.assertEqual(address.all_mailboxes[0].addr_spec, '"example example"@example.com')
-
-
- # get_address_list
-
- def test_get_address_list_mailboxes_simple(self):
- address_list = self._test_get_x(parser.get_address_list,
- 'dinsdale@example.com',
- 'dinsdale@example.com',
- 'dinsdale@example.com',
- [],
- '')
- self.assertEqual(address_list.token_type, 'address-list')
- self.assertEqual(len(address_list.mailboxes), 1)
- self.assertEqual(address_list.mailboxes,
- address_list.all_mailboxes)
- self.assertEqual([str(x) for x in address_list.mailboxes],
- [str(x) for x in address_list.addresses])
- self.assertEqual(address_list.mailboxes[0].domain, 'example.com')
- self.assertEqual(address_list[0].token_type, 'address')
- self.assertIsNone(address_list[0].display_name)
-
- def test_get_address_list_mailboxes_two_simple(self):
- address_list = self._test_get_x(parser.get_address_list,
- 'foo@example.com, "Fred A. Bar"