From c289c2da821834f7526e7a209d83d402cd58bdd5 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 2 Oct 2014 23:23:12 +1000 Subject: [PATCH 001/425] Docs: Add instructions for uploading PDF cheatsheet --- docs/other/upload_future_docs.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/other/upload_future_docs.sh b/docs/other/upload_future_docs.sh index 83b79d77..517af319 100644 --- a/docs/other/upload_future_docs.sh +++ b/docs/other/upload_future_docs.sh @@ -4,6 +4,7 @@ On the local machine git checkout v0.7.0 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 @@ -11,6 +12,7 @@ zip -r /shared/python-future-html-docs.zip * cd /shared scp python-future-html-docs.zip python-future.org: +scp cheatsheet.pdf python-future.org: ssh python-future.org @@ -20,4 +22,6 @@ On the remote machine: cd /var/www/python-future/ 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 From 4f4194868a72a0f9ba0a9de53a5162bb84f300c2 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 2 Oct 2014 23:27:53 +1000 Subject: [PATCH 002/425] Docs: Tweak PDF filename in upload instructions to match link from notebook/HTML --- docs/other/upload_future_docs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/other/upload_future_docs.sh b/docs/other/upload_future_docs.sh index 517af319..dc07f6f1 100644 --- a/docs/other/upload_future_docs.sh +++ b/docs/other/upload_future_docs.sh @@ -22,6 +22,6 @@ On the remote machine: cd /var/www/python-future/ unzip -o ~/python-future-html-docs.zip chmod a+r * html/* html/_static/* -cp ~/cheatsheet.pdf ./html/compatible-idioms.pdf +cp ~/cheatsheet.pdf ./html/compatible_idioms.pdf cp ~/cheatsheet.pdf ./html/cheatsheet.pdf From ea484c1843ed9b952c3c0471347df4bffc5e28dc Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 3 Oct 2014 09:55:30 +1000 Subject: [PATCH 003/425] Add (failing) test for whether futurize displaces PEP 263 source encoding comments (issues #97 and #10) --- tests/test_future/test_futurize.py | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index 57f70d21..f5cf24e2 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -36,6 +36,39 @@ def setUp(self): self.tempdir = tempfile.mkdtemp() + os.path.sep super(TestFuturizeSimple, self).setUp() + def test_encoding_comments_kept_at_top(self): + """ + Issues #10 and #97: If there is a source encoding comment line + (PEP 263), is it kept at the top of a module by ``futurize``? + """ + before = """ + # coding=utf-8 + + print 'Hello' + """ + after = """ + # coding=utf-8 + + from __future__ import print_function + print('Hello') + """ + self.convert_check(before, after) + + before = """ + #!/usr/bin/env python + # -*- coding: latin-1 -*-" + + print 'Hello' + """ + after = """ + #!/usr/bin/env python + # -*- coding: latin-1 -*-" + + from __future__ import print_function + print('Hello') + """ + self.convert_check(before, after) + def test_shebang_blank_with_future_division_import(self): """ Issue #43: Is shebang line preserved as the first From abb191fb29033be029c7848319f5f40891b87364 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 3 Oct 2014 09:57:23 +1000 Subject: [PATCH 004/425] Fix and add more tests of futurize with encoding comments (issues #97 and #10) --- src/libfuturize/fixer_util.py | 36 ++++++++++++++++------ tests/test_future/test_futurize.py | 49 ++++++++++++++++++++++++++---- 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/src/libfuturize/fixer_util.py b/src/libfuturize/fixer_util.py index f2366a6e..a63735cf 100644 --- a/src/libfuturize/fixer_util.py +++ b/src/libfuturize/fixer_util.py @@ -196,13 +196,13 @@ 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 it's a shebang or encoding line, attach the prefix to + if is_shebang_comment(node) or is_encoding_comment(node): + shebang_encoding_idx = idx if node.type == syms.simple_stmt and \ len(node.children) > 0 and node.children[0].type == token.STRING: # skip over docstring @@ -216,9 +216,9 @@ 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'' @@ -424,16 +424,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: diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index f5cf24e2..20214ecc 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -6,7 +6,7 @@ from subprocess import Popen, PIPE import os -from libfuturize.fixer_util import is_shebang_comment +from libfuturize.fixer_util import is_shebang_comment, is_encoding_comment from lib2to3.fixer_util import FromImport from lib2to3.pytree import Leaf, Node from lib2to3.pygram import token @@ -19,11 +19,48 @@ class TestLibFuturize(unittest.TestCase): def test_is_shebang_comment(self): """ - Tests whether the libfuturize.fixer_util.is_shebang_comment() function is working - """ - node = FromImport(u'math', [Leaf(token.NAME, u'cos', prefix=" ")]) - node.prefix = u'#!/usr/bin/env python\n' - self.assertTrue(is_shebang_comment(node)) + Tests whether the fixer_util.is_encoding_comment() function is working. + """ + shebang_comments = [u'#!/usr/bin/env python\n' + u"#!/usr/bin/python2\n", + u"#! /usr/bin/python3\n", + ] + not_shebang_comments = [u"# I saw a giant python\n", + u"# I have never seen a python2\n", + ] + for comment in shebang_comments: + node = FromImport(u'math', [Leaf(token.NAME, u'cos', prefix=" ")]) + node.prefix = comment + self.assertTrue(is_shebang_comment(node)) + + for comment in not_shebang_comments: + node = FromImport(u'math', [Leaf(token.NAME, u'cos', prefix=" ")]) + node.prefix = comment + self.assertFalse(is_shebang_comment(node)) + + + def test_is_encoding_comment(self): + """ + Tests whether the fixer_util.is_encoding_comment() function is working. + """ + encoding_comments = [u"# coding: utf-8", + u"# encoding: utf-8", + u"# -*- coding: latin-1 -*-", + u"# vim: set fileencoding=iso-8859-15 :", + ] + not_encoding_comments = [u"# We use the file encoding utf-8", + u"coding = 'utf-8'", + u"encoding = 'utf-8'", + ] + for comment in encoding_comments: + node = FromImport(u'math', [Leaf(token.NAME, u'cos', prefix=" ")]) + node.prefix = comment + self.assertTrue(is_encoding_comment(node)) + + for comment in not_encoding_comments: + node = FromImport(u'math', [Leaf(token.NAME, u'cos', prefix=" ")]) + node.prefix = comment + self.assertFalse(is_encoding_comment(node)) class TestFuturizeSimple(CodeHandler): From 1eee2cf5e09c049527257d43c397cccc089015fa Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 5 Oct 2014 16:46:30 +1100 Subject: [PATCH 005/425] futurize: Prevent any encoding or shebang prefixes to print statements from appearing twice in the output --- src/libfuturize/fixer_util.py | 12 +++++++----- src/libfuturize/fixes/fix_print_with_import.py | 5 ++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/libfuturize/fixer_util.py b/src/libfuturize/fixer_util.py index a63735cf..0c366366 100644 --- a/src/libfuturize/fixer_util.py +++ b/src/libfuturize/fixer_util.py @@ -200,7 +200,7 @@ def future_import(feature, node): shebang_encoding_idx = None for idx, node in enumerate(root.children): - # If it's a shebang or encoding line, attach the prefix to + # Is it a shebang or encoding line? if is_shebang_comment(node) or is_encoding_comment(node): shebang_encoding_idx = idx if node.type == syms.simple_stmt and \ @@ -218,11 +218,13 @@ def future_import(feature, node): import_ = FromImport(u'__future__', [Leaf(token.NAME, feature, prefix=" ")]) if shebang_encoding_idx == 0 and idx == 0: # If this __future__ import would go on the first line, - # detach the shebang / encoding 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()] + import_.set_prefix(root.children[0].get_prefix()) + root.children[0].set_prefix(u'') + # 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)) diff --git a/src/libfuturize/fixes/fix_print_with_import.py b/src/libfuturize/fixes/fix_print_with_import.py index 57986977..5308d925 100644 --- a/src/libfuturize/fixes/fix_print_with_import.py +++ b/src/libfuturize/fixes/fix_print_with_import.py @@ -14,7 +14,10 @@ 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 From 6d4e51775517de251ef249d24a8ce5c8dde2e4e9 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 5 Oct 2014 17:21:18 +1100 Subject: [PATCH 006/425] Replace the html.escape() function from Py3.3 with the one from Py3.4 The newer function happens to be a polymorphic bytes/unicode interface on Py2. This should fix issue #108: TypeError: character mapping must return integer, None or unicode --- src/future/moves/html/__init__.py | 24 ++++++++++++------------ src/html/__init__.py | 25 +------------------------ 2 files changed, 13 insertions(+), 36 deletions(-) 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/html/__init__.py b/src/html/__init__.py index 8250e6de..5f9c6d4c 100644 --- a/src/html/__init__.py +++ b/src/html/__init__.py @@ -4,28 +4,5 @@ if sys.version_info[0] == 3: raise ImportError('Cannot import module from python-future source folder') - 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) + from future.moves.html import * From 2e9c8bb982e30af2f9488453daa36ee6b33e907b Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 5 Oct 2014 18:30:25 +1100 Subject: [PATCH 007/425] setup.py: Don't forcibly remove the old build dir. Perform a finer-grained check (issue #108) This reverts patch 984ae98 ("Force removal of the old build-dir in setup.py") and checks for the build dir for consistency instead. --- setup.py | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index a0148f3a..7c81a099 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 @@ -120,14 +120,43 @@ # 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 "configparser" 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 configparser" etc. may pick up our Py2 + # substitute packages, instead of the intended system stdlib modules.) + SYSTEM_MODULES = set([ + '_dummy_thread', + '_markupbase', + '_thread', + 'builtins', + '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, From 108bb5883efee94d10fcd8853900fdbc678bf57c Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 5 Oct 2014 18:35:24 +1100 Subject: [PATCH 008/425] Remove some code duplication from html.parser and html.entities --- src/html/entities.py | 3 +-- src/html/parser.py | 10 +++++----- 2 files changed, 6 insertions(+), 7 deletions(-) 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..541def39 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 * From ebd1227ddb2e65218d026cef05f5a9af93f69e69 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 5 Oct 2014 18:45:49 +1100 Subject: [PATCH 009/425] libfuturize.fixer_util: Revert use of node.set_prefix() since this doesn't work on Py3.3 --- src/libfuturize/fixer_util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libfuturize/fixer_util.py b/src/libfuturize/fixer_util.py index 0c366366..0013437b 100644 --- a/src/libfuturize/fixer_util.py +++ b/src/libfuturize/fixer_util.py @@ -220,8 +220,8 @@ def future_import(feature, node): # If this __future__ import would go on the first line, # detach the shebang / encoding prefix from the current first line. # and attach it to our new __future__ import node. - import_.set_prefix(root.children[0].get_prefix()) - root.children[0].set_prefix(u'') + import_.prefix = root.children[0].prefix + root.children[0].prefix = u'' # End the __future__ import line with a newline and add a blank line # afterwards: children = [import_ , Newline()] From 3701cf438ace25c39f5469fd90ed81c371ab2a62 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 5 Oct 2014 18:39:29 +1100 Subject: [PATCH 010/425] Bump version to v0.14.2 --- src/future/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/__init__.py b/src/future/__init__.py index 2ba49608..c3cddc77 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -73,7 +73,7 @@ __copyright__ = 'Copyright 2014 Python Charmers Pty Ltd' __ver_major__ = 0 __ver_minor__ = 14 -__ver_patch__ = 1 +__ver_patch__ = 2 __ver_sub__ = '' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From 9f146eeb46ab13777a4429e5e2b6fc303a252497 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 8 Nov 2014 11:13:53 +1100 Subject: [PATCH 011/425] Include icon files in MANIFEST.in --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 5b62df76..fcdb9c63 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 From d92f3d558a979a3dbe42bacb004530eb5edb8e4e Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 8 Nov 2014 11:29:21 +1100 Subject: [PATCH 012/425] Update docs to refer to install_aliases() instead of install_hooks() --- docs/futurize.rst | 4 ++-- docs/futurize_overview.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/futurize.rst b/docs/futurize.rst index 12bfae04..5a33053f 100644 --- a/docs/futurize.rst +++ b/docs/futurize.rst @@ -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,7 +219,7 @@ 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:: diff --git a/docs/futurize_overview.rst b/docs/futurize_overview.rst index 1192b9da..732b96b2 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 From 67971ec16615b667a8d3974e7d0241f246a72bee Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 8 Nov 2014 11:29:56 +1100 Subject: [PATCH 013/425] Update a couple of tests to use install_aliases() instead of install_hooks() --- tests/test_future/test_futurize.py | 4 ++-- tests/test_future/test_standard_library.py | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index 20214ecc..b75d4f01 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -632,7 +632,7 @@ def test_urllib_refactor(self): """ after = """ from future import standard_library - standard_library.install_hooks() + standard_library.install_aliases() import urllib.request URL = 'http://pypi.python.org/pypi/future/json' @@ -1263,7 +1263,7 @@ def test_all_imports(self): from __future__ import division from __future__ import absolute_import from future import standard_library - standard_library.install_hooks() + standard_library.install_aliases() from builtins import range from builtins import * import math diff --git a/tests/test_future/test_standard_library.py b/tests/test_future/test_standard_library.py index 86e3a22e..fc564a59 100644 --- a/tests/test_future/test_standard_library.py +++ b/tests/test_future/test_standard_library.py @@ -19,11 +19,12 @@ class TestStandardLibraryReorganization(CodeHandler): def setUp(self): self.interpreter = sys.executable - standard_library.install_hooks() + standard_library.install_aliases() super(TestStandardLibraryReorganization, self).setUp() def tearDown(self): - standard_library.remove_hooks() + # standard_library.remove_hooks() + pass def test_can_import_several(self): """ @@ -37,8 +38,7 @@ def test_can_import_several(self): import future.moves.urllib.parse as urllib_parse import future.moves.urllib.request as urllib_request - with standard_library.hooks(): - import http.server + import http.server for m in [urllib_parse, urllib_request, http.server]: self.assertTrue(m is not None) @@ -369,6 +369,17 @@ def test_urllib_imports_moves(self): import future.moves.urllib.response self.assertTrue(True) + def test_urllib_imports_install_aliases(self): + with standard_library.suspend_hooks(): + standard_library.install_aliases() + import urllib + import urllib.parse + import urllib.request + import urllib.robotparser + import urllib.error + import urllib.response + self.assertTrue(True) + def test_urllib_imports_cm(self): with standard_library.hooks(): import urllib From 0270a4b8d586ea1d7078bc03cd959ecfd588b32d Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 8 Nov 2014 22:22:05 +1100 Subject: [PATCH 014/425] Speed up importing of past.translation (issue #117) Also: give more descriptive error messages if top-level packages are imported somehow on Py3. --- src/_dummy_thread/__init__.py | 4 +- src/_markupbase/__init__.py | 4 +- src/_thread/__init__.py | 4 +- src/builtins/__init__.py | 4 +- src/configparser/__init__.py | 8 +++- src/copyreg/__init__.py | 12 ++--- src/future/standard_library/__init__.py | 4 +- src/html/__init__.py | 9 ++-- src/http/__init__.py | 5 ++- src/http/client.py | 8 ++-- src/http/cookiejar.py | 10 ++--- src/http/cookies.py | 12 +++-- src/http/server.py | 29 ++++++------- src/past/__init__.py | 2 - src/past/translation/__init__.py | 58 +++++++++++++++++-------- src/queue/__init__.py | 4 +- src/reprlib/__init__.py | 5 ++- src/socketserver/__init__.py | 5 ++- src/tkinter/__init__.py | 5 ++- src/winreg/__init__.py | 4 +- src/xmlrpc/__init__.py | 5 ++- src/xmlrpc/client.py | 8 ++-- src/xmlrpc/server.py | 8 ++-- 23 files changed, 124 insertions(+), 93 deletions(-) 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..63dced6e 100644 --- a/src/_thread/__init__.py +++ b/src/_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/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 index 9bae099c..3642c5ec 100644 --- a/src/configparser/__init__.py +++ b/src/configparser/__init__.py @@ -3,5 +3,11 @@ if sys.version_info[0] < 3: from ConfigParser import * + try: + from ConfigParser import (_Chainmap, Error, InterpolationMissingOptionError) + 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/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/standard_library/__init__.py b/src/future/standard_library/__init__.py index 307df037..2923e2d3 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -267,7 +267,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 @@ -451,7 +451,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] diff --git a/src/html/__init__.py b/src/html/__init__.py index 5f9c6d4c..e957e745 100644 --- a/src/html/__init__.py +++ b/src/html/__init__.py @@ -1,8 +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') -else: +if sys.version_info[0] < 3: from future.moves.html 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/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..429cac4c 100644 --- a/src/http/client.py +++ b/src/http/client.py @@ -1,8 +1,6 @@ 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 * 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/past/__init__.py b/src/past/__init__.py index b434acb9..8416f228 100644 --- a/src/past/__init__.py +++ b/src/past/__init__.py @@ -82,10 +82,8 @@ --------- Copyright 2013-2014 Python Charmers Pty Ltd, 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__ diff --git a/src/past/translation/__init__.py b/src/past/translation/__init__.py index 29f8be69..7b21d9f5 100644 --- a/src/past/translation/__init__.py +++ b/src/past/translation/__init__.py @@ -43,7 +43,6 @@ from libfuturize import fixes -__version__ = '0.1.0' logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -54,12 +53,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 +115,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 +168,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 +201,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,12 +208,13 @@ 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 @@ -282,13 +303,14 @@ def transform(self, source): # if that's better for you # lib2to3 likes a newline at the end + RTs.setup() source += '\n' try: - tree = _rt.refactor_string(source, self.pathname) + tree = RTs._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) + tree = RTs._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 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..cff06c0e 100644 --- a/src/tkinter/__init__.py +++ b/src/tkinter/__init__.py @@ -1,8 +1,9 @@ from __future__ import absolute_import import sys -__future_module__ = True if sys.version_info[0] < 3: from Tkinter 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/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 * From 91b800dbc46f4c04c907928bbb9ebe271b670e16 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 8 Nov 2014 22:59:13 +1100 Subject: [PATCH 015/425] Add a(nother) test for issue #121 Also fix up the import-order checking code in future.tests.base --- src/future/tests/base.py | 5 +++-- tests/test_future/test_futurize.py | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/future/tests/base.py b/src/future/tests/base.py index 0386bb99..40877306 100644 --- a/src/future/tests/base.py +++ b/src/future/tests/base.py @@ -44,7 +44,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 +57,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' diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index b75d4f01..9eaa595e 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -104,7 +104,22 @@ def test_encoding_comments_kept_at_top(self): from __future__ import print_function print('Hello') """ - self.convert_check(before, after) + self.convert_check(before, after, ignore_imports=False) + + # Issue #121. This fails as of v0.14.1: + before = u""" + # -*- coding: utf-8 -*- + # Author: etc. with some unicode ¿. + 1 / 2 + """ + after = u""" + # -*- coding: utf-8 -*- + # Author: etc. with some unicode ¿. + from __future__ import division + from past.utils import old_div + old_div(1, 2) + """ + self.convert_check(before, after, ignore_imports=False) def test_shebang_blank_with_future_division_import(self): """ @@ -435,7 +450,7 @@ def test_source_coding_utf8(self): """ code = """ # -*- coding: utf-8 -*- - icons = [u"◐", u"◓", u"◑", u"◒"] + icons = [u"◐", u"◓", u"◑", u"◒"] """ self.unchanged(code) From 81a0f181905002332c540b08cffa58cc0c49a11f Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 9 Nov 2014 00:50:33 +1100 Subject: [PATCH 016/425] Add failing tests for the cause of issue #119 It seems that sys.exit(newint) (and other longs) always returns 1! --- tests/test_future/test_futurize.py | 12 ++++++++++++ tests/test_future/test_pasteurize.py | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index 9eaa595e..706d689c 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -17,6 +17,18 @@ class TestLibFuturize(unittest.TestCase): + def test_correct_exit_status(self): + """ + Issue #119: futurize and pasteurize were not exiting with the correct + status code. This is because the status code returned from + libfuturize.main.main() etc. was a ``newint``, which sys.exit() always + translates into 1! + """ + from libfuturize.main import main + # Try futurizing this test script: + retcode = main([__file__]) + self.assertTrue(isinstance(retcode, int)) # i.e. Py2 builtin int + def test_is_shebang_comment(self): """ Tests whether the fixer_util.is_encoding_comment() function is working. diff --git a/tests/test_future/test_pasteurize.py b/tests/test_future/test_pasteurize.py index 9c4ed7e5..aa6ce061 100644 --- a/tests/test_future/test_pasteurize.py +++ b/tests/test_future/test_pasteurize.py @@ -129,6 +129,18 @@ def test_urllib_refactor2(self): filename = urllib_parse.urlparse(url)[2].split('/')[-1] """ + def test_correct_exit_status(self): + """ + Issue #119: futurize and pasteurize were not exiting with the correct + status code. This is because the status code returned from + libfuturize.main.main() etc. was a ``newint``, which sys.exit() always + translates into 1! + """ + from libpasteurize.main import main + # Try pasteurizing this test script: + retcode = main([__file__]) + self.assertTrue(isinstance(retcode, int)) # i.e. Py2 builtin int + class TestFuturizeAnnotations(CodeHandler): @unittest.expectedFailure From 1def87c9cd7f1b0aa312831a704d8f2fbe10df40 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 9 Nov 2014 00:54:46 +1100 Subject: [PATCH 017/425] Fix issue #119 --- src/libfuturize/main.py | 2 -- src/libpasteurize/main.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/libfuturize/main.py b/src/libfuturize/main.py index b0bd77bd..2e8f1b56 100644 --- a/src/libfuturize/main.py +++ b/src/libfuturize/main.py @@ -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__ diff --git a/src/libpasteurize/main.py b/src/libpasteurize/main.py index b0c2ce53..f4a1dd52 100644 --- a/src/libpasteurize/main.py +++ b/src/libpasteurize/main.py @@ -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 From 8dce1a6c8ef86249d1cab9b9bfa85f18c96c46d5 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 9 Nov 2014 01:07:41 +1100 Subject: [PATCH 018/425] Remove the previous workaround for sys.exit()'s wrong exit code with newint --- futurize.py | 7 ++----- pasteurize.py | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/futurize.py b/futurize.py index 1b3f34a0..ffff1ab2 100755 --- a/futurize.py +++ b/futurize.py @@ -17,11 +17,8 @@ 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..19f6aa3a 100755 --- a/pasteurize.py +++ b/pasteurize.py @@ -16,11 +16,8 @@ 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()) From ebefae27e3ddd653a4a6ee75c76342aa2a41e78f Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 9 Nov 2014 01:10:59 +1100 Subject: [PATCH 019/425] Fix doc bug (cheat sheet: iteritems import) (issue #120) --- docs/compatible_idioms.rst | 9 ++++++++- .../Writing Python 2-3 compatible code.ipynb | 18 ++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index 9fe1cc9a..575ab0c2 100644 --- a/docs/compatible_idioms.rst +++ b/docs/compatible_idioms.rst @@ -533,7 +533,14 @@ Iterable dict items: .. 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 diff --git a/docs/notebooks/Writing Python 2-3 compatible code.ipynb b/docs/notebooks/Writing Python 2-3 compatible code.ipynb index d7c98a0b..22b6a987 100644 --- a/docs/notebooks/Writing Python 2-3 compatible code.ipynb +++ b/docs/notebooks/Writing Python 2-3 compatible code.ipynb @@ -1,7 +1,7 @@ { "metadata": { "name": "", - "signature": "sha256:7fe04ffd8c478c4d6b7653aab35c47f3117c50ae383326dfeca599d53603d031" + "signature": "sha256:fa152cbedcb4d5d5c255d3456863fe07938d6196acf812a42c7c9ea3869c9f1b" }, "nbformat": 3, "nbformat_minor": 0, @@ -1147,7 +1147,21 @@ "collapsed": false, "input": [ "# Python 2 and 3: option 2\n", - "from builtins import iteritems\n", + "from future.utils import viewitems\n", + "\n", + "for (key, value) in viewitems(heights): # also behaves like a set\n", + " ..." + ], + "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", From 02bee8d303b6febe84360574ef0a1258257785b6 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Mon, 10 Nov 2014 14:48:03 +1100 Subject: [PATCH 020/425] Remove disabled tests for now (mostly test_email) These tests are being picked up on Py2.6 on Travis and causing a build failure --- tests/test_future/disabled/__init__.py | 0 .../disabled/disabled_test_http_cookies.py | 249 - .../disabled/disabled_test_httpservers.py | 718 --- .../disabled_test_urllib2_localnet.py | 599 -- .../disabled/disabled_test_urllib2net.py | 402 -- .../disabled/disabled_test_xmlrpc.py | 1130 ---- .../disabled/disabled_test_xmlrpc_net.py | 66 - .../disabled/test_email/__init__.py | 164 - .../disabled/test_email/__main__.py | 3 - .../disabled/test_email/data/PyBanner048.gif | Bin 954 -> 0 bytes .../disabled/test_email/data/audiotest.au | Bin 28144 -> 0 bytes .../disabled/test_email/data/msg_01.txt | 19 - .../disabled/test_email/data/msg_02.txt | 135 - .../disabled/test_email/data/msg_03.txt | 16 - .../disabled/test_email/data/msg_04.txt | 37 - .../disabled/test_email/data/msg_05.txt | 28 - .../disabled/test_email/data/msg_06.txt | 33 - .../disabled/test_email/data/msg_07.txt | 83 - .../disabled/test_email/data/msg_08.txt | 24 - .../disabled/test_email/data/msg_09.txt | 24 - .../disabled/test_email/data/msg_10.txt | 39 - .../disabled/test_email/data/msg_11.txt | 7 - .../disabled/test_email/data/msg_12.txt | 36 - .../disabled/test_email/data/msg_12a.txt | 38 - .../disabled/test_email/data/msg_13.txt | 94 - .../disabled/test_email/data/msg_14.txt | 23 - .../disabled/test_email/data/msg_15.txt | 52 - .../disabled/test_email/data/msg_16.txt | 123 - .../disabled/test_email/data/msg_17.txt | 12 - .../disabled/test_email/data/msg_18.txt | 6 - .../disabled/test_email/data/msg_19.txt | 43 - .../disabled/test_email/data/msg_20.txt | 22 - .../disabled/test_email/data/msg_21.txt | 20 - .../disabled/test_email/data/msg_22.txt | 46 - .../disabled/test_email/data/msg_23.txt | 8 - .../disabled/test_email/data/msg_24.txt | 10 - .../disabled/test_email/data/msg_25.txt | 117 - .../disabled/test_email/data/msg_26.txt | 46 - .../disabled/test_email/data/msg_27.txt | 15 - .../disabled/test_email/data/msg_28.txt | 25 - .../disabled/test_email/data/msg_29.txt | 22 - .../disabled/test_email/data/msg_30.txt | 23 - .../disabled/test_email/data/msg_31.txt | 15 - .../disabled/test_email/data/msg_32.txt | 14 - .../disabled/test_email/data/msg_33.txt | 29 - .../disabled/test_email/data/msg_34.txt | 19 - .../disabled/test_email/data/msg_35.txt | 4 - .../disabled/test_email/data/msg_36.txt | 40 - .../disabled/test_email/data/msg_37.txt | 22 - .../disabled/test_email/data/msg_38.txt | 101 - .../disabled/test_email/data/msg_39.txt | 83 - .../disabled/test_email/data/msg_40.txt | 10 - .../disabled/test_email/data/msg_41.txt | 8 - .../disabled/test_email/data/msg_42.txt | 20 - .../disabled/test_email/data/msg_43.txt | 217 - .../disabled/test_email/data/msg_44.txt | 33 - .../disabled/test_email/data/msg_45.txt | 33 - .../disabled/test_email/data/msg_46.txt | 23 - .../disabled_test__encoded_words.py | 190 - .../disabled_test__header_value_parser.py | 2556 --------- .../test_email/disabled_test_asian_codecs.py | 82 - .../disabled_test_defect_handling.py | 320 -- .../test_email/disabled_test_email.py | 5051 ----------------- .../test_email/disabled_test_generator.py | 202 - .../disabled_test_headerregistry.py | 1521 ----- .../test_email/disabled_test_inversion.py | 48 - .../test_email/disabled_test_message.py | 22 - .../test_email/disabled_test_parser.py | 41 - .../test_email/disabled_test_pickleable.py | 79 - .../test_email/disabled_test_policy.py | 329 -- .../test_email/disabled_test_utils.py | 142 - 71 files changed, 15811 deletions(-) delete mode 100644 tests/test_future/disabled/__init__.py delete mode 100644 tests/test_future/disabled/disabled_test_http_cookies.py delete mode 100644 tests/test_future/disabled/disabled_test_httpservers.py delete mode 100644 tests/test_future/disabled/disabled_test_urllib2_localnet.py delete mode 100644 tests/test_future/disabled/disabled_test_urllib2net.py delete mode 100644 tests/test_future/disabled/disabled_test_xmlrpc.py delete mode 100644 tests/test_future/disabled/disabled_test_xmlrpc_net.py delete mode 100644 tests/test_future/disabled/test_email/__init__.py delete mode 100644 tests/test_future/disabled/test_email/__main__.py delete mode 100644 tests/test_future/disabled/test_email/data/PyBanner048.gif delete mode 100644 tests/test_future/disabled/test_email/data/audiotest.au delete mode 100644 tests/test_future/disabled/test_email/data/msg_01.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_02.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_03.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_04.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_05.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_06.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_07.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_08.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_09.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_10.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_11.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_12.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_12a.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_13.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_14.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_15.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_16.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_17.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_18.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_19.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_20.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_21.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_22.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_23.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_24.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_25.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_26.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_27.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_28.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_29.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_30.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_31.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_32.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_33.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_34.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_35.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_36.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_37.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_38.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_39.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_40.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_41.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_42.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_43.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_44.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_45.txt delete mode 100644 tests/test_future/disabled/test_email/data/msg_46.txt delete mode 100644 tests/test_future/disabled/test_email/disabled_test__encoded_words.py delete mode 100644 tests/test_future/disabled/test_email/disabled_test__header_value_parser.py delete mode 100644 tests/test_future/disabled/test_email/disabled_test_asian_codecs.py delete mode 100644 tests/test_future/disabled/test_email/disabled_test_defect_handling.py delete mode 100644 tests/test_future/disabled/test_email/disabled_test_email.py delete mode 100644 tests/test_future/disabled/test_email/disabled_test_generator.py delete mode 100644 tests/test_future/disabled/test_email/disabled_test_headerregistry.py delete mode 100644 tests/test_future/disabled/test_email/disabled_test_inversion.py delete mode 100644 tests/test_future/disabled/test_email/disabled_test_message.py delete mode 100644 tests/test_future/disabled/test_email/disabled_test_parser.py delete mode 100644 tests/test_future/disabled/test_email/disabled_test_pickleable.py delete mode 100644 tests/test_future/disabled/test_email/disabled_test_policy.py delete mode 100644 tests/test_future/disabled/test_email/disabled_test_utils.py 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 1a5c87f647fbf33e5b46103119c9fd42afbe9e5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 954 zcmZ?wbh9u|oW`KcaD;*3|NsAAfBbp<@yCPLU#~s+c;@cAL)TvKIRAX@$tQ~sJ(#)s zZvU2Bt!uAUEx%l_;C$N5GYljG9gupET@0-MA1L>wWX{W2wJzuNz5<=|CAs%2*1fNJ z|Np>(0uLRj{u4`zPUg(xU~Mb*(v@XkI=brNiYoco`%D2DvP|tC#8;NFu&`fKe77qr zlBuDtA!1D-SDhvwJ5wruaus8_I%gGImKHO^L=B#*8C}ery!HM;nrsXl3#*nWaZg^R z5nio3d1~*>RWn)V&Ql66(O_X<(dc8?t<1~DxLBQa>qgb8Gpef?n|RY_E~w8-(OTQ2 z(z#!wbGypk&F7Vl-_<}(vdYkSoBs$OmNTCiw3qwv!kKdqLh z%W$z)u`F1y?1cW0i zrga=;2CU7=Qr-d4nM{n!OgOm&0%ok#R5N)cwIixxD>TOHP&mQ zrv`_R!GjYE?idKxgg;q%WmQXa5%apY%cu(L z@w9n#%IA4EW5cdP);el8gun7|PupwVz&L+yfpnevg-6|t>UdmO^c7C6K()XX?E=|se$9uE<*?e4CIDNmgA zzs{WCVauO0^C5@Ms|u$CZHo;z6tz^8sMTiERh#ai)xO4Cy?RFYG$BLPKUHlWUjDMwcS%4#~szhHJl; zs)t%zEq^Vr?OQ?gMophdx_ZtNZXTX_ovA=?m*a&U%@H#vm22;@IO@GNDxE@x;&-jl*{_$7s9S@K@GDBb0=C4eFXg#fN`cmj|4q$2g;^|)Ow(udpSb~#CL zpfIFlU!}MI{*k6>hM{T7J~drDcfGw^={`(%^NRBar&UXOrN!*>njKx7RlLiXtu{)1 z)A`o8d8r_UruQl(Pp+Zylq`9J$gx2R8+icYmr=j->schD`!-8 zn`5P=Qog@se~8j4Z;wx0yE#2EXSeCn3$BFdO=S{;vXb1b8#nFUyK_{-7{{h&Ce(V{ zXl-|Sp{!M&G3(V6<2P?!zonSf+TC1?_xa5hhO!3R+sbmbZG0`SXj8SyPLGdI+@78_ zoNGH-lDn^JU*ob(HFfLiwOdzjswsZ{GM~{MPtwgOPe?sJG|zg;Bv1e~@h6vwQo7{M{Q1JWbUz zw~cdSQ)-oAxv8dNPf)%6~1=zi4Gw7;fc=hniUjfJ72 zGKGFbd3|1cTjh@|)Rh;P*VKm`ftg9=)U_GaZ3XXfbT_rP)a-BWsFk&r=jQGy+`g~2 zsL*@zu2o^0S1BwGTe!LHa7DxMKz~|kw$9w56w|f^LsZt+Q&HX}uWjtEt*I%Omlc+l z7gTo2TB51A-AdW5{$y;mz2{O-$DP1)0bFC$({{=g;}VN2=?jT;Vj&!Txcbh)_C{Gv zb)~FU-Za?%Wm1d=1CdB9nU17IzsF76Z8nR=YO~oH#)-|t`P?4P%ZHN7r-u%;H`d8y zGI@P-*U__2GVxeM42MK9$a5a>xSgWx;At1@_4zp;>bcxbm&eNn#q{G7gS|bS9i3f0 zhX+SaoO|)?+2cE_OR-RZ^Ew%dLM?~a#|gn;C>UV9ZkNO1ba*&_AR>y1WHOaXW|r?? zdUvGn(1C8WFuJ}N5AxoZ+sff`y1&}w*yh;VJkR-g*2A*+#QB5K)atq6fg=YG^&S~H zeQ`Ar4q*@b0|It4*~nfmTFXG8#y~E4t z_@j$=!{Jag5n_E1CN6AYhPEk{MlO-0Ep#XyciU;75DIu4-auI3d|o^4Sv=Kuw7;jL ztz+=nQYIn#(ItyNkIU(xDGHRf*(f3n7$>0^Ow!ioel7X~_-_mk4p)Yf|iHOa5FBi)9IpU8)#*$gH0_wnVGcR;xj!(%Vx5&!P)GEyF3+!8j}m#oX-d z%=Gl^yiTpu+G&@t*il)OyKD0sKi#P_of=V&-JG?^s}H+nyUK+5IfZh5?&h4ktu1p< zUQv0(ZK6_MgVvVHxD?Z~s@a?4GX{%Z$1L>p-yfzl8>HDl@OP+q%bNVEj0 z<(j;JT5Dj?l=}G*f9S5f`pB}&_gJ7UOk4n$daTM@*Cu8rZz-JM9jp6}$hls~%1R1% zZrZeISDdpk3XCXpX05Zlbl)>SZ86TxX^f29cebxl zCsh`k*{Q^)V5__=XUAJRH*YRzWo=ROtj0S!yxP}MdxkdYbQqPM;hET>=B5J! z?H%ukfz;}<$U1Clg=%hUMxoZ~H3kaBy*T<}y|JQv-=5vO-rBJ}@A<$1g95^v7GiyE zCzd!1Wl*c2G@OCe13iP?9S6_Gc(310)2!cXR!!ZWxqW?lUZYZJ^j3|==yZEMnTGm? z+StEBY3>Q zppT{bfavtFBI|Sc-8PHP2$iT)Y1C@9L8mogOW7$wOb0`OfKzEWi_P14psxDu;|-l> zx)1kt99UfMxbQed`=42~P67LmjwfwspQf!A)^4yl?FNh1XjUn8hIy4vr8BDyR*S*t zp*#+2FzV;++=*}~Ev~Jf4F%g8MlLSJd&B;?_)f*nb~xmN6KO;wpI52{;>)~ULcwu9!Lc0Oo7 zB|h~~?T48Z6Bu+*mbl&Kqx#HN{yazdnMD_sIiGepUa+EPA>(%Y`%c`qos{)6H8o#$ zH}=TgZTZ$7M}xUbJl?+68uocy-ZkDHNE&U2%}=e?#gl@5K@9p2I0xRf9S(K`AJAMs zZSf4-s0SAV?jwtS>dCvbH{mfyd56stTJZ#fsYORPn(_02@Cp?^e$NqFdfVlHn||uO z=WbxumhK(oI|e(Ln(9ogT%D;+(bh%Z2;+H&cYBtC0gKPUJB&$#NPFBtzpvl1km+Za zcxHeJc)d?N%;Ks({N$P0d&J;L@beFRX+Oh-PSGn#(UVyAJ6PW8xc|&aMV+?J7yOWG zWv$1ySmF274YfMy*7dto>!?{(dC-_z=y9$aUA{Jpc`mIWh;GBXh?Qn+{>`~Dc=k3Wgrz5n?_}Du% ze>dO_``A#>=YGydLdy$*w9oIjM+KeF7@zl|*R{g4?26OJJf~St5IR%zIE8@AB|;VX zVA6UR=qTFBQ7&I1BphCN+|)5Buj!Q4bdGc-+>Aw|o>R=t%qf*>gV_${G0<~bR#Ub& zcW18b^&DH{Yq*0Oy!#{FI;MQJ#T8`?g>Tt+-K^!E7xadR90hmacAY$w>E7^?D*lNcAN9J za}6I}(_Fo2S5bQ!+`D$zYbt{ZyYj}|^;^@5DZR+7S5(z)+`Mzk$yaxc?~Z9c*C{?S zU%g5xmA<`Snl}#Gib8IE@TST#KB>3Nx_xYWiL7Yn_8MVhp04XKrPAximF7w1yxne3 z*-jj@v9Q(bCLIh2igL1kRu~>UC~NJjDQzC;TK5P1wt0(HYcd(MW~+&^7$~F3s59yf zI)l+j(N6JvYjtg1Nl{f*-Rb+!d|IPHsaDO+s0_8Ex zs0<2)T4myCbNal?WOwS(034Wkla*RcMq0`%$_uMydrHo@IGag7GpSIk=Z&^__nC!2 zU^J+PUotm)drEDyJEKi|3v#z_ej{%&r^sZ|T)8oGZG2+F*1>fZ7ZsOz*?{Wi__ZrH z#;;GRZT<4X?VI2H@elW&-6^-IuYUCT`1sYUQ%gmKd3m`xTWY3eC%?FI^^=cp%+1r! zdJ8tAy!N9v)Gh536W1<(aCvf8L;IVzy#8i|yrIQ4@%hIee?B&TbH>(@x4Q`ZzInr& zZ+56dQ|hUkpM5@~eWyoWxo5|Q?NzI`x#_9TKK}ic>ng7`QC(P6SSc&tvupdC1uCCf zr=Fkq{MKypV3mAOmb-ILgX#8-sT-HCT)r}C<&y``t)6MFt*)pneCuZ$j`hq>O7n&3Go*jD&dU1zNM~8ub9pdpr)Y!H0vFUl*9$H>Jd+u~JzTRHBd(-P1imMqdl;q^xE)~Sixx27#~DkGDw%*!pVd|=Y+RI@WT z$8Jo{*)prisNLjEo^Gjal@;Y|+K`hUS4>Py&)P9(t#!3>Pf<}_$fi-L=asY5lhZ1z zGZglF9jt$~r?W*~QM`LgPGPNGJxjbs158@_N$37b`Kf>fTrxkeRm~}3A$Z`3FgDuh zj$K+k(J3#>&nv4sPZ@P;>_)TQY;}m~zRvS2VY}IE&{-^cgI=xHnXFFE>mUx1-OYy< zAD=!TtEq1o47u$#J7l91*0aS*xm}#o4vX7HV?*h68qK^0wykMiqtok6HYS+5ck*a& zldPflOTULECN1N0yE$Ru`N-(w49~hKy9H*m5n3Ilv(GR1;mknhVskqAVCLlTaEH9M zR(|BcQu6WffrH&6Yj;AyNF*Lfh2m*JNb>NOU>U=K@W7j5Bay_t=Wm}nd-iBwPe*&b zyrH43v$y9!cY9~=p@A27AEb%HFJ{)(9zA}PT3TFtuo`3?PCgI}1RO3nF8+n(zGFR| z?S~F^9`1&}*4p0F+}YjHb9C^~(1E_=1IHgNEiOE~8w&9sa;TIn)Us#ER zIJZ0C2Yewh8V8f$Lm8J{$@8DGb}g!Xkxf z>WAaVkZI7A#cGC(v@jm5sg?LJ{vaO?$5&S`jdZrk%lB2+b&FVV|Dq@aoW#a-@o<-E zJL9B19=wAS3{c5qvlz5mozYA&aO6N6mp7DNUVd?IsIy64y{~9r-P?@GU~$05a?%#N zjdFW1c$0H`W&>y0RC6&%@~Q*g!>*Lvby&4%VTdJY?PJc=hk%58l}#} zg+!LNo85MwhcTJKu^?msGxD%pkaatqG-Ig1V=5AHvB@GzZBFD_u)#6;rq`@L=t!R=_L%fYx{af(anXBQUN z`|qrEx3%rxU*FjMEdAi_og^P%Y1nAN)nI59{F+RZq z7tdpXA~gXcpsWV?`jm^~oT0@;Uvq6~QSOdS8wzOE*wq_z)cxAQXDv-NrA7OW8)t6a zoHYo;-NWaP9qevzXQrXjrVZY(NE^UWfMpo(soLV4Z5w{}(_G`N-+y}fw!2}EtgfWI zBrmVTIdSFl%eTzUdkXi-TDtoWUbN3%{o?YtT5M^3msZaz;SidcRNcP3?Hhjj!+hPf z4}SZ*N#CB`MRof+TS_V`lw-gD_|q{I(DrHVAg3 zL8l}>qJ6lzvGKwwk%HH(&@Pni-t@*BWk=`6u3Vim)Ys-0msFM2HMRH+b5j#D^Ul*P zvbwg1DL-X4Y361XTIZd<6EUC52}mO{C@aq2vSDjgVsh;AO~p`g!LH)!=GLLE)|5>( zJFT*;w96X0-vxB!G81damL58GR^-?~>QKF`V&CqqyQ-IFZ(O}K8?UY?tdt+=>>cP> zXS7P?yj6U5@DdTiF3UVRowoz9^SFJ{Q}XK4;-cL<_H|iq-?%ZWIMLE9Z#tCh=^J_I zH_c-qe9N(wc+hFK8ccel4#i+~aIxOHnteq@J98ROsHUzf=Ut7>T|+&ImA?L^D5IY@ zSZ!XP%VGuO$-pzV&_2$=c>E`NJIg}A1J@ZFSEn)> zq4pR$$S!($hGo}|4|F#*RTh;rcCmJghr%T|`_D6d(TKqNXvS#J7}S7D>=BBy`W*r` zUm_4nI=aQWW7Vw->pu8}9#@d1_s7gO$y;_54plFsLH^7Zp4?O15|_#n(tTww8O>R)sNa&X@RpW*q<!1BPB>hq2jx>d1@5 zK*ke$bk_5N>2}_;#g5qDHgtL#yq$b#g=x1nYJ;w>bCLb_m@RQ$xMQ;oKB>2*=zFc= zJE3PB)8VS|xHO42ORc%j_f)Qu8D+5*2RlT^hv+u@QY7MS&2uLWGN#nh@1t6&c28i`)ZrC6jTQ5)PO4?TdVV=$ zF4tDutre!h1=}h$><$k)yMxg%{7}EoPY3-=t`xuUSV;OpA(2lyPFhBsX}`VGx++*Y zDIuw^H>Y2yYmA;YZNI}V*L2-6w^FCQp0tA*b~EQ3t|Rs(fxhE+pL2UsLTEY2C(gL; z1;wDa>S0$n;f^nyxhEXwh2f0%qUeA8G!tKr#8zH}yvewDYKaT3XTrdfc|PvvV73Gz zJdLP}5C}yQ0T>M7(vN%CZh9IR`P5|@y2|mgKhIG;n z{1AARf_jA#b~wB)h9JcO9)CVQC_sWI!ZBDVZiHv-z#JtA)QAEY)DA5j2(P|-q^qf} zzNx*fZ)hdN5yTa{$!xX~wT@P8_#==!MX+?M*#dh2n%cz+;&Na8zKVL8{BY;G-wxx` zjHm1j>>oDZq@iko=)#UfJ2zu|!N3n+qB% zlnVO;94tn|ydEQ9I6id$@nR+&3%Mwp2@swd#;8_nu$U>Yc&@dwC?}WfTY2jm?z<>P zaM94DFy~t>U&yM2d_9UC5eSM)k;MkA!LMyrJhV6l0EiQ&eYlHBdPYYU6& z>*NoB(>l%e0A=?2T{aJ5FJ8*xN~D4-$3{ll+KwEDUUfNWK)*1Ebs(YD>E-Wt$jb|I zwr|e6ThkOgIYOIl!F%lTBbpLiW}VffbGj|AC&v%BHXc3O{lsFl>GT%8(T2%Ef@$@P z)s}ubA}`;Qm-E)v&4pC*uF<1Wu>A8LuVe78!C;v)@OqQzOAM`MhI*epkJyY_gITAV zx4=MCD>ZN;jTSoj^xd|avf|JoWHw3*4f?t+!~kvP~u*(sM>Sbox7S5sP;pSyEM zVOeoiW^qxcbs1GgJ9ICx2Ebdy?_mAGhwmIec3|}0$O9i`HV`CcdRj3%tyC-2W~0d` z1nv*@H`i1a7v|^e%q`ec-Z&Jn1CdnBDd0@O=b&6Lb7<@qHy=r@K3iM9vlR8yMy(2t z7Rt=*oMK*Qu-F-Y=HByTZS^(fdkgb+?=C1Tl^TrBH$owFZPsnBdYKZwr}3KXID`| z7_k_2D!W5%9%ZS;0KaUbmnof#x5U;~ntMhY>(6t%%SgyQs|3)i)*B7L9vJVP7o!Ji z%PR_Vc5dIgBQGbv=h=YGWPrNUS{GcNkw@Z#R8(*{os`wf$1_7m4s`bpx;%`Ec$RZB z3YB8sq_HrxSByLu>1$|^?JddA*|j?_r?j>+?KJ_PR;hF*3+)bu66sWk6=*vEHE&3K zboThbdYt#bBvz5Agi@)5ZEW|j!qVOM27B5;?vlcS{Jesq;@Z|@QO0V5H4op?Y{&iy z#lm6;;VVImFRVO%@6z+TVcyMH4LTw#fW~RGMhKPA%Eh6cwkBC+X>nmeLE)Z#m5oR4 zyDcUS`T}^a(OckL_=6!aoX9M%K0kZ@)QR=Qgy5#F1}%^ig+c+rqXpFB7Sd0Lx?ANn z6^L;Z?b%yi+j108vspKP+ry*ho;^wjITzp~ES)mjlEw&L zfw^bWkk4 zeD~Rlb0^*zIo5Z$r>BdQPB{CA`i>nx|7c|~6%`RmZ#baUw(M*_*HWS1j1ggQPw?!cAMX}pS`wkAJLw7qmh7ZfD4Xw zA31rrqqD81TGm!sR8-TkoH%=gQ_rci2AvwFcPJ{be0rew%ODquuY~{xxH-2IAzFCG zU_P)Pcu#Mz*?lfie0+N3!cc#2XUqQm2t1YVE30Zaa4s2QteSZhL4UC6*$6mzb?xq* zg#qNG?Bl{^f;{?j{*_2bi!@ z-hNzk0wpDlX#fj&0*S@7r;i@2EyKWdgLD?OgL|s^d7W0TGh4ua$d>W4elfLn0T^*d zV{JuQX?aaU*NF`8vYW_ug#5R={Gr6k(|1pgoOrf~2%g6Y_nF2T5tT-)CbA7}G&T+p zpI|Kc;KV>T=2x|^cu#R@)&9;?2(i=H-Pl1UJA>U7OC&Q(cOIj>uRdXOUo*2TaKJhg~$#!>akcrKp>cq zY`_J=>DASxL`0C_L9>x8CECI-+A_jM#56cA`s74!dxNZ|3be1Pt#3c{ZYluLYShD3 zLzhruD3Dgi~d%W7*78EES4|1t%+L>hv1YeP~9fjucS=}!TEd~yE%%AGq4u^_S|oP>jkk&9pO zF-YQM1Ca-3hWc7%)fJ_COUtX|t$i<2qTu(DGy|&@M01c-2^5FdAK(LitTALNmemDF z7Q)KmaC5vb5MRkWIN#e`TUk|6UQyf7d15IXgd+ex2SJ^H2%XrC_<;UaFTK;>dt~tJ z`szwL413x`h9%kXL`c&P_yq5bbT(F(AyQCU*)*7doIxShIc$0k?hMou(GwUStap-< zft(E3R@gm$D8*Fz?xi!m9St?5VED?`jMt*oXu$z6(h!{HVvk0L?gX8{jTED*|ud{PQm_=9mWQUz)VliX{o18wKcWv>xlTTb&*|?16d3+}z~NS-rb$ALi1*(~KTC&D6xL z8xu;kZE^X|doLc2cJ}nN9vJR!EYIDtd3$bEK&zgaRnARMPEJf47x(We*emZ1Ld&TY zhyje>m{cKno(Tn4&kl989v(gOE_&R#ZO5)X-5Hx+qnx>QeQfO3ob8+(3t26zYdXdm zk$8cMx2I?3R607*-`3FD(@|Ghn6quurfs{*J5vU;YWn8b*tHuoTJeA!_D=~|sf$-n z-WW%@adTp7&fpLimX{YEpXqM__K~;s%?&wkmHB&8jAriE<*{2+M)6GeVMlXCK~c?7 zZXWc)=*P!z%5#+SZ7+AObctSv9xzTx%F#RX@33J1)}sjFkRRCXc!s;roqwmcVqalF-i{5M3W{vomU|1DiFnvUKpM+qpE-Qzxq~t*R|6+Edwd^4_@>ugyyPqZ|$0z`)*y zj=-+N_5>nqvRI6I?05L24q@R%Uq^j)Wo5-aWU*E5Z*A=yd=%mRKH4lfw~&GmTqNrY zhD6pnA@&Iuz4!K^&X&5`YG|WM2%N6NC)SX`guQ4b0^4AKoW@8W!5zUaJL?RSDxzY0+EF@KXVH8{j4)y8_IvRKbvS3rjs6^3uYBoV{=D zlob@#($$aW^mf&hW_DJkRaj}0FUA^=-P1cnqrzjD)mzkR#^_DmKVM%5M>e;pcx!=t zcj4MFF3v2V;~sXfs)@Y7IhHCYlc1IoJZYH9sf6Xj5U$j-t`l zy=F^FtuU$Q^y+!5RWAf>!IWrA1Q8apAuc*Uk7eOAB*zOSbN=*;&xjQNo(; zn3Tr(Ias4+o1XF8gr_34c){RBx(98ZH5xET8-4#|i>w5hH)T7v*X+(|S>D^P;JgZ@ zQ85P})W+DMBo~Hau2TuK)2C6|byI4cQf)We&h;LZmlWsiEH2$rP+L^?IMJJ4O~=HY4;F2|inCug{GL8FCpI zqAI}Q@Zcb8Z8Sh1mJcQtpZB++^4^l-vWgm6knWMhGT#s1MdVV4-^qEgfpuv z&yEcZ_qJfGRMkQc51xFOh;j~i0v6hX3^Ak%dYuHC(ZfE2=>SH|8bJXhV8#|wq4et0 z;{(0K(yOi6-*M>J@uf_N#TF$YEktlg-W=R(Eux|Do3fK}u)$y`CZ=I+#9oY^Jc<}u zO+{s8Rek5tbBnPsagnk%8?rg!l$iCzGE~E+MWap+G92{8+ICP5XiHX|~q(~^Ed<%z~ko4MixW8LoUIgf9SAJp5(HLtp z8i3f(%qWe{^pTF9Bc~Tb0D$wjw}HycsjcDm z>hhBE<`YqmUNt*$&D-AEUDFmqEyeB0iQ5x1YBS$cDwGNuvHS7~fqpKLEEzhld$&D(br)OK<4W zw8)HO@+Gjh>gu*7uN6W}f&9+f(@Kps z-P2W9v9DF(?~o(`~q_(B*KI&65a*Nb4P3t+a-3Cj>xrWdHR&+7|BqKWjv+QS#8hlh_3 z9X&QUG;;d+i-*gZ7~B;f>~0s%6d(f@|B!MF&j)TfQhnu{K?RaJ%}Na-><$m<` zNm%Vs;V9AjwG+G8!^1EF+mNSw{F}fMhU+XfzxRlYx+hm&ifrfJzbpaTV(* z(T~spTqCWAAgv=hlI~+5pa+SO5V9i^L|R^{D~U)T5vfI%1oI#SL92us*#Qx519Ol; zl8#=UipdTe6{Lp=g-9L3YGf*yjMNypi$}<WblPAvDGhW=N(;{-js%1;$BM z8sh>bJ)VGi=DtQ{Ffk#|Eiq%Ax6CL!n&}y}o}oS zwX!CvSE}aKv(svu%M&`&_0UUsD80tS^5OJAQ$zE>+2)SEwyL6CZ*IOdQ+)37Rg2MD zpE;VB+iPg-HB8Ovt+ZZysC&|?w9LC_!Ieo=886_$_)){=%$O#z*8 zq-mX_Ju679zIWlixO7f-n90;sov+HtF-_P{D5eaj-frwZE$?!w%ucI;d31FBiO6wo zi_y)w7;q_aLcA-x(X258O>|oIFx(7w{-Imwj@X0)g1xV+uLp@^)>-E>#jNw* z>d6t$#rIf+S@2q@!L}8@Ye{rzE#8oYO5WiaFT*h=4FWPa%0t=RK?h7LhV#W6@YS&h9$b_sSKb`M^qPox@=LPWq2c}F%TUWoi84?<`KLju`d ze#lN~_v1F|yp+DuewH>PQ3%=nD>Wdsg9=3R;BjfsVi-gRp%q9ih&`gc5OO7Q1>Hl- zNIH!OWfUC9fh2~?fu4i>$?7cpBN7VZ1wAEE7$B{^{1|{5AnlX>qyjO9alDj0n5ICa zg(STsNlYdpjZO?l!clUFiOeJ%i^k*eD1bs7@qq%w{E7O(|FD2_H^FEOCTWBSBXUg$ zBUuf^;DdNN6bnE?2?=~o#^aGhl3->~p`jEW4+Q{Y`p_fl1!BPn^hXM3r$UI+hJyGI z^!lSbqq{zr>}8(sudVBhdsy12qf%*KqN{C%%Up_iz&l>27hoZ%8b+|&54VNIGKWLJ zR9OjH4InLWA@g{&r>n729;6>9EowWDsO!NEr3p+sDGF#7oMK{+6jU7NOQ<_a6HU#9oZt5=H3dgQH# zI)d{mnpG;<{)g;3l02sO7da-g%Rd{4oS zO;wYJcFlZYJgHK$J+*bMhYrY(YBZutsiea`XS%Py^H?Z)CIpPYg~&7@4rBva^*HQf zv4R4bj9*AErN#7x6DLOxHOu8SrTh8&wd)3V%xqtxjgb>B_d*akV@@u~xkDjDG|9pt z8WrFW5y9atmUZFGnG{xm3rP?Yqz?dSM!0I}(US|K$NIY4nxS7hdd@yf;?N3kQ*tQO z>kS~OB8UdV0vov-&pcX<#UoIx2)C1ViSffACjmKs6mkM!m*NB+@&XW9^?3ML>hXzz zL%kg>jRXB1eL^@!Q5wjz4m*;2$R3GH+YW~;{0OPyFc1PO1muQC zhL92&NGQIvdjHH|{{bYIwjAz0$hq8rI%qu-dF-6i>JxDCim}6!bNSJ?NC<%R3Iu~3 zPQac<=^PirT>vUw$Yl+L!r{zfYT@a#Cnt{fHp_e7J#^UT^4d*qr;!R!6z{UJL72Pf zodImjLhy(o7%#9xIV24rcnl#6+>-zVKrIpb_48sPvAnwS=)Ltv=ZD`O>}+oA>_60* z-~)`6^O|r9#U1D9NDSGFw1Y?Phu53T2LfC`%t5HCs8mz}wiqGi=D?&G>AAJBhm?Hnrw< zBG)kFa07gRhky(R3$QW#N;kJf^Th1kV`(+D6m?yE8=7lEr-618Wnpx=3y zHZ*)-Fk0QAB;af-^0b@+Tmu~4V1)!P zB0T#z^0;GYw5g)7c3Sb|*h$eGsaZET__YqFj=SJt_`snKhYeCKGZU~;X z7;rm63oO73_|gu)-w9yai!)=03E;>jFL)f-{)ic-R|A~*Zbw(IY){keY5%j1Qx;#{ zc`Hr%n?h=P*daujdu=`($Hf_{;F(1?_+JPCq-Om%X^W$T2>Jv#7wdMgE;r>Apn+i~ zI4K8*L|F>51qZo;kJj!DzTFGodthQFc%tC7fL$Z%A@1918G-8|l5!(C|eBrwK+9Qo20IruD!k@H4@a4;NM*9Byf^*)u#j zlj@coGTS@OAX}Ghd89MI9yrVKBT1l>zB?l+0p=dd@)4Xfh7s=fyPYgd01p=p0dnyn z`-bIxRx>%w=|Pm-%>{X!n-K10;%mo;4j*VN*{i-`>Tcifnvd4wB#i04+^4ltzMdi4 zw;sf?Pv=O_s^D>lmIW`#W_H?fxCq4eg#8SQL#|+YB%As@NEbv}hmXgxQRF7Mka6mD z3(4i9C+{9>sN8$;=Iu)2m3=%bAjIz36Lg0l#JB|1BZ1wgFNElzQmG`iHPZQk5`8QTa~zP&RuFP zYu1|v&pz$9+d7AB3}YRwdCrN9V}YZ>N6(xVZFFXNWzp+O-huf|wl5N)VvBKf79eg} zA{h*ZNfx<37Iji?zYt+bzNwf9L{iX%!Igo5XT7rG0`JW!hrF;|qvnp+cI))Z4;Dgt z%~M&|3Z-}6#{>fBI~I@!l05dr?F>XRF}H(*8|-0Yt08!n$l>K9nRJ?^+=x1c9E=zH z&j-$9ynZei;XGc4D*(p}fL=5dU0Pkgw3-oMNI>XtZVwxX1-!`T^zu67SO!F7fw4Yh129{uMZ{<%OS1?Rn8VG+X02h$QEDOf@Ygvt0&JOu|_h$X_oU^Eho$8jzJE^jKCSx6-S z2!uuA0H-pGh_j?4;^Oi`9EbRjTo8?>mx-GUOA8hnnhwMA3PmIFLgbx35hi)E$f<5zm%bE&yhBS)@=XzB&$|e&L(mU0hAav}dL%=F(?Bjr@1$8` zhQtoe+T(;kAc{l?tOOw%DFi%#kt|v5#8^g=o_qO*5KPNy^J1qhK;CY?&9@C5_7m5e9w79sc;031@nU{t(j!IkVBr0HZq2*Q)n zV6p%O?n^8x&6KdKG*m(uxFpi#$)L#}nPYZ3(z6mh2<0XBC`%2p2H9mGlP5$6oyf9e z*NDI^;CB>q+5_yHeNXrpe<g-10({k$)DD4_$UUi$4y8p$ zzoSxOJS^dYm$ zoUI})E~z4MF=>nRiz0QMeM@dhO=Sm$N65NMy^^O0D@cXBBW=HA64HkB5jvGT2ORL< znT}K-7g5MP z(u6wm!R^B^$dt&mv0cgZaEDAAWRXanC66>^vR9>HNvx0lB;c2PAoE4R6$upjccuV` zVDG%#HTXxKkcLPmfI>J(;v(tO%W2^~nJk$VxlCU0PU1C**|HU+yV7;(LFrNQIAK@9 zvDqyNz9xK}r>1=oj&MUYz{j3{wLmV6}QLWV(1l$Xoxhri3i zmVtc;y$9`u5lh1+A7P$LGKG+d%nK7E#Q2Ipm?WW^v^HdJWU}N*Tp{ElMS3GG2w4;I z2J0g=Mo5!=Lv)YSq*Q@CLSO|B7TM;=L$IiDK{72!Eh(lzb}q56 zaQo%M*ak332{9!8B)24*gH9yeA*}%*Nl8Qz4Gxw?-W?E@NHm#>K}2Cp_>hPvlYrZx z&xs6+h9l89a@pdr6iL-6tW12Z41Q904tkyBy2WGhL@Xjb8H*laf7w;25GV z30L&%ajkf?!)X`v_yLP-@m9!`pfQ^?4XG5AomF-n&mFkA)AK0NYCS{)C=JW!ij{T z$Qa2-i4_PRNIXk;21}h?T}j-KorOZgHTf^`O!j`3g$Q$G83wOXGhg*VdPOg!V9Y4w zgT$?pj>aZH|D^kD1u`!31bJ4f_EOE7ths;$YQq zm)LmZ4R|aru|ypF1)CA8i=`*)4-^>Hz#m_!PqMH?HB0>y0+W$ux0dveh{NnWF<5D3 z2o=fhAur^=L^kqPqO|n#@+#R^FKGqBNs2^*%E@g~6Bi{y5IW#WcB%0GfA4Py42f){ zQE7!GGT|C&L0UI5)|VJKX+jzc`jkeTU2gKAG~Vn$N$Z#*5k*pCgpDNwf{=l{mF_@7 zzXZ<-MF=^h?y+)&q0kGI65$Hsgk_l{J3Cx?DHR}&Ay~6QSW;4ilgQH&H{od_TF7v- z9F}cHYKu@!nu#QAUfz(JCmrJtKUsQZhxKwy(g?}yB+^<4zmn$hi8wNFMUXoRy=wSGv2p9%7dHY`MSD)o&O9zn z+#0tQ7hTXO_2NKaUgd0%hxIz|fnL@obay`+mKE(Py!x-7|KR(YPqkZf94`9P-{pL7 z_m#1R{O5-GDRthP6}N8s+5$SIN-W58U7x^m=btuYX$G<#jy8>4d^9<-h$G z&39kFHPKOfr(y5y_pklrmH0KK-_cU6xnkY0$#HX9sdP0ScCS=+sBX`*ss8qu4PS+v(r@y5jr0@SR7R!QJ~Wf2jT0>z#_p8;0D%scTC+^4HZ@Kb_mY z?XX5=j>c(~f-9>K)Cy~VXWGT47w>d8H}89^=#&37{)0E-op&ZwMMX2eyIlL`=IZ%t zV}SyhPC++Sv}mth(HHHOnP-%qRkuM;Ey?zq=S-=AlPejIBi&eoOqKTE|7Lb`dGC(O ziCKA_`uZ(HPhn1#Zq8t{vN0zUS_)FgsrDW24UvpAy8}V@hi8EJ`-Eh`|D?OUxu(2D zeO6GOQ0atsP5+LIv8RS*Wi5dt%VpyWc(D?B=XcWL#(GR{~E3&RIabBr%0B4Ur+ zT*~KgWiA5m6Eknu?|(b_4b5Lwv+M?B4fyd)M>p=2@Lwr8^-jXPUl8-MerP2KHU%Zz}tbLh5 zp1;R~3JBHtgC25#BanD>VsK5P1h@Dw& zzMu!kN7hb_^xfx#^GhzuVb>cd^2HuNjK#J79>jf0pG;p@I+CLur;JKpO}CxF7YU~I zD}`@uKK${=K3U^iMe|DEu;t2Sy0{>(V{(kzQ&d^BYxl(~pW2Ejs_>}3v3q)4Awo)i-@TH*>0{L^_3yvWzOq4Kp80+AD?611o3DNl%DZxH^JcRlNlhsC z_9%J^eeSnj-E{fyKK=Hamv&foIp*e}PvJ@e6bbAPqr z+n0ZHZQ8zhqk7C+w0q|+`3L{JN2_8aOjc!$isPyXt4BV3~TL-C*&F}E*>$21oh%Mm8sor2!)MI z>xKAxC;IF6-Bm>wG>=;i8pgz(qK)sgC)zsV7pp2frV|!L_%7m&{E3Ly5p=9oJY6l! z*UvTUrsZAJwzj}}w{`Y@b8>%8M`f;W`h|6_`MqiCiRnTmqdvA4Dl4hZ>lkMa+zb^@ zE6RDl+;x+wV7h9aRc+0?H7SfMb3L;)o#qp(H)a|mvaP!dH{X1}o4vAO^A*{~*$LaV zYg-Ef+Y1ake)h>f1oEf9^P~4wn}2&{+s(08wqD(`b^50Rf2I1fYd`qbKY#Z9Pd8(SGOC>)#sx{2O^=vR{1m$Lj?@`(NYV`|!h8 zs;kpY`pLI`uKa`RliNOg z;}1K2@t5EHqtCy4?XSOH@)zIvuODa{uKc$@{@~BQ_`j@sUj3g1fAE(d{wMCkvMX=? zLEX>)>JR_qi&x+O$FE=c>p%YHZ?4frfAXDQ{P~~$r;lEn_?tf|`uVs1=>04C@mK%k zH-G-^Km7Hd+Wz7j+kWw_Z+xI`)x7ejzxW^j`On_}=C6MFUqAhqum9j*_1m|8oBs3Q zf4H{4;BU6P_75ArKK84kpZxTdpa0GO`NPj&`}?ck`^S+#togTVZ+z$bfBXOamp^=e z%Ll*t&WG>+@%Mi7;im6?|D!*D^*>&Fdi7skx&G_#eD{lw>%RA$&;I+i|9iaU_CIZ^ z`0%ywU3-7$x8D4>zx>G`F$aG8yDeWBzPICxk8{8A)4%)qH~whc`N3yzUb*qgYnMOD z{l*)A{fn=E*WU6E>MbAb`hnx}#H-)D_5V8lKv8)8pMEm;+wX1o{P(6;{@dsO{k#A9 zH@hzX<4-y!e!TIsk2ila_7~c(Pi(#Q%WYeXZ*<<6PJC~}FMjp)O3l{4yYlMc-0ibh zRd0U#*Z<=WzWcA=`nO;HV6*XO&d+Cl`tASu_y6JQCx7y{?|)CR;rkzcQu3pp{?$MI z$Cgii`1c?D{}r8gRFn7q$B(vE5dl$V$Y2BrBMA_~2nhj1k!@YLscp5xs%`J}*4uG^ zR9giCgdNC)FteFq1PG&yKoVvG*??>my7cz8wf%khKj+DFo}8TXd_V8k`}J{6PWNhJ zZ@~PirM6@dqO8)3L_~Hs&iiBY8>|~e@CtS?mV_DPB}4B$BD_Hp!3uZ|?IVyD2Krd# z_coAXR~f|zTJ7gSZ*uX?W*roAVKgQ@O5#DF&p4!ihqI!6?o-h2+}!6nzzA_>WZSqD zA(Yez7%vgQP{6LmDD(?DhLks-`hJB!YA{P zj%8<#bJ!5%-z3?K6WLK4@2^fk7dK{%SYX~ zjgB6VSoYXRE6AGb%dOJ)O>}MA{ampOMXecxz$|kp9KbLA(RpbJ#2neKy*yd68jpo@4^WE%a#aOya{;SlTb0f$EpKG|4nn#hsWU|sjf%Jw@LCI@F#!F8;uJyP}P zpzZfwggmrOrO2hK*1G=DLHozf*j$Wth0vM9KV0+ZE!)SC@Z11vn%IfcU@iI9>hL23 zlA^HYvmrGsJEtuAzLK(|C8dWk1(1LWPuDE(eNWn43t3iM#ZI@%?4eoC`@h4v|C=D`yb(sKdpaLP<(W(?5InY$Nulg$8(tnZx*@SC_irhZRGx^y~inx1LMWc zXUm+NzxCUfJ>i&F@s^3|{Ac+wr*DJyM6-+HjI!bx9g z*YNE@SPmjBpLmGHhgXZOiobu8knh5$I36j!jbNO$&Mw_gFL$O%&TI?F~4r#&Kme2<&=jU9Y8Df@+P52d(fP6{Q33(WIZSK zdPuM|C9t+8_@eMGelRJf?dBqXnj~#j_sHU6^>OO%cw=|sh5i*qyDIk5Ez(fe=L3B= zb_cb??dr3pi>Kt14`rk23-T0m>%t}VnfQ^#kNPM6X&Sq#k*#P|XZ}6-&u0UhYa`0{ zhx*cW%8co;Z)RrhtABX$I}iygweJrq-%-z8Q_1gq)G?Mav-w`1@`Jg}_Y|Xd%re!5 zg>{pB_WAsuzlsF> zCyeI5ubU^HKNugK-M;iM)%2YCg9j$Hc^v?*h6_JU%ulc0T%6WFFpN$DIK(iyHM@Or zd2;psf=a8&%m7rA_V>({`M>@#HEuBM-kJd(u~#oARbReVPHyP`{281}*B;!RR6aC~ zDS<3aH=)vOfAwNQdv9uVX8G4UGYaj-Pp|(^WqDUUqThM>HE4COe*N25n(fyrwSM>h znqf}4``Wx|`Rln-Y1sY?1nZMqAUM`vHz_Pzo3C}cErV%yb4K^^?3j6N<7e<{ux>D| zOf7soH*EfKbyYiO{?RZ866Q;=#KSAWYU;?j?|NYD~ZQfdbzOrft zp16gjUw4)?hP@%;mf0|4o;Lu)kVRwMer5)K@I~FMY0a=|0qlYaAd{9w&D_e0c4-5M z)b+*<%gXZwjl~2sc3Qx?8_fE}wfT7rv(oUe7|{b;sGLb(BN6I==DpWh7V{BkRO1TYBOL~0UKa6uUZV|^%vXA zpjZV)lRC@ZMV^5h(Hr-mb|cW>X%`mPpP9h4Yfm8oE(GXUz=iBJ4U8b|Uj+QpJm@h4 zgiWUhsN<3ajDxfw>;^)gy^8Mg&MF{a08FKtUYOPbfi-xLz|^Gy_|Sq{4Wt8LJh^N! zZoCHc%A`sIWH`XI_{WPSaKjrPy=zvF0g;Px<-N=gE?>y}@PSg=+%=OF`@CB^c9M`Z zKmFfR^Yx8)A_-?(M1B8`!mFAF5<&vI%BpAgf>EI*%T7s?1FHxh@L{x0n_%e7E{K@Adq>g)1+h)qR}OEdeTQ01Inwuo%*iIRtT z^ww~y-68cnqF5Atk`N-~VmyD0M3z2*yurwWAsc$@IvvjED;)Z(5@MpUeMX4rd(ENV zN^d1IF0C6GM{D(5km0pGAyGr>T8=NWL2{;B9kr=?ySF(8d-?>P%+CM9=5%FL2J?$R zWz;K1r~3^lmZbKI%A+2)uc-@Hi<)|oh^lger@W6qii;x6mr~tqgdA)uYdac?Y%EPc z3=g6cwt_;qd7>j8OdXC}_X*K6w4%x#CrOMv>5u8A=0mNdl~@x)jt@pQ7aLq(@uMQ5 zk%Z!0)ZwA3fC09wp9J}$SOPoU8J(Ko6UL@WkNR*%g80=soG+Ql^$Bd7h=?I!-=k53 z?9#a>`q^WIXn%P{A0keckbKJLa$zytLB>BR=l8CoeR=d$uk@*FlM(Kp6&0iGZ*vU> zep@mc)5QG2J97N$wNw6~jQnX^KejAGEWJVSdq(H^VAQD>=Fq-1c|1FKP3woE_8W12 zBqke;0*fIg!jaASOb6#i-sNV=z2rb#H7nZx%FVRdFdrF}265=9Q?<0DCq&`|%+Gz3 zKZ&-SMk%N?R|jcbe{0YAcfx`jS#P7#EGJS3{=)p4<4}>LMRXw+OeJcn@&1=z>aTc* zmKS0jQyPK1IBDWy_)z^2Cg|kFSW%J0-m|WzQHwsK2)|G*_Cdx*kjwJ|tXCK($kk#3 z7Tr~;fZrCRMeSZ!N&XIOKVSj-1hz06N#_b6=AR+#19ABej`Oi_wv)d1H$Mz zXAWV@`;%&a2#G^am5ba0CBL4SHi($tz=+(24Dz@<342YlC1jo>v%lOaxZlGA2WWn#7BKUgUz8q+;UOkaETuOQDh3dh!sB?%MN z_2J;*40cH1OiCnB=(Nd)cF-p{RYCs!eJSyVp(La)Jtx+RQ1R;Rs&-;1^5^n8WbjDL z*&cEdx0>J{S6`bMJ~k2=HXwNJ| z7%prI3&AL9HE^%)k++wK#9I0UEU3OQ<9dfKIDA+j_xHw(6cjie7ME}D@V^O^_uVTdgsrTOUd?w6+ouK4S$1u17?b$%guyA;e%|`3m$**NLIt;1rQas0mE$7*R|0 zVdM`N#?m}}igVnpx@n1)s^p+^fj#ANw`f_!r5B-WYOrl;MZ=AgAN8ep&#}g#UPMbC z#pWQr5EaE}_w@;9mt)+zg#i)6cxh4XaVUo~d)YXi>K9)n^mIQFUGr4>rbj^&vXL9+ zk5&-R#2#S=+*6uQbvi65OOC3l4}gWSY0=P3aa72xR?O&j^W#*Xj+H4Q zur~!gKJe2el#oN{GI3~A?G;a?q*MY8l&FHpYAJ^k-~|4s=s#P|VIq5Y@h~KxQf7OE zTaminFc5+Y;gpjhiA@d@9Aey!MD7^khXX z5~a>ep12@qi@ki>IKl`*hXNbb(;4aR%X}g};8w;D6V{qgh+CyBFOPxoxTOy>`P^&B zXl@-oLez`J>pF}+i0<4%+aon}qPnWn&pn(~9uH0LA>Z64H_HjuPY@K^r7&w?ifK=Fb>yG<01Uy zQ<+~SjtFI2WP+$Jn%KRCKV?>3M|-zYD(&p+sxvzqUU;EaSm01{MnN?7CG@iU;FxN5 zJa(i%E+nb7*9Z1TK|aj-C1Y5_8wBf(nrSU?Z1>gp@js^Og`qxbmcTz+G965q=}z?Y zW8`#OA?V`MLjLIh|99z3w7W%~5T7aIa06V2Y2tu$P0P6OQQ;++dq-Zrqg8v^Pw7HQ zx`$U?(V!c))0C1jE9p?)z$CMI-U<9P>|N7N0t&^+79Mh|6vZ`&!*G}=CIjs`r%zPg zxh;`w5V1oH8B*IE88)l_2iDU`iSuD%1+OO_NzE=YyrjoU`?f}c^raba$0YqU|lQ$$=@%nzJ3CYmo-X%CkCGt zcg0{sOx&6bZ+VBmoPHacAsD&078oF+N#Gb^oR~#$yV@|fdlTa&E@C=B=uF=eO~h!) zZ0g9ngmmJKN;=eu%fN&R6r}#13t$nI?s9DM0K3t{Q`qoux8slSq~0b)T!t<=`Qxj7 zb)8s3S8rx~;;)~b`DOCu9m7<2hpf9xb$L-ae=j3*<&P_CO3j^X+TO7Xe<=)$qeH-f zV*%DRP=MRiPFj?*@yz+bxrZ9%?w6Vg_3ZkZa%k?}?=xd_cg=&tGwS)R$&snK$(7H+ ziD_yZEPc51k9xn3-PLG3vH<&7fSQQ)@QYmtU>SX;wDDp=%kW3Z`cZ^P7Kf zPLIvJGS6;)u&$n5|Iao+?bS1SaA+`_K$mD;KRIjA85fOU1`q0ZQ)=T&^W?(zGu626 ze@5jbNR)J--f9MXy%8*!0CDOZh{t9ZcT8#};HV8|5PeM<|M!zl4U!`**mwlEb<^ga Xh!X_U;M@RG_pPOQ?cxro7n}YcFvyK} 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" ', - 'foo@example.com, "Fred A. Bar" ', - 'foo@example.com, "Fred A. Bar" ', - [], - '') - self.assertEqual(address_list.token_type, 'address-list') - self.assertEqual(len(address_list.mailboxes), 2) - 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].local_part, 'foo') - self.assertEqual(address_list.mailboxes[1].display_name, "Fred A. Bar") - - def test_get_address_list_mailboxes_complex(self): - address_list = self._test_get_x(parser.get_address_list, - ('"Roy A. Bear" , ' - '(ping) Foo ,' - 'Nobody Is. Special '), - ('"Roy A. Bear" , ' - '(ping) Foo ,' - 'Nobody Is. Special '), - ('"Roy A. Bear" , ' - 'Foo ,' - '"Nobody Is. Special" '), - [errors.ObsoleteHeaderDefect, # period in Is. - errors.ObsoleteHeaderDefect], # cfws in domain - '') - self.assertEqual(address_list.token_type, 'address-list') - self.assertEqual(len(address_list.mailboxes), 3) - 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.mailboxes[0].token_type, 'mailbox') - self.assertEqual(address_list.addresses[0].token_type, 'address') - self.assertEqual(address_list.mailboxes[1].local_part, 'x') - self.assertEqual(address_list.mailboxes[2].display_name, - 'Nobody Is. Special') - - def test_get_address_list_mailboxes_invalid_addresses(self): - address_list = self._test_get_x(parser.get_address_list, - ('"Roy A. Bear" , ' - '(ping) Foo x@example.com[],' - 'Nobody Is. Special <(bird)example.(bad)com>'), - ('"Roy A. Bear" , ' - '(ping) Foo x@example.com[],' - 'Nobody Is. Special <(bird)example.(bad)com>'), - ('"Roy A. Bear" , ' - 'Foo x@example.com[],' - '"Nobody Is. Special" < example. com>'), - [errors.InvalidHeaderDefect, # invalid address in list - errors.InvalidHeaderDefect, # 'Foo x' local part invalid. - errors.InvalidHeaderDefect, # Missing . in 'Foo x' local part - errors.ObsoleteHeaderDefect, # period in 'Is.' disp-name phrase - errors.InvalidHeaderDefect, # no domain part in addr-spec - errors.ObsoleteHeaderDefect], # addr-spec has comment in it - '') - self.assertEqual(address_list.token_type, 'address-list') - self.assertEqual(len(address_list.mailboxes), 1) - self.assertEqual(len(address_list.all_mailboxes), 3) - self.assertEqual([str(x) for x in address_list.all_mailboxes], - [str(x) for x in address_list.addresses]) - self.assertEqual(address_list.mailboxes[0].domain, 'example.com') - self.assertEqual(address_list.mailboxes[0].token_type, 'mailbox') - self.assertEqual(address_list.addresses[0].token_type, 'address') - self.assertEqual(address_list.addresses[1].token_type, 'address') - self.assertEqual(len(address_list.addresses[0].mailboxes), 1) - self.assertEqual(len(address_list.addresses[1].mailboxes), 0) - self.assertEqual(len(address_list.addresses[1].mailboxes), 0) - self.assertEqual( - address_list.addresses[1].all_mailboxes[0].local_part, 'Foo x') - self.assertEqual( - address_list.addresses[2].all_mailboxes[0].display_name, - "Nobody Is. Special") - - def test_get_address_list_group_empty(self): - address_list = self._test_get_x(parser.get_address_list, - 'Monty Python: ;', - 'Monty Python: ;', - 'Monty Python: ;', - [], - '') - self.assertEqual(address_list.token_type, 'address-list') - self.assertEqual(len(address_list.mailboxes), 0) - self.assertEqual(address_list.mailboxes, - address_list.all_mailboxes) - self.assertEqual(len(address_list.addresses), 1) - self.assertEqual(address_list.addresses[0].token_type, 'address') - self.assertEqual(address_list.addresses[0].display_name, 'Monty Python') - self.assertEqual(len(address_list.addresses[0].mailboxes), 0) - - def test_get_address_list_group_simple(self): - address_list = self._test_get_x(parser.get_address_list, - 'Monty Python: dinsdale@example.com;', - 'Monty Python: dinsdale@example.com;', - 'Monty Python: 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(address_list.mailboxes[0].domain, 'example.com') - self.assertEqual(address_list.addresses[0].display_name, - 'Monty Python') - self.assertEqual(address_list.addresses[0].mailboxes[0].domain, - 'example.com') - - def test_get_address_list_group_and_mailboxes(self): - address_list = self._test_get_x(parser.get_address_list, - ('Monty Python: dinsdale@example.com, "Fred" ;, ' - 'Abe , Bee '), - ('Monty Python: dinsdale@example.com, "Fred" ;, ' - 'Abe , Bee '), - ('Monty Python: dinsdale@example.com, "Fred" ;, ' - 'Abe , Bee '), - [], - '') - self.assertEqual(address_list.token_type, 'address-list') - self.assertEqual(len(address_list.mailboxes), 4) - self.assertEqual(address_list.mailboxes, - address_list.all_mailboxes) - self.assertEqual(len(address_list.addresses), 3) - self.assertEqual(address_list.mailboxes[0].local_part, 'dinsdale') - self.assertEqual(address_list.addresses[0].display_name, - 'Monty Python') - self.assertEqual(address_list.addresses[0].mailboxes[0].domain, - 'example.com') - self.assertEqual(address_list.addresses[0].mailboxes[1].local_part, - 'flint') - self.assertEqual(address_list.addresses[1].mailboxes[0].local_part, - 'x') - self.assertEqual(address_list.addresses[2].mailboxes[0].local_part, - 'y') - self.assertEqual(str(address_list.addresses[1]), - str(address_list.mailboxes[2])) - - -@parameterize -class Test_parse_mime_version(TestParserMixin, TestEmailBase): - - def mime_version_as_value(self, - value, - tl_str, - tl_value, - major, - minor, - defects): - mime_version = self._test_parse_x(parser.parse_mime_version, - value, tl_str, tl_value, defects) - self.assertEqual(mime_version.major, major) - self.assertEqual(mime_version.minor, minor) - - mime_version_params = { - - 'rfc_2045_1': ( - '1.0', - '1.0', - '1.0', - 1, - 0, - []), - - 'RFC_2045_2': ( - '1.0 (produced by MetaSend Vx.x)', - '1.0 (produced by MetaSend Vx.x)', - '1.0 ', - 1, - 0, - []), - - 'RFC_2045_3': ( - '(produced by MetaSend Vx.x) 1.0', - '(produced by MetaSend Vx.x) 1.0', - ' 1.0', - 1, - 0, - []), - - 'RFC_2045_4': ( - '1.(produced by MetaSend Vx.x)0', - '1.(produced by MetaSend Vx.x)0', - '1. 0', - 1, - 0, - []), - - 'empty': ( - '', - '', - '', - None, - None, - [errors.HeaderMissingRequiredValue]), - - } - - - -class TestFolding(TestEmailBase): - - policy = policy.default - - def _test(self, tl, folded, policy=policy): - self.assertEqual(tl.fold(policy=policy), folded, tl.ppstr()) - - def test_simple_unstructured_no_folds(self): - self._test(parser.get_unstructured("This is a test"), - "This is a test\n") - - def test_simple_unstructured_folded(self): - self._test(parser.get_unstructured("This is also a test, but this " - "time there are enough words (and even some " - "symbols) to make it wrap; at least in theory."), - "This is also a test, but this time there are enough " - "words (and even some\n" - " symbols) to make it wrap; at least in theory.\n") - - def test_unstructured_with_unicode_no_folds(self): - self._test(parser.get_unstructured("hübsch kleiner beißt"), - "=?utf-8?q?h=C3=BCbsch_kleiner_bei=C3=9Ft?=\n") - - def test_one_ew_on_each_of_two_wrapped_lines(self): - self._test(parser.get_unstructured("Mein kleiner Kaktus ist sehr " - "hübsch. Es hat viele Stacheln " - "und oft beißt mich."), - "Mein kleiner Kaktus ist sehr =?utf-8?q?h=C3=BCbsch=2E?= " - "Es hat viele Stacheln\n" - " und oft =?utf-8?q?bei=C3=9Ft?= mich.\n") - - def test_ews_combined_before_wrap(self): - self._test(parser.get_unstructured("Mein Kaktus ist hübsch. " - "Es beißt mich. " - "And that's all I'm sayin."), - "Mein Kaktus ist =?utf-8?q?h=C3=BCbsch=2E__Es_bei=C3=9Ft?= " - "mich. And that's\n" - " all I'm sayin.\n") - - # XXX Need test of an encoded word so long that it needs to be wrapped - - def test_simple_address(self): - self._test(parser.get_address_list("abc ")[0], - "abc \n") - - def test_address_list_folding_at_commas(self): - self._test(parser.get_address_list('abc , ' - '"Fred Blunt" , ' - '"J.P.Cool" , ' - '"K<>y" , ' - 'Firesale , ' - '')[0], - 'abc , "Fred Blunt" ,\n' - ' "J.P.Cool" , "K<>y" ,\n' - ' Firesale , \n') - - def test_address_list_with_unicode_names(self): - self._test(parser.get_address_list( - 'Hübsch Kaktus , ' - 'beißt beißt ')[0], - '=?utf-8?q?H=C3=BCbsch?= Kaktus ,\n' - ' =?utf-8?q?bei=C3=9Ft_bei=C3=9Ft?= \n') - - def test_address_list_with_unicode_names_in_quotes(self): - self._test(parser.get_address_list( - '"Hübsch Kaktus" , ' - '"beißt" beißt ')[0], - '=?utf-8?q?H=C3=BCbsch?= Kaktus ,\n' - ' =?utf-8?q?bei=C3=9Ft_bei=C3=9Ft?= \n') - - # XXX Need tests with comments on various sides of a unicode token, - # and with unicode tokens in the comments. Spaces inside the quotes - # currently don't do the right thing. - - def test_initial_whitespace_splitting(self): - body = parser.get_unstructured(' ' + 'x'*77) - header = parser.Header([ - parser.HeaderLabel([parser.ValueTerminal('test:', 'atext')]), - parser.CFWSList([parser.WhiteSpaceTerminal(' ', 'fws')]), body]) - self._test(header, 'test: \n ' + 'x'*77 + '\n') - - def test_whitespace_splitting(self): - self._test(parser.get_unstructured('xxx ' + 'y'*77), - 'xxx \n ' + 'y'*77 + '\n') - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_future/disabled/test_email/disabled_test_asian_codecs.py b/tests/test_future/disabled/test_email/disabled_test_asian_codecs.py deleted file mode 100644 index 89999f2a..00000000 --- a/tests/test_future/disabled/test_email/disabled_test_asian_codecs.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright (C) 2002-2006 Python Software Foundation -# Contact: email-sig@python.org -# email package unit tests for (optional) Asian codecs - -from __future__ import absolute_import, division, unicode_literals -from future.standard_library.test.support import run_unittest -from future.standard_library.email.charset import Charset -from future.standard_library.email.header import Header, decode_header -from future.standard_library.email.message import Message -from future.tests.base import unittest -from future.tests.test_email.test_email import TestEmailBase -from future.builtins import str - -# We're compatible with Python 2.3, but it doesn't have the built-in Asian -# codecs, so we have to skip all these tests. -try: - str(b'foo', 'euc-jp') -except LookupError: - raise unittest.SkipTest - - -class TestEmailAsianCodecs(TestEmailBase): - def test_japanese_codecs(self): - eq = self.ndiffAssertEqual - jcode = "euc-jp" - gcode = "iso-8859-1" - j = Charset(jcode) - g = Charset(gcode) - h = Header("Hello World!") - jhello = str(b'\xa5\xcf\xa5\xed\xa1\xbc\xa5\xef\xa1\xbc' - b'\xa5\xeb\xa5\xc9\xa1\xaa', jcode) - ghello = str(b'Gr\xfc\xdf Gott!', gcode) - h.append(jhello, j) - h.append(ghello, g) - # BAW: This used to -- and maybe should -- fold the two iso-8859-1 - # chunks into a single encoded word. However it doesn't violate the - # standard to have them as two encoded chunks and maybe it's - # reasonable for each .append() call to result in a separate - # encoded word. - eq(h.encode(), """\ -Hello World! =?iso-2022-jp?b?GyRCJU8lbSE8JW8hPCVrJUkhKhsoQg==?= - =?iso-8859-1?q?Gr=FC=DF_Gott!?=""") - eq(decode_header(h.encode()), - [(b'Hello World! ', None), - (b'\x1b$B%O%m!<%o!<%k%I!*\x1b(B', 'iso-2022-jp'), - (b'Gr\xfc\xdf Gott!', gcode)]) - subject_bytes = (b'test-ja \xa4\xd8\xc5\xea\xb9\xc6\xa4\xb5' - b'\xa4\xec\xa4\xbf\xa5\xe1\xa1\xbc\xa5\xeb\xa4\xcf\xbb\xca\xb2' - b'\xf1\xbc\xd4\xa4\xce\xbe\xb5\xc7\xa7\xa4\xf2\xc2\xd4\xa4\xc3' - b'\xa4\xc6\xa4\xa4\xa4\xde\xa4\xb9') - subject = str(subject_bytes, jcode) - h = Header(subject, j, header_name="Subject") - # test a very long header - enc = h.encode() - # TK: splitting point may differ by codec design and/or Header encoding - eq(enc , """\ -=?iso-2022-jp?b?dGVzdC1qYSAbJEIkWEVqOUYkNSRsJD8lYSE8JWskTztKGyhC?= - =?iso-2022-jp?b?GyRCMnE8VCROPjVHJyRyQlQkQyRGJCQkXiQ5GyhC?=""") - # TK: full decode comparison - eq(str(h).encode(jcode), subject_bytes) - - def test_payload_encoding_utf8(self): - jhello = str(b'\xa5\xcf\xa5\xed\xa1\xbc\xa5\xef\xa1\xbc' - b'\xa5\xeb\xa5\xc9\xa1\xaa', 'euc-jp') - msg = Message() - msg.set_payload(jhello, 'utf-8') - ustr = msg.get_payload(decode=True).decode(msg.get_content_charset()) - self.assertEqual(jhello, ustr) - - def test_payload_encoding(self): - jcode = 'euc-jp' - jhello = str(b'\xa5\xcf\xa5\xed\xa1\xbc\xa5\xef\xa1\xbc' - b'\xa5\xeb\xa5\xc9\xa1\xaa', jcode) - msg = Message() - msg.set_payload(jhello, jcode) - ustr = msg.get_payload(decode=True).decode(msg.get_content_charset()) - self.assertEqual(jhello, ustr) - - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_future/disabled/test_email/disabled_test_defect_handling.py b/tests/test_future/disabled/test_email/disabled_test_defect_handling.py deleted file mode 100644 index 409f1c3a..00000000 --- a/tests/test_future/disabled/test_email/disabled_test_defect_handling.py +++ /dev/null @@ -1,320 +0,0 @@ -from __future__ import absolute_import, division, unicode_literals -import textwrap -import contextlib -from future.standard_library.email import policy, errors -from future.tests.test_email import TestEmailBase -from future.tests.base import unittest -from future.builtins import str - -class TestDefectsBase(object): - - policy = policy.default - raise_expected = False - - @contextlib.contextmanager - def _raise_point(self, defect): - yield - - def test_same_boundary_inner_outer(self): - source = textwrap.dedent("""\ - Subject: XX - From: xx@xx.dk - To: XX - Mime-version: 1.0 - Content-type: multipart/mixed; - boundary="MS_Mac_OE_3071477847_720252_MIME_Part" - - --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 - - text - - --MS_Mac_OE_3071477847_720252_MIME_Part - Content-type: text/html; charset="ISO-8859-1" - Content-transfer-encoding: quoted-printable - - - - --MS_Mac_OE_3071477847_720252_MIME_Part-- - - --MS_Mac_OE_3071477847_720252_MIME_Part - Content-type: image/gif; name="xx.gif"; - Content-disposition: attachment - Content-transfer-encoding: base64 - - Some removed base64 encoded chars. - - --MS_Mac_OE_3071477847_720252_MIME_Part-- - - """) - # XXX better would be to actually detect the duplicate. - with self._raise_point(errors.StartBoundaryNotFoundDefect): - msg = self._str_msg(source) - if self.raise_expected: return - inner = msg.get_payload(0) - self.assertTrue(hasattr(inner, 'defects')) - self.assertEqual(len(self.get_defects(inner)), 1) - self.assertTrue(isinstance(self.get_defects(inner)[0], - errors.StartBoundaryNotFoundDefect)) - - def test_multipart_no_boundary(self): - source = textwrap.dedent("""\ - Date: Fri, 6 Apr 2001 09:23:06 -0800 (GMT-0800) - From: foobar - Subject: broken mail - MIME-Version: 1.0 - Content-Type: multipart/report; report-type=delivery-status; - - --JAB03225.986577786/zinfandel.lacita.com - - One part - - --JAB03225.986577786/zinfandel.lacita.com - Content-Type: message/delivery-status - - Header: Another part - - --JAB03225.986577786/zinfandel.lacita.com-- - """) - with self._raise_point(errors.NoBoundaryInMultipartDefect): - msg = self._str_msg(source) - if self.raise_expected: return - self.assertTrue(isinstance(msg.get_payload(), str)) - self.assertEqual(len(self.get_defects(msg)), 2) - self.assertTrue(isinstance(self.get_defects(msg)[0], - errors.NoBoundaryInMultipartDefect)) - self.assertTrue(isinstance(self.get_defects(msg)[1], - errors.MultipartInvariantViolationDefect)) - - multipart_msg = textwrap.dedent("""\ - Date: Wed, 14 Nov 2007 12:56:23 GMT - From: foo@bar.invalid - To: foo@bar.invalid - Subject: Content-Transfer-Encoding: base64 and multipart - MIME-Version: 1.0 - Content-Type: multipart/mixed; - boundary="===============3344438784458119861=="{} - - --===============3344438784458119861== - Content-Type: text/plain - - Test message - - --===============3344438784458119861== - Content-Type: application/octet-stream - Content-Transfer-Encoding: base64 - - YWJj - - --===============3344438784458119861==-- - """) - - def test_multipart_invalid_cte(self): - with self._raise_point( - errors.InvalidMultipartContentTransferEncodingDefect): - msg = self._str_msg( - self.multipart_msg.format( - "\nContent-Transfer-Encoding: base64")) - if self.raise_expected: return - self.assertEqual(len(self.get_defects(msg)), 1) - self.assertIsInstance(self.get_defects(msg)[0], - errors.InvalidMultipartContentTransferEncodingDefect) - - def test_multipart_no_cte_no_defect(self): - if self.raise_expected: return - msg = self._str_msg(self.multipart_msg.format('')) - self.assertEqual(len(self.get_defects(msg)), 0) - - def test_multipart_valid_cte_no_defect(self): - if self.raise_expected: return - for cte in ('7bit', '8bit', 'BINary'): - msg = self._str_msg( - self.multipart_msg.format("\nContent-Transfer-Encoding: "+cte)) - self.assertEqual(len(self.get_defects(msg)), 0, "cte="+cte) - - def test_lying_multipart(self): - source = textwrap.dedent("""\ - 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 - """) - with self._raise_point(errors.NoBoundaryInMultipartDefect): - msg = self._str_msg(source) - if self.raise_expected: return - self.assertTrue(hasattr(msg, 'defects')) - self.assertEqual(len(self.get_defects(msg)), 2) - self.assertTrue(isinstance(self.get_defects(msg)[0], - errors.NoBoundaryInMultipartDefect)) - self.assertTrue(isinstance(self.get_defects(msg)[1], - errors.MultipartInvariantViolationDefect)) - - def test_missing_start_boundary(self): - source = textwrap.dedent("""\ - Content-Type: multipart/mixed; boundary="AAA" - From: Mail Delivery Subsystem - To: yyy@example.com - - --AAA - - Stuff - - --AAA - Content-Type: message/rfc822 - - From: webmaster@python.org - To: zzz@example.com - Content-Type: multipart/mixed; boundary="BBB" - - --BBB-- - - --AAA-- - - """) - # The message structure is: - # - # multipart/mixed - # text/plain - # message/rfc822 - # multipart/mixed [*] - # - # [*] This message is missing its start boundary - with self._raise_point(errors.StartBoundaryNotFoundDefect): - outer = self._str_msg(source) - if self.raise_expected: return - bad = outer.get_payload(1).get_payload(0) - self.assertEqual(len(self.get_defects(bad)), 1) - self.assertTrue(isinstance(self.get_defects(bad)[0], - errors.StartBoundaryNotFoundDefect)) - - def test_first_line_is_continuation_header(self): - with self._raise_point(errors.FirstHeaderLineIsContinuationDefect): - msg = self._str_msg(' Line 1\nSubject: test\n\nbody') - if self.raise_expected: return - self.assertEqual(msg.keys(), ['Subject']) - self.assertEqual(msg.get_payload(), 'body') - self.assertEqual(len(self.get_defects(msg)), 1) - self.assertDefectsEqual(self.get_defects(msg), - [errors.FirstHeaderLineIsContinuationDefect]) - self.assertEqual(self.get_defects(msg)[0].line, ' Line 1\n') - - def test_missing_header_body_separator(self): - # Our heuristic if we see a line that doesn't look like a header (no - # leading whitespace but no ':') is to assume that the blank line that - # separates the header from the body is missing, and to stop parsing - # headers and start parsing the body. - with self._raise_point(errors.MissingHeaderBodySeparatorDefect): - msg = self._str_msg('Subject: test\nnot a header\nTo: abc\n\nb\n') - if self.raise_expected: return - self.assertEqual(msg.keys(), ['Subject']) - self.assertEqual(msg.get_payload(), 'not a header\nTo: abc\n\nb\n') - self.assertDefectsEqual(self.get_defects(msg), - [errors.MissingHeaderBodySeparatorDefect]) - - def test_bad_padding_in_base64_payload(self): - source = textwrap.dedent("""\ - Subject: test - MIME-Version: 1.0 - Content-Type: text/plain; charset="utf-8" - Content-Transfer-Encoding: base64 - - dmk - """) - msg = self._str_msg(source) - with self._raise_point(errors.InvalidBase64PaddingDefect): - payload = msg.get_payload(decode=True) - if self.raise_expected: return - self.assertEqual(payload, b'vi') - self.assertDefectsEqual(self.get_defects(msg), - [errors.InvalidBase64PaddingDefect]) - - def test_invalid_chars_in_base64_payload(self): - source = textwrap.dedent("""\ - Subject: test - MIME-Version: 1.0 - Content-Type: text/plain; charset="utf-8" - Content-Transfer-Encoding: base64 - - dm\x01k=== - """) - msg = self._str_msg(source) - with self._raise_point(errors.InvalidBase64CharactersDefect): - payload = msg.get_payload(decode=True) - if self.raise_expected: return - self.assertEqual(payload, b'vi') - self.assertDefectsEqual(self.get_defects(msg), - [errors.InvalidBase64CharactersDefect]) - - def test_missing_ending_boundary(self): - source = textwrap.dedent("""\ - To: 1@harrydomain4.com - Subject: Fwd: 1 - MIME-Version: 1.0 - Content-Type: multipart/alternative; - boundary="------------000101020201080900040301" - - --------------000101020201080900040301 - Content-Type: text/plain; charset=ISO-8859-1 - Content-Transfer-Encoding: 7bit - - Alternative 1 - - --------------000101020201080900040301 - Content-Type: text/html; charset=ISO-8859-1 - Content-Transfer-Encoding: 7bit - - Alternative 2 - - """) - with self._raise_point(errors.CloseBoundaryNotFoundDefect): - msg = self._str_msg(source) - if self.raise_expected: return - self.assertEqual(len(msg.get_payload()), 2) - self.assertEqual(msg.get_payload(1).get_payload(), 'Alternative 2\n') - self.assertDefectsEqual(self.get_defects(msg), - [errors.CloseBoundaryNotFoundDefect]) - - -class TestDefectDetection(TestDefectsBase, TestEmailBase): - - def get_defects(self, obj): - return obj.defects - - -class TestDefectCapture(TestDefectsBase, TestEmailBase): - - class CapturePolicy(policy.EmailPolicy): - captured = None - def register_defect(self, obj, defect): - self.captured.append(defect) - - def setUp(self): - self.policy = self.CapturePolicy(captured=list()) - - def get_defects(self, obj): - return self.policy.captured - - -class TestDefectRaising(TestDefectsBase, TestEmailBase): - - policy = TestDefectsBase.policy - policy = policy.clone(raise_on_defect=True) - raise_expected = True - - @contextlib.contextmanager - def _raise_point(self, defect): - with self.assertRaises(defect): - yield - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_future/disabled/test_email/disabled_test_email.py b/tests/test_future/disabled/test_email/disabled_test_email.py deleted file mode 100644 index 52595ab0..00000000 --- a/tests/test_future/disabled/test_email/disabled_test_email.py +++ /dev/null @@ -1,5051 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2001-2010 Python Software Foundation -# Contact: email-sig@python.org -# email package unit tests - -from __future__ import division -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import absolute_import -from future.builtins import open, int, super, chr, range, bytes, str - -import re -import time -import base64 -import textwrap - -from io import StringIO, BytesIO -from itertools import chain - -import future.standard_library.email as email - -from future.standard_library.email.charset import Charset -from future.standard_library.email.header import Header, decode_header, make_header -from future.standard_library.email.parser import Parser, HeaderParser -from future.standard_library.email.generator import Generator, DecodedGenerator, BytesGenerator -from future.standard_library.email.message import Message -from future.standard_library.email.mime.application import MIMEApplication -from future.standard_library.email.mime.audio import MIMEAudio -from future.standard_library.email.mime.text import MIMEText -from future.standard_library.email.mime.image import MIMEImage -from future.standard_library.email.mime.base import MIMEBase -from future.standard_library.email.mime.message import MIMEMessage -from future.standard_library.email.mime.multipart import MIMEMultipart -from future.standard_library.email import utils -from future.standard_library.email import errors -from future.standard_library.email import encoders -from future.standard_library.email import iterators -from future.standard_library.email import base64mime -from future.standard_library.email import quoprimime -import future.standard_library.email.feedparser as email_feedparser -from future.standard_library.test.support import unlink - -# These imports are documented to work, but we are testing them using a -# different path, so we import them here just to make sure they are importable. -from future.standard_library.email.parser import FeedParser, BytesFeedParser - -from future.tests.base import unittest -from future.tests.test_email import openfile, TestEmailBase - - -NL = '\n' -EMPTYSTRING = '' -SPACE = ' ' - - -# Test various aspects of the Message class's API -class TestMessageAPI(TestEmailBase): - def test_get_all(self): - eq = self.assertEqual - msg = self._msgobj('msg_20.txt') - eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org']) - eq(msg.get_all('xx', 'n/a'), 'n/a') - - def test_getset_charset(self): - eq = self.assertEqual - msg = Message() - eq(msg.get_charset(), None) - charset = Charset('iso-8859-1') - msg.set_charset(charset) - eq(msg['mime-version'], '1.0') - eq(msg.get_content_type(), 'text/plain') - eq(msg['content-type'], 'text/plain; charset="iso-8859-1"') - eq(msg.get_param('charset'), 'iso-8859-1') - eq(msg['content-transfer-encoding'], 'quoted-printable') - eq(msg.get_charset().input_charset, 'iso-8859-1') - # Remove the charset - msg.set_charset(None) - eq(msg.get_charset(), None) - eq(msg['content-type'], 'text/plain') - # Try adding a charset when there's already MIME headers present - msg = Message() - msg['MIME-Version'] = '2.0' - msg['Content-Type'] = 'text/x-weird' - msg['Content-Transfer-Encoding'] = 'quinted-puntable' - msg.set_charset(charset) - eq(msg['mime-version'], '2.0') - eq(msg['content-type'], 'text/x-weird; charset="iso-8859-1"') - eq(msg['content-transfer-encoding'], 'quinted-puntable') - - def test_set_charset_from_string(self): - eq = self.assertEqual - msg = Message() - msg.set_charset('us-ascii') - eq(msg.get_charset().input_charset, 'us-ascii') - eq(msg['content-type'], 'text/plain; charset="us-ascii"') - - def test_set_payload_with_charset(self): - msg = Message() - charset = Charset('iso-8859-1') - msg.set_payload('This is a string payload', charset) - self.assertEqual(msg.get_charset().input_charset, 'iso-8859-1') - - def test_get_charsets(self): - eq = self.assertEqual - - msg = self._msgobj('msg_08.txt') - charsets = msg.get_charsets() - eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r']) - - msg = self._msgobj('msg_09.txt') - charsets = msg.get_charsets('dingbat') - eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat', - 'koi8-r']) - - msg = self._msgobj('msg_12.txt') - charsets = msg.get_charsets() - eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2', - 'iso-8859-3', 'us-ascii', 'koi8-r']) - - def test_get_filename(self): - eq = self.assertEqual - - msg = self._msgobj('msg_04.txt') - filenames = [p.get_filename() for p in msg.get_payload()] - eq(filenames, ['msg.txt', 'msg.txt']) - - msg = self._msgobj('msg_07.txt') - subpart = msg.get_payload(1) - eq(subpart.get_filename(), 'dingusfish.gif') - - def test_get_filename_with_name_parameter(self): - eq = self.assertEqual - - msg = self._msgobj('msg_44.txt') - filenames = [p.get_filename() for p in msg.get_payload()] - eq(filenames, ['msg.txt', 'msg.txt']) - - def test_get_boundary(self): - eq = self.assertEqual - msg = self._msgobj('msg_07.txt') - # No quotes! - eq(msg.get_boundary(), 'BOUNDARY') - - def test_set_boundary(self): - eq = self.assertEqual - # This one has no existing boundary parameter, but the Content-Type: - # header appears fifth. - msg = self._msgobj('msg_01.txt') - msg.set_boundary('BOUNDARY') - header, value = msg.items()[4] - eq(header.lower(), 'content-type') - eq(value, 'text/plain; charset="us-ascii"; boundary="BOUNDARY"') - # This one has a Content-Type: header, with a boundary, stuck in the - # middle of its headers. Make sure the order is preserved; it should - # be fifth. - msg = self._msgobj('msg_04.txt') - msg.set_boundary('BOUNDARY') - header, value = msg.items()[4] - eq(header.lower(), 'content-type') - eq(value, 'multipart/mixed; boundary="BOUNDARY"') - # And this one has no Content-Type: header at all. - msg = self._msgobj('msg_03.txt') - self.assertRaises(errors.HeaderParseError, - msg.set_boundary, 'BOUNDARY') - - def test_make_boundary(self): - msg = MIMEMultipart('form-data') - # Note that when the boundary gets created is an implementation - # detail and might change. - self.assertEqual(msg.items()[0][1], 'multipart/form-data') - # Trigger creation of boundary - msg.as_string() - self.assertEqual(msg.items()[0][1][:33], - 'multipart/form-data; boundary="==') - # XXX: there ought to be tests of the uniqueness of the boundary, too. - - def test_message_rfc822_only(self): - # Issue 7970: message/rfc822 not in multipart parsed by - # HeaderParser caused an exception when flattened. - with openfile('msg_46.txt') as fp: - msgdata = fp.read() - parser = HeaderParser() - msg = parser.parsestr(msgdata) - out = StringIO() - gen = Generator(out, True, 0) - gen.flatten(msg, False) - self.assertEqual(out.getvalue(), msgdata) - - def test_byte_message_rfc822_only(self): - # Make sure new bytes header parser also passes this. - with openfile('msg_46.txt', 'rb') as fp: - msgdata = fp.read() - parser = email.parser.BytesHeaderParser() - msg = parser.parsebytes(msgdata) - out = BytesIO() - gen = email.generator.BytesGenerator(out) - gen.flatten(msg) - self.assertEqual(out.getvalue(), msgdata) - - def test_get_decoded_payload(self): - eq = self.assertEqual - msg = self._msgobj('msg_10.txt') - # The outer message is a multipart - eq(msg.get_payload(decode=True), None) - # Subpart 1 is 7bit encoded - eq(msg.get_payload(0).get_payload(decode=True), - b'This is a 7bit encoded message.\n') - # Subpart 2 is quopri - eq(msg.get_payload(1).get_payload(decode=True), - b'\xa1This is a Quoted Printable encoded message!\n') - # Subpart 3 is base64 - eq(msg.get_payload(2).get_payload(decode=True), - b'This is a Base64 encoded message.') - # Subpart 4 is base64 with a trailing newline, which - # used to be stripped (issue 7143). - eq(msg.get_payload(3).get_payload(decode=True), - b'This is a Base64 encoded message.\n') - # Subpart 5 has no Content-Transfer-Encoding: header. - eq(msg.get_payload(4).get_payload(decode=True), - b'This has no Content-Transfer-Encoding: header.\n') - - def test_get_decoded_uu_payload(self): - eq = self.assertEqual - msg = Message() - msg.set_payload('begin 666 -\n+:&5L;&\\@=V]R;&0 \n \nend\n') - for cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'): - msg['content-transfer-encoding'] = cte - eq(msg.get_payload(decode=True), b'hello world') - # Now try some bogus data - msg.set_payload('foo') - eq(msg.get_payload(decode=True), b'foo') - - def test_get_payload_n_raises_on_non_multipart(self): - msg = Message() - self.assertRaises(TypeError, msg.get_payload, 1) - - def test_decoded_generator(self): - eq = self.assertEqual - msg = self._msgobj('msg_07.txt') - with openfile('msg_17.txt') as fp: - text = fp.read() - s = StringIO() - g = DecodedGenerator(s) - g.flatten(msg) - eq(s.getvalue(), text) - - def test__contains__(self): - msg = Message() - msg['From'] = 'Me' - msg['to'] = 'You' - # Check for case insensitivity - self.assertTrue('from' in msg) - self.assertTrue('From' in msg) - self.assertTrue('FROM' in msg) - self.assertTrue('to' in msg) - self.assertTrue('To' in msg) - self.assertTrue('TO' in msg) - - def test_as_string(self): - eq = self.ndiffAssertEqual - msg = self._msgobj('msg_01.txt') - with openfile('msg_01.txt') as fp: - text = fp.read() - eq(text, str(msg)) - fullrepr = msg.as_string(unixfrom=True) - lines = fullrepr.split('\n') - self.assertTrue(lines[0].startswith('From ')) - eq(text, NL.join(lines[1:])) - - # test_headerregistry.TestContentTypeHeader.bad_params - def test_bad_param(self): - msg = email.message_from_string("Content-Type: blarg; baz; boo\n") - self.assertEqual(msg.get_param('baz'), '') - - def test_missing_filename(self): - msg = email.message_from_string("From: foo\n") - self.assertEqual(msg.get_filename(), None) - - def test_bogus_filename(self): - msg = email.message_from_string( - "Content-Disposition: blarg; filename\n") - self.assertEqual(msg.get_filename(), '') - - def test_missing_boundary(self): - msg = email.message_from_string("From: foo\n") - self.assertEqual(msg.get_boundary(), None) - - def test_get_params(self): - eq = self.assertEqual - msg = email.message_from_string( - 'X-Header: foo=one; bar=two; baz=three\n') - eq(msg.get_params(header='x-header'), - [('foo', 'one'), ('bar', 'two'), ('baz', 'three')]) - msg = email.message_from_string( - 'X-Header: foo; bar=one; baz=two\n') - eq(msg.get_params(header='x-header'), - [('foo', ''), ('bar', 'one'), ('baz', 'two')]) - eq(msg.get_params(), None) - msg = email.message_from_string( - 'X-Header: foo; bar="one"; baz=two\n') - eq(msg.get_params(header='x-header'), - [('foo', ''), ('bar', 'one'), ('baz', 'two')]) - - # test_headerregistry.TestContentTypeHeader.spaces_around_param_equals - def test_get_param_liberal(self): - msg = Message() - msg['Content-Type'] = 'Content-Type: Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"' - self.assertEqual(msg.get_param('boundary'), 'CPIMSSMTPC06p5f3tG') - - def test_get_param(self): - eq = self.assertEqual - msg = email.message_from_string( - "X-Header: foo=one; bar=two; baz=three\n") - eq(msg.get_param('bar', header='x-header'), 'two') - eq(msg.get_param('quuz', header='x-header'), None) - eq(msg.get_param('quuz'), None) - msg = email.message_from_string( - 'X-Header: foo; bar="one"; baz=two\n') - eq(msg.get_param('foo', header='x-header'), '') - eq(msg.get_param('bar', header='x-header'), 'one') - eq(msg.get_param('baz', header='x-header'), 'two') - # XXX: We are not RFC-2045 compliant! We cannot parse: - # msg["Content-Type"] = 'text/plain; weird="hey; dolly? [you] @ <\\"home\\">?"' - # msg.get_param("weird") - # yet. - - # test_headerregistry.TestContentTypeHeader.spaces_around_semis - def test_get_param_funky_continuation_lines(self): - msg = self._msgobj('msg_22.txt') - self.assertEqual(msg.get_payload(1).get_param('name'), 'wibble.JPG') - - # test_headerregistry.TestContentTypeHeader.semis_inside_quotes - def test_get_param_with_semis_in_quotes(self): - msg = email.message_from_string( - 'Content-Type: image/pjpeg; name="Jim&&Jill"\n') - self.assertEqual(msg.get_param('name'), 'Jim&&Jill') - self.assertEqual(msg.get_param('name', unquote=False), - '"Jim&&Jill"') - - # test_headerregistry.TestContentTypeHeader.quotes_inside_rfc2231_value - def test_get_param_with_quotes(self): - msg = email.message_from_string( - 'Content-Type: foo; bar*0="baz\\"foobar"; bar*1="\\"baz"') - self.assertEqual(msg.get_param('bar'), 'baz"foobar"baz') - msg = email.message_from_string( - "Content-Type: foo; bar*0=\"baz\\\"foobar\"; bar*1=\"\\\"baz\"") - self.assertEqual(msg.get_param('bar'), 'baz"foobar"baz') - - def test_field_containment(self): - unless = self.assertTrue - msg = email.message_from_string('Header: exists') - unless('header' in msg) - unless('Header' in msg) - unless('HEADER' in msg) - self.assertFalse('headerx' in msg) - - def test_set_param(self): - eq = self.assertEqual - msg = Message() - msg.set_param('charset', 'iso-2022-jp') - eq(msg.get_param('charset'), 'iso-2022-jp') - msg.set_param('importance', 'high value') - eq(msg.get_param('importance'), 'high value') - eq(msg.get_param('importance', unquote=False), '"high value"') - eq(msg.get_params(), [('text/plain', ''), - ('charset', 'iso-2022-jp'), - ('importance', 'high value')]) - eq(msg.get_params(unquote=False), [('text/plain', ''), - ('charset', '"iso-2022-jp"'), - ('importance', '"high value"')]) - msg.set_param('charset', 'iso-9999-xx', header='X-Jimmy') - eq(msg.get_param('charset', header='X-Jimmy'), 'iso-9999-xx') - - def test_del_param(self): - eq = self.assertEqual - msg = self._msgobj('msg_05.txt') - eq(msg.get_params(), - [('multipart/report', ''), ('report-type', 'delivery-status'), - ('boundary', 'D1690A7AC1.996856090/mail.example.com')]) - old_val = msg.get_param("report-type") - msg.del_param("report-type") - eq(msg.get_params(), - [('multipart/report', ''), - ('boundary', 'D1690A7AC1.996856090/mail.example.com')]) - msg.set_param("report-type", old_val) - eq(msg.get_params(), - [('multipart/report', ''), - ('boundary', 'D1690A7AC1.996856090/mail.example.com'), - ('report-type', old_val)]) - - def test_del_param_on_other_header(self): - msg = Message() - msg.add_header('Content-Disposition', 'attachment', filename='bud.gif') - msg.del_param('filename', 'content-disposition') - self.assertEqual(msg['content-disposition'], 'attachment') - - def test_del_param_on_nonexistent_header(self): - msg = Message() - msg.del_param('filename', 'content-disposition') - - def test_del_nonexistent_param(self): - msg = Message() - msg.add_header('Content-Type', 'text/plain', charset='utf-8') - existing_header = msg['Content-Type'] - msg.del_param('foobar', header='Content-Type') - self.assertEqual(msg['Content-Type'], 'text/plain; charset="utf-8"') - - def test_set_type(self): - eq = self.assertEqual - msg = Message() - self.assertRaises(ValueError, msg.set_type, 'text') - msg.set_type('text/plain') - eq(msg['content-type'], 'text/plain') - msg.set_param('charset', 'us-ascii') - eq(msg['content-type'], 'text/plain; charset="us-ascii"') - msg.set_type('text/html') - eq(msg['content-type'], 'text/html; charset="us-ascii"') - - def test_set_type_on_other_header(self): - msg = Message() - msg['X-Content-Type'] = 'text/plain' - msg.set_type('application/octet-stream', 'X-Content-Type') - self.assertEqual(msg['x-content-type'], 'application/octet-stream') - - def test_get_content_type_missing(self): - msg = Message() - self.assertEqual(msg.get_content_type(), 'text/plain') - - def test_get_content_type_missing_with_default_type(self): - msg = Message() - msg.set_default_type('message/rfc822') - self.assertEqual(msg.get_content_type(), 'message/rfc822') - - def test_get_content_type_from_message_implicit(self): - msg = self._msgobj('msg_30.txt') - self.assertEqual(msg.get_payload(0).get_content_type(), - 'message/rfc822') - - def test_get_content_type_from_message_explicit(self): - msg = self._msgobj('msg_28.txt') - self.assertEqual(msg.get_payload(0).get_content_type(), - 'message/rfc822') - - def test_get_content_type_from_message_text_plain_implicit(self): - msg = self._msgobj('msg_03.txt') - self.assertEqual(msg.get_content_type(), 'text/plain') - - def test_get_content_type_from_message_text_plain_explicit(self): - msg = self._msgobj('msg_01.txt') - self.assertEqual(msg.get_content_type(), 'text/plain') - - def test_get_content_maintype_missing(self): - msg = Message() - self.assertEqual(msg.get_content_maintype(), 'text') - - def test_get_content_maintype_missing_with_default_type(self): - msg = Message() - msg.set_default_type('message/rfc822') - self.assertEqual(msg.get_content_maintype(), 'message') - - def test_get_content_maintype_from_message_implicit(self): - msg = self._msgobj('msg_30.txt') - self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message') - - def test_get_content_maintype_from_message_explicit(self): - msg = self._msgobj('msg_28.txt') - self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message') - - def test_get_content_maintype_from_message_text_plain_implicit(self): - msg = self._msgobj('msg_03.txt') - self.assertEqual(msg.get_content_maintype(), 'text') - - def test_get_content_maintype_from_message_text_plain_explicit(self): - msg = self._msgobj('msg_01.txt') - self.assertEqual(msg.get_content_maintype(), 'text') - - def test_get_content_subtype_missing(self): - msg = Message() - self.assertEqual(msg.get_content_subtype(), 'plain') - - def test_get_content_subtype_missing_with_default_type(self): - msg = Message() - msg.set_default_type('message/rfc822') - self.assertEqual(msg.get_content_subtype(), 'rfc822') - - def test_get_content_subtype_from_message_implicit(self): - msg = self._msgobj('msg_30.txt') - self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822') - - def test_get_content_subtype_from_message_explicit(self): - msg = self._msgobj('msg_28.txt') - self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822') - - def test_get_content_subtype_from_message_text_plain_implicit(self): - msg = self._msgobj('msg_03.txt') - self.assertEqual(msg.get_content_subtype(), 'plain') - - def test_get_content_subtype_from_message_text_plain_explicit(self): - msg = self._msgobj('msg_01.txt') - self.assertEqual(msg.get_content_subtype(), 'plain') - - def test_get_content_maintype_error(self): - msg = Message() - msg['Content-Type'] = 'no-slash-in-this-string' - self.assertEqual(msg.get_content_maintype(), 'text') - - def test_get_content_subtype_error(self): - msg = Message() - msg['Content-Type'] = 'no-slash-in-this-string' - self.assertEqual(msg.get_content_subtype(), 'plain') - - def test_replace_header(self): - eq = self.assertEqual - msg = Message() - msg.add_header('First', 'One') - msg.add_header('Second', 'Two') - msg.add_header('Third', 'Three') - eq(msg.keys(), ['First', 'Second', 'Third']) - eq(msg.values(), ['One', 'Two', 'Three']) - msg.replace_header('Second', 'Twenty') - eq(msg.keys(), ['First', 'Second', 'Third']) - eq(msg.values(), ['One', 'Twenty', 'Three']) - msg.add_header('First', 'Eleven') - msg.replace_header('First', 'One Hundred') - eq(msg.keys(), ['First', 'Second', 'Third', 'First']) - eq(msg.values(), ['One Hundred', 'Twenty', 'Three', 'Eleven']) - self.assertRaises(KeyError, msg.replace_header, 'Fourth', 'Missing') - - # test_defect_handling:test_invalid_chars_in_base64_payload - def test_broken_base64_payload(self): - x = 'AwDp0P7//y6LwKEAcPa/6Q=9' - msg = Message() - msg['content-type'] = 'audio/x-midi' - msg['content-transfer-encoding'] = 'base64' - msg.set_payload(x) - self.assertEqual(msg.get_payload(decode=True), - (b'\x03\x00\xe9\xd0\xfe\xff\xff.\x8b\xc0' - b'\xa1\x00p\xf6\xbf\xe9\x0f')) - self.assertIsInstance(msg.defects[0], - errors.InvalidBase64CharactersDefect) - - def test_broken_unicode_payload(self): - # This test improves coverage but is not a compliance test. - # The behavior in this situation is currently undefined by the API. - x = 'this is a br\xf6ken thing to do' - msg = Message() - msg['content-type'] = 'text/plain' - msg['content-transfer-encoding'] = '8bit' - msg.set_payload(x) - self.assertEqual(msg.get_payload(decode=True), - bytes(x, 'raw-unicode-escape')) - - def test_questionable_bytes_payload(self): - # This test improves coverage but is not a compliance test, - # since it involves poking inside the black box. - x = 'this is a quéstionable thing to do'.encode('utf-8') - msg = Message() - msg['content-type'] = 'text/plain; charset="utf-8"' - msg['content-transfer-encoding'] = '8bit' - msg._payload = x - self.assertEqual(msg.get_payload(decode=True), x) - - # Issue 1078919 - def test_ascii_add_header(self): - msg = Message() - msg.add_header('Content-Disposition', 'attachment', - filename='bud.gif') - self.assertEqual('attachment; filename="bud.gif"', - msg['Content-Disposition']) - - def test_noascii_add_header(self): - msg = Message() - msg.add_header('Content-Disposition', 'attachment', - filename="Fußballer.ppt") - self.assertEqual( - 'attachment; filename*=utf-8\'\'Fu%C3%9Fballer.ppt', - msg['Content-Disposition']) - - def test_nonascii_add_header_via_triple(self): - msg = Message() - msg.add_header('Content-Disposition', 'attachment', - filename=('iso-8859-1', '', 'Fußballer.ppt')) - self.assertEqual( - 'attachment; filename*=iso-8859-1\'\'Fu%DFballer.ppt', - msg['Content-Disposition']) - - def test_ascii_add_header_with_tspecial(self): - msg = Message() - msg.add_header('Content-Disposition', 'attachment', - filename="windows [filename].ppt") - self.assertEqual( - 'attachment; filename="windows [filename].ppt"', - msg['Content-Disposition']) - - def test_nonascii_add_header_with_tspecial(self): - msg = Message() - msg.add_header('Content-Disposition', 'attachment', - filename="Fußballer [filename].ppt") - self.assertEqual( - "attachment; filename*=utf-8''Fu%C3%9Fballer%20%5Bfilename%5D.ppt", - msg['Content-Disposition']) - - def test_add_header_with_name_only_param(self): - msg = Message() - msg.add_header('Content-Disposition', 'inline', foo_bar=None) - self.assertEqual("inline; foo-bar", msg['Content-Disposition']) - - def test_add_header_with_no_value(self): - msg = Message() - msg.add_header('X-Status', None) - self.assertEqual('', msg['X-Status']) - - # Issue 5871: reject an attempt to embed a header inside a header value - # (header injection attack). - def test_embeded_header_via_Header_rejected(self): - msg = Message() - msg['Dummy'] = Header('dummy\nX-Injected-Header: test') - self.assertRaises(errors.HeaderParseError, msg.as_string) - - def test_embeded_header_via_string_rejected(self): - msg = Message() - msg['Dummy'] = 'dummy\nX-Injected-Header: test' - self.assertRaises(errors.HeaderParseError, msg.as_string) - - def test_unicode_header_defaults_to_utf8_encoding(self): - # Issue 14291 - m = MIMEText('abc\n') - m['Subject'] = 'É test' - self.assertEqual(str(m),textwrap.dedent("""\ - Content-Type: text/plain; charset="us-ascii" - MIME-Version: 1.0 - Content-Transfer-Encoding: 7bit - Subject: =?utf-8?q?=C3=89_test?= - - abc - """)) - - def test_unicode_body_defaults_to_utf8_encoding(self): - # Issue 14291 - m = MIMEText('É testabc\n') - self.assertEqual(str(m),textwrap.dedent("""\ - Content-Type: text/plain; charset="utf-8" - MIME-Version: 1.0 - Content-Transfer-Encoding: base64 - - w4kgdGVzdGFiYwo= - """)) - - -# Test the email.encoders module -class TestEncoders(unittest.TestCase): - - def test_EncodersEncode_base64(self): - with openfile('PyBanner048.gif', 'rb') as fp: - bindata = fp.read() - mimed = email.mime.image.MIMEImage(bindata) - base64ed = mimed.get_payload() - # the transfer-encoded body lines should all be <=76 characters - lines = base64ed.split('\n') - self.assertLessEqual(max([ len(x) for x in lines ]), 76) - - def test_encode_empty_payload(self): - eq = self.assertEqual - msg = Message() - msg.set_charset('us-ascii') - eq(msg['content-transfer-encoding'], '7bit') - - def test_default_cte(self): - eq = self.assertEqual - # 7bit data and the default us-ascii _charset - msg = MIMEText('hello world') - eq(msg['content-transfer-encoding'], '7bit') - # Similar, but with 8bit data - msg = MIMEText('hello \xf8 world') - eq(msg['content-transfer-encoding'], 'base64') - # And now with a different charset - msg = MIMEText('hello \xf8 world', _charset='iso-8859-1') - eq(msg['content-transfer-encoding'], 'quoted-printable') - - def test_encode7or8bit(self): - # Make sure a charset whose input character set is 8bit but - # whose output character set is 7bit gets a transfer-encoding - # of 7bit. - eq = self.assertEqual - msg = MIMEText('文', _charset='euc-jp') - eq(msg['content-transfer-encoding'], '7bit') - - def test_qp_encode_latin1(self): - msg = MIMEText('\xe1\xf6\n', 'text', 'ISO-8859-1') - self.assertEqual(str(msg), textwrap.dedent("""\ - MIME-Version: 1.0 - Content-Type: text/text; charset="iso-8859-1" - Content-Transfer-Encoding: quoted-printable - - =E1=F6 - """)) - - def test_qp_encode_non_latin1(self): - # Issue 16948 - msg = MIMEText('\u017c\n', 'text', 'ISO-8859-2') - self.assertEqual(str(msg), textwrap.dedent("""\ - MIME-Version: 1.0 - Content-Type: text/text; charset="iso-8859-2" - Content-Transfer-Encoding: quoted-printable - - =BF - """)) - - -# Test long header wrapping -class TestLongHeaders(TestEmailBase): - - maxDiff = None - - def test_split_long_continuation(self): - eq = self.ndiffAssertEqual - msg = email.message_from_string("""\ -Subject: bug demonstration -\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789 -\tmore text - -test -""") - sfp = StringIO() - g = Generator(sfp) - g.flatten(msg) - eq(sfp.getvalue(), """\ -Subject: bug demonstration -\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789 -\tmore text - -test -""") - - def test_another_long_almost_unsplittable_header(self): - eq = self.ndiffAssertEqual - hstr = """\ -bug demonstration -\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789 -\tmore text""" - h = Header(hstr, continuation_ws='\t') - eq(h.encode(), """\ -bug demonstration -\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789 -\tmore text""") - h = Header(hstr.replace('\t', ' ')) - eq(h.encode(), """\ -bug demonstration - 12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789 - more text""") - - def test_long_nonstring(self): - eq = self.ndiffAssertEqual - g = Charset("iso-8859-1") - cz = Charset("iso-8859-2") - utf8 = Charset("utf-8") - g_head = (b'Die Mieter treten hier ein werden mit einem Foerderband ' - b'komfortabel den Korridor entlang, an s\xfcdl\xfcndischen ' - b'Wandgem\xe4lden vorbei, gegen die rotierenden Klingen ' - b'bef\xf6rdert. ') - cz_head = (b'Finan\xe8ni metropole se hroutily pod tlakem jejich ' - b'd\xf9vtipu.. ') - utf8_head = ('\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f' - '\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00' - '\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c' - '\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067' - '\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das ' - 'Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder ' - 'die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066' - '\u3044\u307e\u3059\u3002') - h = Header(g_head, g, header_name='Subject') - h.append(cz_head, cz) - h.append(utf8_head, utf8) - msg = Message() - msg['Subject'] = h - sfp = StringIO() - g = Generator(sfp) - g.flatten(msg) - eq(sfp.getvalue(), """\ -Subject: =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerderb?= - =?iso-8859-1?q?and_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndischen?= - =?iso-8859-1?q?_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Klingen_bef?= - =?iso-8859-1?q?=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se_hrouti?= - =?iso-8859-2?q?ly_pod_tlakem_jejich_d=F9vtipu=2E=2E_?= =?utf-8?b?5q2j56K6?= - =?utf-8?b?44Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb44KT44CC5LiA?= - =?utf-8?b?6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go44Gv44Gn44Gf44KJ?= - =?utf-8?b?44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGFzIE51bnN0dWNr?= - =?utf-8?b?IGdpdCB1bmQgU2xvdGVybWV5ZXI/IEphISBCZWloZXJodW5kIGRhcyBPZGVyIGRp?= - =?utf-8?b?ZSBGbGlwcGVyd2FsZHQgZ2Vyc3B1dC7jgI3jgajoqIDjgaPjgabjgYTjgb7jgZk=?= - =?utf-8?b?44CC?= - -""") - eq(h.encode(maxlinelen=76), """\ -=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerde?= - =?iso-8859-1?q?rband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndis?= - =?iso-8859-1?q?chen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Klinge?= - =?iso-8859-1?q?n_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se?= - =?iso-8859-2?q?_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?= - =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb?= - =?utf-8?b?44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go?= - =?utf-8?b?44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBp?= - =?utf-8?b?c3QgZGFzIE51bnN0dWNrIGdpdCB1bmQgU2xvdGVybWV5ZXI/IEphISBCZWlo?= - =?utf-8?b?ZXJodW5kIGRhcyBPZGVyIGRpZSBGbGlwcGVyd2FsZHQgZ2Vyc3B1dC7jgI0=?= - =?utf-8?b?44Go6KiA44Gj44Gm44GE44G+44GZ44CC?=""") - - def test_long_header_encode(self): - eq = self.ndiffAssertEqual - h = Header('wasnipoop; giraffes="very-long-necked-animals"; ' - 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"', - header_name='X-Foobar-Spoink-Defrobnit') - eq(h.encode(), '''\ -wasnipoop; giraffes="very-long-necked-animals"; - spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''') - - def test_long_header_encode_with_tab_continuation_is_just_a_hint(self): - eq = self.ndiffAssertEqual - h = Header('wasnipoop; giraffes="very-long-necked-animals"; ' - 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"', - header_name='X-Foobar-Spoink-Defrobnit', - continuation_ws='\t') - eq(h.encode(), '''\ -wasnipoop; giraffes="very-long-necked-animals"; - spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''') - - def test_long_header_encode_with_tab_continuation(self): - eq = self.ndiffAssertEqual - h = Header('wasnipoop; giraffes="very-long-necked-animals";\t' - 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"', - header_name='X-Foobar-Spoink-Defrobnit', - continuation_ws='\t') - eq(h.encode(), '''\ -wasnipoop; giraffes="very-long-necked-animals"; -\tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''') - - def test_header_encode_with_different_output_charset(self): - h = Header('文', 'euc-jp') - self.assertEqual(h.encode(), "=?iso-2022-jp?b?GyRCSjgbKEI=?=") - - def test_long_header_encode_with_different_output_charset(self): - h = Header(b'test-ja \xa4\xd8\xc5\xea\xb9\xc6\xa4\xb5\xa4\xec\xa4' - b'\xbf\xa5\xe1\xa1\xbc\xa5\xeb\xa4\xcf\xbb\xca\xb2\xf1\xbc\xd4' - b'\xa4\xce\xbe\xb5\xc7\xa7\xa4\xf2\xc2\xd4\xa4\xc3\xa4\xc6\xa4' - b'\xa4\xa4\xde\xa4\xb9'.decode('euc-jp'), 'euc-jp') - res = """\ -=?iso-2022-jp?b?dGVzdC1qYSAbJEIkWEVqOUYkNSRsJD8lYSE8JWskTztKMnE8VCROPjUbKEI=?= - =?iso-2022-jp?b?GyRCRyckckJUJEMkRiQkJF4kORsoQg==?=""" - self.assertEqual(h.encode(), res) - - def test_header_splitter(self): - eq = self.ndiffAssertEqual - msg = MIMEText('') - # It'd be great if we could use add_header() here, but that doesn't - # guarantee an order of the parameters. - msg['X-Foobar-Spoink-Defrobnit'] = ( - 'wasnipoop; giraffes="very-long-necked-animals"; ' - 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"') - sfp = StringIO() - g = Generator(sfp) - g.flatten(msg) - eq(sfp.getvalue(), '''\ -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" - -''') - - def test_no_semis_header_splitter(self): - eq = self.ndiffAssertEqual - msg = Message() - msg['From'] = 'test@dom.ain' - msg['References'] = SPACE.join('<%d@dom.ain>' % i for i in range(10)) - msg.set_payload('Test') - sfp = StringIO() - g = Generator(sfp) - g.flatten(msg) - eq(sfp.getvalue(), """\ -From: test@dom.ain -References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain> - <5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain> - -Test""") - - def test_last_split_chunk_does_not_fit(self): - eq = self.ndiffAssertEqual - h = Header('Subject: the first part of this is short, but_the_second' - '_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line' - '_all_by_itself') - eq(h.encode(), """\ -Subject: the first part of this is short, - but_the_second_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself""") - - def test_splittable_leading_char_followed_by_overlong_unsplitable(self): - eq = self.ndiffAssertEqual - h = Header(', but_the_second' - '_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line' - '_all_by_itself') - eq(h.encode(), """\ -, - but_the_second_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself""") - - def test_multiple_splittable_leading_char_followed_by_overlong_unsplitable(self): - eq = self.ndiffAssertEqual - h = Header(', , but_the_second' - '_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line' - '_all_by_itself') - eq(h.encode(), """\ -, , - but_the_second_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself""") - - def test_trailing_splitable_on_overlong_unsplitable(self): - eq = self.ndiffAssertEqual - h = Header('this_part_does_not_fit_within_maxlinelen_and_thus_should_' - 'be_on_a_line_all_by_itself;') - eq(h.encode(), "this_part_does_not_fit_within_maxlinelen_and_thus_should_" - "be_on_a_line_all_by_itself;") - - def test_trailing_splitable_on_overlong_unsplitable_with_leading_splitable(self): - eq = self.ndiffAssertEqual - h = Header('; ' - 'this_part_does_not_fit_within_maxlinelen_and_thus_should_' - 'be_on_a_line_all_by_itself; ') - eq(h.encode(), """\ -; - this_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself; """) - - def test_long_header_with_multiple_sequential_split_chars(self): - eq = self.ndiffAssertEqual - h = Header('This is a long line that has two whitespaces in a row. ' - 'This used to cause truncation of the header when folded') - eq(h.encode(), """\ -This is a long line that has two whitespaces in a row. This used to cause - truncation of the header when folded""") - - def test_splitter_split_on_punctuation_only_if_fws(self): - eq = self.ndiffAssertEqual - h = Header('thisverylongheaderhas;semicolons;and,commas,but' - 'they;arenotlegal;fold,points') - eq(h.encode(), "thisverylongheaderhas;semicolons;and,commas,butthey;" - "arenotlegal;fold,points") - - def test_leading_splittable_in_the_middle_just_before_overlong_last_part(self): - eq = self.ndiffAssertEqual - h = Header('this is a test where we need to have more than one line ' - 'before; our final line that is just too big to fit;; ' - 'this_part_does_not_fit_within_maxlinelen_and_thus_should_' - 'be_on_a_line_all_by_itself;') - eq(h.encode(), """\ -this is a test where we need to have more than one line before; - our final line that is just too big to fit;; - this_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself;""") - - def test_overlong_last_part_followed_by_split_point(self): - eq = self.ndiffAssertEqual - h = Header('this_part_does_not_fit_within_maxlinelen_and_thus_should_' - 'be_on_a_line_all_by_itself ') - eq(h.encode(), "this_part_does_not_fit_within_maxlinelen_and_thus_" - "should_be_on_a_line_all_by_itself ") - - def test_multiline_with_overlong_parts_separated_by_two_split_points(self): - eq = self.ndiffAssertEqual - h = Header('this_is_a__test_where_we_need_to_have_more_than_one_line_' - 'before_our_final_line_; ; ' - 'this_part_does_not_fit_within_maxlinelen_and_thus_should_' - 'be_on_a_line_all_by_itself; ') - eq(h.encode(), """\ -this_is_a__test_where_we_need_to_have_more_than_one_line_before_our_final_line_; - ; - this_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself; """) - - def test_multiline_with_overlong_last_part_followed_by_split_point(self): - eq = self.ndiffAssertEqual - h = Header('this is a test where we need to have more than one line ' - 'before our final line; ; ' - 'this_part_does_not_fit_within_maxlinelen_and_thus_should_' - 'be_on_a_line_all_by_itself; ') - eq(h.encode(), """\ -this is a test where we need to have more than one line before our final line; - ; - this_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself; """) - - def test_long_header_with_whitespace_runs(self): - eq = self.ndiffAssertEqual - msg = Message() - msg['From'] = 'test@dom.ain' - msg['References'] = SPACE.join([' '] * 10) - msg.set_payload('Test') - sfp = StringIO() - g = Generator(sfp) - g.flatten(msg) - eq(sfp.getvalue(), """\ -From: test@dom.ain -References: - - \x20\x20 - -Test""") - - def test_long_run_with_semi_header_splitter(self): - eq = self.ndiffAssertEqual - msg = Message() - msg['From'] = 'test@dom.ain' - msg['References'] = SPACE.join([''] * 10) + '; abc' - msg.set_payload('Test') - sfp = StringIO() - g = Generator(sfp) - g.flatten(msg) - eq(sfp.getvalue(), """\ -From: test@dom.ain -References: - - ; abc - -Test""") - - def test_splitter_split_on_punctuation_only_if_fws(self): - eq = self.ndiffAssertEqual - msg = Message() - msg['From'] = 'test@dom.ain' - msg['References'] = ('thisverylongheaderhas;semicolons;and,commas,but' - 'they;arenotlegal;fold,points') - msg.set_payload('Test') - sfp = StringIO() - g = Generator(sfp) - g.flatten(msg) - # XXX the space after the header should not be there. - eq(sfp.getvalue(), """\ -From: test@dom.ain -References:\x20 - thisverylongheaderhas;semicolons;and,commas,butthey;arenotlegal;fold,points - -Test""") - - def test_no_split_long_header(self): - eq = self.ndiffAssertEqual - hstr = 'References: ' + 'x' * 80 - h = Header(hstr) - # These come on two lines because Headers are really field value - # classes and don't really know about their field names. - eq(h.encode(), """\ -References: - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""") - h = Header('x' * 80) - eq(h.encode(), 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx') - - def test_splitting_multiple_long_lines(self): - eq = self.ndiffAssertEqual - hstr = """\ -from babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for ; Sat, 2 Feb 2002 17:00:06 -0800 (PST) -\tfrom babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for ; Sat, 2 Feb 2002 17:00:06 -0800 (PST) -\tfrom babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for ; Sat, 2 Feb 2002 17:00:06 -0800 (PST) -""" - h = Header(hstr, continuation_ws='\t') - eq(h.encode(), """\ -from babylon.socal-raves.org (localhost [127.0.0.1]); - by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; - for ; - Sat, 2 Feb 2002 17:00:06 -0800 (PST) -\tfrom babylon.socal-raves.org (localhost [127.0.0.1]); - by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; - for ; - Sat, 2 Feb 2002 17:00:06 -0800 (PST) -\tfrom babylon.socal-raves.org (localhost [127.0.0.1]); - by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; - for ; - Sat, 2 Feb 2002 17:00:06 -0800 (PST)""") - - def test_splitting_first_line_only_is_long(self): - eq = self.ndiffAssertEqual - hstr = """\ -from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93] helo=cthulhu.gerg.ca) -\tby kronos.mems-exchange.org with esmtp (Exim 4.05) -\tid 17k4h5-00034i-00 -\tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400""" - h = Header(hstr, maxlinelen=78, header_name='Received', - continuation_ws='\t') - eq(h.encode(), """\ -from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93] - helo=cthulhu.gerg.ca) -\tby kronos.mems-exchange.org with esmtp (Exim 4.05) -\tid 17k4h5-00034i-00 -\tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400""") - - def test_long_8bit_header(self): - eq = self.ndiffAssertEqual - msg = Message() - h = Header('Britische Regierung gibt', 'iso-8859-1', - header_name='Subject') - h.append('gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte') - eq(h.encode(maxlinelen=76), """\ -=?iso-8859-1?q?Britische_Regierung_gibt_gr=FCnes_Licht_f=FCr_Offs?= - =?iso-8859-1?q?hore-Windkraftprojekte?=""") - msg['Subject'] = h - eq(msg.as_string(maxheaderlen=76), """\ -Subject: =?iso-8859-1?q?Britische_Regierung_gibt_gr=FCnes_Licht_f=FCr_Offs?= - =?iso-8859-1?q?hore-Windkraftprojekte?= - -""") - eq(msg.as_string(maxheaderlen=0), """\ -Subject: =?iso-8859-1?q?Britische_Regierung_gibt_gr=FCnes_Licht_f=FCr_Offshore-Windkraftprojekte?= - -""") - - def test_long_8bit_header_no_charset(self): - eq = self.ndiffAssertEqual - msg = Message() - header_string = ('Britische Regierung gibt gr\xfcnes Licht ' - 'f\xfcr Offshore-Windkraftprojekte ' - '') - msg['Reply-To'] = header_string - eq(msg.as_string(maxheaderlen=78), """\ -Reply-To: =?utf-8?q?Britische_Regierung_gibt_gr=C3=BCnes_Licht_f=C3=BCr_Offs?= - =?utf-8?q?hore-Windkraftprojekte_=3Ca-very-long-address=40example=2Ecom=3E?= - -""") - msg = Message() - msg['Reply-To'] = Header(header_string, - header_name='Reply-To') - eq(msg.as_string(maxheaderlen=78), """\ -Reply-To: =?utf-8?q?Britische_Regierung_gibt_gr=C3=BCnes_Licht_f=C3=BCr_Offs?= - =?utf-8?q?hore-Windkraftprojekte_=3Ca-very-long-address=40example=2Ecom=3E?= - -""") - - def test_long_to_header(self): - eq = self.ndiffAssertEqual - to = ('"Someone Test #A" ,' - ', ' - '"Someone Test #B" , ' - '"Someone Test #C" , ' - '"Someone Test #D" ') - msg = Message() - msg['To'] = to - eq(msg.as_string(maxheaderlen=78), '''\ -To: "Someone Test #A" ,, - "Someone Test #B" , - "Someone Test #C" , - "Someone Test #D" - -''') - - def test_long_line_after_append(self): - eq = self.ndiffAssertEqual - s = 'This is an example of string which has almost the limit of header length.' - h = Header(s) - h.append('Add another line.') - eq(h.encode(maxlinelen=76), """\ -This is an example of string which has almost the limit of header length. - Add another line.""") - - def test_shorter_line_with_append(self): - eq = self.ndiffAssertEqual - s = 'This is a shorter line.' - h = Header(s) - h.append('Add another sentence. (Surprise?)') - eq(h.encode(), - 'This is a shorter line. Add another sentence. (Surprise?)') - - def test_long_field_name(self): - eq = self.ndiffAssertEqual - fn = 'X-Very-Very-Very-Long-Header-Name' - gs = ('Die Mieter treten hier ein werden mit einem Foerderband ' - 'komfortabel den Korridor entlang, an s\xfcdl\xfcndischen ' - 'Wandgem\xe4lden vorbei, gegen die rotierenden Klingen ' - 'bef\xf6rdert. ') - h = Header(gs, 'iso-8859-1', header_name=fn) - # BAW: this seems broken because the first line is too long - eq(h.encode(maxlinelen=76), """\ -=?iso-8859-1?q?Die_Mieter_treten_hier_e?= - =?iso-8859-1?q?in_werden_mit_einem_Foerderband_komfortabel_den_Korridor_e?= - =?iso-8859-1?q?ntlang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei=2C_ge?= - =?iso-8859-1?q?gen_die_rotierenden_Klingen_bef=F6rdert=2E_?=""") - - def test_long_received_header(self): - h = ('from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) ' - 'by hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP; ' - 'Wed, 05 Mar 2003 18:10:18 -0700') - msg = Message() - msg['Received-1'] = Header(h, continuation_ws='\t') - msg['Received-2'] = h - # This should be splitting on spaces not semicolons. - self.ndiffAssertEqual(msg.as_string(maxheaderlen=78), """\ -Received-1: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by - hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP; - Wed, 05 Mar 2003 18:10:18 -0700 -Received-2: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by - hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP; - Wed, 05 Mar 2003 18:10:18 -0700 - -""") - - def test_string_headerinst_eq(self): - h = ('<15975.17901.207240.414604@sgigritzmann1.mathematik.' - 'tu-muenchen.de> (David Bremner\'s message of ' - '"Thu, 6 Mar 2003 13:58:21 +0100")') - msg = Message() - msg['Received-1'] = Header(h, header_name='Received-1', - continuation_ws='\t') - msg['Received-2'] = h - # XXX The space after the ':' should not be there. - self.ndiffAssertEqual(msg.as_string(maxheaderlen=78), """\ -Received-1:\x20 - <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David - Bremner's message of \"Thu, 6 Mar 2003 13:58:21 +0100\") -Received-2:\x20 - <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David - Bremner's message of \"Thu, 6 Mar 2003 13:58:21 +0100\") - -""") - - def test_long_unbreakable_lines_with_continuation(self): - eq = self.ndiffAssertEqual - msg = Message() - t = """\ -iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9 - locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp""" - msg['Face-1'] = t - msg['Face-2'] = Header(t, header_name='Face-2') - msg['Face-3'] = ' ' + t - # XXX This splitting is all wrong. It the first value line should be - # snug against the field name or the space after the header not there. - eq(msg.as_string(maxheaderlen=78), """\ -Face-1:\x20 - iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9 - locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp -Face-2:\x20 - iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9 - locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp -Face-3:\x20 - iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9 - locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp - -""") - - def test_another_long_multiline_header(self): - eq = self.ndiffAssertEqual - m = ('Received: from siimage.com ' - '([172.25.1.3]) by zima.siliconimage.com with ' - 'Microsoft SMTPSVC(5.0.2195.4905); ' - 'Wed, 16 Oct 2002 07:41:11 -0700') - msg = email.message_from_string(m) - eq(msg.as_string(maxheaderlen=78), '''\ -Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with - Microsoft SMTPSVC(5.0.2195.4905); Wed, 16 Oct 2002 07:41:11 -0700 - -''') - - def test_long_lines_with_different_header(self): - eq = self.ndiffAssertEqual - h = ('List-Unsubscribe: ' - ',' - ' ') - msg = Message() - msg['List'] = h - msg['List'] = Header(h, header_name='List') - eq(msg.as_string(maxheaderlen=78), """\ -List: List-Unsubscribe: - , - -List: List-Unsubscribe: - , - - -""") - - def test_long_rfc2047_header_with_embedded_fws(self): - h = Header(textwrap.dedent("""\ - We're going to pretend this header is in a non-ascii character set - \tto see if line wrapping with encoded words and embedded - folding white space works"""), - charset='utf-8', - header_name='Test') - self.assertEqual(h.encode()+'\n', textwrap.dedent("""\ - =?utf-8?q?We=27re_going_to_pretend_this_header_is_in_a_non-ascii_chara?= - =?utf-8?q?cter_set?= - =?utf-8?q?_to_see_if_line_wrapping_with_encoded_words_and_embedded?= - =?utf-8?q?_folding_white_space_works?=""")+'\n') - - - -# Test mangling of "From " lines in the body of a message -class TestFromMangling(unittest.TestCase): - def setUp(self): - self.msg = Message() - self.msg['From'] = 'aaa@bbb.org' - self.msg.set_payload("""\ -From the desk of A.A.A.: -Blah blah blah -""") - - def test_mangled_from(self): - s = StringIO() - g = Generator(s, mangle_from_=True) - g.flatten(self.msg) - self.assertEqual(s.getvalue(), """\ -From: aaa@bbb.org - ->From the desk of A.A.A.: -Blah blah blah -""") - - def test_dont_mangle_from(self): - s = StringIO() - g = Generator(s, mangle_from_=False) - g.flatten(self.msg) - self.assertEqual(s.getvalue(), """\ -From: aaa@bbb.org - -From the desk of A.A.A.: -Blah blah blah -""") - - def test_mangle_from_in_preamble_and_epilog(self): - s = StringIO() - g = Generator(s, mangle_from_=True) - msg = email.message_from_string(textwrap.dedent("""\ - From: foo@bar.com - Mime-Version: 1.0 - Content-Type: multipart/mixed; boundary=XXX - - From somewhere unknown - - --XXX - Content-Type: text/plain - - foo - - --XXX-- - - From somewhere unknowable - """)) - g.flatten(msg) - self.assertEqual(len([1 for x in s.getvalue().split('\n') - if x.startswith('>From ')]), 2) - - def test_mangled_from_with_bad_bytes(self): - source = textwrap.dedent("""\ - Content-Type: text/plain; charset="utf-8" - MIME-Version: 1.0 - Content-Transfer-Encoding: 8bit - From: aaa@bbb.org - - """).encode('utf-8') - msg = email.message_from_bytes(source + b'From R\xc3\xb6lli\n') - b = BytesIO() - g = BytesGenerator(b, mangle_from_=True) - g.flatten(msg) - self.assertEqual(b.getvalue(), source + b'>From R\xc3\xb6lli\n') - - -# Test the basic MIMEAudio class -class TestMIMEAudio(unittest.TestCase): - def setUp(self): - with openfile('audiotest.au', 'rb') as fp: - self._audiodata = fp.read() - self._au = MIMEAudio(self._audiodata) - - def test_guess_minor_type(self): - self.assertEqual(self._au.get_content_type(), 'audio/basic') - - def test_encoding(self): - payload = self._au.get_payload() - self.assertEqual(base64.decodebytes(bytes(payload, 'ascii')), - self._audiodata) - - def test_checkSetMinor(self): - au = MIMEAudio(self._audiodata, 'fish') - self.assertEqual(au.get_content_type(), 'audio/fish') - - def test_add_header(self): - eq = self.assertEqual - unless = self.assertTrue - self._au.add_header('Content-Disposition', 'attachment', - filename='audiotest.au') - eq(self._au['content-disposition'], - 'attachment; filename="audiotest.au"') - eq(self._au.get_params(header='content-disposition'), - [('attachment', ''), ('filename', 'audiotest.au')]) - eq(self._au.get_param('filename', header='content-disposition'), - 'audiotest.au') - missing = [] - eq(self._au.get_param('attachment', header='content-disposition'), '') - unless(self._au.get_param('foo', failobj=missing, - header='content-disposition') is missing) - # Try some missing stuff - unless(self._au.get_param('foobar', missing) is missing) - unless(self._au.get_param('attachment', missing, - header='foobar') is missing) - - - -# Test the basic MIMEImage class -class TestMIMEImage(unittest.TestCase): - def setUp(self): - with openfile('PyBanner048.gif', 'rb') as fp: - self._imgdata = fp.read() - self._im = MIMEImage(self._imgdata) - - def test_guess_minor_type(self): - self.assertEqual(self._im.get_content_type(), 'image/gif') - - def test_encoding(self): - payload = self._im.get_payload() - self.assertEqual(base64.decodebytes(bytes(payload, 'ascii')), - self._imgdata) - - def test_checkSetMinor(self): - im = MIMEImage(self._imgdata, 'fish') - self.assertEqual(im.get_content_type(), 'image/fish') - - def test_add_header(self): - eq = self.assertEqual - unless = self.assertTrue - self._im.add_header('Content-Disposition', 'attachment', - filename='dingusfish.gif') - eq(self._im['content-disposition'], - 'attachment; filename="dingusfish.gif"') - eq(self._im.get_params(header='content-disposition'), - [('attachment', ''), ('filename', 'dingusfish.gif')]) - eq(self._im.get_param('filename', header='content-disposition'), - 'dingusfish.gif') - missing = [] - eq(self._im.get_param('attachment', header='content-disposition'), '') - unless(self._im.get_param('foo', failobj=missing, - header='content-disposition') is missing) - # Try some missing stuff - unless(self._im.get_param('foobar', missing) is missing) - unless(self._im.get_param('attachment', missing, - header='foobar') is missing) - - - -# Test the basic MIMEApplication class -class TestMIMEApplication(unittest.TestCase): - def test_headers(self): - eq = self.assertEqual - msg = MIMEApplication(b'\xfa\xfb\xfc\xfd\xfe\xff') - eq(msg.get_content_type(), 'application/octet-stream') - eq(msg['content-transfer-encoding'], 'base64') - - def test_body(self): - eq = self.assertEqual - bytesdata = b'\xfa\xfb\xfc\xfd\xfe\xff' - msg = MIMEApplication(bytesdata) - # whitespace in the cte encoded block is RFC-irrelevant. - eq(msg.get_payload().strip(), '+vv8/f7/') - eq(msg.get_payload(decode=True), bytesdata) - - def test_binary_body_with_encode_7or8bit(self): - # Issue 17171. - bytesdata = b'\xfa\xfb\xfc\xfd\xfe\xff' - msg = MIMEApplication(bytesdata, _encoder=encoders.encode_7or8bit) - # Treated as a string, this will be invalid code points. - self.assertEqual(msg.get_payload(), '\uFFFD' * len(bytesdata)) - self.assertEqual(msg.get_payload(decode=True), bytesdata) - self.assertEqual(msg['Content-Transfer-Encoding'], '8bit') - s = BytesIO() - g = BytesGenerator(s) - g.flatten(msg) - wireform = s.getvalue() - msg2 = email.message_from_bytes(wireform) - self.assertEqual(msg.get_payload(), '\uFFFD' * len(bytesdata)) - self.assertEqual(msg2.get_payload(decode=True), bytesdata) - self.assertEqual(msg2['Content-Transfer-Encoding'], '8bit') - - def test_binary_body_with_encode_noop(self): - # Issue 16564: This does not produce an RFC valid message, since to be - # valid it should have a CTE of binary. But the below works in - # Python2, and is documented as working this way. - bytesdata = b'\xfa\xfb\xfc\xfd\xfe\xff' - msg = MIMEApplication(bytesdata, _encoder=encoders.encode_noop) - # Treated as a string, this will be invalid code points. - self.assertEqual(msg.get_payload(), '\uFFFD' * len(bytesdata)) - self.assertEqual(msg.get_payload(decode=True), bytesdata) - s = BytesIO() - g = BytesGenerator(s) - g.flatten(msg) - wireform = s.getvalue() - msg2 = email.message_from_bytes(wireform) - self.assertEqual(msg.get_payload(), '\uFFFD' * len(bytesdata)) - self.assertEqual(msg2.get_payload(decode=True), bytesdata) - - -# Test the basic MIMEText class -class TestMIMEText(unittest.TestCase): - def setUp(self): - self._msg = MIMEText('hello there') - - def test_types(self): - eq = self.assertEqual - unless = self.assertTrue - eq(self._msg.get_content_type(), 'text/plain') - eq(self._msg.get_param('charset'), 'us-ascii') - missing = [] - unless(self._msg.get_param('foobar', missing) is missing) - unless(self._msg.get_param('charset', missing, header='foobar') - is missing) - - def test_payload(self): - self.assertEqual(self._msg.get_payload(), 'hello there') - self.assertTrue(not self._msg.is_multipart()) - - def test_charset(self): - eq = self.assertEqual - msg = MIMEText('hello there', _charset='us-ascii') - eq(msg.get_charset().input_charset, 'us-ascii') - eq(msg['content-type'], 'text/plain; charset="us-ascii"') - - def test_7bit_input(self): - eq = self.assertEqual - msg = MIMEText('hello there', _charset='us-ascii') - eq(msg.get_charset().input_charset, 'us-ascii') - eq(msg['content-type'], 'text/plain; charset="us-ascii"') - - def test_7bit_input_no_charset(self): - eq = self.assertEqual - msg = MIMEText('hello there') - eq(msg.get_charset(), 'us-ascii') - eq(msg['content-type'], 'text/plain; charset="us-ascii"') - self.assertTrue('hello there' in msg.as_string()) - - def test_utf8_input(self): - teststr = '\u043a\u0438\u0440\u0438\u043b\u0438\u0446\u0430' - eq = self.assertEqual - msg = MIMEText(teststr, _charset='utf-8') - eq(msg.get_charset().output_charset, 'utf-8') - eq(msg['content-type'], 'text/plain; charset="utf-8"') - eq(msg.get_payload(decode=True), teststr.encode('utf-8')) - - @unittest.skip("can't fix because of backward compat in email5, " - "will fix in email6") - def test_utf8_input_no_charset(self): - teststr = '\u043a\u0438\u0440\u0438\u043b\u0438\u0446\u0430' - self.assertRaises(UnicodeEncodeError, MIMEText, teststr) - - - -# Test complicated multipart/* messages -class TestMultipart(TestEmailBase): - def setUp(self): - with openfile('PyBanner048.gif', 'rb') as fp: - data = fp.read() - container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY') - image = MIMEImage(data, name='dingusfish.gif') - image.add_header('content-disposition', 'attachment', - filename='dingusfish.gif') - intro = MIMEText('''\ -Hi there, - -This is the dingus fish. -''') - container.attach(intro) - container.attach(image) - container['From'] = 'Barry ' - container['To'] = 'Dingus Lovers ' - container['Subject'] = 'Here is your dingus fish' - - now = 987809702.54848599 - timetuple = time.localtime(now) - if timetuple[-1] == 0: - tzsecs = time.timezone - else: - tzsecs = time.altzone - if tzsecs > 0: - sign = '-' - else: - sign = '+' - tzoffset = ' %s%04d' % (sign, tzsecs / 36) - container['Date'] = time.strftime( - '%a, %d %b %Y %H:%M:%S', - time.localtime(now)) + tzoffset - self._msg = container - self._im = image - self._txt = intro - - def test_hierarchy(self): - # convenience - eq = self.assertEqual - unless = self.assertTrue - raises = self.assertRaises - # tests - m = self._msg - unless(m.is_multipart()) - eq(m.get_content_type(), 'multipart/mixed') - eq(len(m.get_payload()), 2) - raises(IndexError, m.get_payload, 2) - m0 = m.get_payload(0) - m1 = m.get_payload(1) - unless(m0 is self._txt) - unless(m1 is self._im) - eq(m.get_payload(), [m0, m1]) - unless(not m0.is_multipart()) - unless(not m1.is_multipart()) - - def test_empty_multipart_idempotent(self): - text = """\ -Content-Type: multipart/mixed; boundary="BOUNDARY" -MIME-Version: 1.0 -Subject: A subject -To: aperson@dom.ain -From: bperson@dom.ain - - ---BOUNDARY - - ---BOUNDARY-- -""" - msg = Parser().parsestr(text) - self.ndiffAssertEqual(text, msg.as_string()) - - def test_no_parts_in_a_multipart_with_none_epilogue(self): - outer = MIMEBase('multipart', 'mixed') - outer['Subject'] = 'A subject' - outer['To'] = 'aperson@dom.ain' - outer['From'] = 'bperson@dom.ain' - outer.set_boundary('BOUNDARY') - self.ndiffAssertEqual(outer.as_string(), '''\ -Content-Type: multipart/mixed; boundary="BOUNDARY" -MIME-Version: 1.0 -Subject: A subject -To: aperson@dom.ain -From: bperson@dom.ain - ---BOUNDARY - ---BOUNDARY--''') - - def test_no_parts_in_a_multipart_with_empty_epilogue(self): - outer = MIMEBase('multipart', 'mixed') - outer['Subject'] = 'A subject' - outer['To'] = 'aperson@dom.ain' - outer['From'] = 'bperson@dom.ain' - outer.preamble = '' - outer.epilogue = '' - outer.set_boundary('BOUNDARY') - self.ndiffAssertEqual(outer.as_string(), '''\ -Content-Type: multipart/mixed; boundary="BOUNDARY" -MIME-Version: 1.0 -Subject: A subject -To: aperson@dom.ain -From: bperson@dom.ain - - ---BOUNDARY - ---BOUNDARY-- -''') - - def test_one_part_in_a_multipart(self): - eq = self.ndiffAssertEqual - outer = MIMEBase('multipart', 'mixed') - outer['Subject'] = 'A subject' - outer['To'] = 'aperson@dom.ain' - outer['From'] = 'bperson@dom.ain' - outer.set_boundary('BOUNDARY') - msg = MIMEText('hello world') - outer.attach(msg) - eq(outer.as_string(), '''\ -Content-Type: multipart/mixed; boundary="BOUNDARY" -MIME-Version: 1.0 -Subject: A subject -To: aperson@dom.ain -From: bperson@dom.ain - ---BOUNDARY -Content-Type: text/plain; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit - -hello world ---BOUNDARY--''') - - def test_seq_parts_in_a_multipart_with_empty_preamble(self): - eq = self.ndiffAssertEqual - outer = MIMEBase('multipart', 'mixed') - outer['Subject'] = 'A subject' - outer['To'] = 'aperson@dom.ain' - outer['From'] = 'bperson@dom.ain' - outer.preamble = '' - msg = MIMEText('hello world') - outer.attach(msg) - outer.set_boundary('BOUNDARY') - eq(outer.as_string(), '''\ -Content-Type: multipart/mixed; boundary="BOUNDARY" -MIME-Version: 1.0 -Subject: A subject -To: aperson@dom.ain -From: bperson@dom.ain - - ---BOUNDARY -Content-Type: text/plain; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit - -hello world ---BOUNDARY--''') - - - def test_seq_parts_in_a_multipart_with_none_preamble(self): - eq = self.ndiffAssertEqual - outer = MIMEBase('multipart', 'mixed') - outer['Subject'] = 'A subject' - outer['To'] = 'aperson@dom.ain' - outer['From'] = 'bperson@dom.ain' - outer.preamble = None - msg = MIMEText('hello world') - outer.attach(msg) - outer.set_boundary('BOUNDARY') - eq(outer.as_string(), '''\ -Content-Type: multipart/mixed; boundary="BOUNDARY" -MIME-Version: 1.0 -Subject: A subject -To: aperson@dom.ain -From: bperson@dom.ain - ---BOUNDARY -Content-Type: text/plain; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit - -hello world ---BOUNDARY--''') - - - def test_seq_parts_in_a_multipart_with_none_epilogue(self): - eq = self.ndiffAssertEqual - outer = MIMEBase('multipart', 'mixed') - outer['Subject'] = 'A subject' - outer['To'] = 'aperson@dom.ain' - outer['From'] = 'bperson@dom.ain' - outer.epilogue = None - msg = MIMEText('hello world') - outer.attach(msg) - outer.set_boundary('BOUNDARY') - eq(outer.as_string(), '''\ -Content-Type: multipart/mixed; boundary="BOUNDARY" -MIME-Version: 1.0 -Subject: A subject -To: aperson@dom.ain -From: bperson@dom.ain - ---BOUNDARY -Content-Type: text/plain; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit - -hello world ---BOUNDARY--''') - - - def test_seq_parts_in_a_multipart_with_empty_epilogue(self): - eq = self.ndiffAssertEqual - outer = MIMEBase('multipart', 'mixed') - outer['Subject'] = 'A subject' - outer['To'] = 'aperson@dom.ain' - outer['From'] = 'bperson@dom.ain' - outer.epilogue = '' - msg = MIMEText('hello world') - outer.attach(msg) - outer.set_boundary('BOUNDARY') - eq(outer.as_string(), '''\ -Content-Type: multipart/mixed; boundary="BOUNDARY" -MIME-Version: 1.0 -Subject: A subject -To: aperson@dom.ain -From: bperson@dom.ain - ---BOUNDARY -Content-Type: text/plain; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit - -hello world ---BOUNDARY-- -''') - - - def test_seq_parts_in_a_multipart_with_nl_epilogue(self): - eq = self.ndiffAssertEqual - outer = MIMEBase('multipart', 'mixed') - outer['Subject'] = 'A subject' - outer['To'] = 'aperson@dom.ain' - outer['From'] = 'bperson@dom.ain' - outer.epilogue = '\n' - msg = MIMEText('hello world') - outer.attach(msg) - outer.set_boundary('BOUNDARY') - eq(outer.as_string(), '''\ -Content-Type: multipart/mixed; boundary="BOUNDARY" -MIME-Version: 1.0 -Subject: A subject -To: aperson@dom.ain -From: bperson@dom.ain - ---BOUNDARY -Content-Type: text/plain; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit - -hello world ---BOUNDARY-- - -''') - - def test_message_external_body(self): - eq = self.assertEqual - msg = self._msgobj('msg_36.txt') - eq(len(msg.get_payload()), 2) - msg1 = msg.get_payload(1) - eq(msg1.get_content_type(), 'multipart/alternative') - eq(len(msg1.get_payload()), 2) - for subpart in msg1.get_payload(): - eq(subpart.get_content_type(), 'message/external-body') - eq(len(subpart.get_payload()), 1) - subsubpart = subpart.get_payload(0) - eq(subsubpart.get_content_type(), 'text/plain') - - def test_double_boundary(self): - # msg_37.txt is a multipart that contains two dash-boundary's in a - # row. Our interpretation of RFC 2046 calls for ignoring the second - # and subsequent boundaries. - msg = self._msgobj('msg_37.txt') - self.assertEqual(len(msg.get_payload()), 3) - - def test_nested_inner_contains_outer_boundary(self): - eq = self.ndiffAssertEqual - # msg_38.txt has an inner part that contains outer boundaries. My - # interpretation of RFC 2046 (based on sections 5.1 and 5.1.2) say - # these are illegal and should be interpreted as unterminated inner - # parts. - msg = self._msgobj('msg_38.txt') - sfp = StringIO() - iterators._structure(msg, sfp) - eq(sfp.getvalue(), """\ -multipart/mixed - multipart/mixed - multipart/alternative - text/plain - text/plain - text/plain - text/plain -""") - - def test_nested_with_same_boundary(self): - eq = self.ndiffAssertEqual - # msg 39.txt is similarly evil in that it's got inner parts that use - # the same boundary as outer parts. Again, I believe the way this is - # parsed is closest to the spirit of RFC 2046 - msg = self._msgobj('msg_39.txt') - sfp = StringIO() - iterators._structure(msg, sfp) - eq(sfp.getvalue(), """\ -multipart/mixed - multipart/mixed - multipart/alternative - application/octet-stream - application/octet-stream - text/plain -""") - - def test_boundary_in_non_multipart(self): - msg = self._msgobj('msg_40.txt') - self.assertEqual(msg.as_string(), '''\ -MIME-Version: 1.0 -Content-Type: text/html; boundary="--961284236552522269" - -----961284236552522269 -Content-Type: text/html; -Content-Transfer-Encoding: 7Bit - - - -----961284236552522269-- -''') - - def test_boundary_with_leading_space(self): - eq = self.assertEqual - msg = email.message_from_string('''\ -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary=" XXXX" - --- XXXX -Content-Type: text/plain - - --- XXXX -Content-Type: text/plain - --- XXXX-- -''') - self.assertTrue(msg.is_multipart()) - eq(msg.get_boundary(), ' XXXX') - eq(len(msg.get_payload()), 2) - - def test_boundary_without_trailing_newline(self): - m = Parser().parsestr("""\ -Content-Type: multipart/mixed; boundary="===============0012394164==" -MIME-Version: 1.0 - ---===============0012394164== -Content-Type: image/file1.jpg -MIME-Version: 1.0 -Content-Transfer-Encoding: base64 - -YXNkZg== ---===============0012394164==--""") - self.assertEqual(m.get_payload(0).get_payload(), 'YXNkZg==') - - - -# Test some badly formatted messages -class TestNonConformant(TestEmailBase): - - def test_parse_missing_minor_type(self): - eq = self.assertEqual - msg = self._msgobj('msg_14.txt') - eq(msg.get_content_type(), 'text/plain') - eq(msg.get_content_maintype(), 'text') - eq(msg.get_content_subtype(), 'plain') - - # test_defect_handling - def test_same_boundary_inner_outer(self): - unless = self.assertTrue - msg = self._msgobj('msg_15.txt') - # XXX We can probably eventually do better - inner = msg.get_payload(0) - unless(hasattr(inner, 'defects')) - self.assertEqual(len(inner.defects), 1) - unless(isinstance(inner.defects[0], - errors.StartBoundaryNotFoundDefect)) - - # test_defect_handling - def test_multipart_no_boundary(self): - unless = self.assertTrue - msg = self._msgobj('msg_25.txt') - unless(isinstance(msg.get_payload(), str)) - self.assertEqual(len(msg.defects), 2) - unless(isinstance(msg.defects[0], - errors.NoBoundaryInMultipartDefect)) - unless(isinstance(msg.defects[1], - errors.MultipartInvariantViolationDefect)) - - multipart_msg = textwrap.dedent("""\ - Date: Wed, 14 Nov 2007 12:56:23 GMT - From: foo@bar.invalid - To: foo@bar.invalid - Subject: Content-Transfer-Encoding: base64 and multipart - MIME-Version: 1.0 - Content-Type: multipart/mixed; - boundary="===============3344438784458119861=="{} - - --===============3344438784458119861== - Content-Type: text/plain - - Test message - - --===============3344438784458119861== - Content-Type: application/octet-stream - Content-Transfer-Encoding: base64 - - YWJj - - --===============3344438784458119861==-- - """) - - # test_defect_handling - def test_multipart_invalid_cte(self): - msg = self._str_msg( - self.multipart_msg.format("\nContent-Transfer-Encoding: base64")) - self.assertEqual(len(msg.defects), 1) - self.assertIsInstance(msg.defects[0], - errors.InvalidMultipartContentTransferEncodingDefect) - - # test_defect_handling - def test_multipart_no_cte_no_defect(self): - msg = self._str_msg(self.multipart_msg.format('')) - self.assertEqual(len(msg.defects), 0) - - # test_defect_handling - def test_multipart_valid_cte_no_defect(self): - for cte in ('7bit', '8bit', 'BINary'): - msg = self._str_msg( - self.multipart_msg.format( - "\nContent-Transfer-Encoding: {}".format(cte))) - self.assertEqual(len(msg.defects), 0) - - # test_headerregistry.TestContentTyopeHeader invalid_1 and invalid_2. - def test_invalid_content_type(self): - eq = self.assertEqual - neq = self.ndiffAssertEqual - msg = Message() - # RFC 2045, $5.2 says invalid yields text/plain - msg['Content-Type'] = 'text' - eq(msg.get_content_maintype(), 'text') - eq(msg.get_content_subtype(), 'plain') - eq(msg.get_content_type(), 'text/plain') - # Clear the old value and try something /really/ invalid - del msg['content-type'] - msg['Content-Type'] = 'foo' - eq(msg.get_content_maintype(), 'text') - eq(msg.get_content_subtype(), 'plain') - eq(msg.get_content_type(), 'text/plain') - # Still, make sure that the message is idempotently generated - s = StringIO() - g = Generator(s) - g.flatten(msg) - neq(s.getvalue(), 'Content-Type: foo\n\n') - - def test_no_start_boundary(self): - eq = self.ndiffAssertEqual - msg = self._msgobj('msg_31.txt') - eq(msg.get_payload(), """\ ---BOUNDARY -Content-Type: text/plain - -message 1 - ---BOUNDARY -Content-Type: text/plain - -message 2 - ---BOUNDARY-- -""") - - def test_no_separating_blank_line(self): - eq = self.ndiffAssertEqual - msg = self._msgobj('msg_35.txt') - eq(msg.as_string(), """\ -From: aperson@dom.ain -To: bperson@dom.ain -Subject: here's something interesting - -counter to RFC 2822, there's no separating newline here -""") - - # test_defect_handling - def test_lying_multipart(self): - unless = self.assertTrue - msg = self._msgobj('msg_41.txt') - unless(hasattr(msg, 'defects')) - self.assertEqual(len(msg.defects), 2) - unless(isinstance(msg.defects[0], - errors.NoBoundaryInMultipartDefect)) - unless(isinstance(msg.defects[1], - errors.MultipartInvariantViolationDefect)) - - # test_defect_handling - def test_missing_start_boundary(self): - outer = self._msgobj('msg_42.txt') - # The message structure is: - # - # multipart/mixed - # text/plain - # message/rfc822 - # multipart/mixed [*] - # - # [*] This message is missing its start boundary - bad = outer.get_payload(1).get_payload(0) - self.assertEqual(len(bad.defects), 1) - self.assertTrue(isinstance(bad.defects[0], - errors.StartBoundaryNotFoundDefect)) - - # test_defect_handling - def test_first_line_is_continuation_header(self): - eq = self.assertEqual - m = ' Line 1\nSubject: test\n\nbody' - msg = email.message_from_string(m) - eq(msg.keys(), ['Subject']) - eq(msg.get_payload(), 'body') - eq(len(msg.defects), 1) - self.assertDefectsEqual(msg.defects, - [errors.FirstHeaderLineIsContinuationDefect]) - eq(msg.defects[0].line, ' Line 1\n') - - # test_defect_handling - def test_missing_header_body_separator(self): - # Our heuristic if we see a line that doesn't look like a header (no - # leading whitespace but no ':') is to assume that the blank line that - # separates the header from the body is missing, and to stop parsing - # headers and start parsing the body. - msg = self._str_msg('Subject: test\nnot a header\nTo: abc\n\nb\n') - self.assertEqual(msg.keys(), ['Subject']) - self.assertEqual(msg.get_payload(), 'not a header\nTo: abc\n\nb\n') - self.assertDefectsEqual(msg.defects, - [errors.MissingHeaderBodySeparatorDefect]) - - -# Test RFC 2047 header encoding and decoding -class TestRFC2047(TestEmailBase): - def test_rfc2047_multiline(self): - eq = self.assertEqual - s = """Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz - foo bar =?mac-iceland?q?r=8Aksm=9Arg=8Cs?=""" - dh = decode_header(s) - eq(dh, [ - (b'Re: ', None), - (b'r\x8aksm\x9arg\x8cs', 'mac-iceland'), - (b' baz foo bar ', None), - (b'r\x8aksm\x9arg\x8cs', 'mac-iceland')]) - header = make_header(dh) - eq(str(header), - 'Re: r\xe4ksm\xf6rg\xe5s baz foo bar r\xe4ksm\xf6rg\xe5s') - self.ndiffAssertEqual(header.encode(maxlinelen=76), """\ -Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz foo bar =?mac-iceland?q?r=8Aksm?= - =?mac-iceland?q?=9Arg=8Cs?=""") - - def test_whitespace_keeper_unicode(self): - eq = self.assertEqual - s = '=?ISO-8859-1?Q?Andr=E9?= Pirard ' - dh = decode_header(s) - eq(dh, [(b'Andr\xe9', 'iso-8859-1'), - (b' Pirard ', None)]) - header = str(make_header(dh)) - eq(header, 'Andr\xe9 Pirard ') - - def test_whitespace_keeper_unicode_2(self): - eq = self.assertEqual - s = 'The =?iso-8859-1?b?cXVpY2sgYnJvd24gZm94?= jumped over the =?iso-8859-1?b?bGF6eSBkb2c=?=' - dh = decode_header(s) - eq(dh, [(b'The ', None), (b'quick brown fox', 'iso-8859-1'), - (b' jumped over the ', None), (b'lazy dog', 'iso-8859-1')]) - hu = str(make_header(dh)) - eq(hu, 'The quick brown fox jumped over the lazy dog') - - def test_rfc2047_missing_whitespace(self): - s = 'Sm=?ISO-8859-1?B?9g==?=rg=?ISO-8859-1?B?5Q==?=sbord' - dh = decode_header(s) - self.assertEqual(dh, [(b'Sm', None), (b'\xf6', 'iso-8859-1'), - (b'rg', None), (b'\xe5', 'iso-8859-1'), - (b'sbord', None)]) - - def test_rfc2047_with_whitespace(self): - s = 'Sm =?ISO-8859-1?B?9g==?= rg =?ISO-8859-1?B?5Q==?= sbord' - dh = decode_header(s) - self.assertEqual(dh, [(b'Sm ', None), (b'\xf6', 'iso-8859-1'), - (b' rg ', None), (b'\xe5', 'iso-8859-1'), - (b' sbord', None)]) - - def test_rfc2047_B_bad_padding(self): - s = '=?iso-8859-1?B?%s?=' - data = [ # only test complete bytes - ('dm==', b'v'), ('dm=', b'v'), ('dm', b'v'), - ('dmk=', b'vi'), ('dmk', b'vi') - ] - for q, a in data: - dh = decode_header(s % q) - self.assertEqual(dh, [(a, 'iso-8859-1')]) - - def test_rfc2047_Q_invalid_digits(self): - # issue 10004. - s = '=?iso-8659-1?Q?andr=e9=zz?=' - self.assertEqual(decode_header(s), - [(b'andr\xe9=zz', 'iso-8659-1')]) - - def test_rfc2047_rfc2047_1(self): - # 1st testcase at end of rfc2047 - s = '(=?ISO-8859-1?Q?a?=)' - self.assertEqual(decode_header(s), - [(b'(', None), (b'a', 'iso-8859-1'), (b')', None)]) - - def test_rfc2047_rfc2047_2(self): - # 2nd testcase at end of rfc2047 - s = '(=?ISO-8859-1?Q?a?= b)' - self.assertEqual(decode_header(s), - [(b'(', None), (b'a', 'iso-8859-1'), (b' b)', None)]) - - def test_rfc2047_rfc2047_3(self): - # 3rd testcase at end of rfc2047 - s = '(=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=)' - self.assertEqual(decode_header(s), - [(b'(', None), (b'ab', 'iso-8859-1'), (b')', None)]) - - def test_rfc2047_rfc2047_4(self): - # 4th testcase at end of rfc2047 - s = '(=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=)' - self.assertEqual(decode_header(s), - [(b'(', None), (b'ab', 'iso-8859-1'), (b')', None)]) - - def test_rfc2047_rfc2047_5a(self): - # 5th testcase at end of rfc2047 newline is \r\n - s = '(=?ISO-8859-1?Q?a?=\r\n =?ISO-8859-1?Q?b?=)' - self.assertEqual(decode_header(s), - [(b'(', None), (b'ab', 'iso-8859-1'), (b')', None)]) - - def test_rfc2047_rfc2047_5b(self): - # 5th testcase at end of rfc2047 newline is \n - s = '(=?ISO-8859-1?Q?a?=\n =?ISO-8859-1?Q?b?=)' - self.assertEqual(decode_header(s), - [(b'(', None), (b'ab', 'iso-8859-1'), (b')', None)]) - - def test_rfc2047_rfc2047_6(self): - # 6th testcase at end of rfc2047 - s = '(=?ISO-8859-1?Q?a_b?=)' - self.assertEqual(decode_header(s), - [(b'(', None), (b'a b', 'iso-8859-1'), (b')', None)]) - - def test_rfc2047_rfc2047_7(self): - # 7th testcase at end of rfc2047 - s = '(=?ISO-8859-1?Q?a?= =?ISO-8859-2?Q?_b?=)' - self.assertEqual(decode_header(s), - [(b'(', None), (b'a', 'iso-8859-1'), (b' b', 'iso-8859-2'), - (b')', None)]) - self.assertEqual(make_header(decode_header(s)).encode(), s.lower()) - self.assertEqual(str(make_header(decode_header(s))), '(a b)') - - def test_multiline_header(self): - s = '=?windows-1252?q?=22M=FCller_T=22?=\r\n ' - self.assertEqual(decode_header(s), - [(b'"M\xfcller T"', 'windows-1252'), - (b'', None)]) - self.assertEqual(make_header(decode_header(s)).encode(), - ''.join(s.splitlines())) - self.assertEqual(str(make_header(decode_header(s))), - '"Müller T" ') - - -# Test the MIMEMessage class -class TestMIMEMessage(TestEmailBase): - def setUp(self): - with openfile('msg_11.txt') as fp: - self._text = fp.read() - - def test_type_error(self): - self.assertRaises(TypeError, MIMEMessage, 'a plain string') - - def test_valid_argument(self): - eq = self.assertEqual - unless = self.assertTrue - subject = 'A sub-message' - m = Message() - m['Subject'] = subject - r = MIMEMessage(m) - eq(r.get_content_type(), 'message/rfc822') - payload = r.get_payload() - unless(isinstance(payload, list)) - eq(len(payload), 1) - subpart = payload[0] - unless(subpart is m) - eq(subpart['subject'], subject) - - def test_bad_multipart(self): - eq = self.assertEqual - msg1 = Message() - msg1['Subject'] = 'subpart 1' - msg2 = Message() - msg2['Subject'] = 'subpart 2' - r = MIMEMessage(msg1) - self.assertRaises(errors.MultipartConversionError, r.attach, msg2) - - def test_generate(self): - # First craft the message to be encapsulated - m = Message() - m['Subject'] = 'An enclosed message' - m.set_payload('Here is the body of the message.\n') - r = MIMEMessage(m) - r['Subject'] = 'The enclosing message' - s = StringIO() - g = Generator(s) - g.flatten(r) - self.assertEqual(s.getvalue(), """\ -Content-Type: message/rfc822 -MIME-Version: 1.0 -Subject: The enclosing message - -Subject: An enclosed message - -Here is the body of the message. -""") - - def test_parse_message_rfc822(self): - eq = self.assertEqual - unless = self.assertTrue - msg = self._msgobj('msg_11.txt') - eq(msg.get_content_type(), 'message/rfc822') - payload = msg.get_payload() - unless(isinstance(payload, list)) - eq(len(payload), 1) - submsg = payload[0] - self.assertTrue(isinstance(submsg, Message)) - eq(submsg['subject'], 'An enclosed message') - eq(submsg.get_payload(), 'Here is the body of the message.\n') - - def test_dsn(self): - eq = self.assertEqual - unless = self.assertTrue - # msg 16 is a Delivery Status Notification, see RFC 1894 - msg = self._msgobj('msg_16.txt') - eq(msg.get_content_type(), 'multipart/report') - unless(msg.is_multipart()) - eq(len(msg.get_payload()), 3) - # Subpart 1 is a text/plain, human readable section - subpart = msg.get_payload(0) - eq(subpart.get_content_type(), 'text/plain') - eq(subpart.get_payload(), """\ -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 - -""") - # Subpart 2 contains the machine parsable DSN information. It - # consists of two blocks of headers, represented by two nested Message - # objects. - subpart = msg.get_payload(1) - eq(subpart.get_content_type(), 'message/delivery-status') - eq(len(subpart.get_payload()), 2) - # message/delivery-status should treat each block as a bunch of - # headers, i.e. a bunch of Message objects. - dsn1 = subpart.get_payload(0) - unless(isinstance(dsn1, Message)) - eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu') - eq(dsn1.get_param('dns', header='reporting-mta'), '') - # Try a missing one - eq(dsn1.get_param('nsd', header='reporting-mta'), None) - dsn2 = subpart.get_payload(1) - unless(isinstance(dsn2, Message)) - eq(dsn2['action'], 'failed') - eq(dsn2.get_params(header='original-recipient'), - [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')]) - eq(dsn2.get_param('rfc822', header='final-recipient'), '') - # Subpart 3 is the original message - subpart = msg.get_payload(2) - eq(subpart.get_content_type(), 'message/rfc822') - payload = subpart.get_payload() - unless(isinstance(payload, list)) - eq(len(payload), 1) - subsubpart = payload[0] - unless(isinstance(subsubpart, Message)) - eq(subsubpart.get_content_type(), 'text/plain') - eq(subsubpart['message-id'], - '<002001c144a6$8752e060$56104586@oxy.edu>') - - def test_epilogue(self): - eq = self.ndiffAssertEqual - with openfile('msg_21.txt') as fp: - text = fp.read() - msg = Message() - msg['From'] = 'aperson@dom.ain' - msg['To'] = 'bperson@dom.ain' - msg['Subject'] = 'Test' - msg.preamble = 'MIME message' - msg.epilogue = 'End of MIME message\n' - msg1 = MIMEText('One') - msg2 = MIMEText('Two') - msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY') - msg.attach(msg1) - msg.attach(msg2) - sfp = StringIO() - g = Generator(sfp) - g.flatten(msg) - eq(sfp.getvalue(), text) - - def test_no_nl_preamble(self): - eq = self.ndiffAssertEqual - msg = Message() - msg['From'] = 'aperson@dom.ain' - msg['To'] = 'bperson@dom.ain' - msg['Subject'] = 'Test' - msg.preamble = 'MIME message' - msg.epilogue = '' - msg1 = MIMEText('One') - msg2 = MIMEText('Two') - msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY') - msg.attach(msg1) - msg.attach(msg2) - eq(msg.as_string(), """\ -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-- -""") - - def test_default_type(self): - eq = self.assertEqual - with openfile('msg_30.txt') as fp: - msg = email.message_from_file(fp) - container1 = msg.get_payload(0) - eq(container1.get_default_type(), 'message/rfc822') - eq(container1.get_content_type(), 'message/rfc822') - container2 = msg.get_payload(1) - eq(container2.get_default_type(), 'message/rfc822') - eq(container2.get_content_type(), 'message/rfc822') - container1a = container1.get_payload(0) - eq(container1a.get_default_type(), 'text/plain') - eq(container1a.get_content_type(), 'text/plain') - container2a = container2.get_payload(0) - eq(container2a.get_default_type(), 'text/plain') - eq(container2a.get_content_type(), 'text/plain') - - def test_default_type_with_explicit_container_type(self): - eq = self.assertEqual - with openfile('msg_28.txt') as fp: - msg = email.message_from_file(fp) - container1 = msg.get_payload(0) - eq(container1.get_default_type(), 'message/rfc822') - eq(container1.get_content_type(), 'message/rfc822') - container2 = msg.get_payload(1) - eq(container2.get_default_type(), 'message/rfc822') - eq(container2.get_content_type(), 'message/rfc822') - container1a = container1.get_payload(0) - eq(container1a.get_default_type(), 'text/plain') - eq(container1a.get_content_type(), 'text/plain') - container2a = container2.get_payload(0) - eq(container2a.get_default_type(), 'text/plain') - eq(container2a.get_content_type(), 'text/plain') - - def test_default_type_non_parsed(self): - eq = self.assertEqual - neq = self.ndiffAssertEqual - # Set up container - container = MIMEMultipart('digest', 'BOUNDARY') - container.epilogue = '' - # Set up subparts - subpart1a = MIMEText('message 1\n') - subpart2a = MIMEText('message 2\n') - subpart1 = MIMEMessage(subpart1a) - subpart2 = MIMEMessage(subpart2a) - container.attach(subpart1) - container.attach(subpart2) - eq(subpart1.get_content_type(), 'message/rfc822') - eq(subpart1.get_default_type(), 'message/rfc822') - eq(subpart2.get_content_type(), 'message/rfc822') - eq(subpart2.get_default_type(), 'message/rfc822') - neq(container.as_string(0), '''\ -Content-Type: multipart/digest; boundary="BOUNDARY" -MIME-Version: 1.0 - ---BOUNDARY -Content-Type: message/rfc822 -MIME-Version: 1.0 - -Content-Type: text/plain; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit - -message 1 - ---BOUNDARY -Content-Type: message/rfc822 -MIME-Version: 1.0 - -Content-Type: text/plain; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit - -message 2 - ---BOUNDARY-- -''') - del subpart1['content-type'] - del subpart1['mime-version'] - del subpart2['content-type'] - del subpart2['mime-version'] - eq(subpart1.get_content_type(), 'message/rfc822') - eq(subpart1.get_default_type(), 'message/rfc822') - eq(subpart2.get_content_type(), 'message/rfc822') - eq(subpart2.get_default_type(), 'message/rfc822') - neq(container.as_string(0), '''\ -Content-Type: multipart/digest; boundary="BOUNDARY" -MIME-Version: 1.0 - ---BOUNDARY - -Content-Type: text/plain; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit - -message 1 - ---BOUNDARY - -Content-Type: text/plain; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit - -message 2 - ---BOUNDARY-- -''') - - def test_mime_attachments_in_constructor(self): - eq = self.assertEqual - text1 = MIMEText('') - text2 = MIMEText('') - msg = MIMEMultipart(_subparts=(text1, text2)) - eq(len(msg.get_payload()), 2) - eq(msg.get_payload(0), text1) - eq(msg.get_payload(1), text2) - - def test_default_multipart_constructor(self): - msg = MIMEMultipart() - self.assertTrue(msg.is_multipart()) - - -# A general test of parser->model->generator idempotency. IOW, read a message -# in, parse it into a message object tree, then without touching the tree, -# regenerate the plain text. The original text and the transformed text -# should be identical. Note: that we ignore the Unix-From since that may -# contain a changed date. -class TestIdempotent(TestEmailBase): - - linesep = '\n' - - def _msgobj(self, filename): - with openfile(filename) as fp: - data = fp.read() - msg = email.message_from_string(data) - return msg, data - - def _idempotent(self, msg, text, unixfrom=False): - eq = self.ndiffAssertEqual - s = StringIO() - g = Generator(s, maxheaderlen=0) - g.flatten(msg, unixfrom=unixfrom) - eq(text, s.getvalue()) - - def test_parse_text_message(self): - eq = self.assertEqual - msg, text = self._msgobj('msg_01.txt') - eq(msg.get_content_type(), 'text/plain') - eq(msg.get_content_maintype(), 'text') - eq(msg.get_content_subtype(), 'plain') - eq(msg.get_params()[1], ('charset', 'us-ascii')) - eq(msg.get_param('charset'), 'us-ascii') - eq(msg.preamble, None) - eq(msg.epilogue, None) - self._idempotent(msg, text) - - def test_parse_untyped_message(self): - eq = self.assertEqual - msg, text = self._msgobj('msg_03.txt') - eq(msg.get_content_type(), 'text/plain') - eq(msg.get_params(), None) - eq(msg.get_param('charset'), None) - self._idempotent(msg, text) - - def test_simple_multipart(self): - msg, text = self._msgobj('msg_04.txt') - self._idempotent(msg, text) - - def test_MIME_digest(self): - msg, text = self._msgobj('msg_02.txt') - self._idempotent(msg, text) - - def test_long_header(self): - msg, text = self._msgobj('msg_27.txt') - self._idempotent(msg, text) - - def test_MIME_digest_with_part_headers(self): - msg, text = self._msgobj('msg_28.txt') - self._idempotent(msg, text) - - def test_mixed_with_image(self): - msg, text = self._msgobj('msg_06.txt') - self._idempotent(msg, text) - - def test_multipart_report(self): - msg, text = self._msgobj('msg_05.txt') - self._idempotent(msg, text) - - def test_dsn(self): - msg, text = self._msgobj('msg_16.txt') - self._idempotent(msg, text) - - def test_preamble_epilogue(self): - msg, text = self._msgobj('msg_21.txt') - self._idempotent(msg, text) - - def test_multipart_one_part(self): - msg, text = self._msgobj('msg_23.txt') - self._idempotent(msg, text) - - def test_multipart_no_parts(self): - msg, text = self._msgobj('msg_24.txt') - self._idempotent(msg, text) - - def test_no_start_boundary(self): - msg, text = self._msgobj('msg_31.txt') - self._idempotent(msg, text) - - def test_rfc2231_charset(self): - msg, text = self._msgobj('msg_32.txt') - self._idempotent(msg, text) - - def test_more_rfc2231_parameters(self): - msg, text = self._msgobj('msg_33.txt') - self._idempotent(msg, text) - - def test_text_plain_in_a_multipart_digest(self): - msg, text = self._msgobj('msg_34.txt') - self._idempotent(msg, text) - - def test_nested_multipart_mixeds(self): - msg, text = self._msgobj('msg_12a.txt') - self._idempotent(msg, text) - - def test_message_external_body_idempotent(self): - msg, text = self._msgobj('msg_36.txt') - self._idempotent(msg, text) - - def test_message_delivery_status(self): - msg, text = self._msgobj('msg_43.txt') - self._idempotent(msg, text, unixfrom=True) - - def test_message_signed_idempotent(self): - msg, text = self._msgobj('msg_45.txt') - self._idempotent(msg, text) - - def test_content_type(self): - eq = self.assertEqual - unless = self.assertTrue - # Get a message object and reset the seek pointer for other tests - msg, text = self._msgobj('msg_05.txt') - eq(msg.get_content_type(), 'multipart/report') - # Test the Content-Type: parameters - params = {} - for pk, pv in msg.get_params(): - params[pk] = pv - eq(params['report-type'], 'delivery-status') - eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com') - eq(msg.preamble, 'This is a MIME-encapsulated message.' + self.linesep) - eq(msg.epilogue, self.linesep) - eq(len(msg.get_payload()), 3) - # Make sure the subparts are what we expect - msg1 = msg.get_payload(0) - eq(msg1.get_content_type(), 'text/plain') - eq(msg1.get_payload(), 'Yadda yadda yadda' + self.linesep) - msg2 = msg.get_payload(1) - eq(msg2.get_content_type(), 'text/plain') - eq(msg2.get_payload(), 'Yadda yadda yadda' + self.linesep) - msg3 = msg.get_payload(2) - eq(msg3.get_content_type(), 'message/rfc822') - self.assertTrue(isinstance(msg3, Message)) - payload = msg3.get_payload() - unless(isinstance(payload, list)) - eq(len(payload), 1) - msg4 = payload[0] - unless(isinstance(msg4, Message)) - eq(msg4.get_payload(), 'Yadda yadda yadda' + self.linesep) - - def test_parser(self): - eq = self.assertEqual - unless = self.assertTrue - msg, text = self._msgobj('msg_06.txt') - # Check some of the outer headers - eq(msg.get_content_type(), 'message/rfc822') - # Make sure the payload is a list of exactly one sub-Message, and that - # that submessage has a type of text/plain - payload = msg.get_payload() - unless(isinstance(payload, list)) - eq(len(payload), 1) - msg1 = payload[0] - self.assertTrue(isinstance(msg1, Message)) - eq(msg1.get_content_type(), 'text/plain') - self.assertTrue(isinstance(msg1.get_payload(), str)) - eq(msg1.get_payload(), self.linesep) - - - -# Test various other bits of the package's functionality -class TestMiscellaneous(TestEmailBase): - def test_message_from_string(self): - with openfile('msg_01.txt') as fp: - text = fp.read() - msg = email.message_from_string(text) - s = StringIO() - # Don't wrap/continue long headers since we're trying to test - # idempotency. - g = Generator(s, maxheaderlen=0) - g.flatten(msg) - self.assertEqual(text, s.getvalue()) - - def test_message_from_file(self): - with openfile('msg_01.txt') as fp: - text = fp.read() - fp.seek(0) - msg = email.message_from_file(fp) - s = StringIO() - # Don't wrap/continue long headers since we're trying to test - # idempotency. - g = Generator(s, maxheaderlen=0) - g.flatten(msg) - self.assertEqual(text, s.getvalue()) - - def test_message_from_string_with_class(self): - unless = self.assertTrue - with openfile('msg_01.txt') as fp: - text = fp.read() - - # Create a subclass - class MyMessage(Message): - pass - - msg = email.message_from_string(text, MyMessage) - unless(isinstance(msg, MyMessage)) - # Try something more complicated - with openfile('msg_02.txt') as fp: - text = fp.read() - msg = email.message_from_string(text, MyMessage) - for subpart in msg.walk(): - unless(isinstance(subpart, MyMessage)) - - def test_message_from_file_with_class(self): - unless = self.assertTrue - # Create a subclass - class MyMessage(Message): - pass - - with openfile('msg_01.txt') as fp: - msg = email.message_from_file(fp, MyMessage) - unless(isinstance(msg, MyMessage)) - # Try something more complicated - with openfile('msg_02.txt') as fp: - msg = email.message_from_file(fp, MyMessage) - for subpart in msg.walk(): - unless(isinstance(subpart, MyMessage)) - - def test_custom_message_does_not_require_arguments(self): - class MyMessage(Message): - def __init__(self): - super().__init__() - msg = self._str_msg("Subject: test\n\ntest", MyMessage) - self.assertTrue(isinstance(msg, MyMessage)) - - def test__all__(self): - module = __import__('email') - self.assertEqual(sorted(module.__all__), [ - 'base64mime', 'charset', 'encoders', 'errors', 'feedparser', - 'generator', 'header', 'iterators', 'message', - 'message_from_binary_file', 'message_from_bytes', - 'message_from_file', 'message_from_string', 'mime', 'parser', - 'quoprimime', 'utils', - ]) - - def test_formatdate(self): - now = time.time() - self.assertEqual(utils.parsedate(utils.formatdate(now))[:6], - time.gmtime(now)[:6]) - - def test_formatdate_localtime(self): - now = time.time() - self.assertEqual( - utils.parsedate(utils.formatdate(now, localtime=True))[:6], - time.localtime(now)[:6]) - - def test_formatdate_usegmt(self): - now = time.time() - self.assertEqual( - utils.formatdate(now, localtime=False), - time.strftime('%a, %d %b %Y %H:%M:%S -0000', time.gmtime(now))) - self.assertEqual( - utils.formatdate(now, localtime=False, usegmt=True), - time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(now))) - - # parsedate and parsedate_tz will become deprecated interfaces someday - def test_parsedate_returns_None_for_invalid_strings(self): - self.assertIsNone(utils.parsedate('')) - self.assertIsNone(utils.parsedate_tz('')) - self.assertIsNone(utils.parsedate('0')) - self.assertIsNone(utils.parsedate_tz('0')) - self.assertIsNone(utils.parsedate('A Complete Waste of Time')) - self.assertIsNone(utils.parsedate_tz('A Complete Waste of Time')) - # Not a part of the spec but, but this has historically worked: - self.assertIsNone(utils.parsedate(None)) - self.assertIsNone(utils.parsedate_tz(None)) - - def test_parsedate_compact(self): - # The FWS after the comma is optional - self.assertEqual(utils.parsedate('Wed,3 Apr 2002 14:58:26 +0800'), - utils.parsedate('Wed, 3 Apr 2002 14:58:26 +0800')) - - def test_parsedate_no_dayofweek(self): - eq = self.assertEqual - eq(utils.parsedate_tz('25 Feb 2003 13:47:26 -0800'), - (2003, 2, 25, 13, 47, 26, 0, 1, -1, -28800)) - - def test_parsedate_compact_no_dayofweek(self): - eq = self.assertEqual - eq(utils.parsedate_tz('5 Feb 2003 13:47:26 -0800'), - (2003, 2, 5, 13, 47, 26, 0, 1, -1, -28800)) - - def test_parsedate_no_space_before_positive_offset(self): - self.assertEqual(utils.parsedate_tz('Wed, 3 Apr 2002 14:58:26+0800'), - (2002, 4, 3, 14, 58, 26, 0, 1, -1, 28800)) - - def test_parsedate_no_space_before_negative_offset(self): - # Issue 1155362: we already handled '+' for this case. - self.assertEqual(utils.parsedate_tz('Wed, 3 Apr 2002 14:58:26-0800'), - (2002, 4, 3, 14, 58, 26, 0, 1, -1, -28800)) - - - def test_parsedate_accepts_time_with_dots(self): - eq = self.assertEqual - eq(utils.parsedate_tz('5 Feb 2003 13.47.26 -0800'), - (2003, 2, 5, 13, 47, 26, 0, 1, -1, -28800)) - eq(utils.parsedate_tz('5 Feb 2003 13.47 -0800'), - (2003, 2, 5, 13, 47, 0, 0, 1, -1, -28800)) - - def test_parsedate_acceptable_to_time_functions(self): - eq = self.assertEqual - timetup = utils.parsedate('5 Feb 2003 13:47:26 -0800') - t = int(time.mktime(timetup)) - eq(time.localtime(t)[:6], timetup[:6]) - eq(int(time.strftime('%Y', timetup)), 2003) - timetup = utils.parsedate_tz('5 Feb 2003 13:47:26 -0800') - t = int(time.mktime(timetup[:9])) - eq(time.localtime(t)[:6], timetup[:6]) - eq(int(time.strftime('%Y', timetup[:9])), 2003) - - def test_mktime_tz(self): - self.assertEqual(utils.mktime_tz((1970, 1, 1, 0, 0, 0, - -1, -1, -1, 0)), 0) - self.assertEqual(utils.mktime_tz((1970, 1, 1, 0, 0, 0, - -1, -1, -1, 1234)), -1234) - - def test_parsedate_y2k(self): - """Test for parsing a date with a two-digit year. - - Parsing a date with a two-digit year should return the correct - four-digit year. RFC822 allows two-digit years, but RFC2822 (which - obsoletes RFC822) requires four-digit years. - - """ - self.assertEqual(utils.parsedate_tz('25 Feb 03 13:47:26 -0800'), - utils.parsedate_tz('25 Feb 2003 13:47:26 -0800')) - self.assertEqual(utils.parsedate_tz('25 Feb 71 13:47:26 -0800'), - utils.parsedate_tz('25 Feb 1971 13:47:26 -0800')) - - def test_parseaddr_empty(self): - self.assertEqual(utils.parseaddr('<>'), ('', '')) - self.assertEqual(utils.formataddr(utils.parseaddr('<>')), '') - - def test_noquote_dump(self): - self.assertEqual( - utils.formataddr(('A Silly Person', 'person@dom.ain')), - 'A Silly Person ') - - def test_escape_dump(self): - self.assertEqual( - utils.formataddr(('A (Very) Silly Person', 'person@dom.ain')), - r'"A (Very) Silly Person" ') - self.assertEqual( - utils.parseaddr(r'"A \(Very\) Silly Person" '), - ('A (Very) Silly Person', 'person@dom.ain')) - a = r'A \(Special\) Person' - b = 'person@dom.ain' - self.assertEqual(utils.parseaddr(utils.formataddr((a, b))), (a, b)) - - def test_escape_backslashes(self): - self.assertEqual( - utils.formataddr(('Arthur \Backslash\ Foobar', 'person@dom.ain')), - r'"Arthur \\Backslash\\ Foobar" ') - a = r'Arthur \Backslash\ Foobar' - b = 'person@dom.ain' - self.assertEqual(utils.parseaddr(utils.formataddr((a, b))), (a, b)) - - def test_quotes_unicode_names(self): - # issue 1690608. email.utils.formataddr() should be rfc2047 aware. - name = "H\u00e4ns W\u00fcrst" - addr = 'person@dom.ain' - utf8_base64 = "=?utf-8?b?SMOkbnMgV8O8cnN0?= " - latin1_quopri = "=?iso-8859-1?q?H=E4ns_W=FCrst?= " - self.assertEqual(utils.formataddr((name, addr)), utf8_base64) - self.assertEqual(utils.formataddr((name, addr), 'iso-8859-1'), - latin1_quopri) - - def test_accepts_any_charset_like_object(self): - # issue 1690608. email.utils.formataddr() should be rfc2047 aware. - name = "H\u00e4ns W\u00fcrst" - addr = 'person@dom.ain' - utf8_base64 = "=?utf-8?b?SMOkbnMgV8O8cnN0?= " - foobar = "FOOBAR" - class CharsetMock(object): - def header_encode(self, string): - return foobar - mock = CharsetMock() - mock_expected = "%s <%s>" % (foobar, addr) - self.assertEqual(utils.formataddr((name, addr), mock), mock_expected) - self.assertEqual(utils.formataddr((name, addr), Charset('utf-8')), - utf8_base64) - - def test_invalid_charset_like_object_raises_error(self): - # issue 1690608. email.utils.formataddr() should be rfc2047 aware. - name = "H\u00e4ns W\u00fcrst" - addr = 'person@dom.ain' - # A object without a header_encode method: - bad_charset = object() - self.assertRaises(AttributeError, utils.formataddr, (name, addr), - bad_charset) - - def test_unicode_address_raises_error(self): - # issue 1690608. email.utils.formataddr() should be rfc2047 aware. - addr = 'pers\u00f6n@dom.in' - self.assertRaises(UnicodeError, utils.formataddr, (None, addr)) - self.assertRaises(UnicodeError, utils.formataddr, ("Name", addr)) - - def test_name_with_dot(self): - x = 'John X. Doe ' - y = '"John X. Doe" ' - a, b = ('John X. Doe', 'jxd@example.com') - self.assertEqual(utils.parseaddr(x), (a, b)) - self.assertEqual(utils.parseaddr(y), (a, b)) - # formataddr() quotes the name if there's a dot in it - self.assertEqual(utils.formataddr((a, b)), y) - - def test_parseaddr_preserves_quoted_pairs_in_addresses(self): - # issue 10005. Note that in the third test the second pair of - # backslashes is not actually a quoted pair because it is not inside a - # comment or quoted string: the address being parsed has a quoted - # string containing a quoted backslash, followed by 'example' and two - # backslashes, followed by another quoted string containing a space and - # the word 'example'. parseaddr copies those two backslashes - # literally. Per rfc5322 this is not technically correct since a \ may - # not appear in an address outside of a quoted string. It is probably - # a sensible Postel interpretation, though. - eq = self.assertEqual - eq(utils.parseaddr('""example" example"@example.com'), - ('', '""example" example"@example.com')) - eq(utils.parseaddr('"\\"example\\" example"@example.com'), - ('', '"\\"example\\" example"@example.com')) - eq(utils.parseaddr('"\\\\"example\\\\" example"@example.com'), - ('', '"\\\\"example\\\\" example"@example.com')) - - def test_parseaddr_preserves_spaces_in_local_part(self): - # issue 9286. A normal RFC5322 local part should not contain any - # folding white space, but legacy local parts can (they are a sequence - # of atoms, not dotatoms). On the other hand we strip whitespace from - # before the @ and around dots, on the assumption that the whitespace - # around the punctuation is a mistake in what would otherwise be - # an RFC5322 local part. Leading whitespace is, usual, stripped as well. - self.assertEqual(('', "merwok wok@xample.com"), - utils.parseaddr("merwok wok@xample.com")) - self.assertEqual(('', "merwok wok@xample.com"), - utils.parseaddr("merwok wok@xample.com")) - self.assertEqual(('', "merwok wok@xample.com"), - utils.parseaddr(" merwok wok @xample.com")) - self.assertEqual(('', 'merwok"wok" wok@xample.com'), - utils.parseaddr('merwok"wok" wok@xample.com')) - self.assertEqual(('', 'merwok.wok.wok@xample.com'), - utils.parseaddr('merwok. wok . wok@xample.com')) - - def test_formataddr_does_not_quote_parens_in_quoted_string(self): - addr = ("'foo@example.com' (foo@example.com)", - 'foo@example.com') - addrstr = ('"\'foo@example.com\' ' - '(foo@example.com)" ') - self.assertEqual(utils.parseaddr(addrstr), addr) - self.assertEqual(utils.formataddr(addr), addrstr) - - - def test_multiline_from_comment(self): - x = """\ -Foo -\tBar """ - self.assertEqual(utils.parseaddr(x), ('Foo Bar', 'foo@example.com')) - - def test_quote_dump(self): - self.assertEqual( - utils.formataddr(('A Silly; Person', 'person@dom.ain')), - r'"A Silly; Person" ') - - def test_charset_richcomparisons(self): - eq = self.assertEqual - ne = self.assertNotEqual - cset1 = Charset() - cset2 = Charset() - eq(cset1, 'us-ascii') - eq(cset1, 'US-ASCII') - eq(cset1, 'Us-AsCiI') - eq('us-ascii', cset1) - eq('US-ASCII', cset1) - eq('Us-AsCiI', cset1) - ne(cset1, 'usascii') - ne(cset1, 'USASCII') - ne(cset1, 'UsAsCiI') - ne('usascii', cset1) - ne('USASCII', cset1) - ne('UsAsCiI', cset1) - eq(cset1, cset2) - eq(cset2, cset1) - - def test_getaddresses(self): - eq = self.assertEqual - eq(utils.getaddresses(['aperson@dom.ain (Al Person)', - 'Bud Person ']), - [('Al Person', 'aperson@dom.ain'), - ('Bud Person', 'bperson@dom.ain')]) - - def test_getaddresses_nasty(self): - eq = self.assertEqual - eq(utils.getaddresses(['foo: ;']), [('', '')]) - eq(utils.getaddresses( - ['[]*-- =~$']), - [('', ''), ('', ''), ('', '*--')]) - eq(utils.getaddresses( - ['foo: ;', '"Jason R. Mastaler" ']), - [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]) - - def test_getaddresses_embedded_comment(self): - """Test proper handling of a nested comment""" - eq = self.assertEqual - addrs = utils.getaddresses(['User ((nested comment)) ']) - eq(addrs[0][1], 'foo@bar.com') - - def test_utils_quote_unquote(self): - eq = self.assertEqual - msg = Message() - msg.add_header('content-disposition', 'attachment', - filename='foo\\wacky"name') - eq(msg.get_filename(), 'foo\\wacky"name') - - def test_get_body_encoding_with_bogus_charset(self): - charset = Charset('not a charset') - self.assertEqual(charset.get_body_encoding(), 'base64') - - def test_get_body_encoding_with_uppercase_charset(self): - eq = self.assertEqual - msg = Message() - msg['Content-Type'] = 'text/plain; charset=UTF-8' - eq(msg['content-type'], 'text/plain; charset=UTF-8') - charsets = msg.get_charsets() - eq(len(charsets), 1) - eq(charsets[0], 'utf-8') - charset = Charset(charsets[0]) - eq(charset.get_body_encoding(), 'base64') - msg.set_payload(b'hello world', charset=charset) - eq(msg.get_payload(), 'aGVsbG8gd29ybGQ=\n') - eq(msg.get_payload(decode=True), b'hello world') - eq(msg['content-transfer-encoding'], 'base64') - # Try another one - msg = Message() - msg['Content-Type'] = 'text/plain; charset="US-ASCII"' - charsets = msg.get_charsets() - eq(len(charsets), 1) - eq(charsets[0], 'us-ascii') - charset = Charset(charsets[0]) - eq(charset.get_body_encoding(), encoders.encode_7or8bit) - msg.set_payload('hello world', charset=charset) - eq(msg.get_payload(), 'hello world') - eq(msg['content-transfer-encoding'], '7bit') - - def test_charsets_case_insensitive(self): - lc = Charset('us-ascii') - uc = Charset('US-ASCII') - self.assertEqual(lc.get_body_encoding(), uc.get_body_encoding()) - - def test_partial_falls_inside_message_delivery_status(self): - eq = self.ndiffAssertEqual - # The Parser interface provides chunks of data to FeedParser in 8192 - # byte gulps. SF bug #1076485 found one of those chunks inside - # message/delivery-status header block, which triggered an - # unreadline() of NeedMoreData. - msg = self._msgobj('msg_43.txt') - sfp = StringIO() - iterators._structure(msg, sfp) - eq(sfp.getvalue(), """\ -multipart/report - text/plain - message/delivery-status - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/plain - text/rfc822-headers -""") - - def test_make_msgid_domain(self): - self.assertEqual( - email.utils.make_msgid(domain='testdomain-string')[-19:], - '@testdomain-string>') - - def test_Generator_linend(self): - # Issue 14645. - with openfile('msg_26.txt', newline='\n') as f: - msgtxt = f.read() - msgtxt_nl = msgtxt.replace('\r\n', '\n') - msg = email.message_from_string(msgtxt) - s = StringIO() - g = email.generator.Generator(s) - g.flatten(msg) - self.assertEqual(s.getvalue(), msgtxt_nl) - - def test_BytesGenerator_linend(self): - # Issue 14645. - with openfile('msg_26.txt', newline='\n') as f: - msgtxt = f.read() - msgtxt_nl = msgtxt.replace('\r\n', '\n') - msg = email.message_from_string(msgtxt_nl) - s = BytesIO() - g = email.generator.BytesGenerator(s) - g.flatten(msg, linesep='\r\n') - self.assertEqual(s.getvalue().decode('ascii'), msgtxt) - - def test_BytesGenerator_linend_with_non_ascii(self): - # Issue 14645. - with openfile('msg_26.txt', 'rb') as f: - msgtxt = f.read() - msgtxt = msgtxt.replace(b'with attachment', b'fo\xf6') - msgtxt_nl = msgtxt.replace(b'\r\n', b'\n') - msg = email.message_from_bytes(msgtxt_nl) - s = BytesIO() - g = email.generator.BytesGenerator(s) - g.flatten(msg, linesep='\r\n') - self.assertEqual(s.getvalue(), msgtxt) - - -# Test the iterator/generators -class TestIterators(TestEmailBase): - def test_body_line_iterator(self): - eq = self.assertEqual - neq = self.ndiffAssertEqual - # First a simple non-multipart message - msg = self._msgobj('msg_01.txt') - it = iterators.body_line_iterator(msg) - lines = list(it) - eq(len(lines), 6) - neq(EMPTYSTRING.join(lines), msg.get_payload()) - # Now a more complicated multipart - msg = self._msgobj('msg_02.txt') - it = iterators.body_line_iterator(msg) - lines = list(it) - eq(len(lines), 43) - with openfile('msg_19.txt') as fp: - neq(EMPTYSTRING.join(lines), fp.read()) - - def test_typed_subpart_iterator(self): - eq = self.assertEqual - msg = self._msgobj('msg_04.txt') - it = iterators.typed_subpart_iterator(msg, 'text') - lines = [] - subparts = 0 - for subpart in it: - subparts += 1 - lines.append(subpart.get_payload()) - eq(subparts, 2) - eq(EMPTYSTRING.join(lines), """\ -a simple kind of mirror -to reflect upon our own -a simple kind of mirror -to reflect upon our own -""") - - def test_typed_subpart_iterator_default_type(self): - eq = self.assertEqual - msg = self._msgobj('msg_03.txt') - it = iterators.typed_subpart_iterator(msg, 'text', 'plain') - lines = [] - subparts = 0 - for subpart in it: - subparts += 1 - lines.append(subpart.get_payload()) - eq(subparts, 1) - eq(EMPTYSTRING.join(lines), """\ - -Hi, - -Do you like this message? - --Me -""") - - def test_pushCR_LF(self): - '''FeedParser BufferedSubFile.push() assumed it received complete - line endings. A CR ending one push() followed by a LF starting - the next push() added an empty line. - ''' - imt = [ - ("a\r \n", 2), - ("b", 0), - ("c\n", 1), - ("", 0), - ("d\r\n", 1), - ("e\r", 0), - ("\nf", 1), - ("\r\n", 1), - ] - from email.feedparser import BufferedSubFile, NeedMoreData - bsf = BufferedSubFile() - om = [] - nt = 0 - for il, n in imt: - bsf.push(il) - nt += n - n1 = 0 - while True: - ol = bsf.readline() - if ol == NeedMoreData: - break - om.append(ol) - n1 += 1 - self.assertTrue(n == n1) - self.assertTrue(len(om) == nt) - self.assertTrue(''.join([il for il, n in imt]) == ''.join(om)) - - - -class TestParsers(TestEmailBase): - - def test_header_parser(self): - eq = self.assertEqual - # Parse only the headers of a complex multipart MIME document - with openfile('msg_02.txt') as fp: - msg = HeaderParser().parse(fp) - eq(msg['from'], 'ppp-request@zzz.org') - eq(msg['to'], 'ppp@zzz.org') - eq(msg.get_content_type(), 'multipart/mixed') - self.assertFalse(msg.is_multipart()) - self.assertTrue(isinstance(msg.get_payload(), str)) - - def test_bytes_header_parser(self): - eq = self.assertEqual - # Parse only the headers of a complex multipart MIME document - with openfile('msg_02.txt', 'rb') as fp: - msg = email.parser.BytesHeaderParser().parse(fp) - eq(msg['from'], 'ppp-request@zzz.org') - eq(msg['to'], 'ppp@zzz.org') - eq(msg.get_content_type(), 'multipart/mixed') - self.assertFalse(msg.is_multipart()) - self.assertTrue(isinstance(msg.get_payload(), str)) - self.assertTrue(isinstance(msg.get_payload(decode=True), bytes)) - - def test_whitespace_continuation(self): - eq = self.assertEqual - # This message contains a line after the Subject: header that has only - # whitespace, but it is not empty! - msg = email.message_from_string("""\ -From: aperson@dom.ain -To: bperson@dom.ain -Subject: the next line has a space on it -\x20 -Date: Mon, 8 Apr 2002 15:09:19 -0400 -Message-ID: spam - -Here's the message body -""") - eq(msg['subject'], 'the next line has a space on it\n ') - eq(msg['message-id'], 'spam') - eq(msg.get_payload(), "Here's the message body\n") - - def test_whitespace_continuation_last_header(self): - eq = self.assertEqual - # Like the previous test, but the subject line is the last - # header. - msg = email.message_from_string("""\ -From: aperson@dom.ain -To: bperson@dom.ain -Date: Mon, 8 Apr 2002 15:09:19 -0400 -Message-ID: spam -Subject: the next line has a space on it -\x20 - -Here's the message body -""") - eq(msg['subject'], 'the next line has a space on it\n ') - eq(msg['message-id'], 'spam') - eq(msg.get_payload(), "Here's the message body\n") - - def test_crlf_separation(self): - eq = self.assertEqual - with openfile('msg_26.txt', newline='\n') as fp: - msg = Parser().parse(fp) - eq(len(msg.get_payload()), 2) - part1 = msg.get_payload(0) - eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'Simple email with attachment.\r\n\r\n') - part2 = msg.get_payload(1) - eq(part2.get_content_type(), 'application/riscos') - - def test_crlf_flatten(self): - # Using newline='\n' preserves the crlfs in this input file. - with openfile('msg_26.txt', newline='\n') as fp: - text = fp.read() - msg = email.message_from_string(text) - s = StringIO() - g = Generator(s) - g.flatten(msg, linesep='\r\n') - self.assertEqual(s.getvalue(), text) - - maxDiff = None - - def test_multipart_digest_with_extra_mime_headers(self): - eq = self.assertEqual - neq = self.ndiffAssertEqual - with openfile('msg_28.txt') as fp: - msg = email.message_from_file(fp) - # Structure is: - # multipart/digest - # message/rfc822 - # text/plain - # message/rfc822 - # text/plain - eq(msg.is_multipart(), 1) - eq(len(msg.get_payload()), 2) - part1 = msg.get_payload(0) - eq(part1.get_content_type(), 'message/rfc822') - eq(part1.is_multipart(), 1) - eq(len(part1.get_payload()), 1) - part1a = part1.get_payload(0) - eq(part1a.is_multipart(), 0) - eq(part1a.get_content_type(), 'text/plain') - neq(part1a.get_payload(), 'message 1\n') - # next message/rfc822 - part2 = msg.get_payload(1) - eq(part2.get_content_type(), 'message/rfc822') - eq(part2.is_multipart(), 1) - eq(len(part2.get_payload()), 1) - part2a = part2.get_payload(0) - eq(part2a.is_multipart(), 0) - eq(part2a.get_content_type(), 'text/plain') - neq(part2a.get_payload(), 'message 2\n') - - def test_three_lines(self): - # A bug report by Andrew McNamara - lines = ['From: Andrew Person From', 'From']) - eq(msg.get_payload(), 'body') - - def test_rfc2822_space_not_allowed_in_header(self): - eq = self.assertEqual - m = '>From foo@example.com 11:25:53\nFrom: bar\n!"#QUX;~: zoo\n\nbody' - msg = email.message_from_string(m) - eq(len(msg.keys()), 0) - - def test_rfc2822_one_character_header(self): - eq = self.assertEqual - m = 'A: first header\nB: second header\nCC: third header\n\nbody' - msg = email.message_from_string(m) - headers = msg.keys() - headers.sort() - eq(headers, ['A', 'B', 'CC']) - eq(msg.get_payload(), 'body') - - def test_CRLFLF_at_end_of_part(self): - # issue 5610: feedparser should not eat two chars from body part ending - # with "\r\n\n". - m = ( - "From: foo@bar.com\n" - "To: baz\n" - "Mime-Version: 1.0\n" - "Content-Type: multipart/mixed; boundary=BOUNDARY\n" - "\n" - "--BOUNDARY\n" - "Content-Type: text/plain\n" - "\n" - "body ending with CRLF newline\r\n" - "\n" - "--BOUNDARY--\n" - ) - msg = email.message_from_string(m) - self.assertTrue(msg.get_payload(0).get_payload().endswith('\r\n')) - - -class Test8BitBytesHandling(unittest.TestCase): - # In Python3 all input is string, but that doesn't work if the actual input - # uses an 8bit transfer encoding. To hack around that, in email 5.1 we - # decode byte streams using the surrogateescape error handler, and - # reconvert to binary at appropriate places if we detect surrogates. This - # doesn't allow us to transform headers with 8bit bytes (they get munged), - # but it does allow us to parse and preserve them, and to decode body - # parts that use an 8bit CTE. - - bodytest_msg = textwrap.dedent("""\ - From: foo@bar.com - To: baz - Mime-Version: 1.0 - Content-Type: text/plain; charset={charset} - Content-Transfer-Encoding: {cte} - - {bodyline} - """) - - def test_known_8bit_CTE(self): - m = self.bodytest_msg.format(charset='utf-8', - cte='8bit', - bodyline='pöstal').encode('utf-8') - msg = email.message_from_bytes(m) - self.assertEqual(msg.get_payload(), "pöstal\n") - self.assertEqual(msg.get_payload(decode=True), - "pöstal\n".encode('utf-8')) - - def test_unknown_8bit_CTE(self): - m = self.bodytest_msg.format(charset='notavalidcharset', - cte='8bit', - bodyline='pöstal').encode('utf-8') - msg = email.message_from_bytes(m) - self.assertEqual(msg.get_payload(), "p\uFFFD\uFFFDstal\n") - self.assertEqual(msg.get_payload(decode=True), - "pöstal\n".encode('utf-8')) - - def test_8bit_in_quopri_body(self): - # This is non-RFC compliant data...without 'decode' the library code - # decodes the body using the charset from the headers, and because the - # source byte really is utf-8 this works. This is likely to fail - # against real dirty data (ie: produce mojibake), but the data is - # invalid anyway so it is as good a guess as any. But this means that - # this test just confirms the current behavior; that behavior is not - # necessarily the best possible behavior. With 'decode' it is - # returning the raw bytes, so that test should be of correct behavior, - # or at least produce the same result that email4 did. - m = self.bodytest_msg.format(charset='utf-8', - cte='quoted-printable', - bodyline='p=C3=B6stál').encode('utf-8') - msg = email.message_from_bytes(m) - self.assertEqual(msg.get_payload(), 'p=C3=B6stál\n') - self.assertEqual(msg.get_payload(decode=True), - 'pöstál\n'.encode('utf-8')) - - def test_invalid_8bit_in_non_8bit_cte_uses_replace(self): - # This is similar to the previous test, but proves that if the 8bit - # byte is undecodeable in the specified charset, it gets replaced - # by the unicode 'unknown' character. Again, this may or may not - # be the ideal behavior. Note that if decode=False none of the - # decoders will get involved, so this is the only test we need - # for this behavior. - m = self.bodytest_msg.format(charset='ascii', - cte='quoted-printable', - bodyline='p=C3=B6stál').encode('utf-8') - msg = email.message_from_bytes(m) - self.assertEqual(msg.get_payload(), 'p=C3=B6st\uFFFD\uFFFDl\n') - self.assertEqual(msg.get_payload(decode=True), - 'pöstál\n'.encode('utf-8')) - - # test_defect_handling:test_invalid_chars_in_base64_payload - def test_8bit_in_base64_body(self): - # If we get 8bit bytes in a base64 body, we can just ignore them - # as being outside the base64 alphabet and decode anyway. But - # we register a defect. - m = self.bodytest_msg.format(charset='utf-8', - cte='base64', - bodyline='cMO2c3RhbAá=').encode('utf-8') - msg = email.message_from_bytes(m) - self.assertEqual(msg.get_payload(decode=True), - 'pöstal'.encode('utf-8')) - self.assertIsInstance(msg.defects[0], - errors.InvalidBase64CharactersDefect) - - def test_8bit_in_uuencode_body(self): - # Sticking an 8bit byte in a uuencode block makes it undecodable by - # normal means, so the block is returned undecoded, but as bytes. - m = self.bodytest_msg.format(charset='utf-8', - cte='uuencode', - bodyline='<,.V7bit conversion. - self.assertEqual(out.getvalue(), - self.latin_bin_msg.decode('latin-1')+'\n') - - def test_bytes_feedparser(self): - bfp = email.feedparser.BytesFeedParser() - for i in range(0, len(self.latin_bin_msg), 10): - bfp.feed(self.latin_bin_msg[i:i+10]) - m = bfp.close() - self.assertEqual(str(m), self.latin_bin_msg_as7bit) - - def test_crlf_flatten(self): - with openfile('msg_26.txt', 'rb') as fp: - text = fp.read() - msg = email.message_from_bytes(text) - s = BytesIO() - g = email.generator.BytesGenerator(s) - g.flatten(msg, linesep='\r\n') - self.assertEqual(s.getvalue(), text) - - def test_8bit_multipart(self): - # Issue 11605 - source = textwrap.dedent("""\ - Date: Fri, 18 Mar 2011 17:15:43 +0100 - To: foo@example.com - From: foodwatch-Newsletter - Subject: Aktuelles zu Japan, Klonfleisch und Smiley-System - Message-ID: <76a486bee62b0d200f33dc2ca08220ad@localhost.localdomain> - MIME-Version: 1.0 - Content-Type: multipart/alternative; - boundary="b1_76a486bee62b0d200f33dc2ca08220ad" - - --b1_76a486bee62b0d200f33dc2ca08220ad - Content-Type: text/plain; charset="utf-8" - Content-Transfer-Encoding: 8bit - - Guten Tag, , - - mit großer Betroffenheit verfolgen auch wir im foodwatch-Team die - Nachrichten aus Japan. - - - --b1_76a486bee62b0d200f33dc2ca08220ad - Content-Type: text/html; charset="utf-8" - Content-Transfer-Encoding: 8bit - - - - - foodwatch - Newsletter - - -

mit großer Betroffenheit verfolgen auch wir im foodwatch-Team - die Nachrichten aus Japan.

- - - --b1_76a486bee62b0d200f33dc2ca08220ad-- - - """).encode('utf-8') - msg = email.message_from_bytes(source) - s = BytesIO() - g = email.generator.BytesGenerator(s) - g.flatten(msg) - self.assertEqual(s.getvalue(), source) - - def test_bytes_generator_b_encoding_linesep(self): - # Issue 14062: b encoding was tacking on an extra \n. - m = Message() - # This has enough non-ascii that it should always end up b encoded. - m['Subject'] = Header('žluťoučký kůň') - s = BytesIO() - g = email.generator.BytesGenerator(s) - g.flatten(m, linesep='\r\n') - self.assertEqual( - s.getvalue(), - b'Subject: =?utf-8?b?xb5sdcWlb3XEjWvDvSBrxa/FiA==?=\r\n\r\n') - - def test_generator_b_encoding_linesep(self): - # Since this broke in ByteGenerator, test Generator for completeness. - m = Message() - # This has enough non-ascii that it should always end up b encoded. - m['Subject'] = Header('žluťoučký kůň') - s = StringIO() - g = email.generator.Generator(s) - g.flatten(m, linesep='\r\n') - self.assertEqual( - s.getvalue(), - 'Subject: =?utf-8?b?xb5sdcWlb3XEjWvDvSBrxa/FiA==?=\r\n\r\n') - - maxDiff = None - - -class BaseTestBytesGeneratorIdempotent(object): - - maxDiff = None - - def _msgobj(self, filename): - with openfile(filename, 'rb') as fp: - data = fp.read() - data = self.normalize_linesep_regex.sub(self.blinesep, data) - msg = email.message_from_bytes(data) - return msg, data - - def _idempotent(self, msg, data, unixfrom=False): - b = BytesIO() - g = email.generator.BytesGenerator(b, maxheaderlen=0) - g.flatten(msg, unixfrom=unixfrom, linesep=self.linesep) - self.assertEqual(data, b.getvalue()) - - -class TestBytesGeneratorIdempotentNL(BaseTestBytesGeneratorIdempotent, - TestIdempotent): - linesep = '\n' - blinesep = b'\n' - normalize_linesep_regex = re.compile(br'\r\n') - - -class TestBytesGeneratorIdempotentCRLF(BaseTestBytesGeneratorIdempotent, - TestIdempotent): - linesep = '\r\n' - blinesep = b'\r\n' - normalize_linesep_regex = re.compile(br'(? A+B==2', 'A=1,B=A ==> A+B==2') - - def _test_encode(self, body, expected_encoded_body, maxlinelen=None, eol=None): - kwargs = {} - if maxlinelen is None: - # Use body_encode's default. - maxlinelen = 76 - else: - kwargs['maxlinelen'] = maxlinelen - if eol is None: - # Use body_encode's default. - eol = '\n' - else: - kwargs['eol'] = eol - encoded_body = quoprimime.body_encode(body, **kwargs) - self.assertEqual(encoded_body, expected_encoded_body) - if eol == '\n' or eol == '\r\n': - # We know how to split the result back into lines, so maxlinelen - # can be checked. - for line in encoded_body.splitlines(): - self.assertLessEqual(len(line), maxlinelen) - - def test_encode_null(self): - self._test_encode('', '') - - def test_encode_null_lines(self): - self._test_encode('\n\n', '\n\n') - - def test_encode_one_line(self): - self._test_encode('hello\n', 'hello\n') - - def test_encode_one_line_crlf(self): - self._test_encode('hello\r\n', 'hello\n') - - def test_encode_one_line_eol(self): - self._test_encode('hello\n', 'hello\r\n', eol='\r\n') - - def test_encode_one_space(self): - self._test_encode(' ', '=20') - - def test_encode_one_line_one_space(self): - self._test_encode(' \n', '=20\n') - -# XXX: body_encode() expect strings, but uses ord(char) from these strings -# to index into a 256-entry list. For code points above 255, this will fail. -# Should there be a check for 8-bit only ord() values in body, or at least -# a comment about the expected input? - - def test_encode_two_lines_one_space(self): - self._test_encode(' \n \n', '=20\n=20\n') - - def test_encode_one_word_trailing_spaces(self): - self._test_encode('hello ', 'hello =20') - - def test_encode_one_line_trailing_spaces(self): - self._test_encode('hello \n', 'hello =20\n') - - def test_encode_one_word_trailing_tab(self): - self._test_encode('hello \t', 'hello =09') - - def test_encode_one_line_trailing_tab(self): - self._test_encode('hello \t\n', 'hello =09\n') - - def test_encode_trailing_space_before_maxlinelen(self): - self._test_encode('abcd \n1234', 'abcd =\n\n1234', maxlinelen=6) - - def test_encode_trailing_space_at_maxlinelen(self): - self._test_encode('abcd \n1234', 'abcd=\n=20\n1234', maxlinelen=5) - - def test_encode_trailing_space_beyond_maxlinelen(self): - self._test_encode('abcd \n1234', 'abc=\nd=20\n1234', maxlinelen=4) - - def test_encode_whitespace_lines(self): - self._test_encode(' \n' * 5, '=20\n' * 5) - - def test_encode_quoted_equals(self): - self._test_encode('a = b', 'a =3D b') - - def test_encode_one_long_string(self): - self._test_encode('x' * 100, 'x' * 75 + '=\n' + 'x' * 25) - - def test_encode_one_long_line(self): - self._test_encode('x' * 100 + '\n', 'x' * 75 + '=\n' + 'x' * 25 + '\n') - - def test_encode_one_very_long_line(self): - self._test_encode('x' * 200 + '\n', - 2 * ('x' * 75 + '=\n') + 'x' * 50 + '\n') - - def test_encode_one_long_line(self): - self._test_encode('x' * 100 + '\n', 'x' * 75 + '=\n' + 'x' * 25 + '\n') - - def test_encode_shortest_maxlinelen(self): - self._test_encode('=' * 5, '=3D=\n' * 4 + '=3D', maxlinelen=4) - - def test_encode_maxlinelen_too_small(self): - self.assertRaises(ValueError, self._test_encode, '', '', maxlinelen=3) - - def test_encode(self): - eq = self.assertEqual - eq(quoprimime.body_encode(''), '') - eq(quoprimime.body_encode('hello'), 'hello') - # Test the binary flag - eq(quoprimime.body_encode('hello\r\nworld'), 'hello\nworld') - # Test the maxlinelen arg - eq(quoprimime.body_encode('xxxx ' * 20, maxlinelen=40), """\ -xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx= - xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx= -x xxxx xxxx xxxx xxxx=20""") - # Test the eol argument - eq(quoprimime.body_encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), - """\ -xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=\r - xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=\r -x xxxx xxxx xxxx xxxx=20""") - eq(quoprimime.body_encode("""\ -one line - -two line"""), """\ -one line - -two line""") - - - -# Test the Charset class -class TestCharset(unittest.TestCase): - def tearDown(self): - from email import charset as CharsetModule - try: - del CharsetModule.CHARSETS['fake'] - except KeyError: - pass - - def test_codec_encodeable(self): - eq = self.assertEqual - # Make sure us-ascii = no Unicode conversion - c = Charset('us-ascii') - eq(c.header_encode('Hello World!'), 'Hello World!') - # Test 8-bit idempotency with us-ascii - s = '\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa' - self.assertRaises(UnicodeError, c.header_encode, s) - c = Charset('utf-8') - eq(c.header_encode(s), '=?utf-8?b?wqTCosKkwqTCpMKmwqTCqMKkwqo=?=') - - def test_body_encode(self): - eq = self.assertEqual - # Try a charset with QP body encoding - c = Charset('iso-8859-1') - eq('hello w=F6rld', c.body_encode('hello w\xf6rld')) - # Try a charset with Base64 body encoding - c = Charset('utf-8') - eq('aGVsbG8gd29ybGQ=\n', c.body_encode(b'hello world')) - # Try a charset with None body encoding - c = Charset('us-ascii') - eq('hello world', c.body_encode('hello world')) - # Try the convert argument, where input codec != output codec - c = Charset('euc-jp') - # With apologies to Tokio Kikuchi ;) - # XXX FIXME -## try: -## eq('\x1b$B5FCO;~IW\x1b(B', -## c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7')) -## eq('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7', -## c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7', False)) -## except LookupError: -## # We probably don't have the Japanese codecs installed -## pass - # Testing SF bug #625509, which we have to fake, since there are no - # built-in encodings where the header encoding is QP but the body - # encoding is not. - from email import charset as CharsetModule - CharsetModule.add_charset('fake', CharsetModule.QP, None, 'utf-8') - c = Charset('fake') - eq('hello world', c.body_encode('hello world')) - - def test_unicode_charset_name(self): - charset = Charset('us-ascii') - self.assertEqual(str(charset), 'us-ascii') - self.assertRaises(errors.CharsetError, Charset, 'asc\xffii') - - - -# Test multilingual MIME headers. -class TestHeader(TestEmailBase): - def test_simple(self): - eq = self.ndiffAssertEqual - h = Header('Hello World!') - eq(h.encode(), 'Hello World!') - h.append(' Goodbye World!') - eq(h.encode(), 'Hello World! Goodbye World!') - - def test_simple_surprise(self): - eq = self.ndiffAssertEqual - h = Header('Hello World!') - eq(h.encode(), 'Hello World!') - h.append('Goodbye World!') - eq(h.encode(), 'Hello World! Goodbye World!') - - def test_header_needs_no_decoding(self): - h = 'no decoding needed' - self.assertEqual(decode_header(h), [(h, None)]) - - def test_long(self): - h = Header("I am the very model of a modern Major-General; I've information vegetable, animal, and mineral; I know the kings of England, and I quote the fights historical from Marathon to Waterloo, in order categorical; I'm very well acquainted, too, with matters mathematical; I understand equations, both the simple and quadratical; about binomial theorem I'm teeming with a lot o' news, with many cheerful facts about the square of the hypotenuse.", - maxlinelen=76) - for l in h.encode(splitchars=' ').split('\n '): - self.assertTrue(len(l) <= 76) - - def test_multilingual(self): - eq = self.ndiffAssertEqual - g = Charset("iso-8859-1") - cz = Charset("iso-8859-2") - utf8 = Charset("utf-8") - g_head = (b'Die Mieter treten hier ein werden mit einem ' - b'Foerderband komfortabel den Korridor entlang, ' - b'an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, ' - b'gegen die rotierenden Klingen bef\xf6rdert. ') - cz_head = (b'Finan\xe8ni metropole se hroutily pod tlakem jejich ' - b'd\xf9vtipu.. ') - utf8_head = ('\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f' - '\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00' - '\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c' - '\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067' - '\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das ' - 'Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder ' - 'die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066' - '\u3044\u307e\u3059\u3002') - h = Header(g_head, g) - h.append(cz_head, cz) - h.append(utf8_head, utf8) - enc = h.encode(maxlinelen=76) - eq(enc, """\ -=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerderband_kom?= - =?iso-8859-1?q?fortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndischen_Wand?= - =?iso-8859-1?q?gem=E4lden_vorbei=2C_gegen_die_rotierenden_Klingen_bef=F6r?= - =?iso-8859-1?q?dert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutily?= - =?iso-8859-2?q?_pod_tlakem_jejich_d=F9vtipu=2E=2E_?= =?utf-8?b?5q2j56K6?= - =?utf-8?b?44Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb44KT44CC?= - =?utf-8?b?5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go44Gv44Gn?= - =?utf-8?b?44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGFz?= - =?utf-8?b?IE51bnN0dWNrIGdpdCB1bmQgU2xvdGVybWV5ZXI/IEphISBCZWloZXJodW5k?= - =?utf-8?b?IGRhcyBPZGVyIGRpZSBGbGlwcGVyd2FsZHQgZ2Vyc3B1dC7jgI3jgajoqIA=?= - =?utf-8?b?44Gj44Gm44GE44G+44GZ44CC?=""") - decoded = decode_header(enc) - eq(len(decoded), 3) - eq(decoded[0], (g_head, 'iso-8859-1')) - eq(decoded[1], (cz_head, 'iso-8859-2')) - eq(decoded[2], (utf8_head.encode('utf-8'), 'utf-8')) - ustr = str(h) - eq(ustr, - (b'Die Mieter treten hier ein werden mit einem Foerderband ' - b'komfortabel den Korridor entlang, an s\xc3\xbcdl\xc3\xbcndischen ' - b'Wandgem\xc3\xa4lden vorbei, gegen die rotierenden Klingen ' - b'bef\xc3\xb6rdert. Finan\xc4\x8dni metropole se hroutily pod ' - b'tlakem jejich d\xc5\xafvtipu.. \xe6\xad\xa3\xe7\xa2\xba\xe3\x81' - b'\xab\xe8\xa8\x80\xe3\x81\x86\xe3\x81\xa8\xe7\xbf\xbb\xe8\xa8\xb3' - b'\xe3\x81\xaf\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3' - b'\x81\xbe\xe3\x81\x9b\xe3\x82\x93\xe3\x80\x82\xe4\xb8\x80\xe9\x83' - b'\xa8\xe3\x81\xaf\xe3\x83\x89\xe3\x82\xa4\xe3\x83\x84\xe8\xaa\x9e' - b'\xe3\x81\xa7\xe3\x81\x99\xe3\x81\x8c\xe3\x80\x81\xe3\x81\x82\xe3' - b'\x81\xa8\xe3\x81\xaf\xe3\x81\xa7\xe3\x81\x9f\xe3\x82\x89\xe3\x82' - b'\x81\xe3\x81\xa7\xe3\x81\x99\xe3\x80\x82\xe5\xae\x9f\xe9\x9a\x9b' - b'\xe3\x81\xab\xe3\x81\xaf\xe3\x80\x8cWenn ist das Nunstuck git ' - b'und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt ' - b'gersput.\xe3\x80\x8d\xe3\x81\xa8\xe8\xa8\x80\xe3\x81\xa3\xe3\x81' - b'\xa6\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82' - ).decode('utf-8')) - # Test make_header() - newh = make_header(decode_header(enc)) - eq(newh, h) - - def test_empty_header_encode(self): - h = Header() - self.assertEqual(h.encode(), '') - - def test_header_ctor_default_args(self): - eq = self.ndiffAssertEqual - h = Header() - eq(h, '') - h.append('foo', Charset('iso-8859-1')) - eq(h, 'foo') - - def test_explicit_maxlinelen(self): - eq = self.ndiffAssertEqual - hstr = ('A very long line that must get split to something other ' - 'than at the 76th character boundary to test the non-default ' - 'behavior') - h = Header(hstr) - eq(h.encode(), '''\ -A very long line that must get split to something other than at the 76th - character boundary to test the non-default behavior''') - eq(str(h), hstr) - h = Header(hstr, header_name='Subject') - eq(h.encode(), '''\ -A very long line that must get split to something other than at the - 76th character boundary to test the non-default behavior''') - eq(str(h), hstr) - h = Header(hstr, maxlinelen=1024, header_name='Subject') - eq(h.encode(), hstr) - eq(str(h), hstr) - - def test_quopri_splittable(self): - eq = self.ndiffAssertEqual - h = Header(charset='iso-8859-1', maxlinelen=20) - x = 'xxxx ' * 20 - h.append(x) - s = h.encode() - eq(s, """\ -=?iso-8859-1?q?xxx?= - =?iso-8859-1?q?x_?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?_x?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?x_?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?_x?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?x_?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?_x?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?x_?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?_x?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?x_?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?_x?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?x_?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?_x?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?x_?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?_x?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?x_?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?_x?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?x_?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?_x?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?x_?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?xx?= - =?iso-8859-1?q?_?=""") - eq(x, str(make_header(decode_header(s)))) - h = Header(charset='iso-8859-1', maxlinelen=40) - h.append('xxxx ' * 20) - s = h.encode() - eq(s, """\ -=?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xxx?= - =?iso-8859-1?q?x_xxxx_xxxx_xxxx_xxxx_?= - =?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?= - =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?= - =?iso-8859-1?q?_xxxx_xxxx_?=""") - eq(x, str(make_header(decode_header(s)))) - - def test_base64_splittable(self): - eq = self.ndiffAssertEqual - h = Header(charset='koi8-r', maxlinelen=20) - x = 'xxxx ' * 20 - h.append(x) - s = h.encode() - eq(s, """\ -=?koi8-r?b?eHh4?= - =?koi8-r?b?eCB4?= - =?koi8-r?b?eHh4?= - =?koi8-r?b?IHh4?= - =?koi8-r?b?eHgg?= - =?koi8-r?b?eHh4?= - =?koi8-r?b?eCB4?= - =?koi8-r?b?eHh4?= - =?koi8-r?b?IHh4?= - =?koi8-r?b?eHgg?= - =?koi8-r?b?eHh4?= - =?koi8-r?b?eCB4?= - =?koi8-r?b?eHh4?= - =?koi8-r?b?IHh4?= - =?koi8-r?b?eHgg?= - =?koi8-r?b?eHh4?= - =?koi8-r?b?eCB4?= - =?koi8-r?b?eHh4?= - =?koi8-r?b?IHh4?= - =?koi8-r?b?eHgg?= - =?koi8-r?b?eHh4?= - =?koi8-r?b?eCB4?= - =?koi8-r?b?eHh4?= - =?koi8-r?b?IHh4?= - =?koi8-r?b?eHgg?= - =?koi8-r?b?eHh4?= - =?koi8-r?b?eCB4?= - =?koi8-r?b?eHh4?= - =?koi8-r?b?IHh4?= - =?koi8-r?b?eHgg?= - =?koi8-r?b?eHh4?= - =?koi8-r?b?eCB4?= - =?koi8-r?b?eHh4?= - =?koi8-r?b?IA==?=""") - eq(x, str(make_header(decode_header(s)))) - h = Header(charset='koi8-r', maxlinelen=40) - h.append(x) - s = h.encode() - eq(s, """\ -=?koi8-r?b?eHh4eCB4eHh4IHh4eHggeHh4?= - =?koi8-r?b?eCB4eHh4IHh4eHggeHh4eCB4?= - =?koi8-r?b?eHh4IHh4eHggeHh4eCB4eHh4?= - =?koi8-r?b?IHh4eHggeHh4eCB4eHh4IHh4?= - =?koi8-r?b?eHggeHh4eCB4eHh4IHh4eHgg?= - =?koi8-r?b?eHh4eCB4eHh4IA==?=""") - eq(x, str(make_header(decode_header(s)))) - - def test_us_ascii_header(self): - eq = self.assertEqual - s = 'hello' - x = decode_header(s) - eq(x, [('hello', None)]) - h = make_header(x) - eq(s, h.encode()) - - def test_string_charset(self): - eq = self.assertEqual - h = Header() - h.append('hello', 'iso-8859-1') - eq(h, 'hello') - -## def test_unicode_error(self): -## raises = self.assertRaises -## raises(UnicodeError, Header, u'[P\xf6stal]', 'us-ascii') -## raises(UnicodeError, Header, '[P\xf6stal]', 'us-ascii') -## h = Header() -## raises(UnicodeError, h.append, u'[P\xf6stal]', 'us-ascii') -## raises(UnicodeError, h.append, '[P\xf6stal]', 'us-ascii') -## raises(UnicodeError, Header, u'\u83ca\u5730\u6642\u592b', 'iso-8859-1') - - def test_utf8_shortest(self): - eq = self.assertEqual - h = Header('p\xf6stal', 'utf-8') - eq(h.encode(), '=?utf-8?q?p=C3=B6stal?=') - h = Header('\u83ca\u5730\u6642\u592b', 'utf-8') - eq(h.encode(), '=?utf-8?b?6I+K5Zyw5pmC5aSr?=') - - def test_bad_8bit_header(self): - raises = self.assertRaises - eq = self.assertEqual - x = b'Ynwp4dUEbay Auction Semiar- No Charge \x96 Earn Big' - raises(UnicodeError, Header, x) - h = Header() - raises(UnicodeError, h.append, x) - e = x.decode('utf-8', 'replace') - eq(str(Header(x, errors='replace')), e) - h.append(x, errors='replace') - eq(str(h), e) - - def test_escaped_8bit_header(self): - x = b'Ynwp4dUEbay Auction Semiar- No Charge \x96 Earn Big' - e = x.decode('ascii', 'surrogateescape') - h = Header(e, charset=email.charset.UNKNOWN8BIT) - self.assertEqual(str(h), - 'Ynwp4dUEbay Auction Semiar- No Charge \uFFFD Earn Big') - self.assertEqual(email.header.decode_header(h), [(x, 'unknown-8bit')]) - - def test_header_handles_binary_unknown8bit(self): - x = b'Ynwp4dUEbay Auction Semiar- No Charge \x96 Earn Big' - h = Header(x, charset=email.charset.UNKNOWN8BIT) - self.assertEqual(str(h), - 'Ynwp4dUEbay Auction Semiar- No Charge \uFFFD Earn Big') - self.assertEqual(email.header.decode_header(h), [(x, 'unknown-8bit')]) - - def test_make_header_handles_binary_unknown8bit(self): - x = b'Ynwp4dUEbay Auction Semiar- No Charge \x96 Earn Big' - h = Header(x, charset=email.charset.UNKNOWN8BIT) - h2 = email.header.make_header(email.header.decode_header(h)) - self.assertEqual(str(h2), - 'Ynwp4dUEbay Auction Semiar- No Charge \uFFFD Earn Big') - self.assertEqual(email.header.decode_header(h2), [(x, 'unknown-8bit')]) - - def test_modify_returned_list_does_not_change_header(self): - h = Header('test') - chunks = email.header.decode_header(h) - chunks.append(('ascii', 'test2')) - self.assertEqual(str(h), 'test') - - def test_encoded_adjacent_nonencoded(self): - eq = self.assertEqual - h = Header() - h.append('hello', 'iso-8859-1') - h.append('world') - s = h.encode() - eq(s, '=?iso-8859-1?q?hello?= world') - h = make_header(decode_header(s)) - eq(h.encode(), s) - - def test_whitespace_keeper(self): - eq = self.assertEqual - s = 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztk=?= =?koi8-r?q?=CA?= zz.' - parts = decode_header(s) - eq(parts, [(b'Subject: ', None), (b'\xf0\xd2\xcf\xd7\xc5\xd2\xcb\xc1 \xce\xc1 \xc6\xc9\xce\xc1\xcc\xd8\xce\xd9\xca', 'koi8-r'), (b' zz.', None)]) - hdr = make_header(parts) - eq(hdr.encode(), - 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztnK?= zz.') - - def test_broken_base64_header(self): - raises = self.assertRaises - s = 'Subject: =?EUC-KR?B?CSixpLDtKSC/7Liuvsax4iC6uLmwMcijIKHaILzSwd/H0SC8+LCjwLsgv7W/+Mj3I ?=' - raises(errors.HeaderParseError, decode_header, s) - - def test_shift_jis_charset(self): - h = Header('文', charset='shift_jis') - self.assertEqual(h.encode(), '=?iso-2022-jp?b?GyRCSjgbKEI=?=') - - def test_flatten_header_with_no_value(self): - # Issue 11401 (regression from email 4.x) Note that the space after - # the header doesn't reflect the input, but this is also the way - # email 4.x behaved. At some point it would be nice to fix that. - msg = email.message_from_string("EmptyHeader:") - self.assertEqual(str(msg), "EmptyHeader: \n\n") - - def test_encode_preserves_leading_ws_on_value(self): - msg = Message() - msg['SomeHeader'] = ' value with leading ws' - self.assertEqual(str(msg), "SomeHeader: value with leading ws\n\n") - - - -# Test RFC 2231 header parameters (en/de)coding -class TestRFC2231(TestEmailBase): - - # test_headerregistry.TestContentTypeHeader.rfc2231_encoded_with_double_quotes - # test_headerregistry.TestContentTypeHeader.rfc2231_single_quote_inside_double_quotes - def test_get_param(self): - eq = self.assertEqual - msg = self._msgobj('msg_29.txt') - eq(msg.get_param('title'), - ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!')) - eq(msg.get_param('title', unquote=False), - ('us-ascii', 'en', '"This is even more ***fun*** isn\'t it!"')) - - def test_set_param(self): - eq = self.ndiffAssertEqual - msg = Message() - msg.set_param('title', 'This is even more ***fun*** isn\'t it!', - charset='us-ascii') - eq(msg.get_param('title'), - ('us-ascii', '', 'This is even more ***fun*** isn\'t it!')) - msg.set_param('title', 'This is even more ***fun*** isn\'t it!', - charset='us-ascii', language='en') - eq(msg.get_param('title'), - ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!')) - msg = self._msgobj('msg_01.txt') - msg.set_param('title', 'This is even more ***fun*** isn\'t it!', - charset='us-ascii', language='en') - eq(msg.as_string(maxheaderlen=78), """\ -Return-Path: -Delivered-To: bbb@zzz.org -Received: by mail.zzz.org (Postfix, from userid 889) -\tid 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT) -MIME-Version: 1.0 -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 -Content-Type: text/plain; charset=us-ascii; - title*=us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21 - - -Hi, - -Do you like this message? - --Me -""") - - def test_set_param_requote(self): - msg = Message() - msg.set_param('title', 'foo') - self.assertEqual(msg['content-type'], 'text/plain; title="foo"') - msg.set_param('title', 'bar', requote=False) - self.assertEqual(msg['content-type'], 'text/plain; title=bar') - # tspecial is still quoted. - msg.set_param('title', "(bar)bell", requote=False) - self.assertEqual(msg['content-type'], 'text/plain; title="(bar)bell"') - - def test_del_param(self): - eq = self.ndiffAssertEqual - msg = self._msgobj('msg_01.txt') - msg.set_param('foo', 'bar', charset='us-ascii', language='en') - msg.set_param('title', 'This is even more ***fun*** isn\'t it!', - charset='us-ascii', language='en') - msg.del_param('foo', header='Content-Type') - eq(msg.as_string(maxheaderlen=78), """\ -Return-Path: -Delivered-To: bbb@zzz.org -Received: by mail.zzz.org (Postfix, from userid 889) -\tid 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT) -MIME-Version: 1.0 -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 -Content-Type: text/plain; charset="us-ascii"; - title*=us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21 - - -Hi, - -Do you like this message? - --Me -""") - - # test_headerregistry.TestContentTypeHeader.rfc2231_encoded_charset - # I changed the charset name, though, because the one in the file isn't - # a legal charset name. Should add a test for an illegal charset. - def test_rfc2231_get_content_charset(self): - eq = self.assertEqual - msg = self._msgobj('msg_32.txt') - eq(msg.get_content_charset(), 'us-ascii') - - # test_headerregistry.TestContentTypeHeader.rfc2231_encoded_no_double_quotes - def test_rfc2231_parse_rfc_quoting(self): - m = textwrap.dedent('''\ - Content-Disposition: inline; - \tfilename*0*=''This%20is%20even%20more%20; - \tfilename*1*=%2A%2A%2Afun%2A%2A%2A%20; - \tfilename*2="is it not.pdf" - - ''') - msg = email.message_from_string(m) - self.assertEqual(msg.get_filename(), - 'This is even more ***fun*** is it not.pdf') - self.assertEqual(m, msg.as_string()) - - # test_headerregistry.TestContentTypeHeader.rfc2231_encoded_with_double_quotes - def test_rfc2231_parse_extra_quoting(self): - m = textwrap.dedent('''\ - Content-Disposition: inline; - \tfilename*0*="''This%20is%20even%20more%20"; - \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20"; - \tfilename*2="is it not.pdf" - - ''') - msg = email.message_from_string(m) - self.assertEqual(msg.get_filename(), - 'This is even more ***fun*** is it not.pdf') - self.assertEqual(m, msg.as_string()) - - # test_headerregistry.TestContentTypeHeader.rfc2231_no_language_or_charset - # but new test uses *0* because otherwise lang/charset is not valid. - # test_headerregistry.TestContentTypeHeader.rfc2231_segmented_normal_values - def test_rfc2231_no_language_or_charset(self): - m = '''\ -Content-Transfer-Encoding: 8bit -Content-Disposition: inline; filename="file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm" -Content-Type: text/html; NAME*0=file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEM; NAME*1=P_nsmail.htm - -''' - msg = email.message_from_string(m) - param = msg.get_param('NAME') - self.assertFalse(isinstance(param, tuple)) - self.assertEqual( - param, - 'file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm') - - # test_headerregistry.TestContentTypeHeader.rfc2231_encoded_no_charset - def test_rfc2231_no_language_or_charset_in_filename(self): - m = '''\ -Content-Disposition: inline; -\tfilename*0*="''This%20is%20even%20more%20"; -\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20"; -\tfilename*2="is it not.pdf" - -''' - msg = email.message_from_string(m) - self.assertEqual(msg.get_filename(), - 'This is even more ***fun*** is it not.pdf') - - # Duplicate of previous test? - def test_rfc2231_no_language_or_charset_in_filename_encoded(self): - m = '''\ -Content-Disposition: inline; -\tfilename*0*="''This%20is%20even%20more%20"; -\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20"; -\tfilename*2="is it not.pdf" - -''' - msg = email.message_from_string(m) - self.assertEqual(msg.get_filename(), - 'This is even more ***fun*** is it not.pdf') - - # test_headerregistry.TestContentTypeHeader.rfc2231_partly_encoded, - # but the test below is wrong (the first part should be decoded). - def test_rfc2231_partly_encoded(self): - m = '''\ -Content-Disposition: inline; -\tfilename*0="''This%20is%20even%20more%20"; -\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20"; -\tfilename*2="is it not.pdf" - -''' - msg = email.message_from_string(m) - self.assertEqual( - msg.get_filename(), - 'This%20is%20even%20more%20***fun*** is it not.pdf') - - def test_rfc2231_partly_nonencoded(self): - m = '''\ -Content-Disposition: inline; -\tfilename*0="This%20is%20even%20more%20"; -\tfilename*1="%2A%2A%2Afun%2A%2A%2A%20"; -\tfilename*2="is it not.pdf" - -''' - msg = email.message_from_string(m) - self.assertEqual( - msg.get_filename(), - 'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20is it not.pdf') - - def test_rfc2231_no_language_or_charset_in_boundary(self): - m = '''\ -Content-Type: multipart/alternative; -\tboundary*0*="''This%20is%20even%20more%20"; -\tboundary*1*="%2A%2A%2Afun%2A%2A%2A%20"; -\tboundary*2="is it not.pdf" - -''' - msg = email.message_from_string(m) - self.assertEqual(msg.get_boundary(), - 'This is even more ***fun*** is it not.pdf') - - def test_rfc2231_no_language_or_charset_in_charset(self): - # This is a nonsensical charset value, but tests the code anyway - m = '''\ -Content-Type: text/plain; -\tcharset*0*="This%20is%20even%20more%20"; -\tcharset*1*="%2A%2A%2Afun%2A%2A%2A%20"; -\tcharset*2="is it not.pdf" - -''' - msg = email.message_from_string(m) - self.assertEqual(msg.get_content_charset(), - 'this is even more ***fun*** is it not.pdf') - - # test_headerregistry.TestContentTypeHeader.rfc2231_unknown_charset_treated_as_ascii - def test_rfc2231_bad_encoding_in_filename(self): - m = '''\ -Content-Disposition: inline; -\tfilename*0*="bogus'xx'This%20is%20even%20more%20"; -\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20"; -\tfilename*2="is it not.pdf" - -''' - msg = email.message_from_string(m) - self.assertEqual(msg.get_filename(), - 'This is even more ***fun*** is it not.pdf') - - def test_rfc2231_bad_encoding_in_charset(self): - m = """\ -Content-Type: text/plain; charset*=bogus''utf-8%E2%80%9D - -""" - msg = email.message_from_string(m) - # This should return None because non-ascii characters in the charset - # are not allowed. - self.assertEqual(msg.get_content_charset(), None) - - def test_rfc2231_bad_character_in_charset(self): - m = """\ -Content-Type: text/plain; charset*=ascii''utf-8%E2%80%9D - -""" - msg = email.message_from_string(m) - # This should return None because non-ascii characters in the charset - # are not allowed. - self.assertEqual(msg.get_content_charset(), None) - - def test_rfc2231_bad_character_in_filename(self): - m = '''\ -Content-Disposition: inline; -\tfilename*0*="ascii'xx'This%20is%20even%20more%20"; -\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20"; -\tfilename*2*="is it not.pdf%E2" - -''' - msg = email.message_from_string(m) - self.assertEqual(msg.get_filename(), - 'This is even more ***fun*** is it not.pdf\ufffd') - - def test_rfc2231_unknown_encoding(self): - m = """\ -Content-Transfer-Encoding: 8bit -Content-Disposition: inline; filename*=X-UNKNOWN''myfile.txt - -""" - msg = email.message_from_string(m) - self.assertEqual(msg.get_filename(), 'myfile.txt') - - def test_rfc2231_single_tick_in_filename_extended(self): - eq = self.assertEqual - m = """\ -Content-Type: application/x-foo; -\tname*0*=\"Frank's\"; name*1*=\" Document\" - -""" - msg = email.message_from_string(m) - charset, language, s = msg.get_param('name') - eq(charset, None) - eq(language, None) - eq(s, "Frank's Document") - - # test_headerregistry.TestContentTypeHeader.rfc2231_single_quote_inside_double_quotes - def test_rfc2231_single_tick_in_filename(self): - m = """\ -Content-Type: application/x-foo; name*0=\"Frank's\"; name*1=\" Document\" - -""" - msg = email.message_from_string(m) - param = msg.get_param('name') - self.assertFalse(isinstance(param, tuple)) - self.assertEqual(param, "Frank's Document") - - # test_headerregistry.TestContentTypeHeader.rfc2231_single_quote_in_value_with_charset_and_lang - def test_rfc2231_tick_attack_extended(self): - eq = self.assertEqual - m = """\ -Content-Type: application/x-foo; -\tname*0*=\"us-ascii'en-us'Frank's\"; name*1*=\" Document\" - -""" - msg = email.message_from_string(m) - charset, language, s = msg.get_param('name') - eq(charset, 'us-ascii') - eq(language, 'en-us') - eq(s, "Frank's Document") - - # test_headerregistry.TestContentTypeHeader.rfc2231_single_quote_in_non_encoded_value - def test_rfc2231_tick_attack(self): - m = """\ -Content-Type: application/x-foo; -\tname*0=\"us-ascii'en-us'Frank's\"; name*1=\" Document\" - -""" - msg = email.message_from_string(m) - param = msg.get_param('name') - self.assertFalse(isinstance(param, tuple)) - self.assertEqual(param, "us-ascii'en-us'Frank's Document") - - # test_headerregistry.TestContentTypeHeader.rfc2231_single_quotes_inside_quotes - def test_rfc2231_no_extended_values(self): - eq = self.assertEqual - m = """\ -Content-Type: application/x-foo; name=\"Frank's Document\" - -""" - msg = email.message_from_string(m) - eq(msg.get_param('name'), "Frank's Document") - - # test_headerregistry.TestContentTypeHeader.rfc2231_encoded_then_unencoded_segments - def test_rfc2231_encoded_then_unencoded_segments(self): - eq = self.assertEqual - m = """\ -Content-Type: application/x-foo; -\tname*0*=\"us-ascii'en-us'My\"; -\tname*1=\" Document\"; -\tname*2*=\" For You\" - -""" - msg = email.message_from_string(m) - charset, language, s = msg.get_param('name') - eq(charset, 'us-ascii') - eq(language, 'en-us') - eq(s, 'My Document For You') - - # test_headerregistry.TestContentTypeHeader.rfc2231_unencoded_then_encoded_segments - # test_headerregistry.TestContentTypeHeader.rfc2231_quoted_unencoded_then_encoded_segments - def test_rfc2231_unencoded_then_encoded_segments(self): - eq = self.assertEqual - m = """\ -Content-Type: application/x-foo; -\tname*0=\"us-ascii'en-us'My\"; -\tname*1*=\" Document\"; -\tname*2*=\" For You\" - -""" - msg = email.message_from_string(m) - charset, language, s = msg.get_param('name') - eq(charset, 'us-ascii') - eq(language, 'en-us') - eq(s, 'My Document For You') - - - -# Tests to ensure that signed parts of an email are completely preserved, as -# required by RFC1847 section 2.1. Note that these are incomplete, because the -# email package does not currently always preserve the body. See issue 1670765. -class TestSigned(TestEmailBase): - - def _msg_and_obj(self, filename): - with openfile(filename) as fp: - original = fp.read() - msg = email.message_from_string(original) - return original, msg - - def _signed_parts_eq(self, original, result): - # Extract the first mime part of each message - import re - repart = re.compile(r'^--([^\n]+)\n(.*?)\n--\1$', re.S | re.M) - inpart = repart.search(original).group(2) - outpart = repart.search(result).group(2) - self.assertEqual(outpart, inpart) - - def test_long_headers_as_string(self): - original, msg = self._msg_and_obj('msg_45.txt') - result = msg.as_string() - self._signed_parts_eq(original, result) - - def test_long_headers_as_string_maxheaderlen(self): - original, msg = self._msg_and_obj('msg_45.txt') - result = msg.as_string(maxheaderlen=60) - self._signed_parts_eq(original, result) - - def test_long_headers_flatten(self): - original, msg = self._msg_and_obj('msg_45.txt') - fp = StringIO() - Generator(fp).flatten(msg) - result = fp.getvalue() - self._signed_parts_eq(original, result) - - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_future/disabled/test_email/disabled_test_generator.py b/tests/test_future/disabled/test_email/disabled_test_generator.py deleted file mode 100644 index 34259a4c..00000000 --- a/tests/test_future/disabled/test_email/disabled_test_generator.py +++ /dev/null @@ -1,202 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, division, unicode_literals -import io -import textwrap -from future.tests.base import unittest -from future.standard_library.email import message_from_string, message_from_bytes -from future.standard_library.email.generator import Generator, BytesGenerator -from future.standard_library.email import policy -from future.tests.test_email import TestEmailBase, parameterize -from future.builtins import str - - -@parameterize -class TestGeneratorBase(object): - - policy = policy.default - - def msgmaker(self, msg, policy=None): - policy = self.policy if policy is None else policy - return self.msgfunc(msg, policy=policy) - - refold_long_expected = { - 0: textwrap.dedent("""\ - To: whom_it_may_concern@example.com - From: nobody_you_want_to_know@example.com - Subject: We the willing led by the unknowing are doing the - impossible for the ungrateful. We have done so much for so long with so little - we are now qualified to do anything with nothing. - - None - """), - # From is wrapped because wrapped it fits in 40. - 40: textwrap.dedent("""\ - To: whom_it_may_concern@example.com - From: - nobody_you_want_to_know@example.com - Subject: We the willing led by the - unknowing are doing the impossible for - the ungrateful. We have done so much - for so long with so little we are now - qualified to do anything with nothing. - - None - """), - # Neither to nor from fit even if put on a new line, - # so we leave them sticking out on the first line. - 20: textwrap.dedent("""\ - To: whom_it_may_concern@example.com - From: nobody_you_want_to_know@example.com - Subject: We the - willing led by the - unknowing are doing - the impossible for - the ungrateful. We - have done so much - for so long with so - little we are now - qualified to do - anything with - nothing. - - None - """), - } - refold_long_expected[100] = refold_long_expected[0] - - refold_all_expected = refold_long_expected.copy() - refold_all_expected[0] = ( - "To: whom_it_may_concern@example.com\n" - "From: nobody_you_want_to_know@example.com\n" - "Subject: We the willing led by the unknowing are doing the " - "impossible for the ungrateful. We have done so much for " - "so long with so little we are now qualified to do anything " - "with nothing.\n" - "\n" - "None\n") - refold_all_expected[100] = ( - "To: whom_it_may_concern@example.com\n" - "From: nobody_you_want_to_know@example.com\n" - "Subject: We the willing led by the unknowing are doing the " - "impossible for the ungrateful. We have\n" - " done so much for so long with so little we are now qualified " - "to do anything with nothing.\n" - "\n" - "None\n") - - length_params = [n for n in refold_long_expected] - - def length_as_maxheaderlen_parameter(self, n): - msg = self.msgmaker(self.typ(self.refold_long_expected[0])) - s = self.ioclass() - g = self.genclass(s, maxheaderlen=n, policy=self.policy) - g.flatten(msg) - self.assertEqual(s.getvalue(), self.typ(self.refold_long_expected[n])) - - def length_as_max_line_length_policy(self, n): - msg = self.msgmaker(self.typ(self.refold_long_expected[0])) - s = self.ioclass() - g = self.genclass(s, policy=self.policy.clone(max_line_length=n)) - g.flatten(msg) - self.assertEqual(s.getvalue(), self.typ(self.refold_long_expected[n])) - - def length_as_maxheaderlen_parm_overrides_policy(self, n): - msg = self.msgmaker(self.typ(self.refold_long_expected[0])) - s = self.ioclass() - g = self.genclass(s, maxheaderlen=n, - policy=self.policy.clone(max_line_length=10)) - g.flatten(msg) - self.assertEqual(s.getvalue(), self.typ(self.refold_long_expected[n])) - - def length_as_max_line_length_with_refold_none_does_not_fold(self, n): - msg = self.msgmaker(self.typ(self.refold_long_expected[0])) - s = self.ioclass() - g = self.genclass(s, policy=self.policy.clone(refold_source='none', - max_line_length=n)) - g.flatten(msg) - self.assertEqual(s.getvalue(), self.typ(self.refold_long_expected[0])) - - def length_as_max_line_length_with_refold_all_folds(self, n): - msg = self.msgmaker(self.typ(self.refold_long_expected[0])) - s = self.ioclass() - g = self.genclass(s, policy=self.policy.clone(refold_source='all', - max_line_length=n)) - g.flatten(msg) - self.assertEqual(s.getvalue(), self.typ(self.refold_all_expected[n])) - - def test_crlf_control_via_policy(self): - source = "Subject: test\r\n\r\ntest body\r\n" - expected = source - msg = self.msgmaker(self.typ(source)) - s = self.ioclass() - g = self.genclass(s, policy=policy.SMTP) - g.flatten(msg) - self.assertEqual(s.getvalue(), self.typ(expected)) - - def test_flatten_linesep_overrides_policy(self): - source = "Subject: test\n\ntest body\n" - expected = source - msg = self.msgmaker(self.typ(source)) - s = self.ioclass() - g = self.genclass(s, policy=policy.SMTP) - g.flatten(msg, linesep='\n') - self.assertEqual(s.getvalue(), self.typ(expected)) - - -class TestGenerator(TestGeneratorBase, TestEmailBase): - - msgfunc = staticmethod(message_from_string) - genclass = Generator - ioclass = io.StringIO - typ = str - - -class TestBytesGenerator(TestGeneratorBase, TestEmailBase): - - msgfunc = staticmethod(message_from_bytes) - genclass = BytesGenerator - ioclass = io.BytesIO - typ = lambda self, x: x.encode('ascii') - - def test_cte_type_7bit_handles_unknown_8bit(self): - source = ("Subject: Maintenant je vous présente mon " - "collègue\n\n").encode('utf-8') - expected = ('Subject: Maintenant je vous =?unknown-8bit?q?' - 'pr=C3=A9sente_mon_coll=C3=A8gue?=\n\n').encode('ascii') - msg = message_from_bytes(source) - s = io.BytesIO() - g = BytesGenerator(s, policy=self.policy.clone(cte_type='7bit')) - g.flatten(msg) - self.assertEqual(s.getvalue(), expected) - - def test_cte_type_7bit_transforms_8bit_cte(self): - source = textwrap.dedent("""\ - From: foo@bar.com - To: Dinsdale - Subject: Nudge nudge, wink, wink - Mime-Version: 1.0 - Content-Type: text/plain; charset="latin-1" - Content-Transfer-Encoding: 8bit - - oh là là, know what I mean, know what I mean? - """).encode('latin1') - msg = message_from_bytes(source) - expected = textwrap.dedent("""\ - From: foo@bar.com - To: Dinsdale - Subject: Nudge nudge, wink, wink - Mime-Version: 1.0 - Content-Type: text/plain; charset="iso-8859-1" - Content-Transfer-Encoding: quoted-printable - - oh l=E0 l=E0, know what I mean, know what I mean? - """).encode('ascii') - s = io.BytesIO() - g = BytesGenerator(s, policy=self.policy.clone(cte_type='7bit', - linesep='\n')) - g.flatten(msg) - self.assertEqual(s.getvalue(), expected) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_future/disabled/test_email/disabled_test_headerregistry.py b/tests/test_future/disabled/test_email/disabled_test_headerregistry.py deleted file mode 100644 index a77e0cf7..00000000 --- a/tests/test_future/disabled/test_email/disabled_test_headerregistry.py +++ /dev/null @@ -1,1521 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import -import textwrap -from future.standard_library.email import errors -from future.standard_library.email import policy -from future.standard_library.email.message import Message -from future.standard_library.email import headerregistry -from future.standard_library.email.headerregistry import Address, Group -from future.standard_library import datetime -from future.tests.test_email import TestEmailBase, parameterize -from future.tests.base import unittest -from future.builtins import range, str - - -DITTO = object() - - -class TestHeaderRegistry(TestEmailBase): - - def test_arbitrary_name_unstructured(self): - factory = headerregistry.HeaderRegistry() - h = factory('foobar', 'test') - self.assertIsInstance(h, headerregistry.BaseHeader) - self.assertIsInstance(h, headerregistry.UnstructuredHeader) - - def test_name_case_ignored(self): - factory = headerregistry.HeaderRegistry() - # Whitebox check that test is valid - self.assertNotIn('Subject', factory.registry) - h = factory('Subject', 'test') - self.assertIsInstance(h, headerregistry.BaseHeader) - self.assertIsInstance(h, headerregistry.UniqueUnstructuredHeader) - - class FooBase(object): - def __init__(self, *args, **kw): - pass - - def test_override_default_base_class(self): - factory = headerregistry.HeaderRegistry(base_class=self.FooBase) - h = factory('foobar', 'test') - self.assertIsInstance(h, self.FooBase) - self.assertIsInstance(h, headerregistry.UnstructuredHeader) - - class FooDefault(object): - parse = headerregistry.UnstructuredHeader.parse - - def test_override_default_class(self): - factory = headerregistry.HeaderRegistry(default_class=self.FooDefault) - h = factory('foobar', 'test') - self.assertIsInstance(h, headerregistry.BaseHeader) - self.assertIsInstance(h, self.FooDefault) - - def test_override_default_class_only_overrides_default(self): - factory = headerregistry.HeaderRegistry(default_class=self.FooDefault) - h = factory('subject', 'test') - self.assertIsInstance(h, headerregistry.BaseHeader) - self.assertIsInstance(h, headerregistry.UniqueUnstructuredHeader) - - def test_dont_use_default_map(self): - factory = headerregistry.HeaderRegistry(use_default_map=False) - h = factory('subject', 'test') - self.assertIsInstance(h, headerregistry.BaseHeader) - self.assertIsInstance(h, headerregistry.UnstructuredHeader) - - def test_map_to_type(self): - factory = headerregistry.HeaderRegistry() - h1 = factory('foobar', 'test') - factory.map_to_type('foobar', headerregistry.UniqueUnstructuredHeader) - h2 = factory('foobar', 'test') - self.assertIsInstance(h1, headerregistry.BaseHeader) - self.assertIsInstance(h1, headerregistry.UnstructuredHeader) - self.assertIsInstance(h2, headerregistry.BaseHeader) - self.assertIsInstance(h2, headerregistry.UniqueUnstructuredHeader) - - -class TestHeaderBase(TestEmailBase): - - factory = headerregistry.HeaderRegistry() - - def make_header(self, name, value): - return self.factory(name, value) - - -class TestBaseHeaderFeatures(TestHeaderBase): - - def test_str(self): - h = self.make_header('subject', 'this is a test') - self.assertIsInstance(h, str) - self.assertEqual(h, 'this is a test') - self.assertEqual(str(h), 'this is a test') - - def test_substr(self): - h = self.make_header('subject', 'this is a test') - self.assertEqual(h[5:7], 'is') - - def test_has_name(self): - h = self.make_header('subject', 'this is a test') - self.assertEqual(h.name, 'subject') - - def _test_attr_ro(self, attr): - h = self.make_header('subject', 'this is a test') - with self.assertRaises(AttributeError): - setattr(h, attr, 'foo') - - def test_name_read_only(self): - self._test_attr_ro('name') - - def test_defects_read_only(self): - self._test_attr_ro('defects') - - def test_defects_is_tuple(self): - h = self.make_header('subject', 'this is a test') - self.assertEqual(len(h.defects), 0) - self.assertIsInstance(h.defects, tuple) - # Make sure it is still true when there are defects. - h = self.make_header('date', '') - self.assertEqual(len(h.defects), 1) - self.assertIsInstance(h.defects, tuple) - - # XXX: FIXME - #def test_CR_in_value(self): - # # XXX: this also re-raises the issue of embedded headers, - # # need test and solution for that. - # value = '\r'.join(['this is', ' a test']) - # h = self.make_header('subject', value) - # self.assertEqual(h, value) - # self.assertDefectsEqual(h.defects, [errors.ObsoleteHeaderDefect]) - - def test_RFC2047_value_decoded(self): - value = '=?utf-8?q?this_is_a_test?=' - h = self.make_header('subject', value) - self.assertEqual(h, 'this is a test') - - -class TestDateHeader(TestHeaderBase): - - datestring = 'Sun, 23 Sep 2001 20:10:55 -0700' - utcoffset = datetime.timedelta(hours=-7) - tz = datetime.timezone(utcoffset) - dt = datetime.datetime(2001, 9, 23, 20, 10, 55, tzinfo=tz) - - def test_parse_date(self): - h = self.make_header('date', self.datestring) - self.assertEqual(h, self.datestring) - self.assertEqual(h.datetime, self.dt) - self.assertEqual(h.datetime.utcoffset(), self.utcoffset) - self.assertEqual(h.defects, ()) - - def test_set_from_datetime(self): - h = self.make_header('date', self.dt) - self.assertEqual(h, self.datestring) - self.assertEqual(h.datetime, self.dt) - self.assertEqual(h.defects, ()) - - def test_date_header_properties(self): - h = self.make_header('date', self.datestring) - self.assertIsInstance(h, headerregistry.UniqueDateHeader) - self.assertEqual(h.max_count, 1) - self.assertEqual(h.defects, ()) - - def test_resent_date_header_properties(self): - h = self.make_header('resent-date', self.datestring) - self.assertIsInstance(h, headerregistry.DateHeader) - self.assertEqual(h.max_count, None) - self.assertEqual(h.defects, ()) - - def test_no_value_is_defect(self): - h = self.make_header('date', '') - self.assertEqual(len(h.defects), 1) - self.assertIsInstance(h.defects[0], errors.HeaderMissingRequiredValue) - - def test_datetime_read_only(self): - h = self.make_header('date', self.datestring) - with self.assertRaises(AttributeError): - h.datetime = 'foo' - - def test_set_date_header_from_datetime(self): - m = Message(policy=policy.default) - m['Date'] = self.dt - self.assertEqual(m['Date'], self.datestring) - self.assertEqual(m['Date'].datetime, self.dt) - - -@parameterize -class TestContentTypeHeader(TestHeaderBase): - - def content_type_as_value(self, - source, - content_type, - maintype, - subtype, - *args): - l = len(args) - parmdict = args[0] if l>0 else {} - defects = args[1] if l>1 else [] - decoded = args[2] if l>2 and args[2] is not DITTO else source - header = 'Content-Type:' + ' ' if source else '' - folded = args[3] if l>3 else header + source + '\n' - h = self.make_header('Content-Type', source) - self.assertEqual(h.content_type, content_type) - self.assertEqual(h.maintype, maintype) - self.assertEqual(h.subtype, subtype) - self.assertEqual(h.params, parmdict) - self.assertDefectsEqual(h.defects, defects) - self.assertEqual(h, decoded) - self.assertEqual(h.fold(policy=policy.default), folded) - - content_type_params = { - - # Examples from RFC 2045. - - 'RFC_2045_1': ( - 'text/plain; charset=us-ascii (Plain text)', - 'text/plain', - 'text', - 'plain', - {'charset': 'us-ascii'}, - [], - 'text/plain; charset="us-ascii"'), - - 'RFC_2045_2': ( - 'text/plain; charset=us-ascii', - 'text/plain', - 'text', - 'plain', - {'charset': 'us-ascii'}, - [], - 'text/plain; charset="us-ascii"'), - - 'RFC_2045_3': ( - 'text/plain; charset="us-ascii"', - 'text/plain', - 'text', - 'plain', - {'charset': 'us-ascii'}), - - # RFC 2045 5.2 says syntactically invalid values are to be treated as - # text/plain. - - 'no_subtype_in_content_type': ( - 'text/', - 'text/plain', - 'text', - 'plain', - {}, - [errors.InvalidHeaderDefect]), - - 'no_slash_in_content_type': ( - 'foo', - 'text/plain', - 'text', - 'plain', - {}, - [errors.InvalidHeaderDefect]), - - 'junk_text_in_content_type': ( - '', - 'text/plain', - 'text', - 'plain', - {}, - [errors.InvalidHeaderDefect]), - - 'too_many_slashes_in_content_type': ( - 'image/jpeg/foo', - 'text/plain', - 'text', - 'plain', - {}, - [errors.InvalidHeaderDefect]), - - # But unknown names are OK. We could make non-IANA names a defect, but - # by not doing so we make ourselves future proof. The fact that they - # are unknown will be detectable by the fact that they don't appear in - # the mime_registry...and the application is free to extend that list - # to handle them even if the core library doesn't. - - 'unknown_content_type': ( - 'bad/names', - 'bad/names', - 'bad', - 'names'), - - # The content type is case insensitive, and CFWS is ignored. - - 'mixed_case_content_type': ( - 'ImAge/JPeg', - 'image/jpeg', - 'image', - 'jpeg'), - - 'spaces_in_content_type': ( - ' text / plain ', - 'text/plain', - 'text', - 'plain'), - - 'cfws_in_content_type': ( - '(foo) text (bar)/(baz)plain(stuff)', - 'text/plain', - 'text', - 'plain'), - - # test some parameters (more tests could be added for parameters - # associated with other content types, but since parameter parsing is - # generic they would be redundant for the current implementation). - - 'charset_param': ( - 'text/plain; charset="utf-8"', - 'text/plain', - 'text', - 'plain', - {'charset': 'utf-8'}), - - 'capitalized_charset': ( - 'text/plain; charset="US-ASCII"', - 'text/plain', - 'text', - 'plain', - {'charset': 'US-ASCII'}), - - 'unknown_charset': ( - 'text/plain; charset="fOo"', - 'text/plain', - 'text', - 'plain', - {'charset': 'fOo'}), - - 'capitalized_charset_param_name_and_comment': ( - 'text/plain; (interjection) Charset="utf-8"', - 'text/plain', - 'text', - 'plain', - {'charset': 'utf-8'}, - [], - # Should the parameter name be lowercased here? - 'text/plain; Charset="utf-8"'), - - # Since this is pretty much the ur-mimeheader, we'll put all the tests - # that exercise the parameter parsing and formatting here. - # - # XXX: question: is minimal quoting preferred? - - 'unquoted_param_value': ( - 'text/plain; title=foo', - 'text/plain', - 'text', - 'plain', - {'title': 'foo'}, - [], - 'text/plain; title="foo"'), - - 'param_value_with_tspecials': ( - 'text/plain; title="(bar)foo blue"', - 'text/plain', - 'text', - 'plain', - {'title': '(bar)foo blue'}), - - 'param_with_extra_quoted_whitespace': ( - 'text/plain; title=" a loong way \t home "', - 'text/plain', - 'text', - 'plain', - {'title': ' a loong way \t home '}), - - 'bad_params': ( - 'blarg; baz; boo', - 'text/plain', - 'text', - 'plain', - {'baz': '', 'boo': ''}, - [errors.InvalidHeaderDefect]*3), - - 'spaces_around_param_equals': ( - 'Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"', - 'multipart/mixed', - 'multipart', - 'mixed', - {'boundary': 'CPIMSSMTPC06p5f3tG'}, - [], - 'Multipart/mixed; boundary="CPIMSSMTPC06p5f3tG"'), - - 'spaces_around_semis': ( - ('image/jpeg; name="wibble.JPG" ; x-mac-type="4A504547" ; ' - 'x-mac-creator="474B4F4E"'), - 'image/jpeg', - 'image', - 'jpeg', - {'name': 'wibble.JPG', - 'x-mac-type': '4A504547', - 'x-mac-creator': '474B4F4E'}, - [], - ('image/jpeg; name="wibble.JPG"; x-mac-type="4A504547"; ' - 'x-mac-creator="474B4F4E"'), - # XXX: it could be that we will eventually prefer to fold starting - # from the decoded value, in which case these spaces and similar - # spaces in other tests will be wrong. - ('Content-Type: image/jpeg; name="wibble.JPG" ; ' - 'x-mac-type="4A504547" ;\n' - ' x-mac-creator="474B4F4E"\n'), - ), - - 'semis_inside_quotes': ( - 'image/jpeg; name="Jim&&Jill"', - 'image/jpeg', - 'image', - 'jpeg', - {'name': 'Jim&&Jill'}), - - 'single_quotes_inside_quotes': ( - 'image/jpeg; name="Jim \'Bob\' Jill"', - 'image/jpeg', - 'image', - 'jpeg', - {'name': "Jim 'Bob' Jill"}), - - 'double_quotes_inside_quotes': ( - r'image/jpeg; name="Jim \"Bob\" Jill"', - 'image/jpeg', - 'image', - 'jpeg', - {'name': 'Jim "Bob" Jill'}, - [], - r'image/jpeg; name="Jim \"Bob\" Jill"'), - - # XXX: This test works except for the refolding of the header. I'll - # deal with that bug when I deal with the other folding bugs. - #'non_ascii_in_params': ( - # ('foo\xa7/bar; b\xa7r=two; ' - # 'baz=thr\xa7e'.encode('latin-1').decode('us-ascii', - # 'surrogateescape')), - # 'foo\uFFFD/bar', - # 'foo\uFFFD', - # 'bar', - # {'b\uFFFDr': 'two', 'baz': 'thr\uFFFDe'}, - # [errors.UndecodableBytesDefect]*3, - # 'foo�/bar; b�r="two"; baz="thr�e"', - # ), - - # RFC 2231 parameter tests. - - 'rfc2231_segmented_normal_values': ( - 'image/jpeg; name*0="abc"; name*1=".html"', - 'image/jpeg', - 'image', - 'jpeg', - {'name': "abc.html"}, - [], - 'image/jpeg; name="abc.html"'), - - 'quotes_inside_rfc2231_value': ( - r'image/jpeg; bar*0="baz\"foobar"; bar*1="\"baz"', - 'image/jpeg', - 'image', - 'jpeg', - {'bar': 'baz"foobar"baz'}, - [], - r'image/jpeg; bar="baz\"foobar\"baz"'), - - # XXX: This test works except for the refolding of the header. I'll - # deal with that bug when I deal with the other folding bugs. - #'non_ascii_rfc2231_value': ( - # ('text/plain; charset=us-ascii; ' - # "title*=us-ascii'en'This%20is%20" - # 'not%20f\xa7n').encode('latin-1').decode('us-ascii', - # 'surrogateescape'), - # 'text/plain', - # 'text', - # 'plain', - # {'charset': 'us-ascii', 'title': 'This is not f\uFFFDn'}, - # [errors.UndecodableBytesDefect], - # 'text/plain; charset="us-ascii"; title="This is not f�n"'), - - 'rfc2231_encoded_charset': ( - 'text/plain; charset*=ansi-x3.4-1968\'\'us-ascii', - 'text/plain', - 'text', - 'plain', - {'charset': 'us-ascii'}, - [], - 'text/plain; charset="us-ascii"'), - - # This follows the RFC: no double quotes around encoded values. - 'rfc2231_encoded_no_double_quotes': ( - ("text/plain;" - "\tname*0*=''This%20is%20;" - "\tname*1*=%2A%2A%2Afun%2A%2A%2A%20;" - '\tname*2="is it not.pdf"'), - 'text/plain', - 'text', - 'plain', - {'name': 'This is ***fun*** is it not.pdf'}, - [], - 'text/plain; name="This is ***fun*** is it not.pdf"', - ('Content-Type: text/plain;\tname*0*=\'\'This%20is%20;\n' - '\tname*1*=%2A%2A%2Afun%2A%2A%2A%20;\tname*2="is it not.pdf"\n'), - ), - - # Make sure we also handle it if there are spurrious double qoutes. - 'rfc2231_encoded_with_double_quotes': ( - ("text/plain;" - '\tname*0*="us-ascii\'\'This%20is%20even%20more%20";' - '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";' - '\tname*2="is it not.pdf"'), - 'text/plain', - 'text', - 'plain', - {'name': 'This is even more ***fun*** is it not.pdf'}, - [errors.InvalidHeaderDefect]*2, - 'text/plain; name="This is even more ***fun*** is it not.pdf"', - ('Content-Type: text/plain;\t' - 'name*0*="us-ascii\'\'This%20is%20even%20more%20";\n' - '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";\tname*2="is it not.pdf"\n'), - ), - - 'rfc2231_single_quote_inside_double_quotes': ( - ('text/plain; charset=us-ascii;' - '\ttitle*0*="us-ascii\'en\'This%20is%20really%20";' - '\ttitle*1*="%2A%2A%2Afun%2A%2A%2A%20";' - '\ttitle*2="isn\'t it!"'), - 'text/plain', - 'text', - 'plain', - {'charset': 'us-ascii', 'title': "This is really ***fun*** isn't it!"}, - [errors.InvalidHeaderDefect]*2, - ('text/plain; charset="us-ascii"; ' - 'title="This is really ***fun*** isn\'t it!"'), - ('Content-Type: text/plain; charset=us-ascii;\n' - '\ttitle*0*="us-ascii\'en\'This%20is%20really%20";\n' - '\ttitle*1*="%2A%2A%2Afun%2A%2A%2A%20";\ttitle*2="isn\'t it!"\n'), - ), - - 'rfc2231_single_quote_in_value_with_charset_and_lang': ( - ('application/x-foo;' - "\tname*0*=\"us-ascii'en-us'Frank's\"; name*1*=\" Document\""), - 'application/x-foo', - 'application', - 'x-foo', - {'name': "Frank's Document"}, - [errors.InvalidHeaderDefect]*2, - 'application/x-foo; name="Frank\'s Document"', - ('Content-Type: application/x-foo;\t' - 'name*0*="us-ascii\'en-us\'Frank\'s";\n' - ' name*1*=" Document"\n'), - ), - - 'rfc2231_single_quote_in_non_encoded_value': ( - ('application/x-foo;' - "\tname*0=\"us-ascii'en-us'Frank's\"; name*1=\" Document\""), - 'application/x-foo', - 'application', - 'x-foo', - {'name': "us-ascii'en-us'Frank's Document"}, - [], - 'application/x-foo; name="us-ascii\'en-us\'Frank\'s Document"', - ('Content-Type: application/x-foo;\t' - 'name*0="us-ascii\'en-us\'Frank\'s";\n' - ' name*1=" Document"\n'), - ), - - 'rfc2231_no_language_or_charset': ( - 'text/plain; NAME*0*=english_is_the_default.html', - 'text/plain', - 'text', - 'plain', - {'name': 'english_is_the_default.html'}, - [errors.InvalidHeaderDefect], - 'text/plain; NAME="english_is_the_default.html"'), - - 'rfc2231_encoded_no_charset': ( - ("text/plain;" - '\tname*0*="\'\'This%20is%20even%20more%20";' - '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";' - '\tname*2="is it.pdf"'), - 'text/plain', - 'text', - 'plain', - {'name': 'This is even more ***fun*** is it.pdf'}, - [errors.InvalidHeaderDefect]*2, - 'text/plain; name="This is even more ***fun*** is it.pdf"', - ('Content-Type: text/plain;\t' - 'name*0*="\'\'This%20is%20even%20more%20";\n' - '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";\tname*2="is it.pdf"\n'), - ), - - # XXX: see below...the first name line here should be *0 not *0*. - 'rfc2231_partly_encoded': ( - ("text/plain;" - '\tname*0*="\'\'This%20is%20even%20more%20";' - '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";' - '\tname*2="is it.pdf"'), - 'text/plain', - 'text', - 'plain', - {'name': 'This is even more ***fun*** is it.pdf'}, - [errors.InvalidHeaderDefect]*2, - 'text/plain; name="This is even more ***fun*** is it.pdf"', - ('Content-Type: text/plain;\t' - 'name*0*="\'\'This%20is%20even%20more%20";\n' - '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";\tname*2="is it.pdf"\n'), - ), - - 'rfc2231_partly_encoded_2': ( - ("text/plain;" - '\tname*0*="\'\'This%20is%20even%20more%20";' - '\tname*1="%2A%2A%2Afun%2A%2A%2A%20";' - '\tname*2="is it.pdf"'), - 'text/plain', - 'text', - 'plain', - {'name': 'This is even more %2A%2A%2Afun%2A%2A%2A%20is it.pdf'}, - [errors.InvalidHeaderDefect], - 'text/plain; name="This is even more %2A%2A%2Afun%2A%2A%2A%20is it.pdf"', - ('Content-Type: text/plain;\t' - 'name*0*="\'\'This%20is%20even%20more%20";\n' - '\tname*1="%2A%2A%2Afun%2A%2A%2A%20";\tname*2="is it.pdf"\n'), - ), - - 'rfc2231_unknown_charset_treated_as_ascii': ( - "text/plain; name*0*=bogus'xx'ascii_is_the_default", - 'text/plain', - 'text', - 'plain', - {'name': 'ascii_is_the_default'}, - [], - 'text/plain; name="ascii_is_the_default"'), - - 'rfc2231_bad_character_in_charset_parameter_value': ( - "text/plain; charset*=ascii''utf-8%E2%80%9D", - 'text/plain', - 'text', - 'plain', - {'charset': 'utf-8\uFFFD\uFFFD\uFFFD'}, - [errors.UndecodableBytesDefect], - 'text/plain; charset="utf-8\uFFFD\uFFFD\uFFFD"'), - - 'rfc2231_encoded_then_unencoded_segments': ( - ('application/x-foo;' - '\tname*0*="us-ascii\'en-us\'My";' - '\tname*1=" Document";' - '\tname*2=" For You"'), - 'application/x-foo', - 'application', - 'x-foo', - {'name': 'My Document For You'}, - [errors.InvalidHeaderDefect], - 'application/x-foo; name="My Document For You"', - ('Content-Type: application/x-foo;\t' - 'name*0*="us-ascii\'en-us\'My";\n' - '\tname*1=" Document";\tname*2=" For You"\n'), - ), - - # My reading of the RFC is that this is an invalid header. The RFC - # says that if charset and language information is given, the first - # segment *must* be encoded. - 'rfc2231_unencoded_then_encoded_segments': ( - ('application/x-foo;' - '\tname*0=us-ascii\'en-us\'My;' - '\tname*1*=" Document";' - '\tname*2*=" For You"'), - 'application/x-foo', - 'application', - 'x-foo', - {'name': 'My Document For You'}, - [errors.InvalidHeaderDefect]*3, - 'application/x-foo; name="My Document For You"', - ("Content-Type: application/x-foo;\tname*0=us-ascii'en-us'My;\t" - # XXX: the newline is in the wrong place, come back and fix - # this when the rest of tests pass. - 'name*1*=" Document"\n;' - '\tname*2*=" For You"\n'), - ), - - # XXX: I would say this one should default to ascii/en for the - # "encoded" segment, since the first segment is not encoded and is - # in double quotes, making the value a valid non-encoded string. The - # old parser decodes this just like the previous case, which may be the - # better Postel rule, but could equally result in borking headers that - # intentially have quoted quotes in them. We could get this 98% right - # if we treat it as a quoted string *unless* it matches the - # charset'lang'value pattern exactly *and* there is at least one - # encoded segment. Implementing that algorithm will require some - # refactoring, so I haven't done it (yet). - - 'rfc2231_qouted_unencoded_then_encoded_segments': ( - ('application/x-foo;' - '\tname*0="us-ascii\'en-us\'My";' - '\tname*1*=" Document";' - '\tname*2*=" For You"'), - 'application/x-foo', - 'application', - 'x-foo', - {'name': "us-ascii'en-us'My Document For You"}, - [errors.InvalidHeaderDefect]*2, - 'application/x-foo; name="us-ascii\'en-us\'My Document For You"', - ('Content-Type: application/x-foo;\t' - 'name*0="us-ascii\'en-us\'My";\n' - '\tname*1*=" Document";\tname*2*=" For You"\n'), - ), - - } - - -@parameterize -class TestContentTransferEncoding(TestHeaderBase): - - def cte_as_value(self, - source, - cte, - *args): - l = len(args) - defects = args[0] if l>0 else [] - decoded = args[1] if l>1 and args[1] is not DITTO else source - header = 'Content-Transfer-Encoding:' + ' ' if source else '' - folded = args[2] if l>2 else header + source + '\n' - h = self.make_header('Content-Transfer-Encoding', source) - self.assertEqual(h.cte, cte) - self.assertDefectsEqual(h.defects, defects) - self.assertEqual(h, decoded) - self.assertEqual(h.fold(policy=policy.default), folded) - - cte_params = { - - 'RFC_2183_1': ( - 'base64', - 'base64',), - - 'no_value': ( - '', - '7bit', - [errors.HeaderMissingRequiredValue], - '', - 'Content-Transfer-Encoding:\n', - ), - - 'junk_after_cte': ( - '7bit and a bunch more', - '7bit', - [errors.InvalidHeaderDefect]), - - } - - -@parameterize -class TestContentDisposition(TestHeaderBase): - - def content_disp_as_value(self, - source, - content_disposition, - *args): - l = len(args) - parmdict = args[0] if l>0 else {} - defects = args[1] if l>1 else [] - decoded = args[2] if l>2 and args[2] is not DITTO else source - header = 'Content-Disposition:' + ' ' if source else '' - folded = args[3] if l>3 else header + source + '\n' - h = self.make_header('Content-Disposition', source) - self.assertEqual(h.content_disposition, content_disposition) - self.assertEqual(h.params, parmdict) - self.assertDefectsEqual(h.defects, defects) - self.assertEqual(h, decoded) - self.assertEqual(h.fold(policy=policy.default), folded) - - content_disp_params = { - - # Examples from RFC 2183. - - 'RFC_2183_1': ( - 'inline', - 'inline',), - - 'RFC_2183_2': ( - ('attachment; filename=genome.jpeg;' - ' modification-date="Wed, 12 Feb 1997 16:29:51 -0500";'), - 'attachment', - {'filename': 'genome.jpeg', - 'modification-date': 'Wed, 12 Feb 1997 16:29:51 -0500'}, - [], - ('attachment; filename="genome.jpeg"; ' - 'modification-date="Wed, 12 Feb 1997 16:29:51 -0500"'), - ('Content-Disposition: attachment; filename=genome.jpeg;\n' - ' modification-date="Wed, 12 Feb 1997 16:29:51 -0500";\n'), - ), - - 'no_value': ( - '', - None, - {}, - [errors.HeaderMissingRequiredValue], - '', - 'Content-Disposition:\n'), - - 'invalid_value': ( - 'ab./k', - 'ab.', - {}, - [errors.InvalidHeaderDefect]), - - 'invalid_value_with_params': ( - 'ab./k; filename="foo"', - 'ab.', - {'filename': 'foo'}, - [errors.InvalidHeaderDefect]), - - } - - -@parameterize -class TestMIMEVersionHeader(TestHeaderBase): - - def version_string_as_MIME_Version(self, - source, - decoded, - version, - major, - minor, - defects): - h = self.make_header('MIME-Version', source) - self.assertEqual(h, decoded) - self.assertEqual(h.version, version) - self.assertEqual(h.major, major) - self.assertEqual(h.minor, minor) - self.assertDefectsEqual(h.defects, defects) - if source: - source = ' ' + source - self.assertEqual(h.fold(policy=policy.default), - 'MIME-Version:' + source + '\n') - - version_string_params = { - - # Examples from the RFC. - - 'RFC_2045_1': ( - '1.0', - '1.0', - '1.0', - 1, - 0, - []), - - 'RFC_2045_2': ( - '1.0 (produced by MetaSend Vx.x)', - '1.0 (produced by MetaSend Vx.x)', - '1.0', - 1, - 0, - []), - - 'RFC_2045_3': ( - '(produced by MetaSend Vx.x) 1.0', - '(produced by MetaSend Vx.x) 1.0', - '1.0', - 1, - 0, - []), - - 'RFC_2045_4': ( - '1.(produced by MetaSend Vx.x)0', - '1.(produced by MetaSend Vx.x)0', - '1.0', - 1, - 0, - []), - - # Other valid values. - - '1_1': ( - '1.1', - '1.1', - '1.1', - 1, - 1, - []), - - '2_1': ( - '2.1', - '2.1', - '2.1', - 2, - 1, - []), - - 'whitespace': ( - '1 .0', - '1 .0', - '1.0', - 1, - 0, - []), - - 'leading_trailing_whitespace_ignored': ( - ' 1.0 ', - ' 1.0 ', - '1.0', - 1, - 0, - []), - - # Recoverable invalid values. We can recover here only because we - # already have a valid value by the time we encounter the garbage. - # Anywhere else, and we don't know where the garbage ends. - - 'non_comment_garbage_after': ( - '1.0 ', - '1.0 ', - '1.0', - 1, - 0, - [errors.InvalidHeaderDefect]), - - # Unrecoverable invalid values. We *could* apply more heuristics to - # get someing out of the first two, but doing so is not worth the - # effort. - - 'non_comment_garbage_before': ( - ' 1.0', - ' 1.0', - None, - None, - None, - [errors.InvalidHeaderDefect]), - - 'non_comment_garbage_inside': ( - '1.0', - '1.0', - None, - None, - None, - [errors.InvalidHeaderDefect]), - - 'two_periods': ( - '1..0', - '1..0', - None, - None, - None, - [errors.InvalidHeaderDefect]), - - '2_x': ( - '2.x', - '2.x', - None, # This could be 2, but it seems safer to make it None. - None, - None, - [errors.InvalidHeaderDefect]), - - 'foo': ( - 'foo', - 'foo', - None, - None, - None, - [errors.InvalidHeaderDefect]), - - 'missing': ( - '', - '', - None, - None, - None, - [errors.HeaderMissingRequiredValue]), - - } - - -@parameterize -class TestAddressHeader(TestHeaderBase): - - example_params = { - - 'empty': - ('<>', - [errors.InvalidHeaderDefect], - '<>', - '', - '<>', - '', - '', - None), - - 'address_only': - ('zippy@pinhead.com', - [], - 'zippy@pinhead.com', - '', - 'zippy@pinhead.com', - 'zippy', - 'pinhead.com', - None), - - 'name_and_address': - ('Zaphrod Beblebrux ', - [], - 'Zaphrod Beblebrux ', - 'Zaphrod Beblebrux', - 'zippy@pinhead.com', - 'zippy', - 'pinhead.com', - None), - - 'quoted_local_part': - ('Zaphrod Beblebrux <"foo bar"@pinhead.com>', - [], - 'Zaphrod Beblebrux <"foo bar"@pinhead.com>', - 'Zaphrod Beblebrux', - '"foo bar"@pinhead.com', - 'foo bar', - 'pinhead.com', - None), - - 'quoted_parens_in_name': - (r'"A \(Special\) Person" ', - [], - '"A (Special) Person" ', - 'A (Special) Person', - 'person@dom.ain', - 'person', - 'dom.ain', - None), - - 'quoted_backslashes_in_name': - (r'"Arthur \\Backslash\\ Foobar" ', - [], - r'"Arthur \\Backslash\\ Foobar" ', - r'Arthur \Backslash\ Foobar', - 'person@dom.ain', - 'person', - 'dom.ain', - None), - - 'name_with_dot': - ('John X. Doe ', - [errors.ObsoleteHeaderDefect], - '"John X. Doe" ', - 'John X. Doe', - 'jxd@example.com', - 'jxd', - 'example.com', - None), - - 'quoted_strings_in_local_part': - ('""example" example"@example.com', - [errors.InvalidHeaderDefect]*3, - '"example example"@example.com', - '', - '"example example"@example.com', - 'example example', - 'example.com', - None), - - 'escaped_quoted_strings_in_local_part': - (r'"\"example\" example"@example.com', - [], - r'"\"example\" example"@example.com', - '', - r'"\"example\" example"@example.com', - r'"example" example', - 'example.com', - None), - - 'escaped_escapes_in_local_part': - (r'"\\"example\\" example"@example.com', - [errors.InvalidHeaderDefect]*5, - r'"\\example\\\\ example"@example.com', - '', - r'"\\example\\\\ example"@example.com', - r'\example\\ example', - 'example.com', - None), - - 'spaces_in_unquoted_local_part_collapsed': - ('merwok wok @example.com', - [errors.InvalidHeaderDefect]*2, - '"merwok wok"@example.com', - '', - '"merwok wok"@example.com', - 'merwok wok', - 'example.com', - None), - - 'spaces_around_dots_in_local_part_removed': - ('merwok. wok . wok@example.com', - [errors.ObsoleteHeaderDefect], - 'merwok.wok.wok@example.com', - '', - 'merwok.wok.wok@example.com', - 'merwok.wok.wok', - 'example.com', - None), - - } - - # XXX: Need many more examples, and in particular some with names in - # trailing comments, which aren't currently handled. comments in - # general are not handled yet. - - def example_as_address(self, source, defects, decoded, display_name, - addr_spec, username, domain, comment): - h = self.make_header('sender', source) - self.assertEqual(h, decoded) - self.assertDefectsEqual(h.defects, defects) - a = h.address - self.assertEqual(str(a), decoded) - self.assertEqual(len(h.groups), 1) - self.assertEqual([a], list(h.groups[0].addresses)) - self.assertEqual([a], list(h.addresses)) - self.assertEqual(a.display_name, display_name) - self.assertEqual(a.addr_spec, addr_spec) - self.assertEqual(a.username, username) - self.assertEqual(a.domain, domain) - # XXX: we have no comment support yet. - #self.assertEqual(a.comment, comment) - - def example_as_group(self, source, defects, decoded, display_name, - addr_spec, username, domain, comment): - source = 'foo: {};'.format(source) - gdecoded = 'foo: {};'.format(decoded) if decoded else 'foo:;' - h = self.make_header('to', source) - self.assertEqual(h, gdecoded) - self.assertDefectsEqual(h.defects, defects) - self.assertEqual(h.groups[0].addresses, h.addresses) - self.assertEqual(len(h.groups), 1) - self.assertEqual(len(h.addresses), 1) - a = h.addresses[0] - self.assertEqual(str(a), decoded) - self.assertEqual(a.display_name, display_name) - self.assertEqual(a.addr_spec, addr_spec) - self.assertEqual(a.username, username) - self.assertEqual(a.domain, domain) - - def test_simple_address_list(self): - value = ('Fred , foo@example.com, ' - '"Harry W. Hastings" ') - h = self.make_header('to', value) - self.assertEqual(h, value) - self.assertEqual(len(h.groups), 3) - self.assertEqual(len(h.addresses), 3) - for i in range(3): - self.assertEqual(h.groups[i].addresses[0], h.addresses[i]) - self.assertEqual(str(h.addresses[0]), 'Fred ') - self.assertEqual(str(h.addresses[1]), 'foo@example.com') - self.assertEqual(str(h.addresses[2]), - '"Harry W. Hastings" ') - self.assertEqual(h.addresses[2].display_name, - 'Harry W. Hastings') - - def test_complex_address_list(self): - examples = list(self.example_params.values()) - source = ('dummy list:;, another: (empty);,' + - ', '.join([x[0] for x in examples[:4]]) + ', ' + - r'"A \"list\"": ' + - ', '.join([x[0] for x in examples[4:6]]) + ';,' + - ', '.join([x[0] for x in examples[6:]]) - ) - # XXX: the fact that (empty) disappears here is a potential API design - # bug. We don't currently have a way to preserve comments. - expected = ('dummy list:;, another:;, ' + - ', '.join([x[2] for x in examples[:4]]) + ', ' + - r'"A \"list\"": ' + - ', '.join([x[2] for x in examples[4:6]]) + ';, ' + - ', '.join([x[2] for x in examples[6:]]) - ) - - h = self.make_header('to', source) - self.assertEqual(h.split(','), expected.split(',')) - self.assertEqual(h, expected) - self.assertEqual(len(h.groups), 7 + len(examples) - 6) - self.assertEqual(h.groups[0].display_name, 'dummy list') - self.assertEqual(h.groups[1].display_name, 'another') - self.assertEqual(h.groups[6].display_name, 'A "list"') - self.assertEqual(len(h.addresses), len(examples)) - for i in range(4): - self.assertIsNone(h.groups[i+2].display_name) - self.assertEqual(str(h.groups[i+2].addresses[0]), examples[i][2]) - for i in range(7, 7 + len(examples) - 6): - self.assertIsNone(h.groups[i].display_name) - self.assertEqual(str(h.groups[i].addresses[0]), examples[i-1][2]) - for i in range(len(examples)): - self.assertEqual(str(h.addresses[i]), examples[i][2]) - self.assertEqual(h.addresses[i].addr_spec, examples[i][4]) - - def test_address_read_only(self): - h = self.make_header('sender', 'abc@xyz.com') - with self.assertRaises(AttributeError): - h.address = 'foo' - - def test_addresses_read_only(self): - h = self.make_header('sender', 'abc@xyz.com') - with self.assertRaises(AttributeError): - h.addresses = 'foo' - - def test_groups_read_only(self): - h = self.make_header('sender', 'abc@xyz.com') - with self.assertRaises(AttributeError): - h.groups = 'foo' - - def test_addresses_types(self): - source = 'me ' - h = self.make_header('to', source) - self.assertIsInstance(h.addresses, tuple) - self.assertIsInstance(h.addresses[0], Address) - - def test_groups_types(self): - source = 'me ' - h = self.make_header('to', source) - self.assertIsInstance(h.groups, tuple) - self.assertIsInstance(h.groups[0], Group) - - def test_set_from_Address(self): - h = self.make_header('to', Address('me', 'foo', 'example.com')) - self.assertEqual(h, 'me ') - - def test_set_from_Address_list(self): - h = self.make_header('to', [Address('me', 'foo', 'example.com'), - Address('you', 'bar', 'example.com')]) - self.assertEqual(h, 'me , you ') - - def test_set_from_Address_and_Group_list(self): - h = self.make_header('to', [Address('me', 'foo', 'example.com'), - Group('bing', [Address('fiz', 'z', 'b.com'), - Address('zif', 'f', 'c.com')]), - Address('you', 'bar', 'example.com')]) - self.assertEqual(h, 'me , bing: fiz , ' - 'zif ;, you ') - self.assertEqual(h.fold(policy=policy.default.clone(max_line_length=40)), - 'to: me ,\n' - ' bing: fiz , zif ;,\n' - ' you \n') - - def test_set_from_Group_list(self): - h = self.make_header('to', [Group('bing', [Address('fiz', 'z', 'b.com'), - Address('zif', 'f', 'c.com')])]) - self.assertEqual(h, 'bing: fiz , zif ;') - - -class TestAddressAndGroup(TestEmailBase): - - def _test_attr_ro(self, obj, attr): - with self.assertRaises(AttributeError): - setattr(obj, attr, 'foo') - - def test_address_display_name_ro(self): - self._test_attr_ro(Address('foo', 'bar', 'baz'), 'display_name') - - def test_address_username_ro(self): - self._test_attr_ro(Address('foo', 'bar', 'baz'), 'username') - - def test_address_domain_ro(self): - self._test_attr_ro(Address('foo', 'bar', 'baz'), 'domain') - - def test_group_display_name_ro(self): - self._test_attr_ro(Group('foo'), 'display_name') - - def test_group_addresses_ro(self): - self._test_attr_ro(Group('foo'), 'addresses') - - def test_address_from_username_domain(self): - a = Address('foo', 'bar', 'baz') - self.assertEqual(a.display_name, 'foo') - self.assertEqual(a.username, 'bar') - self.assertEqual(a.domain, 'baz') - self.assertEqual(a.addr_spec, 'bar@baz') - self.assertEqual(str(a), 'foo ') - - def test_address_from_addr_spec(self): - a = Address('foo', addr_spec='bar@baz') - self.assertEqual(a.display_name, 'foo') - self.assertEqual(a.username, 'bar') - self.assertEqual(a.domain, 'baz') - self.assertEqual(a.addr_spec, 'bar@baz') - self.assertEqual(str(a), 'foo ') - - def test_address_with_no_display_name(self): - a = Address(addr_spec='bar@baz') - self.assertEqual(a.display_name, '') - self.assertEqual(a.username, 'bar') - self.assertEqual(a.domain, 'baz') - self.assertEqual(a.addr_spec, 'bar@baz') - self.assertEqual(str(a), 'bar@baz') - - def test_null_address(self): - a = Address() - self.assertEqual(a.display_name, '') - self.assertEqual(a.username, '') - self.assertEqual(a.domain, '') - self.assertEqual(a.addr_spec, '<>') - self.assertEqual(str(a), '<>') - - def test_domain_only(self): - # This isn't really a valid address. - a = Address(domain='buzz') - self.assertEqual(a.display_name, '') - self.assertEqual(a.username, '') - self.assertEqual(a.domain, 'buzz') - self.assertEqual(a.addr_spec, '@buzz') - self.assertEqual(str(a), '@buzz') - - def test_username_only(self): - # This isn't really a valid address. - a = Address(username='buzz') - self.assertEqual(a.display_name, '') - self.assertEqual(a.username, 'buzz') - self.assertEqual(a.domain, '') - self.assertEqual(a.addr_spec, 'buzz') - self.assertEqual(str(a), 'buzz') - - def test_display_name_only(self): - a = Address('buzz') - self.assertEqual(a.display_name, 'buzz') - self.assertEqual(a.username, '') - self.assertEqual(a.domain, '') - self.assertEqual(a.addr_spec, '<>') - self.assertEqual(str(a), 'buzz <>') - - def test_quoting(self): - # Ideally we'd check every special individually, but I'm not up for - # writing that many tests. - a = Address('Sara J.', 'bad name', 'example.com') - self.assertEqual(a.display_name, 'Sara J.') - self.assertEqual(a.username, 'bad name') - self.assertEqual(a.domain, 'example.com') - self.assertEqual(a.addr_spec, '"bad name"@example.com') - self.assertEqual(str(a), '"Sara J." <"bad name"@example.com>') - - def test_il8n(self): - a = Address('Éric', 'wok', 'exàmple.com') - self.assertEqual(a.display_name, 'Éric') - self.assertEqual(a.username, 'wok') - self.assertEqual(a.domain, 'exàmple.com') - self.assertEqual(a.addr_spec, 'wok@exàmple.com') - self.assertEqual(str(a), 'Éric ') - - # XXX: there is an API design issue that needs to be solved here. - #def test_non_ascii_username_raises(self): - # with self.assertRaises(ValueError): - # Address('foo', 'wők', 'example.com') - - def test_non_ascii_username_in_addr_spec_raises(self): - with self.assertRaises(ValueError): - Address('foo', addr_spec='wők@example.com') - - def test_address_addr_spec_and_username_raises(self): - with self.assertRaises(TypeError): - Address('foo', username='bing', addr_spec='bar@baz') - - def test_address_addr_spec_and_domain_raises(self): - with self.assertRaises(TypeError): - Address('foo', domain='bing', addr_spec='bar@baz') - - def test_address_addr_spec_and_username_and_domain_raises(self): - with self.assertRaises(TypeError): - Address('foo', username='bong', domain='bing', addr_spec='bar@baz') - - def test_space_in_addr_spec_username_raises(self): - with self.assertRaises(ValueError): - Address('foo', addr_spec="bad name@example.com") - - def test_bad_addr_sepc_raises(self): - with self.assertRaises(ValueError): - Address('foo', addr_spec="name@ex[]ample.com") - - def test_empty_group(self): - g = Group('foo') - self.assertEqual(g.display_name, 'foo') - self.assertEqual(g.addresses, tuple()) - self.assertEqual(str(g), 'foo:;') - - def test_empty_group_list(self): - g = Group('foo', addresses=[]) - self.assertEqual(g.display_name, 'foo') - self.assertEqual(g.addresses, tuple()) - self.assertEqual(str(g), 'foo:;') - - def test_null_group(self): - g = Group() - self.assertIsNone(g.display_name) - self.assertEqual(g.addresses, tuple()) - self.assertEqual(str(g), 'None:;') - - def test_group_with_addresses(self): - addrs = [Address('b', 'b', 'c'), Address('a', 'b','c')] - g = Group('foo', addrs) - self.assertEqual(g.display_name, 'foo') - self.assertEqual(g.addresses, tuple(addrs)) - self.assertEqual(str(g), 'foo: b , a ;') - - def test_group_with_addresses_no_display_name(self): - addrs = [Address('b', 'b', 'c'), Address('a', 'b','c')] - g = Group(addresses=addrs) - self.assertIsNone(g.display_name) - self.assertEqual(g.addresses, tuple(addrs)) - self.assertEqual(str(g), 'None: b , a ;') - - def test_group_with_one_address_no_display_name(self): - addrs = [Address('b', 'b', 'c')] - g = Group(addresses=addrs) - self.assertIsNone(g.display_name) - self.assertEqual(g.addresses, tuple(addrs)) - self.assertEqual(str(g), 'b ') - - def test_display_name_quoting(self): - g = Group('foo.bar') - self.assertEqual(g.display_name, 'foo.bar') - self.assertEqual(g.addresses, tuple()) - self.assertEqual(str(g), '"foo.bar":;') - - def test_display_name_blanks_not_quoted(self): - g = Group('foo bar') - self.assertEqual(g.display_name, 'foo bar') - self.assertEqual(g.addresses, tuple()) - self.assertEqual(str(g), 'foo bar:;') - - def test_set_message_header_from_address(self): - a = Address('foo', 'bar', 'example.com') - m = Message(policy=policy.default) - m['To'] = a - self.assertEqual(m['to'], 'foo ') - self.assertEqual(m['to'].addresses, (a,)) - - def test_set_message_header_from_group(self): - g = Group('foo bar') - m = Message(policy=policy.default) - m['To'] = g - self.assertEqual(m['to'], 'foo bar:;') - self.assertEqual(m['to'].addresses, g.addresses) - - -class TestFolding(TestHeaderBase): - - def test_short_unstructured(self): - h = self.make_header('subject', 'this is a test') - self.assertEqual(h.fold(policy=policy.default), - 'subject: this is a test\n') - - def test_long_unstructured(self): - h = self.make_header('Subject', 'This is a long header ' - 'line that will need to be folded into two lines ' - 'and will demonstrate basic folding') - self.assertEqual(h.fold(policy=policy.default), - 'Subject: This is a long header line that will ' - 'need to be folded into two lines\n' - ' and will demonstrate basic folding\n') - - def test_unstructured_short_max_line_length(self): - h = self.make_header('Subject', 'this is a short header ' - 'that will be folded anyway') - self.assertEqual( - h.fold(policy=policy.default.clone(max_line_length=20)), - textwrap.dedent("""\ - Subject: this is a - short header that - will be folded - anyway - """)) - - def test_fold_unstructured_single_word(self): - h = self.make_header('Subject', 'test') - self.assertEqual(h.fold(policy=policy.default), 'Subject: test\n') - - def test_fold_unstructured_short(self): - h = self.make_header('Subject', 'test test test') - self.assertEqual(h.fold(policy=policy.default), - 'Subject: test test test\n') - - def test_fold_unstructured_with_overlong_word(self): - h = self.make_header('Subject', 'thisisaverylonglineconsistingofa' - 'singlewordthatwontfit') - self.assertEqual( - h.fold(policy=policy.default.clone(max_line_length=20)), - 'Subject: thisisaverylonglineconsistingofasinglewordthatwontfit\n') - - def test_fold_unstructured_with_two_overlong_words(self): - h = self.make_header('Subject', 'thisisaverylonglineconsistingofa' - 'singlewordthatwontfit plusanotherverylongwordthatwontfit') - self.assertEqual( - h.fold(policy=policy.default.clone(max_line_length=20)), - 'Subject: thisisaverylonglineconsistingofasinglewordthatwontfit\n' - ' plusanotherverylongwordthatwontfit\n') - - def test_fold_unstructured_with_slightly_long_word(self): - h = self.make_header('Subject', 'thislongwordislessthanmaxlinelen') - self.assertEqual( - h.fold(policy=policy.default.clone(max_line_length=35)), - 'Subject:\n thislongwordislessthanmaxlinelen\n') - - def test_fold_unstructured_with_commas(self): - # The old wrapper would fold this at the commas. - h = self.make_header('Subject', "This header is intended to " - "demonstrate, in a fairly susinct way, that we now do " - "not give a , special treatment in unstructured headers.") - self.assertEqual( - h.fold(policy=policy.default.clone(max_line_length=60)), - textwrap.dedent("""\ - Subject: This header is intended to demonstrate, in a fairly - susinct way, that we now do not give a , special treatment - in unstructured headers. - """)) - - def test_fold_address_list(self): - h = self.make_header('To', '"Theodore H. Perfect" , ' - '"My address is very long because my name is long" , ' - '"Only A. Friend" ') - self.assertEqual(h.fold(policy=policy.default), textwrap.dedent("""\ - To: "Theodore H. Perfect" , - "My address is very long because my name is long" , - "Only A. Friend" - """)) - - def test_fold_date_header(self): - h = self.make_header('Date', 'Sat, 2 Feb 2002 17:00:06 -0800') - self.assertEqual(h.fold(policy=policy.default), - 'Date: Sat, 02 Feb 2002 17:00:06 -0800\n') - - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_future/disabled/test_email/disabled_test_inversion.py b/tests/test_future/disabled/test_email/disabled_test_inversion.py deleted file mode 100644 index cb19f5e5..00000000 --- a/tests/test_future/disabled/test_email/disabled_test_inversion.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Test the parser and generator are inverses. - -Note that this is only strictly true if we are parsing RFC valid messages and -producing RFC valid messages. -""" -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import -import io -from future.standard_library.email import policy, message_from_bytes -from future.standard_library.email.generator import BytesGenerator -from future.tests.test_email import TestEmailBase, parameterize -from future.tests.base import unittest - -# This is like textwrap.dedent for bytes, except that it uses \r\n for the line -# separators on the rebuilt string. -def dedent(bstr): - lines = bstr.splitlines() - if not lines[0].strip(): - raise ValueError("First line must contain text") - stripamt = len(lines[0]) - len(lines[0].lstrip()) - return b'\r\n'.join( - [x[stripamt:] if len(x)>=stripamt else b'' - for x in lines]) - - -@parameterize -class TestInversion(TestEmailBase, unittest.TestCase): - - def msg_as_input(self, msg): - m = message_from_bytes(msg, policy=policy.SMTP) - b = io.BytesIO() - g = BytesGenerator(b) - g.flatten(m) - self.assertEqual(b.getvalue(), msg) - - # XXX: spaces are not preserved correctly here yet in the general case. - msg_params = { - 'header_with_one_space_body': (dedent(b"""\ - From: abc@xyz.com - X-Status:\x20 - Subject: test - - foo - """),), - - } diff --git a/tests/test_future/disabled/test_email/disabled_test_message.py b/tests/test_future/disabled/test_email/disabled_test_message.py deleted file mode 100644 index d4d5112c..00000000 --- a/tests/test_future/disabled/test_email/disabled_test_message.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import -from future.standard_library.email import policy -from future.tests.base import unittest -from future.tests.test_email import TestEmailBase - - -class Test(TestEmailBase): - - policy = policy.default - - def test_error_on_setitem_if_max_count_exceeded(self): - m = self._str_msg("") - m['To'] = 'abc@xyz' - with self.assertRaises(ValueError): - m['To'] = 'xyz@abc' - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_future/disabled/test_email/disabled_test_parser.py b/tests/test_future/disabled/test_email/disabled_test_parser.py deleted file mode 100644 index d1725b7c..00000000 --- a/tests/test_future/disabled/test_email/disabled_test_parser.py +++ /dev/null @@ -1,41 +0,0 @@ -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import -import io -import future.standard_library.email as email -from future.standard_library.email.message import Message -from future.tests.test_email import TestEmailBase -from future.tests.base import unittest -from future.builtins import super - - -class TestCustomMessage(TestEmailBase): - - class MyMessage(Message): - def __init__(self, policy): - self.check_policy = policy - super().__init__() - - MyPolicy = TestEmailBase.policy.clone(linesep='boo') - - def test_custom_message_gets_policy_if_possible_from_string(self): - msg = email.message_from_string("Subject: bogus\n\nmsg\n", - self.MyMessage, - policy=self.MyPolicy) - self.assertTrue(isinstance(msg, self.MyMessage)) - self.assertIs(msg.check_policy, self.MyPolicy) - - def test_custom_message_gets_policy_if_possible_from_file(self): - source_file = io.StringIO("Subject: bogus\n\nmsg\n") - msg = email.message_from_file(source_file, - self.MyMessage, - policy=self.MyPolicy) - self.assertTrue(isinstance(msg, self.MyMessage)) - self.assertIs(msg.check_policy, self.MyPolicy) - - # XXX add tests for other functions that take Message arg. - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_future/disabled/test_email/disabled_test_pickleable.py b/tests/test_future/disabled/test_email/disabled_test_pickleable.py deleted file mode 100644 index 250d86ab..00000000 --- a/tests/test_future/disabled/test_email/disabled_test_pickleable.py +++ /dev/null @@ -1,79 +0,0 @@ -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import -import textwrap -import copy -import pickle -import future.standard_library.email as email -import future.standard_library.email.message as email_message -from future.standard_library.email import policy -from future.standard_library.email.headerregistry import HeaderRegistry -from future.tests.test_email import TestEmailBase, parameterize -from future.tests.base import unittest -from future.builtins import str - - -@parameterize -class TestPickleCopyHeader(TestEmailBase): - - header_factory = HeaderRegistry() - - unstructured = header_factory('subject', 'this is a test') - - header_params = { - 'subject': ('subject', 'this is a test'), - 'from': ('from', 'frodo@mordor.net'), - 'to': ('to', 'a: k@b.com, y@z.com;, j@f.com'), - 'date': ('date', 'Tue, 29 May 2012 09:24:26 +1000'), - } - - def header_as_deepcopy(self, name, value): - header = self.header_factory(name, value) - h = copy.deepcopy(header) - self.assertEqual(str(h), str(header)) - - def header_as_pickle(self, name, value): - header = self.header_factory(name, value) - p = pickle.dumps(header) - h = pickle.loads(p) - self.assertEqual(str(h), str(header)) - - -@parameterize -class TestPickleCopyMessage(TestEmailBase): - - # Message objects are a sequence, so we have to make them a one-tuple in - # msg_params so they get passed to the parameterized test method as a - # single argument instead of as a list of headers. - msg_params = {} - - # Note: there will be no custom header objects in the parsed message. - msg_params['parsed'] = (email.message_from_string(textwrap.dedent("""\ - Date: Tue, 29 May 2012 09:24:26 +1000 - From: frodo@mordor.net - To: bilbo@underhill.org - Subject: help - - I think I forgot the ring. - """), policy=policy.default),) - - msg_params['created'] = (email_message.Message(policy=policy.default),) - msg_params['created'][0]['Date'] = 'Tue, 29 May 2012 09:24:26 +1000' - msg_params['created'][0]['From'] = 'frodo@mordor.net' - msg_params['created'][0]['To'] = 'bilbo@underhill.org' - msg_params['created'][0]['Subject'] = 'help' - msg_params['created'][0].set_payload('I think I forgot the ring.') - - def msg_as_deepcopy(self, msg): - msg2 = copy.deepcopy(msg) - self.assertEqual(msg2.as_string(), msg.as_string()) - - def msg_as_pickle(self, msg): - p = pickle.dumps(msg) - msg2 = pickle.loads(p) - self.assertEqual(msg2.as_string(), msg.as_string()) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_future/disabled/test_email/disabled_test_policy.py b/tests/test_future/disabled/test_email/disabled_test_policy.py deleted file mode 100644 index 7db40336..00000000 --- a/tests/test_future/disabled/test_email/disabled_test_policy.py +++ /dev/null @@ -1,329 +0,0 @@ -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import -import io -import types -import textwrap -from future.standard_library import email -import future.standard_library.email.policy as email_policy -import future.standard_library.email.parser as email_parser -import future.standard_library.email.generator as email_generator -from future.standard_library.email import headerregistry -from future.tests.base import unittest -from future.builtins import str, super - - -def make_defaults(base_defaults, differences): - defaults = base_defaults.copy() - defaults.update(differences) - return defaults - -class PolicyAPITests(unittest.TestCase): - - longMessage = True - - # Base default values. - compat32_defaults = { - 'max_line_length': 78, - 'linesep': '\n', - 'cte_type': '8bit', - 'raise_on_defect': False, - } - # These default values are the ones set on email_policy.default. - # If any of these defaults change, the docs must be updated. - policy_defaults = compat32_defaults.copy() - policy_defaults.update({ - 'raise_on_defect': False, - 'header_factory': email_policy.EmailPolicy.header_factory, - 'refold_source': 'long', - }) - - # For each policy under test, we give here what we expect the defaults to - # be for that policy. The second argument to make defaults is the - # difference between the base defaults and that for the particular policy. - new_policy = email_policy.EmailPolicy() - policies = { - email_policy.compat32: make_defaults(compat32_defaults, {}), - email_policy.default: make_defaults(policy_defaults, {}), - email_policy.SMTP: make_defaults(policy_defaults, - {'linesep': '\r\n'}), - email_policy.HTTP: make_defaults(policy_defaults, - {'linesep': '\r\n', - 'max_line_length': None}), - email_policy.strict: make_defaults(policy_defaults, - {'raise_on_defect': True}), - new_policy: make_defaults(policy_defaults, {}), - } - # Creating a new policy creates a new header factory. There is a test - # later that proves this. - policies[new_policy]['header_factory'] = new_policy.header_factory - - def test_defaults(self): - for policy, expected in self.policies.items(): - for attr, value in expected.items(): - self.assertEqual(getattr(policy, attr), value, - ("change {} docs/docstrings if defaults have " - "changed").format(policy)) - - def test_all_attributes_covered(self): - for policy, expected in self.policies.items(): - for attr in dir(policy): - if (attr.startswith('_') or - isinstance(getattr(email_policy.EmailPolicy, attr), - types.FunctionType)): - continue - else: - self.assertIn(attr, expected, - "{} is not fully tested".format(attr)) - - def test_abc(self): - with self.assertRaises(TypeError) as cm: - email_policy.Policy() - msg = str(cm.exception) - abstract_methods = ('fold', - 'fold_binary', - 'header_fetch_parse', - 'header_source_parse', - 'header_store_parse') - for method in abstract_methods: - self.assertIn(method, msg) - - def test_policy_is_immutable(self): - for policy, defaults in self.policies.items(): - for attr in defaults: - with self.assertRaisesRegex(AttributeError, attr+".*read-only"): - setattr(policy, attr, None) - with self.assertRaisesRegex(AttributeError, 'no attribute.*foo'): - policy.foo = None - - def test_set_policy_attrs_when_cloned(self): - # None of the attributes has a default value of None, so we set them - # all to None in the clone call and check that it worked. - for policyclass, defaults in self.policies.items(): - testattrdict = dict((attr, None) for attr in defaults) - policy = policyclass.clone(**testattrdict) - for attr in defaults: - self.assertIsNone(getattr(policy, attr)) - - def test_reject_non_policy_keyword_when_called(self): - for policyclass in self.policies: - with self.assertRaises(TypeError): - policyclass(this_keyword_should_not_be_valid=None) - with self.assertRaises(TypeError): - policyclass(newtline=None) - - def test_policy_addition(self): - expected = self.policy_defaults.copy() - p1 = email_policy.default.clone(max_line_length=100) - p2 = email_policy.default.clone(max_line_length=50) - added = p1 + p2 - expected.update(max_line_length=50) - for attr, value in expected.items(): - self.assertEqual(getattr(added, attr), value) - added = p2 + p1 - expected.update(max_line_length=100) - for attr, value in expected.items(): - self.assertEqual(getattr(added, attr), value) - added = added + email_policy.default - for attr, value in expected.items(): - self.assertEqual(getattr(added, attr), value) - - def test_register_defect(self): - class Dummy(object): - def __init__(self): - self.defects = [] - obj = Dummy() - defect = object() - policy = email_policy.EmailPolicy() - policy.register_defect(obj, defect) - self.assertEqual(obj.defects, [defect]) - defect2 = object() - policy.register_defect(obj, defect2) - self.assertEqual(obj.defects, [defect, defect2]) - - class MyObj(object): - def __init__(self): - self.defects = [] - - class MyDefect(Exception): - pass - - def test_handle_defect_raises_on_strict(self): - foo = self.MyObj() - defect = self.MyDefect("the telly is broken") - with self.assertRaisesRegex(self.MyDefect, "the telly is broken"): - email_policy.strict.handle_defect(foo, defect) - - def test_handle_defect_registers_defect(self): - foo = self.MyObj() - defect1 = self.MyDefect("one") - email_policy.default.handle_defect(foo, defect1) - self.assertEqual(foo.defects, [defect1]) - defect2 = self.MyDefect("two") - email_policy.default.handle_defect(foo, defect2) - self.assertEqual(foo.defects, [defect1, defect2]) - - class MyPolicy(email_policy.EmailPolicy): - defects = None - def __init__(self, *args, **kw): - super().__init__(*args, defects=[], **kw) - def register_defect(self, obj, defect): - self.defects.append(defect) - - def test_overridden_register_defect_still_raises(self): - foo = self.MyObj() - defect = self.MyDefect("the telly is broken") - with self.assertRaisesRegex(self.MyDefect, "the telly is broken"): - self.MyPolicy(raise_on_defect=True).handle_defect(foo, defect) - - def test_overriden_register_defect_works(self): - foo = self.MyObj() - defect1 = self.MyDefect("one") - my_policy = self.MyPolicy() - my_policy.handle_defect(foo, defect1) - self.assertEqual(my_policy.defects, [defect1]) - self.assertEqual(foo.defects, []) - defect2 = self.MyDefect("two") - my_policy.handle_defect(foo, defect2) - self.assertEqual(my_policy.defects, [defect1, defect2]) - self.assertEqual(foo.defects, []) - - def test_default_header_factory(self): - h = email_policy.default.header_factory('Test', 'test') - self.assertEqual(h.name, 'Test') - self.assertIsInstance(h, headerregistry.UnstructuredHeader) - self.assertIsInstance(h, headerregistry.BaseHeader) - - class Foo(object): - parse = headerregistry.UnstructuredHeader.parse - - def test_each_Policy_gets_unique_factory(self): - policy1 = email_policy.EmailPolicy() - policy2 = email_policy.EmailPolicy() - policy1.header_factory.map_to_type('foo', self.Foo) - h = policy1.header_factory('foo', 'test') - self.assertIsInstance(h, self.Foo) - self.assertNotIsInstance(h, headerregistry.UnstructuredHeader) - h = policy2.header_factory('foo', 'test') - self.assertNotIsInstance(h, self.Foo) - self.assertIsInstance(h, headerregistry.UnstructuredHeader) - - def test_clone_copies_factory(self): - policy1 = email_policy.EmailPolicy() - policy2 = policy1.clone() - policy1.header_factory.map_to_type('foo', self.Foo) - h = policy1.header_factory('foo', 'test') - self.assertIsInstance(h, self.Foo) - h = policy2.header_factory('foo', 'test') - self.assertIsInstance(h, self.Foo) - - def test_new_factory_overrides_default(self): - mypolicy = email_policy.EmailPolicy() - myfactory = mypolicy.header_factory - newpolicy = mypolicy + email_policy.strict - self.assertEqual(newpolicy.header_factory, myfactory) - newpolicy = email_policy.strict + mypolicy - self.assertEqual(newpolicy.header_factory, myfactory) - - def test_adding_default_policies_preserves_default_factory(self): - newpolicy = email_policy.default + email_policy.strict - self.assertEqual(newpolicy.header_factory, - email_policy.EmailPolicy.header_factory) - self.assertEqual(newpolicy.__dict__, {'raise_on_defect': True}) - - # XXX: Need subclassing tests. - # For adding subclassed objects, make sure the usual rules apply (subclass - # wins), but that the order still works (right overrides left). - - -class TestPolicyPropagation(unittest.TestCase): - - # The abstract methods are used by the parser but not by the wrapper - # functions that call it, so if the exception gets raised we know that the - # policy was actually propagated all the way to feedparser. - class MyPolicy(email_policy.Policy): - def badmethod(self, *args, **kw): - raise Exception("test") - fold = fold_binary = header_fetch_parser = badmethod - header_source_parse = header_store_parse = badmethod - - def test_message_from_string(self): - with self.assertRaisesRegex(Exception, "^test$"): - email.message_from_string("Subject: test\n\n", - policy=self.MyPolicy) - - def test_message_from_bytes(self): - with self.assertRaisesRegex(Exception, "^test$"): - email.message_from_bytes(b"Subject: test\n\n", - policy=self.MyPolicy) - - def test_message_from_file(self): - f = io.StringIO('Subject: test\n\n') - with self.assertRaisesRegex(Exception, "^test$"): - email.message_from_file(f, policy=self.MyPolicy) - - def test_message_from_binary_file(self): - f = io.BytesIO(b'Subject: test\n\n') - with self.assertRaisesRegex(Exception, "^test$"): - email.message_from_binary_file(f, policy=self.MyPolicy) - - # These are redundant, but we need them for black-box completeness. - - def test_parser(self): - p = email_parser.Parser(policy=self.MyPolicy) - with self.assertRaisesRegex(Exception, "^test$"): - p.parsestr('Subject: test\n\n') - - def test_bytes_parser(self): - p = email_parser.BytesParser(policy=self.MyPolicy) - with self.assertRaisesRegex(Exception, "^test$"): - p.parsebytes(b'Subject: test\n\n') - - # Now that we've established that all the parse methods get the - # policy in to feedparser, we can use message_from_string for - # the rest of the propagation tests. - - def _make_msg(self, source='Subject: test\n\n', policy=None): - self.policy = email_policy.default.clone() if policy is None else policy - return email.message_from_string(source, policy=self.policy) - - def test_parser_propagates_policy_to_message(self): - msg = self._make_msg() - self.assertIs(msg.policy, self.policy) - - def test_parser_propagates_policy_to_sub_messages(self): - msg = self._make_msg(textwrap.dedent("""\ - Subject: mime test - MIME-Version: 1.0 - Content-Type: multipart/mixed, boundary="XXX" - - --XXX - Content-Type: text/plain - - test - --XXX - Content-Type: text/plain - - test2 - --XXX-- - """)) - for part in msg.walk(): - self.assertIs(part.policy, self.policy) - - def test_message_policy_propagates_to_generator(self): - msg = self._make_msg("Subject: test\nTo: foo\n\n", - policy=email_policy.default.clone(linesep='X')) - s = io.StringIO() - g = email_generator.Generator(s) - g.flatten(msg) - self.assertEqual(s.getvalue(), "Subject: testXTo: fooXX") - - def test_message_policy_used_by_as_string(self): - msg = self._make_msg("Subject: test\nTo: foo\n\n", - policy=email_policy.default.clone(linesep='X')) - self.assertEqual(msg.as_string(), "Subject: testXTo: fooXX") - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_future/disabled/test_email/disabled_test_utils.py b/tests/test_future/disabled/test_email/disabled_test_utils.py deleted file mode 100644 index 4b67c74a..00000000 --- a/tests/test_future/disabled/test_email/disabled_test_utils.py +++ /dev/null @@ -1,142 +0,0 @@ -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import -# import datetime -import time -import sys -from future.standard_library import datetime -from future.standard_library.email import utils -from future.standard_library.test import support as test_support -from future.tests.base import unittest - - -class DateTimeTests(unittest.TestCase): - - datestring = 'Sun, 23 Sep 2001 20:10:55' - dateargs = (2001, 9, 23, 20, 10, 55) - offsetstring = ' -0700' - utcoffset = datetime.timedelta(hours=-7) - tz = datetime.timezone(utcoffset) - naive_dt = datetime.datetime(*dateargs) - aware_dt = datetime.datetime(*dateargs, tzinfo=tz) - - def test_naive_datetime(self): - self.assertEqual(utils.format_datetime(self.naive_dt), - self.datestring + ' -0000') - - def test_aware_datetime(self): - self.assertEqual(utils.format_datetime(self.aware_dt), - self.datestring + self.offsetstring) - - def test_usegmt(self): - utc_dt = datetime.datetime(*self.dateargs, - tzinfo=datetime.timezone.utc) - self.assertEqual(utils.format_datetime(utc_dt, usegmt=True), - self.datestring + ' GMT') - - def test_usegmt_with_naive_datetime_raises(self): - with self.assertRaises(ValueError): - utils.format_datetime(self.naive_dt, usegmt=True) - - def test_usegmt_with_non_utc_datetime_raises(self): - with self.assertRaises(ValueError): - utils.format_datetime(self.aware_dt, usegmt=True) - - def test_parsedate_to_datetime(self): - self.assertEqual( - utils.parsedate_to_datetime(self.datestring + self.offsetstring), - self.aware_dt) - - def test_parsedate_to_datetime_naive(self): - self.assertEqual( - utils.parsedate_to_datetime(self.datestring + ' -0000'), - self.naive_dt) - - -class LocaltimeTests(unittest.TestCase): - - def test_localtime_is_tz_aware_daylight_true(self): - test_support.patch(self, time, 'daylight', True) - t = utils.localtime() - self.assertIsNot(t.tzinfo, None) - - def test_localtime_is_tz_aware_daylight_false(self): - test_support.patch(self, time, 'daylight', False) - t = utils.localtime() - self.assertIsNot(t.tzinfo, None) - - def test_localtime_daylight_true_dst_false(self): - test_support.patch(self, time, 'daylight', True) - t0 = datetime.datetime(2012, 3, 12, 1, 1) - t1 = utils.localtime(t0, isdst=-1) - t2 = utils.localtime(t1) - self.assertEqual(t1, t2) - - def test_localtime_daylight_false_dst_false(self): - test_support.patch(self, time, 'daylight', False) - t0 = datetime.datetime(2012, 3, 12, 1, 1) - t1 = utils.localtime(t0, isdst=-1) - t2 = utils.localtime(t1) - self.assertEqual(t1, t2) - - def test_localtime_daylight_true_dst_true(self): - test_support.patch(self, time, 'daylight', True) - t0 = datetime.datetime(2012, 3, 12, 1, 1) - t1 = utils.localtime(t0, isdst=1) - t2 = utils.localtime(t1) - self.assertEqual(t1, t2) - - def test_localtime_daylight_false_dst_true(self): - test_support.patch(self, time, 'daylight', False) - t0 = datetime.datetime(2012, 3, 12, 1, 1) - t1 = utils.localtime(t0, isdst=1) - t2 = utils.localtime(t1) - self.assertEqual(t1, t2) - - @test_support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0') - def test_localtime_epoch_utc_daylight_true(self): - test_support.patch(self, time, 'daylight', True) - t0 = datetime.datetime(1990, 1, 1, tzinfo = datetime.timezone.utc) - t1 = utils.localtime(t0) - t2 = t0 - datetime.timedelta(hours=5) - t2 = t2.replace(tzinfo = datetime.timezone(datetime.timedelta(hours=-5))) - self.assertEqual(t1, t2) - - @test_support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0') - def test_localtime_epoch_utc_daylight_false(self): - test_support.patch(self, time, 'daylight', False) - t0 = datetime.datetime(1990, 1, 1, tzinfo = datetime.timezone.utc) - t1 = utils.localtime(t0) - t2 = t0 - datetime.timedelta(hours=5) - t2 = t2.replace(tzinfo = datetime.timezone(datetime.timedelta(hours=-5))) - self.assertEqual(t1, t2) - - def test_localtime_epoch_notz_daylight_true(self): - test_support.patch(self, time, 'daylight', True) - t0 = datetime.datetime(1990, 1, 1) - t1 = utils.localtime(t0) - t2 = utils.localtime(t0.replace(tzinfo=None)) - self.assertEqual(t1, t2) - - def test_localtime_epoch_notz_daylight_false(self): - test_support.patch(self, time, 'daylight', False) - t0 = datetime.datetime(1990, 1, 1) - t1 = utils.localtime(t0) - t2 = utils.localtime(t0.replace(tzinfo=None)) - self.assertEqual(t1, t2) - - # XXX: Need a more robust test for Olson's tzdata - @unittest.skipIf(sys.platform.startswith('win'), - "Windows does not use Olson's TZ database") - @test_support.run_with_tz('Europe/Kiev') - def test_variable_tzname(self): - t0 = datetime.datetime(1984, 1, 1, tzinfo=datetime.timezone.utc) - t1 = utils.localtime(t0) - self.assertEqual(t1.tzname(), 'MSK') - t0 = datetime.datetime(1994, 1, 1, tzinfo=datetime.timezone.utc) - t1 = utils.localtime(t0) - self.assertEqual(t1.tzname(), 'EET') - -if __name__ == '__main__': - unittest.main() From 986fb91263c7a55c25c400fd22e193cd43dfc759 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sun, 9 Nov 2014 23:52:42 -0500 Subject: [PATCH 021/425] Fix ResourceWarnings when script tests fail. --- src/future/tests/base.py | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/future/tests/base.py b/src/future/tests/base.py index 40877306..7834d060 100644 --- a/src/future/tests/base.py +++ b/src/future/tests/base.py @@ -326,13 +326,18 @@ 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) raise ErrorClass(msg, e.returncode, e.cmd, output=e.output) return output @@ -345,13 +350,18 @@ 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(), + ) raise VerboseCalledProcessError(msg, e.returncode, e.cmd, output=e.output) return output From 05a40eab62504d6e3dad0a3935609dea948b01c0 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 21 Nov 2014 20:33:09 +1100 Subject: [PATCH 022/425] Remove some remaining invocations of test.test_support (issue #109) --- src/future/backports/test/support.py | 3 --- tests/test_future/test_urllib_response.py | 5 ++--- tests/test_past/test_olddict.py | 18 +++--------------- 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/future/backports/test/support.py b/src/future/backports/test/support.py index afe59d03..5216c02f 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 diff --git a/tests/test_future/test_urllib_response.py b/tests/test_future/test_urllib_response.py index 6bd790ae..27da4a31 100644 --- a/tests/test_future/test_urllib_response.py +++ b/tests/test_future/test_urllib_response.py @@ -22,6 +22,7 @@ def readline(self): def close(self): self.closed = True + class Testaddbase(unittest.TestCase): # TODO(jhylton): Write tests for other functionality of addbase() @@ -39,8 +40,6 @@ def f(): self.assertTrue(self.fp.closed) self.assertRaises(ValueError, f) -def test_main(): - test_support.run_unittest(Testaddbase) if __name__ == '__main__': - test_main() + unittest.main() diff --git a/tests/test_past/test_olddict.py b/tests/test_past/test_olddict.py index afd08b4e..4ea2471b 100644 --- a/tests/test_past/test_olddict.py +++ b/tests/test_past/test_olddict.py @@ -7,7 +7,7 @@ import os import sys -from future.utils import implements_iterator, PY26, PY3 +from future.utils import implements_iterator, PY3 from future.tests.base import unittest, skip26 from past.builtins import dict @@ -785,19 +785,7 @@ def test_tuple_keyerror(self): # self._tracked(MyDict()) -def test_main(): - if PY3: - from test import support as test_support - else: - from test import test_support - +if __name__ == '__main__': # Only run these tests on Python 3 ... if PY3: - test_support.run_unittest( - TestOldDict, - Py2DictTest, - ) - - -if __name__ == '__main__': - test_main() + unittest.main() From 7ee8efe23ceb1c0a820876f0fb9fe375adc31c57 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 21 Nov 2014 20:48:12 +1100 Subject: [PATCH 023/425] Attempt to fix sporadic travis-ci.org test failures (builds #429, #430) --- src/future/backports/test/support.py | 10 ++++++---- src/future/tests/base.py | 26 +++++--------------------- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/src/future/backports/test/support.py b/src/future/backports/test/support.py index 5216c02f..b59c4ff7 100644 --- a/src/future/backports/test/support.py +++ b/src/future/backports/test/support.py @@ -956,10 +956,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 diff --git a/src/future/tests/base.py b/src/future/tests/base.py index 40877306..60e682fe 100644 --- a/src/future/tests/base.py +++ b/src/future/tests/base.py @@ -9,12 +9,12 @@ 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.moves.subprocess import check_output, STDOUT, CalledProcessError +if PY26: + import unittest2 as unittest + def reformat_code(code): """ @@ -369,29 +369,13 @@ def expectedFailurePY3(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 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: From 56b46dc04c8d8ac175e92d82905ea39c49a5cd2c Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 21 Nov 2014 21:14:35 +1100 Subject: [PATCH 024/425] Revert botched encoding in a test (QuLogic's comment on commit 91b800d) --- tests/test_future/test_futurize.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index 706d689c..db557a5e 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -116,22 +116,7 @@ def test_encoding_comments_kept_at_top(self): from __future__ import print_function print('Hello') """ - self.convert_check(before, after, ignore_imports=False) - - # Issue #121. This fails as of v0.14.1: - before = u""" - # -*- coding: utf-8 -*- - # Author: etc. with some unicode ¿. - 1 / 2 - """ - after = u""" - # -*- coding: utf-8 -*- - # Author: etc. with some unicode ¿. - from __future__ import division - from past.utils import old_div - old_div(1, 2) - """ - self.convert_check(before, after, ignore_imports=False) + self.convert_check(before, after) def test_shebang_blank_with_future_division_import(self): """ @@ -462,7 +447,7 @@ def test_source_coding_utf8(self): """ code = """ # -*- coding: utf-8 -*- - icons = [u"◐", u"◓", u"◑", u"◒"] + icons = [u"◐", u"◓", u"◑", u"◒"] """ self.unchanged(code) From a21357356666d4b4717a1e80f1d5de5663fce616 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 21 Nov 2014 22:06:54 +1100 Subject: [PATCH 025/425] Fix more Python 2.6 test failures --- tests/test_future/test_builtins.py | 6 +++++- tests/test_future/test_futurize.py | 2 +- tests/test_future/test_pasteurize.py | 2 ++ tests/test_past/test_translation.py | 22 ---------------------- 4 files changed, 8 insertions(+), 24 deletions(-) diff --git a/tests/test_future/test_builtins.py b/tests/test_future/test_builtins.py index 8cae7a6e..715d7bd9 100644 --- a/tests/test_future/test_builtins.py +++ b/tests/test_future/test_builtins.py @@ -9,7 +9,8 @@ filter, map, zip) from future.utils import PY3, exec_, native_str, implements_iterator -from future.tests.base import unittest, skip26, expectedFailurePY2 +from future.tests.base import (unittest, skip26, expectedFailurePY2, + expectedFailurePY26) import sys import textwrap @@ -1389,15 +1390,18 @@ def check_input_tty(self, prompt, terminal_input, stdio_encoding=None): expected = terminal_input.decode(sys.stdin.encoding) # what else? self.assertEqual(input_result, expected) + @expectedFailurePY26 def test_input_tty(self): # Test input() functionality when wired to a tty (the code path # is different and invokes GNU readline if available). self.check_input_tty("prompt", b"quux") + @expectedFailurePY26 def test_input_tty_non_ascii(self): # Check stdin/stdout encoding is used when invoking GNU readline self.check_input_tty("prompté", b"quux\xe9", "utf-8") + @expectedFailurePY26 def test_input_tty_non_ascii_unicode_errors(self): # Check stdin/stdout error handler is used when invoking GNU readline self.check_input_tty("prompté", b"quux\xe9", "ascii") diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index db557a5e..4d38dfb7 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -17,6 +17,7 @@ class TestLibFuturize(unittest.TestCase): + @skip26 # mysterious sporadic UnicodeDecodeError raised by lib2to3 ... def test_correct_exit_status(self): """ Issue #119: futurize and pasteurize were not exiting with the correct @@ -437,7 +438,6 @@ def test_xrange(self): """ self.convert_check(before, after, ignore_imports=False) - @expectedFailurePY26 def test_source_coding_utf8(self): """ Tests to ensure that the source coding line is not corrupted or diff --git a/tests/test_future/test_pasteurize.py b/tests/test_future/test_pasteurize.py index aa6ce061..77024cfc 100644 --- a/tests/test_future/test_pasteurize.py +++ b/tests/test_future/test_pasteurize.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ This module contains snippets of Python 3 code (invalid Python 2) and tests for whether they can be passed to ``pasteurize`` and @@ -129,6 +130,7 @@ def test_urllib_refactor2(self): filename = urllib_parse.urlparse(url)[2].split('/')[-1] """ + @skip26 # mysterious sporadic UnicodeDecodeError raised by lib2to3 ... def test_correct_exit_status(self): """ Issue #119: futurize and pasteurize were not exiting with the correct diff --git a/tests/test_past/test_translation.py b/tests/test_past/test_translation.py index 7d0122cc..bb263695 100644 --- a/tests/test_past/test_translation.py +++ b/tests/test_past/test_translation.py @@ -90,27 +90,6 @@ def test_div(self): module = self.write_and_import(code, 'div') self.assertEqual(module.x, 1) - @expectedFailurePY26 - @expectedFailurePY3 - def test_stdlib(self): - """ - Have the old stdlib names been mapped onto the new ones? - The translation code should do this on Py3: - - >>> import configparser as ConfigParser - >>> import html.parser as HTMLParser - - """ - code = """ - import ConfigParser - import HTMLParser - import collections # check that normal ones succeed too - """ - module = self.write_and_import(code, 'stdlib') - self.assertTrue('SafeConfigParser' in dir(module.ConfigParser)) - self.assertTrue('endendtag' in dir(module.HTMLParser)) - self.assertTrue(issubclass(module.collections.defaultdict, dict)) - def test_import_future_standard_library(self): """ Does futurized Py3-like code like this work under autotranslation?? @@ -154,7 +133,6 @@ def double(x): self.assertEqual(module.d, [0, 4, 8, 12, 16]) self.assertTrue(module.e) - @expectedFailurePY26 @expectedFailurePY3 def test_import_builtin_types(self): code = """ From 5924c81359458161aeda561f5e3ac1deb97dacc4 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 21 Nov 2014 22:45:17 +1100 Subject: [PATCH 026/425] Update What's New doc for v0.14.2 --- docs/whatsnew.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 41be242d..7083ef32 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -5,6 +5,22 @@ What's New .. _whats-new-0.14.x: +What's new in version 0.14.2 +============================ + +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) + What's new in version 0.14.1 ============================ From bbc6ce8225cf54e56331ec75fa2de007d02162af Mon Sep 17 00:00:00 2001 From: Morten Enemark Lund Date: Wed, 3 Dec 2014 10:06:34 +0100 Subject: [PATCH 027/425] Fix bug where dummy_thread was always imported The code always import the dummy_thread module even on platforms where the real thread module is available. This caused bugs in other packages that use this import style: try: from _thread import interrupt_main # Py 3 except ImportError: from thread import interrupt_main # Py 2 See https://github.com/ipython/ipython/pull/7079 --- src/_thread/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/_thread/__init__.py b/src/_thread/__init__.py index 63dced6e..d7a220d5 100644 --- a/src/_thread/__init__.py +++ b/src/_thread/__init__.py @@ -3,7 +3,10 @@ __future_module__ = True if sys.version_info[0] < 3: - from dummy_thread import * + try: + from thread import * + except ImportError: + from dummy_thread 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 ' From 2a1144726ec8a5a7baea52cabf7af89d3954e5c0 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 8 Dec 2014 16:20:45 -0500 Subject: [PATCH 028/425] Add missing import to fix OrderedDict.clear. --- src/future/backports/misc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/backports/misc.py b/src/future/backports/misc.py index b69547c0..03c68c13 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -11,7 +11,7 @@ from math import ceil as oldceil import subprocess -from future.utils import iteritems, PY26 +from future.utils import iteritems, itervalues, PY26 def ceil(x): From 8ec7ab0ff16e9d32a5e424dde919d51148479283 Mon Sep 17 00:00:00 2001 From: Jacob Beck Date: Sat, 13 Dec 2014 10:41:37 -0700 Subject: [PATCH 029/425] Add signed support to newint_bytes --- src/future/types/newint.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/future/types/newint.py b/src/future/types/newint.py index 7157f95e..d4712c06 100644 --- a/src/future/types/newint.py +++ b/src/future/types/newint.py @@ -299,18 +299,19 @@ 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 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 len(s) > length: @@ -335,8 +336,6 @@ 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): @@ -351,6 +350,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) From 8ff9ff58a8152a55bf204bd178cc473c7d55775a Mon Sep 17 00:00:00 2001 From: Jacob Beck Date: Sat, 13 Dec 2014 10:52:03 -0700 Subject: [PATCH 030/425] Error handling is hard --- src/future/types/newint.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/future/types/newint.py b/src/future/types/newint.py index d4712c06..1917b877 100644 --- a/src/future/types/newint.py +++ b/src/future/types/newint.py @@ -306,6 +306,8 @@ def to_bytes(self, length, byteorder='big', signed=False): if signed and self < 0: bits = length * 8 num = (2**bits) + self + if num <= 0: + raise OverflowError("int too smal to convert") else: if self < 0: raise OverflowError("can't convert negative int to unsigned") @@ -314,6 +316,12 @@ def to_bytes(self, length, byteorder='big', signed=False): raise ValueError("byteorder must be either 'little' or 'big'") 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] From 8fe268b72a152609916b1a624c63b6413ed32f47 Mon Sep 17 00:00:00 2001 From: Brad Walker Date: Sat, 13 Dec 2014 19:40:10 -0700 Subject: [PATCH 031/425] Fixed newrange equality for empty ranges and irrelevant stop differences Fixed newrange slicing Added test_range.py suite --- src/future/types/newrange.py | 32 +++------- tests/test_future/test_range.py | 104 ++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 25 deletions(-) create mode 100644 tests/test_future/test_range.py diff --git a/src/future/types/newrange.py b/src/future/types/newrange.py index 2438d205..b78c5700 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 @@ -18,11 +18,8 @@ https://late.am/post/2012/06/18/what-the-heck-is-an-xrange """ -from math import ceil from collections import Sequence, Iterator -from future.utils import PY3 - class newrange(Sequence): """ @@ -64,10 +61,10 @@ def __repr__(self): 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, self._len))) def __len__(self): return self._len @@ -121,23 +118,8 @@ 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 + start, stop, step = slce.indices(self._len) + return newrange(self[start], stop + self._start, step * self._step) def __iter__(self): """Return an iterator which enumerates the elements of the diff --git a/tests/test_future/test_range.py b/tests/test_future/test_range.py new file mode 100644 index 00000000..0f158dc8 --- /dev/null +++ b/tests/test_future/test_range.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +""" +Tests for the backported class:`range` class. +""" + +from future.builtins import range +from future.tests.base import unittest + +from collections import Sequence + + +class RangeTests(unittest.TestCase): + def test_range(self): + self.assertTrue(isinstance(range(0), Sequence)) + + def test_bool_range(self): + self.assertFalse(range(0)) + self.assertTrue(range(1)) + self.assertFalse(range(1, 1)) + self.assertFalse(range(5, 2)) + + def test_equality(self): + self.assertEqual(range(7), range(7)) + self.assertEqual(range(0), range(1, 1)) + self.assertEqual(range(0, 10, 3), range(0, 11, 3)) + + def test_slice_range(self): + r = range(8) + self.assertEqual(r[:], range(8)) + self.assertEqual(r[:2], range(2)) + self.assertEqual(r[:-2], range(6)) + self.assertEqual(r[2:], range(2, 8)) + self.assertEqual(r[-2:], range(6, 8)) + self.assertEqual(r[2:-2], range(2, 6)) + self.assertEqual(r[-2:2:-1], range(6, 2, -1)) + r = r[::-1] + self.assertEqual(r, range(7, -1, -1)) + self.assertEqual(r[:], range(7, -1, -1)) + self.assertEqual(r[:2], range(7, 5, -1)) + self.assertEqual(r[:-2], range(7, 1, -1)) + self.assertEqual(r[2:], range(5, -1, -1)) + self.assertEqual(r[-2:], range(1, -1, -1)) + self.assertEqual(r[2:-2], range(5, 1, -1)) + self.assertEqual(r[-2:2:-1], range(1, 5)) + + def test_slice_offsetted_range(self): + r = range(4, 16) + self.assertEqual(r[:], range(4, 16)) + self.assertEqual(r[::-1], range(15, 3, -1)) + self.assertEqual(r[:4], range(4, 8)) + self.assertEqual(r[:-4], range(4, 12)) + self.assertEqual(r[4:], range(8, 16)) + self.assertEqual(r[-4:], range(12, 16)) + self.assertEqual(r[4:-4], range(8, 12)) + self.assertEqual(r[-4:4:-1], range(12, 8, -1)) + + def test_slice_overflow_range(self): + r = range(8) + self.assertEqual(r[2:200], range(2, 8)) + self.assertEqual(r[-200:-2], range(0, 6)) + + def test_stepped_slice_range(self): + r = range(8) + self.assertEqual(r[::2], range(0, 8, 2)) + self.assertEqual(r[::-2], range(7, -1, -2)) + self.assertEqual(r[:2:2], range(0, 2, 2)) + self.assertEqual(r[:-2:2], range(0, 6, 2)) + self.assertEqual(r[2::2], range(2, 8, 2)) + self.assertEqual(r[-2::2], range(6, 8, 2)) + self.assertEqual(r[2:-2:2], range(2, 6, 2)) + + def test_stepped_slice_stepped_range(self): + r = range(0, 16, 2) + self.assertEqual(r[::2], range(0, 16, 4)) + self.assertEqual(r[:2:2], range(0, 4, 4)) + self.assertEqual(r[:-2:2], range(0, 12, 4)) + self.assertEqual(r[2::2], range(4, 16, 4)) + self.assertEqual(r[-2::2], range(12, 16, 4)) + self.assertEqual(r[2:-2:2], range(4, 12, 4)) + + def test_rev_slice_range(self): + r = range(8) + self.assertEqual(r[::-1], range(7, -1, -1)) + self.assertEqual(r[:2:-1], range(7, 2, -1)) + self.assertEqual(r[:-2:-1], range(7, 6, -1)) + self.assertEqual(r[2::-1], range(2, -1, -1)) + self.assertEqual(r[-2::-1], range(6, -1, -1)) + self.assertEqual(r[-2:2:-1], range(6, 2, -1)) + r = range(0, 16, 2) + self.assertEqual(r[::-2], range(14, -2, -4)) + self.assertEqual(r[:2:-2], range(14, 4, -4)) + self.assertEqual(r[:-2:-2], range(14, 12, -4)) + self.assertEqual(r[2::-2], range(4, -2, -4)) + self.assertEqual(r[-2::-2], range(12, -2, -4)) + self.assertEqual(r[-2:2:-2], range(12, 4, -4)) + + def test_slice_zero_step(self): + msg = '^slice step cannot be zero$' + with self.assertRaisesRegexp(ValueError, msg): + range(8)[::0] + + +if __name__ == '__main__': + unittest.main() From c736a7a8f50561da1b5c05e9395d41e1115ffaa7 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 14 Dec 2014 17:28:11 +1100 Subject: [PATCH 032/425] Uncomment some newint.to_bytes tests --- tests/test_future/test_int.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_future/test_int.py b/tests/test_future/test_int.py index a257105a..b503d977 100644 --- a/tests/test_future/test_int.py +++ b/tests/test_future/test_int.py @@ -827,7 +827,7 @@ def check(tests, byteorder, signed=False): -65536: bytes(b'\xff\x00\x00'), -8388608: bytes(b'\x80\x00\x00') } - # check(tests1, 'big', signed=True) + check(tests1, 'big', signed=True) # Convert integers to signed little-endian byte arrays. tests2 = { @@ -849,7 +849,7 @@ def check(tests, byteorder, signed=False): -65536: bytes(b'\x00\x00\xff'), -8388608: bytes(b'\x00\x00\x80') } - # check(tests2, 'little', signed=True) + check(tests2, 'little', signed=True) # Convert integers to unsigned big-endian byte arrays. tests3 = { @@ -882,16 +882,16 @@ def check(tests, byteorder, signed=False): check(tests4, 'little', signed=False) self.assertRaises(OverflowError, int(256).to_bytes, 1, 'big', signed=False) - # self.assertRaises(OverflowError, int(256).to_bytes, 1, 'big', signed=True) + self.assertRaises(OverflowError, int(256).to_bytes, 1, 'big', signed=True) self.assertRaises(OverflowError, int(256).to_bytes, 1, 'little', signed=False) - # self.assertRaises(OverflowError, int(256).to_bytes, 1, 'little', signed=True) + self.assertRaises(OverflowError, int(256).to_bytes, 1, 'little', signed=True) self.assertRaises(OverflowError, int(-1).to_bytes, 2, 'big', signed=False), self.assertRaises(OverflowError, int(-1).to_bytes, 2, 'little', signed=False) self.assertEqual(int(0).to_bytes(0, 'big'), b'') self.assertEqual(int(1).to_bytes(5, 'big'), b'\x00\x00\x00\x00\x01') self.assertEqual(int(0).to_bytes(5, 'big'), b'\x00\x00\x00\x00\x00') - # self.assertEqual(int(-1).to_bytes(5, 'big', signed=True), - # bytes(b'\xff\xff\xff\xff\xff')) + self.assertEqual(int(-1).to_bytes(5, 'big', signed=True), + bytes(b'\xff\xff\xff\xff\xff')) self.assertRaises(OverflowError, int(1).to_bytes, 0, 'big') def test_from_bytes(self): From 09e17d4071e09cea67f52f3141938c8a7636ab34 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 14 Dec 2014 18:06:34 +1100 Subject: [PATCH 033/425] Remove import of dummy_thread from _thread module (issue #124) --- src/_thread/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/_thread/__init__.py b/src/_thread/__init__.py index d7a220d5..9f2a51c7 100644 --- a/src/_thread/__init__.py +++ b/src/_thread/__init__.py @@ -3,10 +3,7 @@ __future_module__ = True if sys.version_info[0] < 3: - try: - from thread import * - except ImportError: - from dummy_thread import * + from thread 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 ' From 0ac83dd3446ee3fa6e95fc87a28c725a26694827 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 14 Dec 2014 18:14:04 +1100 Subject: [PATCH 034/425] Add authors of recent patches to credits.rst --- docs/credits.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/credits.rst b/docs/credits.rst index 1f1b2416..ee4ea034 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -51,6 +51,7 @@ Development Lead Patches ~~~~~~~ +- Jacob Beck - Denis Cornehl - Nicolas Delaby - Corey Farwell @@ -65,6 +66,7 @@ Patches - Tim Shaffer - Daniel Szoska - Jeff Tratner +- Brad Walker - Mystic-Mirage (GitHub) - str4d (GitHub) - 9seconds (GitHub) @@ -75,6 +77,7 @@ Suggestions and Feedback - Chris Adams - Martijn Faassen - Lion Krischer +- Danielle Madeley - wluebbe (GitHub) From 606cfd1cc940f5f6d69d9582aafade06aa44b703 Mon Sep 17 00:00:00 2001 From: Brad Walker Date: Sat, 13 Dec 2014 23:57:46 -0700 Subject: [PATCH 035/425] newrange: added attr read-only properties --- src/future/types/newrange.py | 12 ++++++++++++ tests/test_future/test_range.py | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/future/types/newrange.py b/src/future/types/newrange.py index b78c5700..4a61b54f 100644 --- a/src/future/types/newrange.py +++ b/src/future/types/newrange.py @@ -55,6 +55,18 @@ 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) diff --git a/tests/test_future/test_range.py b/tests/test_future/test_range.py index 0f158dc8..805aa88b 100644 --- a/tests/test_future/test_range.py +++ b/tests/test_future/test_range.py @@ -99,6 +99,16 @@ def test_slice_zero_step(self): with self.assertRaisesRegexp(ValueError, msg): range(8)[::0] + def test_properties(self): + # Exception string differs between PY2/3 + r = range(0) + with self.assertRaises(AttributeError): + r.start = 0 + with self.assertRaises(AttributeError): + r.stop = 0 + with self.assertRaises(AttributeError): + r.step = 0 + if __name__ == '__main__': unittest.main() From 5047aeb097941c6713191b2827926130ac6d43cf Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 14 Dec 2014 18:19:51 +1100 Subject: [PATCH 036/425] Update What's New page for v0.14.3 --- docs/whatsnew.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 7083ef32..ae38f43c 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -5,6 +5,16 @@ What's New .. _whats-new-0.14.x: +What's new in version 0.14.3 +============================ + +This is a bug-fix release: + +- Import ``thread`` contents as ``_thread`` on Py2, not ``dummy_thread`` (issue #124) +- Add signed support for ``newint.to_bytes()`` (issue #128) +- Fix ``OrderedDict.clear()`` on Py2.6 (issue #125) +- Fix ``newrange`` equality and slicing (issue #129) + What's new in version 0.14.2 ============================ From d0e7e4342c029320cdc3770f3c81a0314006e401 Mon Sep 17 00:00:00 2001 From: Brad Walker Date: Sat, 13 Dec 2014 23:35:11 -0700 Subject: [PATCH 037/425] simplified range_iterator (renamed for consistency) --- src/future/types/newrange.py | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/future/types/newrange.py b/src/future/types/newrange.py index 4a61b54f..09f7161e 100644 --- a/src/future/types/newrange.py +++ b/src/future/types/newrange.py @@ -19,6 +19,7 @@ """ from collections import Sequence, Iterator +from itertools import islice class newrange(Sequence): @@ -136,35 +137,27 @@ def __getitem_slice(self, slce): 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 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) + + +# itertools.count in Py 2.6 doesn't accept a step parameter +def _count(start=0, step=1): + while True: + yield start + start += step __all__ = ['newrange'] From e84bab2833ddfd89261c2d3eeba033e957c6f2b8 Mon Sep 17 00:00:00 2001 From: Brad Walker Date: Sat, 13 Dec 2014 23:50:04 -0700 Subject: [PATCH 038/425] newrange: fixed slices on empty ranges --- src/future/types/newrange.py | 4 +++- tests/test_future/test_range.py | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/future/types/newrange.py b/src/future/types/newrange.py index 09f7161e..a2ebeec0 100644 --- a/src/future/types/newrange.py +++ b/src/future/types/newrange.py @@ -132,7 +132,9 @@ def __getitem_slice(self, slce): of the sequence represented by this range. """ start, stop, step = slce.indices(self._len) - return newrange(self[start], stop + self._start, step * self._step) + return newrange(self._start + self._step*start, + self._start + stop, + self._step * step) def __iter__(self): """Return an iterator which enumerates the elements of the diff --git a/tests/test_future/test_range.py b/tests/test_future/test_range.py index 805aa88b..bc87b155 100644 --- a/tests/test_future/test_range.py +++ b/tests/test_future/test_range.py @@ -19,11 +19,15 @@ def test_bool_range(self): self.assertFalse(range(1, 1)) self.assertFalse(range(5, 2)) - def test_equality(self): + def test_equality_range(self): self.assertEqual(range(7), range(7)) self.assertEqual(range(0), range(1, 1)) self.assertEqual(range(0, 10, 3), range(0, 11, 3)) + def test_slice_empty_range(self): + self.assertEqual(range(0)[:], range(0)) + self.assertEqual(range(0)[::-1], range(-1, -1, -1)) + def test_slice_range(self): r = range(8) self.assertEqual(r[:], range(8)) From 2b096827217f5e23f8cbbfe5ca40fc14afc63e9f Mon Sep 17 00:00:00 2001 From: Brad Walker Date: Sat, 13 Dec 2014 23:51:17 -0700 Subject: [PATCH 039/425] simplified newrange.__reversed__ and corrected return type --- src/future/types/newrange.py | 7 +------ tests/test_future/test_range.py | 3 ++- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/future/types/newrange.py b/src/future/types/newrange.py index a2ebeec0..432f11a1 100644 --- a/src/future/types/newrange.py +++ b/src/future/types/newrange.py @@ -107,12 +107,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 diff --git a/tests/test_future/test_range.py b/tests/test_future/test_range.py index bc87b155..90f72712 100644 --- a/tests/test_future/test_range.py +++ b/tests/test_future/test_range.py @@ -6,12 +6,13 @@ from future.builtins import range from future.tests.base import unittest -from collections import Sequence +from collections import Iterator, Sequence class RangeTests(unittest.TestCase): def test_range(self): self.assertTrue(isinstance(range(0), Sequence)) + self.assertTrue(isinstance(reversed(range(0)), Iterator)) def test_bool_range(self): self.assertFalse(range(0)) From ae772c390cbe45baa9f8181d5f9f5a89d0d85e0e Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 14 Dec 2014 18:49:09 +1100 Subject: [PATCH 040/425] Docs: tweak What's New page --- docs/whatsnew.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index ae38f43c..ffce1215 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -10,7 +10,7 @@ What's new in version 0.14.3 This is a bug-fix release: -- Import ``thread`` contents as ``_thread`` on Py2, not ``dummy_thread`` (issue #124) +- 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) - Fix ``newrange`` equality and slicing (issue #129) From ec5a7c0507c334c3a7cd637f370d4ab0b72dc844 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 14 Dec 2014 19:04:46 +1100 Subject: [PATCH 041/425] Docs: mention PR #130 in What's New page --- docs/whatsnew.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index ffce1215..408bab42 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -13,7 +13,7 @@ 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) -- Fix ``newrange`` equality and slicing (issue #129) +- Improve ``newrange``: equality and slicing, start/stop/step properties, refactoring (issues #129, #130) What's new in version 0.14.2 ============================ From 952874f7bfea74c2e9c3599b463dc96e05c571ef Mon Sep 17 00:00:00 2001 From: amatellanes Date: Sun, 14 Dec 2014 11:56:19 +0100 Subject: [PATCH 042/425] Fix minor bug from byte object doc. --- docs/bytes_object.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/bytes_object.rst b/docs/bytes_object.rst index 556edb4b..e3de27d7 100644 --- a/docs/bytes_object.rst +++ b/docs/bytes_object.rst @@ -47,7 +47,7 @@ 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 From c0809f50a3b012ce2bbc2bd52667c12145d187f9 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Mon, 15 Dec 2014 08:48:28 +1100 Subject: [PATCH 043/425] Small doc updates --- LICENSE.txt | 2 +- README.rst | 13 ++-- docs/automatic_conversion.rst | 4 +- docs/changelog.rst | 72 ++++++++++++++++++- docs/compatible_idioms.rst | 2 +- docs/conf.py | 2 +- docs/credits.rst | 2 +- docs/futurize.rst | 2 +- docs/futurize_cheatsheet.rst | 2 +- .../Writing Python 2-3 compatible code.ipynb | 4 +- docs/pasteurize.rst | 2 +- docs/quickstart.rst | 2 +- docs/whatsnew.rst | 70 ++---------------- futurize.py | 2 +- pasteurize.py | 2 +- src/future/__init__.py | 49 ++++++++----- src/libfuturize/main.py | 4 +- src/libpasteurize/main.py | 2 +- src/past/__init__.py | 2 +- 19 files changed, 134 insertions(+), 106 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 65c70446..19572a0b 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2013-2014 Python Charmers Pty Ltd, Australia +Copyright (c) 2013-2015 Python Charmers Pty Ltd, Australia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.rst b/README.rst index 13706189..2235432d 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ 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 +Notable projects that use ``python-future`` for Python 2/3 compatibility are `Mezzanine `_ and `ObsPy `_. @@ -149,9 +149,12 @@ 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 Counter, OrderedDict # backported to Py2.6 + from collections import UserDict, UserList, UserString + from subprocess import getoutput, getstatusoutput Automatic conversion to Py2/3-compatible code @@ -159,7 +162,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 @@ -261,7 +264,7 @@ Licensing :Author: Ed Schofield -:Copyright: 2013-2014 Python Charmers Pty Ltd, Australia. +:Copyright: 2013-2015 Python Charmers Pty Ltd, Australia. :Sponsor: Python Charmers Pty Ltd, Australia, and Python Charmers Pte Ltd, Singapore. http://pythoncharmers.com diff --git a/docs/automatic_conversion.rst b/docs/automatic_conversion.rst index 7821101a..d3540a1a 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 diff --git a/docs/changelog.rst b/docs/changelog.rst index b540b0dc..a27a2953 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,67 @@ Changes in previous versions Changes in the most recent two versions are here: :ref:`whats-new`. +.. _whats-new-0.13.x: + +Changes 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) + + +Changes 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). + + .. _whats-new-0.12.4: Changes in version 0.12.4 @@ -116,7 +177,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 @@ -704,6 +765,15 @@ deprecated. Summary of all changes ====================== +v0.14.3: + * Bug fixes + +v0.14.2: + * Bug fixes + +v0.14.1: + * Bug fixes + v0.14: * New top-level ``builtins`` package on Py2 for cleaner imports. Equivalent to ``future.builtins`` diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index 575ab0c2..a8e72850 100644 --- a/docs/compatible_idioms.rst +++ b/docs/compatible_idioms.rst @@ -3,7 +3,7 @@ Cheat Sheet: Writing Python 2-3 compatible code =============================================== -- **Copyright (c):** 2013-2014 Python Charmers Pty Ltd, Australia. +- **Copyright (c):** 2013-2015 Python Charmers Pty Ltd, Australia. - **Author:** Ed Schofield. - **Licence:** Creative Commons Attribution. diff --git a/docs/conf.py b/docs/conf.py index e2482230..67e92a80 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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-2015, Python Charmers Pty Ltd, Australia' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/credits.rst b/docs/credits.rst index ee4ea034..81967654 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -8,7 +8,7 @@ Licence The software is distributed under an MIT licence. The text is as follows (from ``LICENSE.txt``):: - Copyright (c) 2013-2014 Python Charmers Pty Ltd, Australia + Copyright (c) 2013-2015 Python Charmers Pty Ltd, Australia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/docs/futurize.rst b/docs/futurize.rst index 5a33053f..8f13b4a9 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 diff --git a/docs/futurize_cheatsheet.rst b/docs/futurize_cheatsheet.rst index 0ecad572..274df286 100644 --- a/docs/futurize_cheatsheet.rst +++ b/docs/futurize_cheatsheet.rst @@ -1,6 +1,6 @@ .. _futurize_cheatsheet: -``futurize`` quick-start: automatic conversion from Py2 to Py2&3 +``futurize`` quick-start: automatic conversion from Py2 to Py2/3 ================================================================ Instructions and notes on converting code from supporting only Python 2 to diff --git a/docs/notebooks/Writing Python 2-3 compatible code.ipynb b/docs/notebooks/Writing Python 2-3 compatible code.ipynb index 22b6a987..d7c6bdc4 100644 --- a/docs/notebooks/Writing Python 2-3 compatible code.ipynb +++ b/docs/notebooks/Writing Python 2-3 compatible code.ipynb @@ -20,7 +20,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "- **Copyright (c):** 2013-2014 Python Charmers Pty Ltd, Australia.\n", + "- **Copyright (c):** 2013-2015 Python Charmers Pty Ltd, Australia.\n", "- **Author:** Ed Schofield.\n", "- **Licence:** Creative Commons Attribution.\n", "\n", @@ -3004,4 +3004,4 @@ "metadata": {} } ] -} \ No newline at end of file +} diff --git a/docs/pasteurize.rst b/docs/pasteurize.rst index 52719020..9ed6cb1d 100644 --- a/docs/pasteurize.rst +++ b/docs/pasteurize.rst @@ -1,6 +1,6 @@ .. _backwards-conversion: -``pasteurize``: Py3 to Py2&3 +``pasteurize``: Py3 to Py2/3 ---------------------------- Running ``pasteurize -w mypy3module.py`` turns this Python 3 code:: diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 3a802714..e2e4a260 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -42,7 +42,7 @@ The easiest way is to start each new module with these lines:: 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. -- 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`. diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 408bab42..aca8d8ad 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -14,6 +14,7 @@ This is a bug-fix release: - 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 What's new in version 0.14.2 ============================ @@ -42,8 +43,8 @@ This is a minor bug-fix release: exists on Py2 (issue #109) -What's new in version 0.14.0 -============================ +What's new in version 0.14 +========================== This is a major new release that offers a cleaner interface for most imports in Python 2/3 compatible code. @@ -59,7 +60,7 @@ Instead of this interface:: ... import tkinter.dialog ... # etc. -you can now use the following interface for Python 2/3 compatible code:: +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 @@ -131,68 +132,7 @@ The following internal functions have been deprecated and will be removed in a f - ``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). - - Previous versions ================= -See the :ref:`whats-old` for versions prior to v0.13. +See :ref:`whats-old` for versions prior to v0.14. diff --git a/futurize.py b/futurize.py index ffff1ab2..b8b734d4 100755 --- a/futurize.py +++ b/futurize.py @@ -13,7 +13,7 @@ Licensing --------- -Copyright 2013 Python Charmers Pty Ltd, Australia. +Copyright 2013-2015 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ diff --git a/pasteurize.py b/pasteurize.py index 19f6aa3a..591c1414 100755 --- a/pasteurize.py +++ b/pasteurize.py @@ -12,7 +12,7 @@ Licensing --------- -Copyright 2013-2014 Python Charmers Pty Ltd, Australia. +Copyright 2013-2015 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ diff --git a/src/future/__init__.py b/src/future/__init__.py index c3cddc77..7430e46c 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,13 +27,31 @@ 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 configparser + >>> 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 code (from either Python 2 or Python 3) to code compatible with both @@ -62,7 +77,7 @@ Licensing --------- -Copyright 2013-2014 Python Charmers Pty Ltd, Australia. +Copyright 2013-2015 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ @@ -70,7 +85,7 @@ __title__ = 'future' __author__ = 'Ed Schofield' __license__ = 'MIT' -__copyright__ = 'Copyright 2014 Python Charmers Pty Ltd' +__copyright__ = 'Copyright 2013-2015 Python Charmers Pty Ltd' __ver_major__ = 0 __ver_minor__ = 14 __ver_patch__ = 2 diff --git a/src/libfuturize/main.py b/src/libfuturize/main.py index 2e8f1b56..18f33ec0 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. diff --git a/src/libpasteurize/main.py b/src/libpasteurize/main.py index f4a1dd52..cc0e6ec3 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 diff --git a/src/past/__init__.py b/src/past/__init__.py index 8416f228..e31678b3 100644 --- a/src/past/__init__.py +++ b/src/past/__init__.py @@ -80,7 +80,7 @@ Licensing --------- -Copyright 2013-2014 Python Charmers Pty Ltd, Australia. +Copyright 2013-2015 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ From dc4b2f1617829df31ad9033aba03b8037b86d3f6 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Mon, 15 Dec 2014 09:04:14 +1100 Subject: [PATCH 044/425] Bump version to v0.14.3 --- src/future/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/__init__.py b/src/future/__init__.py index 7430e46c..a93381ff 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -88,7 +88,7 @@ __copyright__ = 'Copyright 2013-2015 Python Charmers Pty Ltd' __ver_major__ = 0 __ver_minor__ = 14 -__ver_patch__ = 2 +__ver_patch__ = 3 __ver_sub__ = '' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From fd44b5ec04d1e08ed9ef38a69d042b158acb0595 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Mon, 15 Dec 2014 10:11:13 +1100 Subject: [PATCH 045/425] Docs: improve organization Consolidate several smaller sections into larger ones --- docs/automatic_conversion.rst | 2 + docs/contents.rst.inc | 6 +- docs/conversion_limitations.rst | 27 ++--- docs/futurize_cheatsheet.rst | 15 ++- docs/imports.rst | 26 ++--- docs/older_interfaces.rst | 141 +++++++++++++++++++++++++ docs/quickstart.rst | 2 +- docs/standard_library_imports.rst | 164 ++---------------------------- docs/translation.rst | 4 +- docs/unicode_literals.rst | 10 +- docs/whatsnew.rst | 4 +- 11 files changed, 195 insertions(+), 206 deletions(-) create mode 100644 docs/older_interfaces.rst diff --git a/docs/automatic_conversion.rst b/docs/automatic_conversion.rst index d3540a1a..fc3428bf 100644 --- a/docs/automatic_conversion.rst +++ b/docs/automatic_conversion.rst @@ -22,6 +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/contents.rst.inc b/docs/contents.rst.inc index 2b118454..cedf7bb7 100644 --- a/docs/contents.rst.inc +++ b/docs/contents.rst.inc @@ -9,13 +9,11 @@ Contents: quickstart compatible_idioms imports - standard_library_imports what_else automatic_conversion - futurize_cheatsheet - translation - stdlib_incompatibilities faq + stdlib_incompatibilities + older_interfaces changelog credits reference 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/futurize_cheatsheet.rst b/docs/futurize_cheatsheet.rst index 274df286..5f4d853c 100644 --- a/docs/futurize_cheatsheet.rst +++ b/docs/futurize_cheatsheet.rst @@ -1,15 +1,14 @@ .. _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. @@ -23,7 +22,7 @@ d. Install Python 3.3 with e.g. ``sudo apt-get install python3``. On other platf .. _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. @@ -45,7 +44,7 @@ See :ref:`forwards-conversion-stage1` for more info. Example error -~~~~~~~~~~~~~ +************* One relatively common error after conversion is:: @@ -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. diff --git a/docs/imports.rst b/docs/imports.rst index cde02a2d..50f97995 100644 --- a/docs/imports.rst +++ b/docs/imports.rst @@ -60,17 +60,12 @@ 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/older_interfaces.rst b/docs/older_interfaces.rst new file mode 100644 index 00000000..bbf07f16 --- /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). + + +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:: + + 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. + + +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``. + + diff --git a/docs/quickstart.rst b/docs/quickstart.rst index e2e4a260..aab5470e 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -123,7 +123,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: diff --git a/docs/standard_library_imports.rst b/docs/standard_library_imports.rst index 961c6765..b9aec3ca 100644 --- a/docs/standard_library_imports.rst +++ b/docs/standard_library_imports.rst @@ -1,13 +1,15 @@ .. _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 @@ -29,12 +31,6 @@ modules in Python 2/3 compatible code. For example, the following clean Python 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 ----------------------------------------- - Of the 44 modules that were refactored with PEP 3108 (standard library reorganization), 30 are supported with direct imports in the above manner. The complete list is here:: @@ -85,8 +81,8 @@ complete list is here:: .. _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`` @@ -119,148 +115,8 @@ package makes the Python 3.x APIs available on Python 2.x as follows:: 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:: - - 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 - - -``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. - - -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:: @@ -285,8 +141,8 @@ are also available:: 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``:: diff --git a/docs/translation.rst b/docs/translation.rst index 9e844a1c..e59c6676 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. @@ -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``. diff --git a/docs/unicode_literals.rst b/docs/unicode_literals.rst index b54b7cb6..7af644c8 100644 --- a/docs/unicode_literals.rst +++ b/docs/unicode_literals.rst @@ -7,15 +7,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 diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index aca8d8ad..7321a217 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -74,7 +74,7 @@ you can now use the following interface for much Python 2/3 compatible code:: 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`.) +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 @@ -107,7 +107,7 @@ 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. + (Issue #104). - This release also removes the broken ``--doctests_only`` option from the ``futurize`` and ``pasteurize`` scripts for now (issue #103). From 18035cc6bf0cbe0f20d73c728e7a45d569a578cb Mon Sep 17 00:00:00 2001 From: Brad Walker Date: Tue, 16 Dec 2014 03:48:53 -0700 Subject: [PATCH 046/425] Fixed newrange slicing for some slice/range combos The previous slicing had problems slicing stepped ranges and reverse slicing reversed ranges. Comprehensive test suite finished. --- src/future/types/newrange.py | 9 +- tests/test_future/test_range.py | 215 ++++++++++++++++++++++---------- 2 files changed, 155 insertions(+), 69 deletions(-) diff --git a/src/future/types/newrange.py b/src/future/types/newrange.py index 432f11a1..c719bb74 100644 --- a/src/future/types/newrange.py +++ b/src/future/types/newrange.py @@ -126,10 +126,11 @@ def __getitem_slice(self, slce): """Return a range which represents the requested slce of the sequence represented by this range. """ - start, stop, step = slce.indices(self._len) - return newrange(self._start + self._step*start, - self._start + stop, - self._step * step) + 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 diff --git a/tests/test_future/test_range.py b/tests/test_future/test_range.py index 90f72712..c14f168f 100644 --- a/tests/test_future/test_range.py +++ b/tests/test_future/test_range.py @@ -7,6 +7,7 @@ from future.tests.base import unittest from collections import Iterator, Sequence +from operator import attrgetter class RangeTests(unittest.TestCase): @@ -25,79 +26,163 @@ def test_equality_range(self): self.assertEqual(range(0), range(1, 1)) self.assertEqual(range(0, 10, 3), range(0, 11, 3)) - def test_slice_empty_range(self): - self.assertEqual(range(0)[:], range(0)) - self.assertEqual(range(0)[::-1], range(-1, -1, -1)) + # Use strict equality of attributes when slicing to catch subtle differences + def assertRangesEqual(self, r1, r2): + by_attrs = attrgetter('start', 'stop', 'step') + self.assertEqual(by_attrs(r1), by_attrs(r2)) - def test_slice_range(self): - r = range(8) - self.assertEqual(r[:], range(8)) - self.assertEqual(r[:2], range(2)) - self.assertEqual(r[:-2], range(6)) - self.assertEqual(r[2:], range(2, 8)) - self.assertEqual(r[-2:], range(6, 8)) - self.assertEqual(r[2:-2], range(2, 6)) - self.assertEqual(r[-2:2:-1], range(6, 2, -1)) - r = r[::-1] - self.assertEqual(r, range(7, -1, -1)) - self.assertEqual(r[:], range(7, -1, -1)) - self.assertEqual(r[:2], range(7, 5, -1)) - self.assertEqual(r[:-2], range(7, 1, -1)) - self.assertEqual(r[2:], range(5, -1, -1)) - self.assertEqual(r[-2:], range(1, -1, -1)) - self.assertEqual(r[2:-2], range(5, 1, -1)) - self.assertEqual(r[-2:2:-1], range(1, 5)) - - def test_slice_offsetted_range(self): - r = range(4, 16) - self.assertEqual(r[:], range(4, 16)) - self.assertEqual(r[::-1], range(15, 3, -1)) - self.assertEqual(r[:4], range(4, 8)) - self.assertEqual(r[:-4], range(4, 12)) - self.assertEqual(r[4:], range(8, 16)) - self.assertEqual(r[-4:], range(12, 16)) - self.assertEqual(r[4:-4], range(8, 12)) - self.assertEqual(r[-4:4:-1], range(12, 8, -1)) + def test_slice_empty_range(self): + self.assertRangesEqual(range(0)[:], range(0)) + self.assertRangesEqual(range(0)[::-1], range(-1, -1, -1)) def test_slice_overflow_range(self): r = range(8) - self.assertEqual(r[2:200], range(2, 8)) - self.assertEqual(r[-200:-2], range(0, 6)) + self.assertRangesEqual(r[2:200], range(2, 8)) + self.assertRangesEqual(r[-200:-2], range(0, 6)) + + def test_slice_range(self): + r = range(-8, 8) + self.assertRangesEqual(r[:], range(-8, 8)) + self.assertRangesEqual(r[:2], range(-8, -6)) + self.assertRangesEqual(r[:-2], range(-8, 6)) + self.assertRangesEqual(r[2:], range(-6, 8)) + self.assertRangesEqual(r[-2:], range(6, 8)) + self.assertRangesEqual(r[2:-2], range(-6, 6)) + + def test_rev_slice_range(self): + r = range(-8, 8) + self.assertRangesEqual(r[::-1], range(7, -9, -1)) + self.assertRangesEqual(r[:2:-1], range(7, -6, -1)) + self.assertRangesEqual(r[:-2:-1], range(7, 6, -1)) + self.assertRangesEqual(r[2::-1], range(-6, -9, -1)) + self.assertRangesEqual(r[-2::-1], range(6, -9, -1)) + self.assertRangesEqual(r[-2:2:-1], range(6, -6, -1)) + + def test_slice_rev_range(self): + r = range(8, -8, -1) + self.assertRangesEqual(r[:], range(8, -8, -1)) + self.assertRangesEqual(r[:2], range(8, 6, -1)) + self.assertRangesEqual(r[:-2], range(8, -6, -1)) + self.assertRangesEqual(r[2:], range(6, -8, -1)) + self.assertRangesEqual(r[-2:], range(-6, -8, -1)) + self.assertRangesEqual(r[2:-2], range(6, -6, -1)) + + def test_rev_slice_rev_range(self): + r = range(8, -8, -1) + self.assertRangesEqual(r[::-1], range(-7, 9)) + self.assertRangesEqual(r[:2:-1], range(-7, 6)) + self.assertRangesEqual(r[:-2:-1], range(-7, -6)) + self.assertRangesEqual(r[2::-1], range(6, 9)) + self.assertRangesEqual(r[-2::-1], range(-6, 9)) + self.assertRangesEqual(r[-2:2:-1], range(-6, 6)) def test_stepped_slice_range(self): - r = range(8) - self.assertEqual(r[::2], range(0, 8, 2)) - self.assertEqual(r[::-2], range(7, -1, -2)) - self.assertEqual(r[:2:2], range(0, 2, 2)) - self.assertEqual(r[:-2:2], range(0, 6, 2)) - self.assertEqual(r[2::2], range(2, 8, 2)) - self.assertEqual(r[-2::2], range(6, 8, 2)) - self.assertEqual(r[2:-2:2], range(2, 6, 2)) + r = range(-8, 8) + self.assertRangesEqual(r[::2], range(-8, 8, 2)) + self.assertRangesEqual(r[:2:2], range(-8, -6, 2)) + self.assertRangesEqual(r[:-2:2], range(-8, 6, 2)) + self.assertRangesEqual(r[2::2], range(-6, 8, 2)) + self.assertRangesEqual(r[-2::2], range(6, 8, 2)) + self.assertRangesEqual(r[2:-2:2], range(-6, 6, 2)) + + def test_rev_stepped_slice_range(self): + r = range(-8, 8) + self.assertRangesEqual(r[::-2], range(7, -9, -2)) + self.assertRangesEqual(r[:2:-2], range(7, -6, -2)) + self.assertRangesEqual(r[:-2:-2], range(7, 6, -2)) + self.assertRangesEqual(r[2::-2], range(-6, -9, -2)) + self.assertRangesEqual(r[-2::-2], range(6, -9, -2)) + self.assertRangesEqual(r[-2:2:-2], range(6, -6, -2)) + + def test_stepped_slice_rev_range(self): + r = range(8, -8, -1) + self.assertRangesEqual(r[::2], range(8, -8, -2)) + self.assertRangesEqual(r[:2:2], range(8, 6, -2)) + self.assertRangesEqual(r[:-2:2], range(8, -6, -2)) + self.assertRangesEqual(r[2::2], range(6, -8, -2)) + self.assertRangesEqual(r[-2::2], range(-6, -8, -2)) + self.assertRangesEqual(r[2:-2:2], range(6, -6, -2)) + + def test_rev_stepped_slice_rev_range(self): + r = range(8, -8, -1) + self.assertRangesEqual(r[::-2], range(-7, 9, 2)) + self.assertRangesEqual(r[:2:-2], range(-7, 6, 2)) + self.assertRangesEqual(r[:-2:-2], range(-7, -6, 2)) + self.assertRangesEqual(r[2::-2], range(6, 9, 2)) + self.assertRangesEqual(r[-2::-2], range(-6, 9, 2)) + self.assertRangesEqual(r[-2:2:-2], range(-6, 6, 2)) + + def test_slice_stepped_range(self): + r = range(-8, 8, 2) + self.assertRangesEqual(r[:], range(-8, 8, 2)) + self.assertRangesEqual(r[:2], range(-8, -4, 2)) + self.assertRangesEqual(r[:-2], range(-8, 4, 2)) + self.assertRangesEqual(r[2:], range(-4, 8, 2)) + self.assertRangesEqual(r[-2:], range(4, 8, 2)) + self.assertRangesEqual(r[2:-2], range(-4, 4, 2)) + + def test_rev_slice_stepped_range(self): + r = range(-8, 8, 2) + self.assertRangesEqual(r[::-1], range(6, -10, -2)) + self.assertRangesEqual(r[:2:-1], range(6, -4, -2)) + self.assertRangesEqual(r[:-2:-1], range(6, 4, -2)) + self.assertRangesEqual(r[2::-1], range(-4, -10, -2)) + self.assertRangesEqual(r[-2::-1], range(4, -10, -2)) + self.assertRangesEqual(r[-2:2:-1], range(4, -4, -2)) + + def test_slice_rev_stepped_range(self): + r = range(8, -8, -2) + self.assertRangesEqual(r[:], range(8, -8, -2)) + self.assertRangesEqual(r[:2], range(8, 4, -2)) + self.assertRangesEqual(r[:-2], range(8, -4, -2)) + self.assertRangesEqual(r[2:], range(4, -8, -2)) + self.assertRangesEqual(r[-2:], range(-4, -8, -2)) + self.assertRangesEqual(r[2:-2], range(4, -4, -2)) + + def test_rev_slice_rev_stepped_range(self): + r = range(8, -8, -2) + self.assertRangesEqual(r[::-1], range(-6, 10, 2)) + self.assertRangesEqual(r[:2:-1], range(-6, 4, 2)) + self.assertRangesEqual(r[:-2:-1], range(-6, -4, 2)) + self.assertRangesEqual(r[2::-1], range(4, 10, 2)) + self.assertRangesEqual(r[-2::-1], range(-4, 10, 2)) + self.assertRangesEqual(r[-2:2:-1], range(-4, 4, 2)) def test_stepped_slice_stepped_range(self): - r = range(0, 16, 2) - self.assertEqual(r[::2], range(0, 16, 4)) - self.assertEqual(r[:2:2], range(0, 4, 4)) - self.assertEqual(r[:-2:2], range(0, 12, 4)) - self.assertEqual(r[2::2], range(4, 16, 4)) - self.assertEqual(r[-2::2], range(12, 16, 4)) - self.assertEqual(r[2:-2:2], range(4, 12, 4)) - - def test_rev_slice_range(self): - r = range(8) - self.assertEqual(r[::-1], range(7, -1, -1)) - self.assertEqual(r[:2:-1], range(7, 2, -1)) - self.assertEqual(r[:-2:-1], range(7, 6, -1)) - self.assertEqual(r[2::-1], range(2, -1, -1)) - self.assertEqual(r[-2::-1], range(6, -1, -1)) - self.assertEqual(r[-2:2:-1], range(6, 2, -1)) - r = range(0, 16, 2) - self.assertEqual(r[::-2], range(14, -2, -4)) - self.assertEqual(r[:2:-2], range(14, 4, -4)) - self.assertEqual(r[:-2:-2], range(14, 12, -4)) - self.assertEqual(r[2::-2], range(4, -2, -4)) - self.assertEqual(r[-2::-2], range(12, -2, -4)) - self.assertEqual(r[-2:2:-2], range(12, 4, -4)) + r = range(-8, 8, 2) + self.assertRangesEqual(r[::2], range(-8, 8, 4)) + self.assertRangesEqual(r[:2:2], range(-8, -4, 4)) + self.assertRangesEqual(r[:-2:2], range(-8, 4, 4)) + self.assertRangesEqual(r[2::2], range(-4, 8, 4)) + self.assertRangesEqual(r[-2::2], range(4, 8, 4)) + self.assertRangesEqual(r[2:-2:2], range(-4, 4, 4)) + + def test_rev_stepped_slice_stepped_range(self): + r = range(-8, 8, 2) + self.assertRangesEqual(r[::-2], range(6, -10, -4)) + self.assertRangesEqual(r[:2:-2], range(6, -4, -4)) + self.assertRangesEqual(r[:-2:-2], range(6, 4, -4)) + self.assertRangesEqual(r[2::-2], range(-4, -10, -4)) + self.assertRangesEqual(r[-2::-2], range(4, -10, -4)) + self.assertRangesEqual(r[-2:2:-2], range(4, -4, -4)) + + def test_stepped_slice_rev_stepped_range(self): + r = range(8, -8, -2) + self.assertRangesEqual(r[::2], range(8, -8, -4)) + self.assertRangesEqual(r[:2:2], range(8, 4, -4)) + self.assertRangesEqual(r[:-2:2], range(8, -4, -4)) + self.assertRangesEqual(r[2::2], range(4, -8, -4)) + self.assertRangesEqual(r[-2::2], range(-4, -8, -4)) + self.assertRangesEqual(r[2:-2:2], range(4, -4, -4)) + + def test_rev_stepped_slice_rev_stepped_range(self): + r = range(8, -8, -2) + self.assertRangesEqual(r[::-2], range(-6, 10, 4)) + self.assertRangesEqual(r[:2:-2], range(-6, 4, 4)) + self.assertRangesEqual(r[:-2:-2], range(-6, -4, 4)) + self.assertRangesEqual(r[2::-2], range(4, 10, 4)) + self.assertRangesEqual(r[-2::-2], range(-4, 10, 4)) + self.assertRangesEqual(r[-2:2:-2], range(-4, 4, 4)) def test_slice_zero_step(self): msg = '^slice step cannot be zero$' From d7e4722e01bcb46414103f6fc12781b8985ab934 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Tue, 13 Jan 2015 10:54:41 +1100 Subject: [PATCH 047/425] Docs: minor tweaks --- docs/dict_object.rst | 6 +++--- docs/isinstance.rst | 2 +- docs/open_function.rst | 8 ++++---- docs/quickstart.rst | 3 ++- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/dict_object.rst b/docs/dict_object.rst index 0142b240..c967d6bb 100644 --- a/docs/dict_object.rst +++ b/docs/dict_object.rst @@ -22,8 +22,8 @@ stick with standard Python 3 code in your Py2/3 compatible codebase:: 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 @@ -70,7 +70,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; diff --git a/docs/isinstance.rst b/docs/isinstance.rst index 5e6a20b0..9f49250d 100644 --- a/docs/isinstance.rst +++ b/docs/isinstance.rst @@ -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 diff --git a/docs/open_function.rst b/docs/open_function.rst index 91308c2a..656c9127 100644 --- a/docs/open_function.rst +++ b/docs/open_function.rst @@ -10,10 +10,10 @@ contents as (unicode) strings unless the binary (``b``) flag is passed, as in:: 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.6 and 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 diff --git a/docs/quickstart.rst b/docs/quickstart.rst index aab5470e..4075cdfa 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -40,7 +40,8 @@ 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.6, 2.7, and 3.3+ mostly +unchanged. - For explicit import forms, see :ref:`explicit-imports`. - For more details, see :ref:`what-else`. From 521477b69e4148f720b2f0b6b5287e5947bff203 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Tue, 3 Feb 2015 14:29:20 +1100 Subject: [PATCH 048/425] Docs: Add release dates to What's New and Changelog pages (issue #135) Suggested by @AnneTheAgile on GitHub. --- docs/changelog.rst | 58 +++++++++++++++++++++++----------------------- docs/whatsnew.rst | 16 ++++++------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index a27a2953..240d5614 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -3,12 +3,12 @@ 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`. .. _whats-new-0.13.x: -Changes in version 0.13.1 -========================= +Changes in version 0.13.1 (2014-09-23) +====================================== This is a bug-fix release: @@ -19,8 +19,8 @@ This is a bug-fix release: - Doc formatting fix (issues #98, 100) -Changes in version 0.13 -======================= +Changes in version 0.13 (2014-08-13) +==================================== This is mostly a clean-up release. It adds some small new compatibility features and fixes several bugs. @@ -68,16 +68,16 @@ Bug fixes .. _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). .. _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). @@ -107,8 +107,8 @@ Changes in version 0.12.3 .. _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) @@ -118,8 +118,8 @@ Changes in version 0.12.2 .. _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 @@ -130,8 +130,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 @@ -348,8 +348,8 @@ 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: @@ -375,8 +375,8 @@ This release contains various small improvements and fixes: .. _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: @@ -443,8 +443,8 @@ The ``pasteurize`` script for converting from Py3 to Py2/3 still adds .. _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. @@ -571,8 +571,8 @@ 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 ------------------------------------------------------- @@ -611,8 +611,8 @@ 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 ------------------------ @@ -656,8 +656,8 @@ Bugfixes .. _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 @@ -716,8 +716,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 ------------------ diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 7321a217..4bc808b0 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -5,8 +5,8 @@ What's New .. _whats-new-0.14.x: -What's new in version 0.14.3 -============================ +What's new in version 0.14.3 (2014-12-15) +========================================= This is a bug-fix release: @@ -16,8 +16,8 @@ This is a bug-fix release: - Improve ``newrange``: equality and slicing, start/stop/step properties, refactoring (issues #129, #130) - Minor doc updates -What's new in version 0.14.2 -============================ +What's new in version 0.14.2 (2014-11-21) +========================================= This is a bug-fix release: @@ -32,8 +32,8 @@ This is a bug-fix release: - 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) -What's new in version 0.14.1 -============================ +What's new in version 0.14.1 (2014-10-02) +========================================= This is a minor bug-fix release: @@ -43,8 +43,8 @@ This is a minor bug-fix release: exists on Py2 (issue #109) -What's new in version 0.14 -========================== +What's new in version 0.14 (2014-10-02) +======================================= This is a major new release that offers a cleaner interface for most imports in Python 2/3 compatible code. From 209d0e1ea067de8558cbc94ac2d353f9c2a115a6 Mon Sep 17 00:00:00 2001 From: Michael Joseph Date: Thu, 16 Apr 2015 12:13:36 +0200 Subject: [PATCH 049/425] Correct `install_aliases` --- docs/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index aab5470e..b4318f92 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -94,7 +94,7 @@ 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 From b6f8457ecd2b3711931e424581abb5d1eff5fe23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Tr=C3=B6ndle?= Date: Thu, 14 May 2015 23:57:17 +0200 Subject: [PATCH 050/425] fixed unicode literals flag in futurize doc --- docs/futurize.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/futurize.rst b/docs/futurize.rst index 8f13b4a9..3e1e278d 100644 --- a/docs/futurize.rst +++ b/docs/futurize.rst @@ -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 @@ -297,9 +297,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,7 +311,7 @@ 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 From a0286550abaea082c6e8ecd1c21f1152ad17d9bc Mon Sep 17 00:00:00 2001 From: Matt Bogosian Date: Thu, 14 May 2015 22:32:54 -0700 Subject: [PATCH 051/425] Fix #144. Expand newint.__divmod__ and newint.__rdivmod__ to fall back to implementations where appropriate. --- src/future/types/newint.py | 6 +++++ tests/test_future/test_int.py | 21 ++++++++++++++++ tests/test_future/test_int_old_division.py | 29 +++++++++++++++++++--- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/future/types/newint.py b/src/future/types/newint.py index 1917b877..705b8fa9 100644 --- a/src/future/types/newint.py +++ b/src/future/types/newint.py @@ -201,10 +201,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): diff --git a/tests/test_future/test_int.py b/tests/test_future/test_int.py index b503d977..f1d9c5d8 100644 --- a/tests/test_future/test_int.py +++ b/tests/test_future/test_int.py @@ -49,6 +49,9 @@ def cpython_only(f): class IntTestCases(unittest.TestCase): + def setUp(self): + self.longMessage = True + def test_isinstance_int_subclass(self): """ Issue #89 @@ -454,6 +457,24 @@ def test_divmod(self): assert divmod(int(x), int(-y)) == divmod(x, -y) assert divmod(int(-x), int(-y)) == divmod(-x, -y) + assert divmod(int(x), float(y)) == divmod(x, float(y)) + assert divmod(int(-x), float(y)) == divmod(-x, float(y)) + assert divmod(int(x), float(-y)) == divmod(x, float(-y)) + assert divmod(int(-x), float(-y)) == divmod(-x, float(-y)) + + def _frange(x, y, step): + _x = x ; i = 0 + while _x < y: + yield _x + i += 1 ; _x = x + i * step + + for i in range(20): + for d in _frange(0.005, 5.0, 0.005): + self.assertEqual(divmod(int(i), d), divmod(i, d), msg='i={0}; d={1}'.format(i, d)) + self.assertEqual(divmod(int(-i), d), divmod(-i, d), msg='i={0}; d={1}'.format(i, d)) + self.assertEqual(divmod(int(i), -d), divmod(i, -d), msg='i={0}; d={1}'.format(i, d)) + self.assertEqual(divmod(int(-i), -d), divmod(-i, -d), msg='i={0}; d={1}'.format(i, d)) + def test_div(self): """ Issue #38 diff --git a/tests/test_future/test_int_old_division.py b/tests/test_future/test_int_old_division.py index 2acf8652..be6fdedc 100644 --- a/tests/test_future/test_int_old_division.py +++ b/tests/test_future/test_int_old_division.py @@ -6,7 +6,7 @@ is not in effect. """ -from __future__ import (absolute_import, +from __future__ import (absolute_import, print_function, unicode_literals) from future import standard_library from future.builtins import * @@ -20,6 +20,10 @@ @unittest.skipIf(not PY2, 'old division tests only for Py2') class IntTestCasesOldDivision(unittest.TestCase): + def setUp(self): + self.longMessage = True + + def test_div(self): """ Issue #38 @@ -27,7 +31,7 @@ def test_div(self): a = int(3) self.assertEqual(a / 5., 0.6) self.assertEqual(a / 5, 0) - + def test_idiv(self): a = int(3) @@ -42,7 +46,7 @@ def test_idiv(self): c /= 2.0 self.assertEqual(c, -1.5) self.assertTrue(isinstance(c, float)) - + def test_truediv(self): """ @@ -74,5 +78,24 @@ def test_truediv(self): self.assertTrue(isinstance(e, int)) + def test_divmod(self): + """ + Test int.__divmod__ + """ + vals = [10**i for i in range(0, 20)] + for i in range(200): + x = random.choice(vals) + y = random.choice(vals) + self.assertEqual(int(y).__rdivmod__(int(x)), divmod(x, y), msg='x={0}; y={1}'.format(x, y)) + self.assertEqual(int(-y).__rdivmod__(int(x)), divmod(x, -y), msg='x={0}; y={1}'.format(x, y)) + self.assertEqual(int(y).__rdivmod__(int(-x)), divmod(-x, y), msg='x={0}; y={1}'.format(x, y)) + self.assertEqual(int(-y).__rdivmod__(int(-x)), divmod(-x, -y), msg='x={0}; y={1}'.format(x, y)) + + self.assertEqual(int(x).__rdivmod__(int(y)), long(x).__rdivmod__(y), msg='x={0}; y={1}'.format(x, y)) + self.assertEqual(int(-x).__rdivmod__(int(y)), long(-x).__rdivmod__(y), msg='x={0}; y={1}'.format(x, y)) + self.assertEqual(int(x).__rdivmod__(int(-y)), long(x).__rdivmod__(-y), msg='x={0}; y={1}'.format(x, y)) + self.assertEqual(int(-x).__rdivmod__(int(-y)), long(-x).__rdivmod__(-y), msg='x={0}; y={1}'.format(x, y)) + + if __name__ == "__main__": unittest.main() From 5b62d1759d5756c2957cfcb3a59f17662c3e0a47 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 27 May 2015 22:35:33 +1000 Subject: [PATCH 052/425] Experimental tkinter.ttk support --- docs/standard_library_imports.rst | 1 + src/tkinter/ttk.py | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 src/tkinter/ttk.py diff --git a/docs/standard_library_imports.rst b/docs/standard_library_imports.rst index b9aec3ca..fcee748f 100644 --- a/docs/standard_library_imports.rst +++ b/docs/standard_library_imports.rst @@ -68,6 +68,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 diff --git a/src/tkinter/ttk.py b/src/tkinter/ttk.py new file mode 100644 index 00000000..b75d4bca --- /dev/null +++ b/src/tkinter/ttk.py @@ -0,0 +1,13 @@ +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?') + From 313b7e4cfc0916003b923042b99f4bcad1f4be52 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 27 May 2015 22:44:16 +1000 Subject: [PATCH 053/425] Also add ttk support to future.moves --- src/future/moves/tkinter/ttk.py | 13 +++++++++++++ src/future/standard_library/__init__.py | 1 - src/libpasteurize/fixes/fix_imports.py | 1 + src/tkinter/ttk.py | 2 +- 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 src/future/moves/tkinter/ttk.py diff --git a/src/future/moves/tkinter/ttk.py b/src/future/moves/tkinter/ttk.py new file mode 100644 index 00000000..22ac9774 --- /dev/null +++ b/src/future/moves/tkinter/ttk.py @@ -0,0 +1,13 @@ +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/standard_library/__init__.py b/src/future/standard_library/__init__.py index 2923e2d3..a6b2a005 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -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 """ diff --git a/src/libpasteurize/fixes/fix_imports.py b/src/libpasteurize/fixes/fix_imports.py index d79558ce..4db0d548 100644 --- a/src/libpasteurize/fixes/fix_imports.py +++ b/src/libpasteurize/fixes/fix_imports.py @@ -40,6 +40,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", diff --git a/src/tkinter/ttk.py b/src/tkinter/ttk.py index b75d4bca..22ac9774 100644 --- a/src/tkinter/ttk.py +++ b/src/tkinter/ttk.py @@ -9,5 +9,5 @@ from ttk import * except ImportError: raise ImportError('The ttk module is missing. Does your Py2 ' - 'installation include Tkinter?') + 'installation include tkinter?') From e7abecd88de3b283372d21f71f7293f9ff40ad61 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 3 Jun 2015 22:37:46 +1000 Subject: [PATCH 054/425] Update What's New page for v0.14.x --- docs/whatsnew.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 4bc808b0..a2da19f0 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -5,6 +5,24 @@ What's New .. _whats-new-0.14.x: +What's new in version 0.14.4 (2015-06-03) +========================================= + +This release adds two new minor features and fixes some small bugs. + +Minor features: + +- ``tkinter.ttk`` support (issue #151) +- ``collections.ChainMap`` backport (issue #150) + +Bug fixes: + +- Expand newint.__divmod__ and newint.__rdivmod__ to fall back to + implementations where appropriate (issue #146 - thanks to Matt Bogosian) +- Fixed newrange slicing for some slice/range combos (issue #132, thanks to Brad Walker) +- Small doc fixes + + What's new in version 0.14.3 (2014-12-15) ========================================= From 1b8ef510652eec238f835ba8d2b28bfef677833a Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sat, 20 Jun 2015 00:57:38 +0200 Subject: [PATCH 055/425] Add count from 2.7 to 2.6 The future.types.newrange already has a _count method which is now moved to the backports to allow make this public. --- src/future/backports/misc.py | 10 ++++++ src/future/types/newrange.py | 10 ++---- tests/test_future/test_count.py | 57 +++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 tests/test_future/test_count.py diff --git a/src/future/backports/misc.py b/src/future/backports/misc.py index 03c68c13..71acad27 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -22,6 +22,16 @@ def ceil(x): return int(oldceil(x)) +if PY26: + # itertools.count in Py 2.6 doesn't accept a step parameter + def count(start=0, step=1): + while True: + yield start + start += step +else: + from itertools import count + + # OrderedDict Shim from Raymond Hettinger, python core dev # http://code.activestate.com/recipes/576693-ordered-dictionary-for-py24/ # here to support version 2.6. diff --git a/src/future/types/newrange.py b/src/future/types/newrange.py index 432f11a1..17b6736b 100644 --- a/src/future/types/newrange.py +++ b/src/future/types/newrange.py @@ -21,6 +21,7 @@ from collections import Sequence, Iterator from itertools import islice +from future.backports.misc import count class newrange(Sequence): """ @@ -141,7 +142,7 @@ class range_iterator(Iterator): """An iterator for a :class:`range`. """ def __init__(self, range_): - self._stepper = islice(_count(range_.start, range_.step), len(range_)) + self._stepper = islice(count(range_.start, range_.step), len(range_)) def __iter__(self): return self @@ -150,11 +151,4 @@ def next(self): return next(self._stepper) -# itertools.count in Py 2.6 doesn't accept a step parameter -def _count(start=0, step=1): - while True: - yield start - start += step - - __all__ = ['newrange'] diff --git a/tests/test_future/test_count.py b/tests/test_future/test_count.py new file mode 100644 index 00000000..cc849bd5 --- /dev/null +++ b/tests/test_future/test_count.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +""" +Tests for the backported class:`range` class. +""" +from itertools import count as it_count + +from future.backports.misc import count +from future.tests.base import unittest, skip26 + + +class CountTest(unittest.TestCase): + + """Test the count function.""" + + def _test_count_func(self, func): + self.assertEqual(next(func(1)), 1) + self.assertEqual(next(func(start=1)), 1) + + c = func() + self.assertEqual(next(c), 0) + self.assertEqual(next(c), 1) + self.assertEqual(next(c), 2) + c = func(1, 1) + self.assertEqual(next(c), 1) + self.assertEqual(next(c), 2) + c = func(step=1) + self.assertEqual(next(c), 0) + self.assertEqual(next(c), 1) + c = func(start=1, step=1) + self.assertEqual(next(c), 1) + self.assertEqual(next(c), 2) + + c = func(-1) + self.assertEqual(next(c), -1) + self.assertEqual(next(c), 0) + self.assertEqual(next(c), 1) + c = func(1, -1) + self.assertEqual(next(c), 1) + self.assertEqual(next(c), 0) + self.assertEqual(next(c), -1) + c = func(-1, -1) + self.assertEqual(next(c), -1) + self.assertEqual(next(c), -2) + self.assertEqual(next(c), -3) + + def test_count(self): + """Test the count function.""" + self._test_count_func(count) + + @skip26 + def test_own_count(self): + """Test own count implementation.""" + self._test_count_func(it_count) + + +if __name__ == '__main__': + unittest.main() From a23a71d8390ba0f109ed48d864792f88df139dd8 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 1 Jul 2015 00:14:41 -0400 Subject: [PATCH 056/425] Enable Travis' container-based infrastructure. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3747af6a..6e20573f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,10 +6,10 @@ python: - "2.7" - "2.6" +sudo: false + # 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: From ed1c6a6c3cd09458ef0398070933adeaf8e777b5 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 11 Jul 2015 14:35:12 +1000 Subject: [PATCH 057/425] Improve robustness of test_futurize.py and test_pasteurize.py Previously two tests within these scripts tried to access the filename using __file__, assuming this to be the name of the .py script. This assumption was incorrect sometimes on Py2 when the __file__ string pointed to a ``.pyc`` file. Removed two xfail decorators --- docs/whatsnew.rst | 3 ++- tests/test_future/test_futurize.py | 21 ++++++++++++--------- tests/test_future/test_pasteurize.py | 11 +++++++++-- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index a2da19f0..7a2fc111 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -8,7 +8,7 @@ What's New What's new in version 0.14.4 (2015-06-03) ========================================= -This release adds two new minor features and fixes some small bugs. +This is primarily a bug-fix release. It adds two new minor features and fixes several small bugs. Minor features: @@ -21,6 +21,7 @@ Bug fixes: implementations where appropriate (issue #146 - thanks to Matt Bogosian) - Fixed newrange slicing for some slice/range combos (issue #132, thanks to Brad Walker) - Small doc fixes +- Improve robustness of test suite with .pyc files on Py2 What's new in version 0.14.3 (2014-12-15) diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index 4d38dfb7..22b01cf1 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -17,7 +17,15 @@ class TestLibFuturize(unittest.TestCase): - @skip26 # mysterious sporadic UnicodeDecodeError raised by lib2to3 ... + + def setUp(self): + # For tests that need a text file: + _, self.textfilename = tempfile.mkstemp(text=True) + super(TestLibFuturize, self).setUp() + + def tearDown(self): + os.unlink(self.textfilename) + def test_correct_exit_status(self): """ Issue #119: futurize and pasteurize were not exiting with the correct @@ -26,8 +34,7 @@ def test_correct_exit_status(self): translates into 1! """ from libfuturize.main import main - # Try futurizing this test script: - retcode = main([__file__]) + retcode = main([self.textfilename]) self.assertTrue(isinstance(retcode, int)) # i.e. Py2 builtin int def test_is_shebang_comment(self): @@ -82,9 +89,6 @@ class TestFuturizeSimple(CodeHandler): tests for whether they can be passed to ``futurize`` and immediately run under both Python 2 again and Python 3. """ - def setUp(self): - self.tempdir = tempfile.mkdtemp() + os.path.sep - super(TestFuturizeSimple, self).setUp() def test_encoding_comments_kept_at_top(self): """ @@ -449,7 +453,6 @@ def test_source_coding_utf8(self): # -*- coding: utf-8 -*- icons = [u"◐", u"◓", u"◑", u"◒"] """ - self.unchanged(code) def test_exception_syntax(self): """ @@ -488,7 +491,7 @@ def test_file(self): file() as a synonym for open() is obsolete and invalid on Python 3. """ before = ''' - f = file(__file__) + f = file(self.textfilename) data = f.read() f.close() ''' @@ -851,7 +854,7 @@ def test_absolute_import_changes(self): Issue #16 (with porting bokeh/bbmodel.py) """ - with open(tempdir + 'specialmodels.py', 'w') as f: + with open(self.tempdir + 'specialmodels.py', 'w') as f: f.write('pass') before = """ diff --git a/tests/test_future/test_pasteurize.py b/tests/test_future/test_pasteurize.py index 77024cfc..05884f0e 100644 --- a/tests/test_future/test_pasteurize.py +++ b/tests/test_future/test_pasteurize.py @@ -21,6 +21,14 @@ class TestPasteurize(CodeHandler): on both Py3 and Py2. """ + def setUp(self): + # For tests that need a text file: + _, self.textfilename = tempfile.mkstemp(text=True) + super(TestPasteurize, self).setUp() + + def tearDown(self): + os.unlink(self.textfilename) + @skip26 # Python 2.6's lib2to3 causes the "from builtins import # range" line to be stuck at the bottom of the module! def test_range_slice(self): @@ -130,7 +138,6 @@ def test_urllib_refactor2(self): filename = urllib_parse.urlparse(url)[2].split('/')[-1] """ - @skip26 # mysterious sporadic UnicodeDecodeError raised by lib2to3 ... def test_correct_exit_status(self): """ Issue #119: futurize and pasteurize were not exiting with the correct @@ -140,7 +147,7 @@ def test_correct_exit_status(self): """ from libpasteurize.main import main # Try pasteurizing this test script: - retcode = main([__file__]) + retcode = main([self.textfilename]) self.assertTrue(isinstance(retcode, int)) # i.e. Py2 builtin int From 41ba13a523b993577b92a24fbb3fb3d32c60c4eb Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 11 Jul 2015 13:30:16 +1000 Subject: [PATCH 058/425] Add constants from httplib to http.client (issue #137) This patch imports the complete set of constants from Python 2.7.10's httplib module into http.client. --- src/http/client.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/http/client.py b/src/http/client.py index 429cac4c..cd1183e9 100644 --- a/src/http/client.py +++ b/src/http/client.py @@ -4,3 +4,77 @@ assert sys.version_info[0] < 3 from httplib import * + +# These constants aren't included in __all__ in httplib.py: + +from httplib import (HTTP_PORT, + HTTPS_PORT, + + _CS_IDLE, + _CS_REQ_STARTED, + _CS_REQ_SENT, + + 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, + _MAXLINE, + _MAXHEADERS, + _is_legal_header_name, + _is_illegal_header_value, + _METHODS_EXPECTING_BODY, + + LineTooLong, + LineAndFileWrapper, + ) From a4d0162a59599b98e9b49df6565cc8b7286d7e66 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 11 Jul 2015 14:55:57 +1000 Subject: [PATCH 059/425] Bump version to v0.14.4-dev --- src/future/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/future/__init__.py b/src/future/__init__.py index a93381ff..a75d2cec 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -88,7 +88,7 @@ __copyright__ = 'Copyright 2013-2015 Python Charmers Pty Ltd' __ver_major__ = 0 __ver_minor__ = 14 -__ver_patch__ = 3 -__ver_sub__ = '' +__ver_patch__ = 4 +__ver_sub__ = '-dev' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From 661a1c59fa95cf4897435dccb65746c528476d7e Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 11 Jul 2015 15:14:32 +1000 Subject: [PATCH 060/425] Update docs for struct.pack(u'...') (issue #147) --- docs/stdlib_incompatibilities.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/stdlib_incompatibilities.rst b/docs/stdlib_incompatibilities.rst index ec72b4aa..e5129253 100644 --- a/docs/stdlib_incompatibilities.rst +++ b/docs/stdlib_incompatibilities.rst @@ -88,14 +88,17 @@ 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')``. +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. From 5363a9f5339f03041c2512b739bff5a6e2b33b3d Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 11 Jul 2015 17:05:12 +1000 Subject: [PATCH 061/425] Backport `count()` with step param for Py2.6 (issue #152) --- docs/standard_library_imports.rst | 34 ++++++++++---- docs/whatsnew.rst | 4 +- src/future/backports/__init__.py | 2 + src/future/backports/misc.py | 55 +++++++++++++++------- src/future/standard_library/__init__.py | 1 + src/future/types/newrange.py | 13 ++---- tests/test_future/test_backports.py | 61 +++++++++++++++++++++++++ 7 files changed, 135 insertions(+), 35 deletions(-) create mode 100644 tests/test_future/test_backports.py diff --git a/docs/standard_library_imports.rst b/docs/standard_library_imports.rst index fcee748f..5377b533 100644 --- a/docs/standard_library_imports.rst +++ b/docs/standard_library_imports.rst @@ -15,9 +15,9 @@ 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 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``:: >>> # Alias for future.builtins on Py2: >>> from builtins import str, open, range, dict @@ -28,8 +28,8 @@ modules in Python 2/3 compatible code. For example, the following clean Python >>> import tkinter.dialog >>> etc. -Notice that this code actually runs on Python 3 without the presence of the ``future`` -package. +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 @@ -92,21 +92,21 @@ 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 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 + from math import ceil # now returns an int + import test.support import urllib.error @@ -116,6 +116,22 @@ package makes the Python 3.x APIs available on Python 2.x as follows:: import urllib.robotparser +Backports also exist of the following features from Python 2.7+ for Python 2.6: + +- collections.Counter +- collections.OrderedDict +- itertools.count (with an optional step parameter) + +These can be imported on Python 2.6 as follows:: + + from future.standard_library import install_aliases + install_aliases() + + from collections import Counter, OrderedDict + from itertools import count + from subprocess import check_output + + External standard-library backports ----------------------------------- diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 7a2fc111..9e476fa9 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -8,12 +8,14 @@ What's New What's new in version 0.14.4 (2015-06-03) ========================================= -This is primarily a bug-fix release. It adds two new minor features and fixes several small bugs. +This is primarily a bug-fix release. It adds some minor new features and +fixes several small bugs. Minor features: - ``tkinter.ttk`` support (issue #151) - ``collections.ChainMap`` backport (issue #150) +- ``itertools.count`` backport for Py2.6 (issue #152) Bug fixes: diff --git a/src/future/backports/__init__.py b/src/future/backports/__init__.py index d68c0f48..aa610c55 100644 --- a/src/future/backports/__init__.py +++ b/src/future/backports/__init__.py @@ -6,3 +6,5 @@ if sys.version_info[0] == 3: import_top_level_modules() + +from .misc import ceil, OrderedDict, Counter, check_output, count diff --git a/src/future/backports/misc.py b/src/future/backports/misc.py index 03c68c13..28e79788 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -1,25 +1,25 @@ """ -Miscellaneous function (re)definitions from the Py3.3 standard library for -Python 2.6/2.7. +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) +- math.ceil (for Python 2.7) +- collections.OrderedDict (for Python 2.6) +- collections.Counter (for Python 2.6) +- itertools.count (for Python 2.6, with step parameter) """ -from math import ceil as oldceil +from math import ceil import subprocess from future.utils import iteritems, itervalues, PY26 -def ceil(x): +def _ceil(x): """ Return the ceiling of x as an int. This is the smallest integral value >= x. """ - return int(oldceil(x)) + return int(ceil(x)) # OrderedDict Shim from Raymond Hettinger, python core dev @@ -482,16 +482,13 @@ def __and__(self, other): 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 _check_output(*popenargs, **kwargs): + """ + For Python 2.6 compatibility: see + http://stackoverflow.com/questions/4814970/ + """ -# For Python 2.6 compatibility: see http://stackoverflow.com/questions/4814970/ -def check_output(*popenargs, **kwargs): if 'stdout' in kwargs: raise ValueError('stdout argument not allowed, it will be overridden.') process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) @@ -503,3 +500,27 @@ 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 + + +if not PY26: + from math import ceil + from collections import OrderedDict, Counter + from subprocess import check_output + from itertools import count +else: + ceil = _ceil + OrderedDict = _OrderedDict + Counter = _Counter + check_output = _check_output + count = _count diff --git a/src/future/standard_library/__init__.py b/src/future/standard_library/__init__.py index a6b2a005..40d9f5ff 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -196,6 +196,7 @@ ('math', 'ceil', 'future.backports.misc', 'ceil'), ('collections', 'OrderedDict', 'future.backports.misc', 'OrderedDict'), ('collections', 'Counter', 'future.backports.misc', 'Counter'), + ('itertools', 'count', 'future.backports.misc', 'count'), # This is no use, since "import urllib.request" etc. still fails: # ('urllib', 'error', 'future.moves.urllib', 'error'), diff --git a/src/future/types/newrange.py b/src/future/types/newrange.py index c719bb74..322a9704 100644 --- a/src/future/types/newrange.py +++ b/src/future/types/newrange.py @@ -21,6 +21,10 @@ from collections 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: +from future.backports.misc import _count + class newrange(Sequence): """ @@ -142,7 +146,7 @@ class range_iterator(Iterator): """An iterator for a :class:`range`. """ def __init__(self, range_): - self._stepper = islice(_count(range_.start, range_.step), len(range_)) + self._stepper = islice(count(range_.start, range_.step), len(range_)) def __iter__(self): return self @@ -151,11 +155,4 @@ def next(self): return next(self._stepper) -# itertools.count in Py 2.6 doesn't accept a step parameter -def _count(start=0, step=1): - while True: - yield start - start += step - - __all__ = ['newrange'] diff --git a/tests/test_future/test_backports.py b/tests/test_future/test_backports.py new file mode 100644 index 00000000..46296fd4 --- /dev/null +++ b/tests/test_future/test_backports.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +""" +Tests for various backported functions and classes in ``future.backports`` +""" + +from __future__ import absolute_import, unicode_literals, print_function + +from future.backports.misc import count, _count +from future.utils import PY26 +from future.tests.base import unittest, skip26 + + +class CountTest(unittest.TestCase): + """Test the count function.""" + + def _test_count_func(self, func): + self.assertEqual(next(func(1)), 1) + self.assertEqual(next(func(start=1)), 1) + + c = func() + self.assertEqual(next(c), 0) + self.assertEqual(next(c), 1) + self.assertEqual(next(c), 2) + c = func(1, 1) + self.assertEqual(next(c), 1) + self.assertEqual(next(c), 2) + c = func(step=1) + self.assertEqual(next(c), 0) + self.assertEqual(next(c), 1) + c = func(start=1, step=1) + self.assertEqual(next(c), 1) + self.assertEqual(next(c), 2) + + c = func(-1) + self.assertEqual(next(c), -1) + self.assertEqual(next(c), 0) + self.assertEqual(next(c), 1) + c = func(1, -1) + self.assertEqual(next(c), 1) + self.assertEqual(next(c), 0) + self.assertEqual(next(c), -1) + c = func(-1, -1) + self.assertEqual(next(c), -1) + self.assertEqual(next(c), -2) + self.assertEqual(next(c), -3) + + def test_count(self): + """Test the count function.""" + self._test_count_func(count) + + def test_own_count(self): + """Test own count implementation.""" + if PY26: + self.assertIs(count, _count) + else: + self.assertNotEqual(count, _count) + self._test_count_func(_count) + + +if __name__ == '__main__': + unittest.main() From 9e0f21df186a7868230a8bd87c9fc2361dea1a57 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 11 Jul 2015 17:21:50 +1000 Subject: [PATCH 062/425] Add tests for backported collections classes from Py3.4 --- tests/test_future/test_backports.py | 614 +++++++++++++++++++++++++++- 1 file changed, 613 insertions(+), 1 deletion(-) diff --git a/tests/test_future/test_backports.py b/tests/test_future/test_backports.py index 46296fd4..abd5d735 100644 --- a/tests/test_future/test_backports.py +++ b/tests/test_future/test_backports.py @@ -5,7 +5,11 @@ from __future__ import absolute_import, unicode_literals, print_function -from future.backports.misc import count, _count +from future.backports.misc import (count, + _count, + OrderedDict, + Counter, + ChainMap) from future.utils import PY26 from future.tests.base import unittest, skip26 @@ -57,5 +61,613 @@ def test_own_count(self): self._test_count_func(_count) +################################################################################ +### ChainMap (helper class for configparser and the string module) +################################################################################ + +class TestChainMap(unittest.TestCase): + + def test_basics(self): + c = ChainMap() + c['a'] = 1 + c['b'] = 2 + d = c.new_child() + d['b'] = 20 + d['c'] = 30 + self.assertEqual(d.maps, [{'b':20, 'c':30}, {'a':1, 'b':2}]) # check internal state + self.assertEqual(d.items(), dict(a=1, b=20, c=30).items()) # check items/iter/getitem + self.assertEqual(len(d), 3) # check len + for key in 'abc': # check contains + self.assertIn(key, d) + for k, v in dict(a=1, b=20, c=30, z=100).items(): # check get + self.assertEqual(d.get(k, 100), v) + + del d['b'] # unmask a value + self.assertEqual(d.maps, [{'c':30}, {'a':1, 'b':2}]) # check internal state + self.assertEqual(d.items(), dict(a=1, b=2, c=30).items()) # check items/iter/getitem + self.assertEqual(len(d), 3) # check len + for key in 'abc': # check contains + self.assertIn(key, d) + for k, v in dict(a=1, b=2, c=30, z=100).items(): # check get + self.assertEqual(d.get(k, 100), v) + self.assertIn(repr(d), [ # check repr + type(d).__name__ + "({'c': 30}, {'a': 1, 'b': 2})", + type(d).__name__ + "({'c': 30}, {'b': 2, 'a': 1})" + ]) + + for e in d.copy(), copy.copy(d): # check shallow copies + self.assertEqual(d, e) + self.assertEqual(d.maps, e.maps) + self.assertIsNot(d, e) + self.assertIsNot(d.maps[0], e.maps[0]) + for m1, m2 in zip(d.maps[1:], e.maps[1:]): + self.assertIs(m1, m2) + + for e in [pickle.loads(pickle.dumps(d)), + copy.deepcopy(d), + eval(repr(d)) + ]: # check deep copies + self.assertEqual(d, e) + self.assertEqual(d.maps, e.maps) + self.assertIsNot(d, e) + for m1, m2 in zip(d.maps, e.maps): + self.assertIsNot(m1, m2, e) + + f = d.new_child() + f['b'] = 5 + self.assertEqual(f.maps, [{'b': 5}, {'c':30}, {'a':1, 'b':2}]) + self.assertEqual(f.parents.maps, [{'c':30}, {'a':1, 'b':2}]) # check parents + self.assertEqual(f['b'], 5) # find first in chain + self.assertEqual(f.parents['b'], 2) # look beyond maps[0] + + def test_contructor(self): + self.assertEqual(ChainMap().maps, [{}]) # no-args --> one new dict + self.assertEqual(ChainMap({1:2}).maps, [{1:2}]) # 1 arg --> list + + def test_bool(self): + self.assertFalse(ChainMap()) + self.assertFalse(ChainMap({}, {})) + self.assertTrue(ChainMap({1:2}, {})) + self.assertTrue(ChainMap({}, {1:2})) + + def test_missing(self): + class DefaultChainMap(ChainMap): + def __missing__(self, key): + return 999 + d = DefaultChainMap(dict(a=1, b=2), dict(b=20, c=30)) + for k, v in dict(a=1, b=2, c=30, d=999).items(): + self.assertEqual(d[k], v) # check __getitem__ w/missing + for k, v in dict(a=1, b=2, c=30, d=77).items(): + self.assertEqual(d.get(k, 77), v) # check get() w/ missing + for k, v in dict(a=True, b=True, c=True, d=False).items(): + self.assertEqual(k in d, v) # check __contains__ w/missing + self.assertEqual(d.pop('a', 1001), 1, d) + self.assertEqual(d.pop('a', 1002), 1002) # check pop() w/missing + self.assertEqual(d.popitem(), ('b', 2)) # check popitem() w/missing + with self.assertRaises(KeyError): + d.popitem() + + def test_dict_coercion(self): + d = ChainMap(dict(a=1, b=2), dict(b=20, c=30)) + self.assertEqual(dict(d), dict(a=1, b=2, c=30)) + self.assertEqual(dict(d.items()), dict(a=1, b=2, c=30)) + + +################################################################################ +### Counter +################################################################################ + +class CounterSubclassWithSetItem(Counter): + # Test a counter subclass that overrides __setitem__ + def __init__(self, *args, **kwds): + self.called = False + Counter.__init__(self, *args, **kwds) + def __setitem__(self, key, value): + self.called = True + Counter.__setitem__(self, key, value) + +class CounterSubclassWithGet(Counter): + # Test a counter subclass that overrides get() + def __init__(self, *args, **kwds): + self.called = False + Counter.__init__(self, *args, **kwds) + def get(self, key, default): + self.called = True + return Counter.get(self, key, default) + +class TestCounter(unittest.TestCase): + + def test_basics(self): + c = Counter('abcaba') + self.assertEqual(c, Counter({'a':3 , 'b': 2, 'c': 1})) + self.assertEqual(c, Counter(a=3, b=2, c=1)) + self.assertIsInstance(c, dict) + self.assertIsInstance(c, Mapping) + self.assertTrue(issubclass(Counter, dict)) + self.assertTrue(issubclass(Counter, Mapping)) + self.assertEqual(len(c), 3) + self.assertEqual(sum(c.values()), 6) + self.assertEqual(sorted(c.values()), [1, 2, 3]) + self.assertEqual(sorted(c.keys()), ['a', 'b', 'c']) + self.assertEqual(sorted(c), ['a', 'b', 'c']) + self.assertEqual(sorted(c.items()), + [('a', 3), ('b', 2), ('c', 1)]) + self.assertEqual(c['b'], 2) + self.assertEqual(c['z'], 0) + self.assertEqual(c.__contains__('c'), True) + self.assertEqual(c.__contains__('z'), False) + self.assertEqual(c.get('b', 10), 2) + self.assertEqual(c.get('z', 10), 10) + self.assertEqual(c, dict(a=3, b=2, c=1)) + self.assertEqual(repr(c), "Counter({'a': 3, 'b': 2, 'c': 1})") + self.assertEqual(c.most_common(), [('a', 3), ('b', 2), ('c', 1)]) + for i in range(5): + self.assertEqual(c.most_common(i), + [('a', 3), ('b', 2), ('c', 1)][:i]) + self.assertEqual(''.join(sorted(c.elements())), 'aaabbc') + c['a'] += 1 # increment an existing value + c['b'] -= 2 # sub existing value to zero + del c['c'] # remove an entry + del c['c'] # make sure that del doesn't raise KeyError + c['d'] -= 2 # sub from a missing value + c['e'] = -5 # directly assign a missing value + c['f'] += 4 # add to a missing value + self.assertEqual(c, dict(a=4, b=0, d=-2, e=-5, f=4)) + self.assertEqual(''.join(sorted(c.elements())), 'aaaaffff') + self.assertEqual(c.pop('f'), 4) + self.assertNotIn('f', c) + for i in range(3): + elem, cnt = c.popitem() + self.assertNotIn(elem, c) + c.clear() + self.assertEqual(c, {}) + self.assertEqual(repr(c), 'Counter()') + self.assertRaises(NotImplementedError, Counter.fromkeys, 'abc') + self.assertRaises(TypeError, hash, c) + c.update(dict(a=5, b=3)) + c.update(c=1) + c.update(Counter('a' * 50 + 'b' * 30)) + c.update() # test case with no args + c.__init__('a' * 500 + 'b' * 300) + c.__init__('cdc') + c.__init__() + self.assertEqual(c, dict(a=555, b=333, c=3, d=1)) + self.assertEqual(c.setdefault('d', 5), 1) + self.assertEqual(c['d'], 1) + self.assertEqual(c.setdefault('e', 5), 5) + self.assertEqual(c['e'], 5) + + def test_copying(self): + # Check that counters are copyable, deepcopyable, picklable, and + #have a repr/eval round-trip + words = Counter('which witch had which witches wrist watch'.split()) + update_test = Counter() + update_test.update(words) + for i, dup in enumerate([ + words.copy(), + copy.copy(words), + copy.deepcopy(words), + pickle.loads(pickle.dumps(words, 0)), + pickle.loads(pickle.dumps(words, 1)), + pickle.loads(pickle.dumps(words, 2)), + pickle.loads(pickle.dumps(words, -1)), + eval(repr(words)), + update_test, + Counter(words), + ]): + msg = (i, dup, words) + self.assertTrue(dup is not words) + self.assertEqual(dup, words) + self.assertEqual(len(dup), len(words)) + self.assertEqual(type(dup), type(words)) + + def test_copy_subclass(self): + class MyCounter(Counter): + pass + c = MyCounter('slartibartfast') + d = c.copy() + self.assertEqual(d, c) + self.assertEqual(len(d), len(c)) + self.assertEqual(type(d), type(c)) + + def test_conversions(self): + # Convert to: set, list, dict + s = 'she sells sea shells by the sea shore' + self.assertEqual(sorted(Counter(s).elements()), sorted(s)) + self.assertEqual(sorted(Counter(s)), sorted(set(s))) + self.assertEqual(dict(Counter(s)), dict(Counter(s).items())) + self.assertEqual(set(Counter(s)), set(s)) + + def test_invariant_for_the_in_operator(self): + c = Counter(a=10, b=-2, c=0) + for elem in c: + self.assertTrue(elem in c) + self.assertIn(elem, c) + + def test_multiset_operations(self): + # Verify that adding a zero counter will strip zeros and negatives + c = Counter(a=10, b=-2, c=0) + Counter() + self.assertEqual(dict(c), dict(a=10)) + + elements = 'abcd' + for i in range(1000): + # test random pairs of multisets + p = Counter(dict((elem, randrange(-2,4)) for elem in elements)) + p.update(e=1, f=-1, g=0) + q = Counter(dict((elem, randrange(-2,4)) for elem in elements)) + q.update(h=1, i=-1, j=0) + for counterop, numberop in [ + (Counter.__add__, lambda x, y: max(0, x+y)), + (Counter.__sub__, lambda x, y: max(0, x-y)), + (Counter.__or__, lambda x, y: max(0,x,y)), + (Counter.__and__, lambda x, y: max(0, min(x,y))), + ]: + result = counterop(p, q) + for x in elements: + self.assertEqual(numberop(p[x], q[x]), result[x], + (counterop, x, p, q)) + # verify that results exclude non-positive counts + self.assertTrue(x>0 for x in result.values()) + + elements = 'abcdef' + for i in range(100): + # verify that random multisets with no repeats are exactly like sets + p = Counter(dict((elem, randrange(0, 2)) for elem in elements)) + q = Counter(dict((elem, randrange(0, 2)) for elem in elements)) + for counterop, setop in [ + (Counter.__sub__, set.__sub__), + (Counter.__or__, set.__or__), + (Counter.__and__, set.__and__), + ]: + counter_result = counterop(p, q) + set_result = setop(set(p.elements()), set(q.elements())) + self.assertEqual(counter_result, dict.fromkeys(set_result, 1)) + + def test_inplace_operations(self): + elements = 'abcd' + for i in range(1000): + # test random pairs of multisets + p = Counter(dict((elem, randrange(-2,4)) for elem in elements)) + p.update(e=1, f=-1, g=0) + q = Counter(dict((elem, randrange(-2,4)) for elem in elements)) + q.update(h=1, i=-1, j=0) + for inplace_op, regular_op in [ + (Counter.__iadd__, Counter.__add__), + (Counter.__isub__, Counter.__sub__), + (Counter.__ior__, Counter.__or__), + (Counter.__iand__, Counter.__and__), + ]: + c = p.copy() + c_id = id(c) + regular_result = regular_op(c, q) + inplace_result = inplace_op(c, q) + self.assertEqual(inplace_result, regular_result) + self.assertEqual(id(inplace_result), c_id) + + def test_subtract(self): + c = Counter(a=-5, b=0, c=5, d=10, e=15,g=40) + c.subtract(a=1, b=2, c=-3, d=10, e=20, f=30, h=-50) + self.assertEqual(c, Counter(a=-6, b=-2, c=8, d=0, e=-5, f=-30, g=40, h=50)) + c = Counter(a=-5, b=0, c=5, d=10, e=15,g=40) + c.subtract(Counter(a=1, b=2, c=-3, d=10, e=20, f=30, h=-50)) + self.assertEqual(c, Counter(a=-6, b=-2, c=8, d=0, e=-5, f=-30, g=40, h=50)) + c = Counter('aaabbcd') + c.subtract('aaaabbcce') + self.assertEqual(c, Counter(a=-1, b=0, c=-1, d=1, e=-1)) + + def test_unary(self): + c = Counter(a=-5, b=0, c=5, d=10, e=15,g=40) + self.assertEqual(dict(+c), dict(c=5, d=10, e=15, g=40)) + self.assertEqual(dict(-c), dict(a=5)) + + def test_repr_nonsortable(self): + c = Counter(a=2, b=None) + r = repr(c) + self.assertIn("'a': 2", r) + self.assertIn("'b': None", r) + + def test_helper_function(self): + # two paths, one for real dicts and one for other mappings + elems = list('abracadabra') + + d = dict() + _count_elements(d, elems) + self.assertEqual(d, {'a': 5, 'r': 2, 'b': 2, 'c': 1, 'd': 1}) + + m = OrderedDict() + _count_elements(m, elems) + self.assertEqual(m, + OrderedDict([('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)])) + + # test fidelity to the pure python version + c = CounterSubclassWithSetItem('abracadabra') + self.assertTrue(c.called) + c = CounterSubclassWithGet('abracadabra') + self.assertTrue(c.called) + + +################################################################################ +### OrderedDict +################################################################################ + +class TestOrderedDict(unittest.TestCase): + + def test_init(self): + with self.assertRaises(TypeError): + OrderedDict([('a', 1), ('b', 2)], None) # too many args + pairs = [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)] + self.assertEqual(sorted(OrderedDict(dict(pairs)).items()), pairs) # dict input + self.assertEqual(sorted(OrderedDict(**dict(pairs)).items()), pairs) # kwds input + self.assertEqual(list(OrderedDict(pairs).items()), pairs) # pairs input + self.assertEqual(list(OrderedDict([('a', 1), ('b', 2), ('c', 9), ('d', 4)], + c=3, e=5).items()), pairs) # mixed input + + # make sure no positional args conflict with possible kwdargs + self.assertEqual(inspect.getargspec(OrderedDict.__dict__['__init__']).args, + ['self']) + + # Make sure that direct calls to __init__ do not clear previous contents + d = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 44), ('e', 55)]) + d.__init__([('e', 5), ('f', 6)], g=7, d=4) + self.assertEqual(list(d.items()), + [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7)]) + + def test_update(self): + with self.assertRaises(TypeError): + OrderedDict().update([('a', 1), ('b', 2)], None) # too many args + pairs = [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)] + od = OrderedDict() + od.update(dict(pairs)) + self.assertEqual(sorted(od.items()), pairs) # dict input + od = OrderedDict() + od.update(**dict(pairs)) + self.assertEqual(sorted(od.items()), pairs) # kwds input + od = OrderedDict() + od.update(pairs) + self.assertEqual(list(od.items()), pairs) # pairs input + od = OrderedDict() + od.update([('a', 1), ('b', 2), ('c', 9), ('d', 4)], c=3, e=5) + self.assertEqual(list(od.items()), pairs) # mixed input + + # Issue 9137: Named argument called 'other' or 'self' + # shouldn't be treated specially. + od = OrderedDict() + od.update(self=23) + self.assertEqual(list(od.items()), [('self', 23)]) + od = OrderedDict() + od.update(other={}) + self.assertEqual(list(od.items()), [('other', {})]) + od = OrderedDict() + od.update(red=5, blue=6, other=7, self=8) + self.assertEqual(sorted(list(od.items())), + [('blue', 6), ('other', 7), ('red', 5), ('self', 8)]) + + # Make sure that direct calls to update do not clear previous contents + # add that updates items are not moved to the end + d = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 44), ('e', 55)]) + d.update([('e', 5), ('f', 6)], g=7, d=4) + self.assertEqual(list(d.items()), + [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7)]) + + def test_abc(self): + self.assertIsInstance(OrderedDict(), MutableMapping) + self.assertTrue(issubclass(OrderedDict, MutableMapping)) + + def test_clear(self): + pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] + shuffle(pairs) + od = OrderedDict(pairs) + self.assertEqual(len(od), len(pairs)) + od.clear() + self.assertEqual(len(od), 0) + + def test_delitem(self): + pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] + od = OrderedDict(pairs) + del od['a'] + self.assertNotIn('a', od) + with self.assertRaises(KeyError): + del od['a'] + self.assertEqual(list(od.items()), pairs[:2] + pairs[3:]) + + def test_setitem(self): + od = OrderedDict([('d', 1), ('b', 2), ('c', 3), ('a', 4), ('e', 5)]) + od['c'] = 10 # existing element + od['f'] = 20 # new element + self.assertEqual(list(od.items()), + [('d', 1), ('b', 2), ('c', 10), ('a', 4), ('e', 5), ('f', 20)]) + + def test_iterators(self): + pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] + shuffle(pairs) + od = OrderedDict(pairs) + self.assertEqual(list(od), [t[0] for t in pairs]) + self.assertEqual(list(od.keys()), [t[0] for t in pairs]) + self.assertEqual(list(od.values()), [t[1] for t in pairs]) + self.assertEqual(list(od.items()), pairs) + self.assertEqual(list(reversed(od)), + [t[0] for t in reversed(pairs)]) + + def test_popitem(self): + pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] + shuffle(pairs) + od = OrderedDict(pairs) + while pairs: + self.assertEqual(od.popitem(), pairs.pop()) + with self.assertRaises(KeyError): + od.popitem() + self.assertEqual(len(od), 0) + + def test_pop(self): + pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] + shuffle(pairs) + od = OrderedDict(pairs) + shuffle(pairs) + while pairs: + k, v = pairs.pop() + self.assertEqual(od.pop(k), v) + with self.assertRaises(KeyError): + od.pop('xyz') + self.assertEqual(len(od), 0) + self.assertEqual(od.pop(k, 12345), 12345) + + # make sure pop still works when __missing__ is defined + class Missing(OrderedDict): + def __missing__(self, key): + return 0 + m = Missing(a=1) + self.assertEqual(m.pop('b', 5), 5) + self.assertEqual(m.pop('a', 6), 1) + self.assertEqual(m.pop('a', 6), 6) + with self.assertRaises(KeyError): + m.pop('a') + + def test_equality(self): + pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] + shuffle(pairs) + od1 = OrderedDict(pairs) + od2 = OrderedDict(pairs) + self.assertEqual(od1, od2) # same order implies equality + pairs = pairs[2:] + pairs[:2] + od2 = OrderedDict(pairs) + self.assertNotEqual(od1, od2) # different order implies inequality + # comparison to regular dict is not order sensitive + self.assertEqual(od1, dict(od2)) + self.assertEqual(dict(od2), od1) + # different length implied inequality + self.assertNotEqual(od1, OrderedDict(pairs[:-1])) + + def test_copying(self): + # Check that ordered dicts are copyable, deepcopyable, picklable, + # and have a repr/eval round-trip + pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] + od = OrderedDict(pairs) + update_test = OrderedDict() + update_test.update(od) + for i, dup in enumerate([ + od.copy(), + copy.copy(od), + copy.deepcopy(od), + pickle.loads(pickle.dumps(od, 0)), + pickle.loads(pickle.dumps(od, 1)), + pickle.loads(pickle.dumps(od, 2)), + pickle.loads(pickle.dumps(od, 3)), + pickle.loads(pickle.dumps(od, -1)), + eval(repr(od)), + update_test, + OrderedDict(od), + ]): + self.assertTrue(dup is not od) + self.assertEqual(dup, od) + self.assertEqual(list(dup.items()), list(od.items())) + self.assertEqual(len(dup), len(od)) + self.assertEqual(type(dup), type(od)) + + def test_yaml_linkage(self): + # Verify that __reduce__ is setup in a way that supports PyYAML's dump() feature. + # In yaml, lists are native but tuples are not. + pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] + od = OrderedDict(pairs) + # yaml.dump(od) --> + # '!!python/object/apply:__main__.OrderedDict\n- - [a, 1]\n - [b, 2]\n' + self.assertTrue(all(type(pair)==list for pair in od.__reduce__()[1])) + + def test_reduce_not_too_fat(self): + # do not save instance dictionary if not needed + pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] + od = OrderedDict(pairs) + self.assertEqual(len(od.__reduce__()), 2) + od.x = 10 + self.assertEqual(len(od.__reduce__()), 3) + + def test_repr(self): + od = OrderedDict([('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]) + self.assertEqual(repr(od), + "OrderedDict([('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)])") + self.assertEqual(eval(repr(od)), od) + self.assertEqual(repr(OrderedDict()), "OrderedDict()") + + def test_repr_recursive(self): + # See issue #9826 + od = OrderedDict.fromkeys('abc') + od['x'] = od + self.assertEqual(repr(od), + "OrderedDict([('a', None), ('b', None), ('c', None), ('x', ...)])") + + def test_setdefault(self): + pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] + shuffle(pairs) + od = OrderedDict(pairs) + pair_order = list(od.items()) + self.assertEqual(od.setdefault('a', 10), 3) + # make sure order didn't change + self.assertEqual(list(od.items()), pair_order) + self.assertEqual(od.setdefault('x', 10), 10) + # make sure 'x' is added to the end + self.assertEqual(list(od.items())[-1], ('x', 10)) + + # make sure setdefault still works when __missing__ is defined + class Missing(OrderedDict): + def __missing__(self, key): + return 0 + self.assertEqual(Missing().setdefault(5, 9), 9) + + def test_reinsert(self): + # Given insert a, insert b, delete a, re-insert a, + # verify that a is now later than b. + od = OrderedDict() + od['a'] = 1 + od['b'] = 2 + del od['a'] + od['a'] = 1 + self.assertEqual(list(od.items()), [('b', 2), ('a', 1)]) + + def test_move_to_end(self): + od = OrderedDict.fromkeys('abcde') + self.assertEqual(list(od), list('abcde')) + od.move_to_end('c') + self.assertEqual(list(od), list('abdec')) + od.move_to_end('c', 0) + self.assertEqual(list(od), list('cabde')) + od.move_to_end('c', 0) + self.assertEqual(list(od), list('cabde')) + od.move_to_end('e') + self.assertEqual(list(od), list('cabde')) + with self.assertRaises(KeyError): + od.move_to_end('x') + + def test_sizeof(self): + # Wimpy test: Just verify the reported size is larger than a regular dict + d = dict(a=1) + od = OrderedDict(**d) + self.assertGreater(sys.getsizeof(od), sys.getsizeof(d)) + + def test_override_update(self): + # Verify that subclasses can override update() without breaking __init__() + class MyOD(OrderedDict): + def update(self, *args, **kwds): + raise Exception() + items = [('a', 1), ('c', 3), ('b', 2)] + self.assertEqual(list(MyOD(items).items()), items) + +class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol): + type2test = OrderedDict + + def test_popitem(self): + d = self._empty_mapping() + self.assertRaises(KeyError, d.popitem) + +class MyOrderedDict(OrderedDict): + pass + +class SubclassMappingTests(mapping_tests.BasicTestMappingProtocol): + type2test = MyOrderedDict + + def test_popitem(self): + d = self._empty_mapping() + self.assertRaises(KeyError, d.popitem) + + + if __name__ == '__main__': unittest.main() From 9c59bd58a48e2ea8112ed64c9b42a77b06be6368 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 3 Jun 2015 22:41:52 +1000 Subject: [PATCH 063/425] Add ChainMap backport from Py3.4 (issue #150) --- src/future/backports/__init__.py | 10 +- src/future/backports/misc.py | 193 ++++++++++++++++++++++++++-- src/future/moves/collections.py | 5 + tests/test_future/test_backports.py | 46 +++---- 4 files changed, 214 insertions(+), 40 deletions(-) diff --git a/src/future/backports/__init__.py b/src/future/backports/__init__.py index aa610c55..396e9496 100644 --- a/src/future/backports/__init__.py +++ b/src/future/backports/__init__.py @@ -7,4 +7,12 @@ if sys.version_info[0] == 3: import_top_level_modules() -from .misc import ceil, OrderedDict, Counter, check_output, count +from .misc import (ceil, + OrderedDict, + Counter, + ChainMap, + check_output, + count, + recursive_repr, + _count_elements, + ) diff --git a/src/future/backports/misc.py b/src/future/backports/misc.py index 28e79788..c6c9f101 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -5,13 +5,17 @@ - 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) """ -from math import ceil +import sys import subprocess +from math import ceil +from collections import MutableMapping -from future.utils import iteritems, itervalues, PY26 +from future.utils import iteritems, itervalues, PY26, PY3 def _ceil(x): @@ -297,6 +301,15 @@ def viewitems(self): except ImportError: pass +######################################################################## +### Counter +######################################################################## + +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 class _Counter(dict): @@ -513,14 +526,176 @@ def _count(start=0, step=1): start += step -if not PY26: - from math import ceil - from collections import OrderedDict, Counter - from subprocess import check_output - from itertools import count -else: - ceil = _ceil +######################################################################## +### reprlib.recursive_repr decorator from Py3.4 +######################################################################## + +from itertools import islice +try: + from _thread import get_ident +except ImportError: + from _dummy_thread import get_ident + +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 + + return decorating_function + + +######################################################################## +### 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: {!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: {!r}'.format(key)) + + def clear(self): + 'Clear maps[0], leaving maps[1:] intact.' + self.maps[0].clear() + + +if sys.version_info <= (2, 6): OrderedDict = _OrderedDict Counter = _Counter check_output = _check_output count = _count +else: + from collections import OrderedDict, Counter + from subprocess import check_output + from itertools import count + +if sys.version_info < (3, 0): + ceil = _ceil + _count_elements = __count_elements +else: + from math import ceil + from collections import _count_elements + +if sys.version_info < (3, 3): + recursive_repr = _recursive_repr + ChainMap = _ChainMap +else: + from reprlib import recursive_repr + from collections import ChainMap 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/tests/test_future/test_backports.py b/tests/test_future/test_backports.py index abd5d735..f5b7d51c 100644 --- a/tests/test_future/test_backports.py +++ b/tests/test_future/test_backports.py @@ -5,11 +5,19 @@ from __future__ import absolute_import, unicode_literals, print_function +import sys +import copy +import inspect +import pickle +from random import randrange, shuffle +from collections import Mapping, MutableMapping + from future.backports.misc import (count, _count, OrderedDict, Counter, - ChainMap) + ChainMap, + _count_elements) from future.utils import PY26 from future.tests.base import unittest, skip26 @@ -402,10 +410,6 @@ def test_init(self): self.assertEqual(list(OrderedDict([('a', 1), ('b', 2), ('c', 9), ('d', 4)], c=3, e=5).items()), pairs) # mixed input - # make sure no positional args conflict with possible kwdargs - self.assertEqual(inspect.getargspec(OrderedDict.__dict__['__init__']).args, - ['self']) - # Make sure that direct calls to __init__ do not clear previous contents d = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 44), ('e', 55)]) d.__init__([('e', 5), ('f', 6)], g=7, d=4) @@ -572,13 +576,13 @@ def test_yaml_linkage(self): # '!!python/object/apply:__main__.OrderedDict\n- - [a, 1]\n - [b, 2]\n' self.assertTrue(all(type(pair)==list for pair in od.__reduce__()[1])) - def test_reduce_not_too_fat(self): - # do not save instance dictionary if not needed - pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] - od = OrderedDict(pairs) - self.assertEqual(len(od.__reduce__()), 2) - od.x = 10 - self.assertEqual(len(od.__reduce__()), 3) + # def test_reduce_not_too_fat(self): + # # do not save instance dictionary if not needed + # pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] + # od = OrderedDict(pairs) + # self.assertEqual(len(od.__reduce__()), 2) + # od.x = 10 + # self.assertEqual(len(od.__reduce__()), 3) def test_repr(self): od = OrderedDict([('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]) @@ -650,24 +654,6 @@ def update(self, *args, **kwds): items = [('a', 1), ('c', 3), ('b', 2)] self.assertEqual(list(MyOD(items).items()), items) -class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol): - type2test = OrderedDict - - def test_popitem(self): - d = self._empty_mapping() - self.assertRaises(KeyError, d.popitem) - -class MyOrderedDict(OrderedDict): - pass - -class SubclassMappingTests(mapping_tests.BasicTestMappingProtocol): - type2test = MyOrderedDict - - def test_popitem(self): - d = self._empty_mapping() - self.assertRaises(KeyError, d.popitem) - - if __name__ == '__main__': unittest.main() From 37e12789a5a1ee0a552643b0acab449061a1255e Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 11 Jul 2015 18:26:32 +1000 Subject: [PATCH 064/425] Fix remaining test failures from extra Py3 collections tests Now there are tests for Counter, OrderedDict, ChainMap etc. --- tests/test_future/test_backports.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/test_future/test_backports.py b/tests/test_future/test_backports.py index f5b7d51c..f2dacefb 100644 --- a/tests/test_future/test_backports.py +++ b/tests/test_future/test_backports.py @@ -3,7 +3,7 @@ Tests for various backported functions and classes in ``future.backports`` """ -from __future__ import absolute_import, unicode_literals, print_function +from __future__ import absolute_import, print_function import sys import copy @@ -19,7 +19,7 @@ ChainMap, _count_elements) from future.utils import PY26 -from future.tests.base import unittest, skip26 +from future.tests.base import unittest, skip26, expectedFailurePY2 class CountTest(unittest.TestCase): @@ -111,6 +111,8 @@ def test_basics(self): for m1, m2 in zip(d.maps[1:], e.maps[1:]): self.assertIs(m1, m2) + _ChainMap = ChainMap + for e in [pickle.loads(pickle.dumps(d)), copy.deepcopy(d), eval(repr(d)) @@ -331,6 +333,7 @@ def test_multiset_operations(self): set_result = setop(set(p.elements()), set(q.elements())) self.assertEqual(counter_result, dict.fromkeys(set_result, 1)) + @expectedFailurePY2 def test_inplace_operations(self): elements = 'abcd' for i in range(1000): @@ -363,6 +366,7 @@ def test_subtract(self): c.subtract('aaaabbcce') self.assertEqual(c, Counter(a=-1, b=0, c=-1, d=1, e=-1)) + @expectedFailurePY2 def test_unary(self): c = Counter(a=-5, b=0, c=5, d=10, e=15,g=40) self.assertEqual(dict(+c), dict(c=5, d=10, e=15, g=40)) @@ -555,7 +559,7 @@ def test_copying(self): pickle.loads(pickle.dumps(od, 0)), pickle.loads(pickle.dumps(od, 1)), pickle.loads(pickle.dumps(od, 2)), - pickle.loads(pickle.dumps(od, 3)), + # pickle.loads(pickle.dumps(od, 3)), pickle.loads(pickle.dumps(od, -1)), eval(repr(od)), update_test, @@ -626,6 +630,7 @@ def test_reinsert(self): od['a'] = 1 self.assertEqual(list(od.items()), [('b', 2), ('a', 1)]) + @expectedFailurePY2 def test_move_to_end(self): od = OrderedDict.fromkeys('abcde') self.assertEqual(list(od), list('abcde')) @@ -640,12 +645,6 @@ def test_move_to_end(self): with self.assertRaises(KeyError): od.move_to_end('x') - def test_sizeof(self): - # Wimpy test: Just verify the reported size is larger than a regular dict - d = dict(a=1) - od = OrderedDict(**d) - self.assertGreater(sys.getsizeof(od), sys.getsizeof(d)) - def test_override_update(self): # Verify that subclasses can override update() without breaking __init__() class MyOD(OrderedDict): From 92f28dfb7bfdcfdee6dd886d16206ed680d91790 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 11 Jul 2015 18:37:37 +1000 Subject: [PATCH 065/425] Update What's New doc re http.client constants --- docs/whatsnew.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 9e476fa9..96a44e4a 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -16,12 +16,13 @@ Minor features: - ``tkinter.ttk`` support (issue #151) - ``collections.ChainMap`` backport (issue #150) - ``itertools.count`` backport for Py2.6 (issue #152) +- Add constants to ``http.client`` such as ``HTTP_PORT`` and ``BAD_REQUEST`` (issue #137) Bug fixes: - Expand newint.__divmod__ and newint.__rdivmod__ to fall back to implementations where appropriate (issue #146 - thanks to Matt Bogosian) -- Fixed newrange slicing for some slice/range combos (issue #132, thanks to Brad Walker) +- Fix newrange slicing for some slice/range combos (issue #132, thanks to Brad Walker) - Small doc fixes - Improve robustness of test suite with .pyc files on Py2 From 4dc5145f575658ef0f49803589c7ec4e4a95067d Mon Sep 17 00:00:00 2001 From: Waldemar Kornewald Date: Fri, 24 Jul 2015 10:58:10 +0200 Subject: [PATCH 066/425] fixed missing http.client.HTTPMessage --- src/http/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/http/client.py b/src/http/client.py index 429cac4c..3102e2fd 100644 --- a/src/http/client.py +++ b/src/http/client.py @@ -4,3 +4,4 @@ assert sys.version_info[0] < 3 from httplib import * +from httplib import HTTPMessage From 479d66e5acb7a6ecbb24ec3cd9fe6c92d8026f05 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 05:44:40 +1000 Subject: [PATCH 067/425] Add test highlighting urllib3 missing HTTPMessage import from http.client (issue #159) --- tests/test_future/test_imports_httplib.py | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/test_future/test_imports_httplib.py diff --git a/tests/test_future/test_imports_httplib.py b/tests/test_future/test_imports_httplib.py new file mode 100644 index 00000000..3e84287c --- /dev/null +++ b/tests/test_future/test_imports_httplib.py @@ -0,0 +1,25 @@ +from __future__ import absolute_import, print_function +import sys + +from future.utils import PY2 +from future.tests.base import unittest + + +class ImportHttplibTest(unittest.TestCase): + def test_issue_159(self): + """ + The latest version of urllib3 (as of 2015-07-25) + uses http.client.HTTPMessage, which isn't normally + exported on Py2 through __all__ in httplib.py. + """ + from http.client import HTTPMessage + if PY2: + import mimetools + assert issubclass(HTTPMessage, mimetools.Message) + else: + import email.message + assert issubclass(HTTPMessage, email.message.Message) + + +if __name__ == '__main__': + unittest.main() From 462ced2650fb4dc90353000dc0bef9bbc55d9a16 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 05:50:26 +1000 Subject: [PATCH 068/425] Update What's New doc re http.client.HTTPMessage (issue #159) --- docs/whatsnew.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 96a44e4a..50747ae3 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -5,11 +5,11 @@ What's New .. _whats-new-0.14.x: -What's new in version 0.14.4 (2015-06-03) +What's new in version 0.14.4 (2015-07-25) ========================================= -This is primarily a bug-fix release. It adds some minor new features and -fixes several small bugs. +This is primarily a bug-fix release. It adds some minor new backward-compatible features and +fixes several bugs. Minor features: @@ -20,6 +20,7 @@ Minor features: Bug fixes: +- Add ``HTTPMessage`` to ``http.client``, which is missing from ``httplib.__all__`` on Python <= 2.7.10. This fixes 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) From 0e12a7f6a58b2bcda81df596b2cd7bf71b086567 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 05:58:38 +1000 Subject: [PATCH 069/425] Also expose HTTPMessage in ``future.moves.http.client`` --- src/future/moves/http/client.py | 1 + 1 file changed, 1 insertion(+) 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 From e7316ab1e913e21de8e76bd009ebd9c31e41cc1e Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 06:06:45 +1000 Subject: [PATCH 070/425] Update list of contributors --- docs/credits.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/credits.rst b/docs/credits.rst index 81967654..26f56ec3 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -56,6 +56,8 @@ Patches - Nicolas Delaby - Corey Farwell - Eric Firing +- Michael Joseph +- Waldemar Kornewald - Alexey Kotlyarov - Lion Krischer - Marcin Kuzminski @@ -66,6 +68,7 @@ Patches - Tim Shaffer - Daniel Szoska - Jeff Tratner +- Tim Tröndle - Brad Walker - Mystic-Mirage (GitHub) - str4d (GitHub) From 76066266d358aef44911a5fe2f414dea42f0e93e Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 06:17:14 +1000 Subject: [PATCH 071/425] Make imports of http.client constants compatible with Py2.6 (issue #137) --- src/http/client.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/http/client.py b/src/http/client.py index 76f568c8..7566fe4d 100644 --- a/src/http/client.py +++ b/src/http/client.py @@ -70,12 +70,22 @@ NOT_EXTENDED, MAXAMOUNT, - _MAXLINE, - _MAXHEADERS, - _is_legal_header_name, - _is_illegal_header_value, - _METHODS_EXPECTING_BODY, - - LineTooLong, - LineAndFileWrapper, ) + +# 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 ( + _MAXLINE, + _MAXHEADERS, + _is_legal_header_name, + _is_illegal_header_value, + _METHODS_EXPECTING_BODY + ) +except ImportError: + pass From 47c57e24ac316185cf11923786b8c702fc32ed69 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 06:21:38 +1000 Subject: [PATCH 072/425] OrderedDict: restore compat with Py2.6 --- src/future/backports/misc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/backports/misc.py b/src/future/backports/misc.py index c6c9f101..8ed1724c 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -676,7 +676,7 @@ def clear(self): self.maps[0].clear() -if sys.version_info <= (2, 6): +if sys.version_info < (2, 7): OrderedDict = _OrderedDict Counter = _Counter check_output = _check_output From d784bab63229a0667b70f1b5ee227c6a241e7056 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 06:22:00 +1000 Subject: [PATCH 073/425] Update What's New doc --- docs/whatsnew.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 50747ae3..98e17799 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -20,12 +20,12 @@ Minor features: Bug fixes: -- Add ``HTTPMessage`` to ``http.client``, which is missing from ``httplib.__all__`` on Python <= 2.7.10. This fixes compatibility with the latest ``urllib3`` package (issue #159, thanks to Waldemar Kornewald) +- 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 -- Improve robustness of test suite with .pyc files on Py2 +- Small doc fixes (thanks to Michael Joseph and Tim Tröndle) +- Improve robustness of test suite against opening .pyc files as text on Py2 What's new in version 0.14.3 (2014-12-15) From a6b6bce64e207b4a80f5afb653016cbd3d8ceb25 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 06:41:43 +1000 Subject: [PATCH 074/425] Restore usual naming of backports (OrderedDict etc.) to fix `repr` tests --- src/future/backports/misc.py | 51 ++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/future/backports/misc.py b/src/future/backports/misc.py index 8ed1724c..5acdcd2b 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -12,18 +12,18 @@ import sys import subprocess -from math import ceil +from math import ceil as oldceil from collections import MutableMapping from future.utils import iteritems, itervalues, PY26, PY3 -def _ceil(x): +def ceil(x): """ Return the ceiling of x as an int. This is the smallest integral value >= x. """ - return int(ceil(x)) + return int(oldceil(x)) # OrderedDict Shim from Raymond Hettinger, python core dev @@ -43,7 +43,7 @@ def _ceil(x): pass -class _OrderedDict(dict): +class OrderedDict(dict): 'Dictionary that remembers insertion order' # An inherited dict maps keys to values. @@ -305,13 +305,13 @@ def viewitems(self): ### Counter ######################################################################## -def __count_elements(mapping, iterable): +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 -class _Counter(dict): +class Counter(dict): '''Dict subclass for counting hashable objects. Sometimes called a bag or multiset. Elements are stored as dictionary keys and their counts @@ -496,7 +496,7 @@ def __and__(self, other): return result -def _check_output(*popenargs, **kwargs): +def check_output(*popenargs, **kwargs): """ For Python 2.6 compatibility: see http://stackoverflow.com/questions/4814970/ @@ -515,7 +515,7 @@ def _check_output(*popenargs, **kwargs): return output -def _count(start=0, step=1): +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`` @@ -536,7 +536,7 @@ def _count(start=0, step=1): except ImportError: from _dummy_thread import get_ident -def _recursive_repr(fillvalue='...'): +def recursive_repr(fillvalue='...'): 'Decorator to make a repr function return fillvalue for a recursive call' def decorating_function(user_function): @@ -569,7 +569,7 @@ def wrapper(self): ### https://github.com/kkxue/Py2ChainMap/blob/master/py2chainmap.py ######################################################################## -class _ChainMap(MutableMapping): +class ChainMap(MutableMapping): ''' A ChainMap groups multiple dicts (or other mappings) together to create a single, updateable view. @@ -618,7 +618,7 @@ def __bool__(self): # Py2 compatibility: __nonzero__ = __bool__ - @_recursive_repr() + @recursive_repr() def __repr__(self): return '{0.__class__.__name__}({1})'.format( self, ', '.join(map(repr, self.maps))) @@ -676,26 +676,27 @@ def clear(self): self.maps[0].clear() -if sys.version_info < (2, 7): - OrderedDict = _OrderedDict - Counter = _Counter - check_output = _check_output - count = _count -else: +# 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 + +# Overwrite the definitions above with the usual ones +# from the standard library: +if sys.version_info >= (2, 7): from collections import OrderedDict, Counter from subprocess import check_output from itertools import count -if sys.version_info < (3, 0): - ceil = _ceil - _count_elements = __count_elements -else: +if sys.version_info >= (3, 0): from math import ceil from collections import _count_elements -if sys.version_info < (3, 3): - recursive_repr = _recursive_repr - ChainMap = _ChainMap -else: +if sys.version_info >= (3, 3): from reprlib import recursive_repr from collections import ChainMap From b668cc0d581305d85cddcd6d6cdeae212eca8236 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 07:00:43 +1000 Subject: [PATCH 075/425] Fix .copy() with subclasses of OrderedDict on Py2.6 --- src/future/backports/misc.py | 7 +++++-- tests/test_future/test_backports.py | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/future/backports/misc.py b/src/future/backports/misc.py index 5acdcd2b..88f5e11d 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -43,8 +43,11 @@ def ceil(x): pass -class OrderedDict(dict): +################################################################################ +### OrderedDict +################################################################################ +class OrderedDict(dict): 'Dictionary that remembers insertion order' # An inherited dict maps keys to values. # The inherited dict provides __getitem__, __len__, __contains__, and get. @@ -403,7 +406,7 @@ def update(self, iterable=None, **kwds): def copy(self): 'Like dict.copy() but returns a Counter instance instead of a dict.' - return Counter(self) + return self.__class__(self) def __delitem__(self, elem): '''Like dict.__delitem__() but does not raise KeyError for missing diff --git a/tests/test_future/test_backports.py b/tests/test_future/test_backports.py index f2dacefb..e4db080d 100644 --- a/tests/test_future/test_backports.py +++ b/tests/test_future/test_backports.py @@ -355,6 +355,7 @@ def test_inplace_operations(self): self.assertEqual(inplace_result, regular_result) self.assertEqual(id(inplace_result), c_id) + @expectedFailurePY2 def test_subtract(self): c = Counter(a=-5, b=0, c=5, d=10, e=15,g=40) c.subtract(a=1, b=2, c=-3, d=10, e=20, f=30, h=-50) From f677a05efc860d6fa035d34b1ca5f05ccb73a6f7 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 07:26:13 +1000 Subject: [PATCH 076/425] Update future.backports.misc.OrderedDict to use the implementation from Py3.4 --- src/future/backports/misc.py | 348 +++++++++++++++-------------------- 1 file changed, 149 insertions(+), 199 deletions(-) diff --git a/src/future/backports/misc.py b/src/future/backports/misc.py index 88f5e11d..cffac784 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -14,6 +14,8 @@ import subprocess from math import ceil as oldceil from collections import MutableMapping +from operator import eq as _eq +from _weakref import proxy as _proxy from future.utils import iteritems, itervalues, PY26, PY3 @@ -26,195 +28,206 @@ 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. - -if PY26: - # don't need this except in 2.6 - try: - from thread import get_ident - except ImportError: - from dummy_thread import get_ident +######################################################################## +### reprlib.recursive_repr decorator from Py3.4 +######################################################################## +from itertools import islice try: - from _abcoll import KeysView, ValuesView, ItemsView + from _thread import get_ident except ImportError: - pass + from _dummy_thread import get_ident + +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 + + return decorating_function ################################################################################ ### 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] @@ -231,28 +244,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' @@ -260,41 +264,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) @@ -529,43 +516,6 @@ def count(start=0, step=1): start += step -######################################################################## -### reprlib.recursive_repr decorator from Py3.4 -######################################################################## - -from itertools import islice -try: - from _thread import get_ident -except ImportError: - from _dummy_thread import get_ident - -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 - - return decorating_function - - ######################################################################## ### ChainMap (helper for configparser and string.Template) ### From the Py3.4 source code. See also: From ec4327847d726da95f6ddc55f8786b19c3c9ed1c Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 07:36:19 +1000 Subject: [PATCH 077/425] Update future.backports.misc.Counter to use the implementation from Py3.4 --- src/future/backports/misc.py | 301 +++++++++++++++++++++++----- tests/test_future/test_backports.py | 1 - 2 files changed, 249 insertions(+), 53 deletions(-) diff --git a/src/future/backports/misc.py b/src/future/backports/misc.py index cffac784..e51c46b6 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -10,12 +10,16 @@ - subprocess.check_output (for Python 2.6) """ -import sys import subprocess from math import ceil as oldceil -from collections import MutableMapping -from operator import eq as _eq +from collections import Mapping, MutableMapping + +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 future.utils import iteritems, itervalues, PY26, PY3 @@ -207,7 +211,7 @@ def move_to_end(self, key, last=True): root.next = first.prev = link def __sizeof__(self): - sizeof = _sys.getsizeof + 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 @@ -302,43 +306,94 @@ def _count_elements(mapping, iterable): mapping[elem] = mapping_get(elem, 0) + 1 class Counter(dict): - - '''Dict subclass for counting hashable objects. Sometimes called a bag + '''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)] ''' + # 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__(self, iterable=None, **kwds): + 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. @@ -347,22 +402,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. @@ -375,37 +439,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 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 @@ -422,15 +539,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): @@ -443,10 +562,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): @@ -458,12 +580,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): @@ -475,16 +600,88 @@ 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 + 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() + def check_output(*popenargs, **kwargs): """ diff --git a/tests/test_future/test_backports.py b/tests/test_future/test_backports.py index e4db080d..f2dacefb 100644 --- a/tests/test_future/test_backports.py +++ b/tests/test_future/test_backports.py @@ -355,7 +355,6 @@ def test_inplace_operations(self): self.assertEqual(inplace_result, regular_result) self.assertEqual(id(inplace_result), c_id) - @expectedFailurePY2 def test_subtract(self): c = Counter(a=-5, b=0, c=5, d=10, e=15,g=40) c.subtract(a=1, b=2, c=-3, d=10, e=20, f=30, h=-50) From 42667ac6419c1b53fc3a1d37c3dd9fb9a35d9895 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 07:46:55 +1000 Subject: [PATCH 078/425] Some additional Counter tests now work on Py2.6 --- src/future/tests/base.py | 8 +++++++- src/future/utils/__init__.py | 1 + tests/test_future/test_backports.py | 8 ++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/future/tests/base.py b/src/future/tests/base.py index f81c66d7..279b4f0f 100644 --- a/src/future/tests/base.py +++ b/src/future/tests/base.py @@ -9,7 +9,7 @@ import functools from textwrap import dedent -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: @@ -382,6 +382,12 @@ def expectedFailurePY26(func): return unittest.expectedFailure(func) +def expectedFailurePY27(func): + if not PY27: + return func + return unittest.expectedFailure(func) + + def expectedFailurePY2(func): if not PY2: return func diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index 6466fa28..046fb54d 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -80,6 +80,7 @@ PY3 = sys.version_info[0] == 3 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') diff --git a/tests/test_future/test_backports.py b/tests/test_future/test_backports.py index f2dacefb..e66ee1f0 100644 --- a/tests/test_future/test_backports.py +++ b/tests/test_future/test_backports.py @@ -19,7 +19,7 @@ ChainMap, _count_elements) from future.utils import PY26 -from future.tests.base import unittest, skip26, expectedFailurePY2 +from future.tests.base import unittest, skip26, expectedFailurePY27 class CountTest(unittest.TestCase): @@ -333,7 +333,7 @@ def test_multiset_operations(self): set_result = setop(set(p.elements()), set(q.elements())) self.assertEqual(counter_result, dict.fromkeys(set_result, 1)) - @expectedFailurePY2 + @expectedFailurePY27 def test_inplace_operations(self): elements = 'abcd' for i in range(1000): @@ -366,7 +366,7 @@ def test_subtract(self): c.subtract('aaaabbcce') self.assertEqual(c, Counter(a=-1, b=0, c=-1, d=1, e=-1)) - @expectedFailurePY2 + @expectedFailurePY27 def test_unary(self): c = Counter(a=-5, b=0, c=5, d=10, e=15,g=40) self.assertEqual(dict(+c), dict(c=5, d=10, e=15, g=40)) @@ -630,7 +630,7 @@ def test_reinsert(self): od['a'] = 1 self.assertEqual(list(od.items()), [('b', 2), ('a', 1)]) - @expectedFailurePY2 + @expectedFailurePY27 def test_move_to_end(self): od = OrderedDict.fromkeys('abcde') self.assertEqual(list(od), list('abcde')) From d6973c250f19143b4a2bc1f38d19097c9f118c4f Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 07:52:09 +1000 Subject: [PATCH 079/425] Update docs for new backported features from Py3.4 --- docs/quickstart.rst | 2 +- docs/standard_library_imports.rst | 20 ++++++++++++-------- docs/whatsnew.rst | 2 ++ src/future/backports/misc.py | 3 ++- src/future/standard_library/__init__.py | 1 + 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index f2540949..e241d8ae 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -102,7 +102,7 @@ to be accessed under their Python 3 names and locations in Python 2:: import queue import configparser from collections import UserDict, UserList, UserString - from collections import Counter, OrderedDict # even on Py2.6 + from collections import Counter, OrderedDict, ChainMap # even on Py2.6 from itertools import filterfalse, zip_longest import html diff --git a/docs/standard_library_imports.rst b/docs/standard_library_imports.rst index 5377b533..c1a1f2fb 100644 --- a/docs/standard_library_imports.rst +++ b/docs/standard_library_imports.rst @@ -105,8 +105,6 @@ package makes the Python 3.x APIs available on Python 2.x as follows:: from sys import intern - from math import ceil # now returns an int - import test.support import urllib.error @@ -116,20 +114,26 @@ package makes the Python 3.x APIs available on Python 2.x as follows:: import urllib.robotparser -Backports also exist of the following features from Python 2.7+ for Python 2.6: +Backports also exist of the following features from Python 3.4: -- collections.Counter -- collections.OrderedDict -- itertools.count (with an optional step parameter) +- ``math.ceil`` returns an int on Py3 +- ``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`` -These can be imported on Python 2.6 as follows:: +These can then be imported on Python 2.6+ as follows:: from future.standard_library import install_aliases install_aliases() - from collections import Counter, OrderedDict + from math import ceil # now returns an int + from collections import Counter, OrderedDict, ChainMap from itertools import count from subprocess import check_output + from reprlib import recursive_repr External standard-library backports diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 98e17799..f281afa4 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -17,6 +17,8 @@ Minor features: - ``collections.ChainMap`` backport (issue #150) - ``itertools.count`` backport for Py2.6 (issue #152) - Add constants to ``http.client`` such as ``HTTP_PORT`` and ``BAD_REQUEST`` (issue #137) +- ``reprlib.recursive_repr`` backport for Py2 +- Update backports of ``Counter`` and ``OrderedDict`` to use Py3.4 implementations. This fixes ``.copy()`` for subclasses etc. Bug fixes: diff --git a/src/future/backports/misc.py b/src/future/backports/misc.py index e51c46b6..178c5564 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -1,5 +1,5 @@ """ -Miscellaneous function (re)definitions from the Py3.3+ standard library +Miscellaneous function (re)definitions from the Py3.4+ standard library for Python 2.6/2.7. - math.ceil (for Python 2.7) @@ -8,6 +8,7 @@ - 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+) """ import subprocess diff --git a/src/future/standard_library/__init__.py b/src/future/standard_library/__init__.py index 40d9f5ff..beaaab2b 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -197,6 +197,7 @@ ('collections', 'OrderedDict', 'future.backports.misc', 'OrderedDict'), ('collections', 'Counter', 'future.backports.misc', 'Counter'), ('itertools', 'count', 'future.backports.misc', 'count'), + ('reprlib', 'recursive_repr', 'future.backports.misc', 'recursive_repr'), # This is no use, since "import urllib.request" etc. still fails: # ('urllib', 'error', 'future.moves.urllib', 'error'), From 7448097ae059737b27572e037d1b3f9a0c261407 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 08:05:37 +1000 Subject: [PATCH 080/425] Disable a broken OrderedDict test on Py2.6 --- tests/test_future/test_backports.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_future/test_backports.py b/tests/test_future/test_backports.py index e66ee1f0..21ebb202 100644 --- a/tests/test_future/test_backports.py +++ b/tests/test_future/test_backports.py @@ -437,6 +437,9 @@ def test_update(self): od.update([('a', 1), ('b', 2), ('c', 9), ('d', 4)], c=3, e=5) self.assertEqual(list(od.items()), pairs) # mixed input + ### The tests below fail on Py2.6 + if PY26: + return # Issue 9137: Named argument called 'other' or 'self' # shouldn't be treated specially. od = OrderedDict() From a27aa52ceed1fc045d63c3dbe9c833d7d136d365 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 08:49:30 +1000 Subject: [PATCH 081/425] Add test that highlights issue #158 --- tests/test_future/test_imports_urllib.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_future/test_imports_urllib.py b/tests/test_future/test_imports_urllib.py index ecf260ae..cbc616ff 100644 --- a/tests/test_future/test_imports_urllib.py +++ b/tests/test_future/test_imports_urllib.py @@ -2,6 +2,8 @@ import sys from future.tests.base import unittest +from future.standard_library import install_aliases + class ImportUrllibTest(unittest.TestCase): def test_urllib(self): @@ -16,6 +18,27 @@ def test_urllib(self): import urllib.response self.assertEqual(orig_file, urllib.__file__) + def test_issue_158(self): + """ + CherryPy conditional import in _cpcompat.py: issue 158 + """ + install_aliases() + try: + from urllib.parse import unquote as parse_unquote + + def unquote_qs(atom, encoding, errors='strict'): + return parse_unquote( + atom.replace('+', ' '), + encoding=encoding, + errors=errors) + except ImportError: + from urllib import unquote as parse_unquote + + def unquote_qs(atom, encoding, errors='strict'): + return parse_unquote(atom.replace('+', ' ')).decode(encoding, errors) + self.assertEqual(unquote_qs('/%7Econnolly/', 'utf-8'), + '/~connolly/') + if __name__ == '__main__': unittest.main() From 617bddf00e59071b5cc6ba570a1114db01e82946 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 09:15:01 +1000 Subject: [PATCH 082/425] install_aliases(): expose the full backported urllib modules instead of the Py2 aliases (issue #158) --- src/future/standard_library/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/future/standard_library/__init__.py b/src/future/standard_library/__init__.py index beaaab2b..9e22c59d 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -462,11 +462,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 From 0971652d428c0a3a01608d1b28064dde44f57576 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 09:17:49 +1000 Subject: [PATCH 083/425] Make a urllib test run on Py2.6 and modernize a test --- tests/test_future/test_urllib.py | 3 ++- tests/test_future/test_urllib2.py | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_future/test_urllib.py b/tests/test_future/test_urllib.py index 84697864..278bafb5 100644 --- a/tests/test_future/test_urllib.py +++ b/tests/test_future/test_urllib.py @@ -1005,10 +1005,11 @@ def test_nonstring_values(self): self.assertEqual("a=None", urllib_parse.urlencode({"a": None})) def test_nonstring_seq_values(self): + from future.backports import OrderedDict self.assertEqual("a=1&a=2", urllib_parse.urlencode({"a": [1, 2]}, True)) self.assertEqual("a=None&a=a", urllib_parse.urlencode({"a": [None, "a"]}, True)) - data = collections.OrderedDict([("a", 1), ("b", 1)]) + data = OrderedDict([("a", 1), ("b", 1)]) self.assertEqual("a=a&a=b", urllib_parse.urlencode({"a": data}, True)) diff --git a/tests/test_future/test_urllib2.py b/tests/test_future/test_urllib2.py index ec88f375..e7fb4dd7 100644 --- a/tests/test_future/test_urllib2.py +++ b/tests/test_future/test_urllib2.py @@ -5,7 +5,8 @@ import array import sys -from future.standard_library import import_, install_aliases +import http.client +from future.standard_library import install_aliases from future.backports.test import support import future.backports.urllib.request as urllib_request # The proxy bypass method imported below has logic specific to the OSX @@ -448,8 +449,6 @@ def reset(self): self.requests = [] def http_open(self, req): import future.backports.email as email - from future import standard_library - http = import_('http.client', backport=True) import copy self.requests.append(copy.deepcopy(req)) if self._count == 0: From e6d72b6c9e32ded5566995ceb51e5d1aa5abc70f Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 09:48:01 +1000 Subject: [PATCH 084/425] Update docs for v0.15.x --- docs/standard_library_imports.rst | 22 ++++++++++++++-------- docs/whatsnew.rst | 24 ++++++++++++++++-------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/docs/standard_library_imports.rst b/docs/standard_library_imports.rst index c1a1f2fb..42503ebe 100644 --- a/docs/standard_library_imports.rst +++ b/docs/standard_library_imports.rst @@ -86,14 +86,22 @@ 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:: +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 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 @@ -107,12 +115,10 @@ package makes the Python 3.x APIs available on Python 2.x as follows:: import test.support - import urllib.error - import urllib.parse - import urllib.request - import urllib.response - import urllib.robotparser +The newly exposed ``urllib`` submodules are full 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. Backports also exist of the following features from Python 3.4: @@ -122,7 +128,7 @@ Backports also exist of the following features from Python 3.4: - ``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`` +- ``reprlib.recursive_repr`` (for Python 2.6 and 2.7) These can then be imported on Python 2.6+ as follows:: diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index f281afa4..deec0c85 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -5,20 +5,26 @@ What's New .. _whats-new-0.14.x: -What's new in version 0.14.4 (2015-07-25) +What's new in version 0.15.0 (2015-07-25) ========================================= -This is primarily a bug-fix release. It adds some minor new backward-compatible features and -fixes several bugs. +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. -Minor features: +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) -- ``collections.ChainMap`` backport (issue #150) -- ``itertools.count`` backport for Py2.6 (issue #152) +- Backport of ``collections.ChainMap`` (issue #150) +- Backport of ``itertools.count`` for Py2.6 (issue #152) - Add constants to ``http.client`` such as ``HTTP_PORT`` and ``BAD_REQUEST`` (issue #137) -- ``reprlib.recursive_repr`` backport for Py2 -- Update backports of ``Counter`` and ``OrderedDict`` to use Py3.4 implementations. This fixes ``.copy()`` for subclasses etc. +- Backport of ``reprlib.recursive_repr`` to Py2 Bug fixes: @@ -28,6 +34,8 @@ Bug fixes: - 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. What's new in version 0.14.3 (2014-12-15) From 4c6705a77feea975cd05462c003f24a350ff2563 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 09:48:41 +1000 Subject: [PATCH 085/425] Bump version to 0.15.0-dev --- src/future/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/future/__init__.py b/src/future/__init__.py index a75d2cec..1bdd43a0 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -87,8 +87,8 @@ __license__ = 'MIT' __copyright__ = 'Copyright 2013-2015 Python Charmers Pty Ltd' __ver_major__ = 0 -__ver_minor__ = 14 -__ver_patch__ = 4 +__ver_minor__ = 15 +__ver_patch__ = 0 __ver_sub__ = '-dev' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From 00d5149d7b0d4c2a3bbb143a037fb28291ba4ff6 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 10:07:11 +1000 Subject: [PATCH 086/425] Fix a test that failed on 32-bit architectures (issue #149) --- tests/test_future/test_pasteurize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_future/test_pasteurize.py b/tests/test_future/test_pasteurize.py index 05884f0e..b09e3cba 100644 --- a/tests/test_future/test_pasteurize.py +++ b/tests/test_future/test_pasteurize.py @@ -37,7 +37,7 @@ def test_range_slice(self): quickly on both Py3 and Py2 without a MemoryError """ code = ''' - for i in range(10**15)[:10]: + for i in range(10**8)[:10]: pass ''' self.unchanged(code, from3=True) From ade1bd3cc90241fe8c8e22544854aac7640c2756 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 11:03:31 +1000 Subject: [PATCH 087/425] Document `surrogateescape` support and enable it for bytes decoding (issue #116) --- README.rst | 5 +++- docs/bytes_object.rst | 37 ++++++++++++----------------- docs/faq.rst | 7 ------ docs/str_object.rst | 32 ++++++++++++------------- docs/whatsnew.rst | 1 + src/future/types/newbytes.py | 5 ++++ src/future/utils/surrogateescape.py | 19 ++++++++------- tests/test_future/test_bytes.py | 13 ++++++++++ tests/test_future/test_str.py | 13 ++++++++++ 9 files changed, 76 insertions(+), 56 deletions(-) diff --git a/README.rst b/README.rst index 2235432d..adfe5915 100644 --- a/README.rst +++ b/README.rst @@ -53,6 +53,9 @@ Features ``past.utils`` selected from Py2/3 compatibility interfaces from projects like ``six``, ``IPython``, ``Jinja2``, ``Django``, and ``Pandas``. +- partial support for the ``surrogateescape`` error handler when encoding and + decoding the backported ``str`` and ``bytes`` objects. (This is currently + in alpha.) .. _code-examples: @@ -152,7 +155,7 @@ interface works like this: # Then, for example: from itertools import filterfalse, zip_longest from urllib.request import urlopen - from collections import Counter, OrderedDict # backported to Py2.6 + from collections import Counter, OrderedDict, ChainMap # backported to Py2.6 from collections import UserDict, UserList, UserString from subprocess import getoutput, getstatusoutput diff --git a/docs/bytes_object.rst b/docs/bytes_object.rst index e3de27d7..cca43d2c 100644 --- a/docs/bytes_object.rst +++ b/docs/bytes_object.rst @@ -66,26 +66,19 @@ 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. +surrogateescape +~~~~~~~~~~~~~~~ + +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/faq.rst b/docs/faq.rst index 6ee3decd..fc14945c 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -265,13 +265,6 @@ 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 diff --git a/docs/str_object.rst b/docs/str_object.rst index 9be2d00d..ae32ccd7 100644 --- a/docs/str_object.rst +++ b/docs/str_object.rst @@ -84,21 +84,19 @@ 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. +surrogateescape +~~~~~~~~~~~~~~~ + +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/whatsnew.rst b/docs/whatsnew.rst index deec0c85..a582672c 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -25,6 +25,7 @@ New features: - Backport of ``itertools.count`` for Py2.6 (issue #152) - Add constants to ``http.client`` such as ``HTTP_PORT`` and ``BAD_REQUEST`` (issue #137) - Backport of ``reprlib.recursive_repr`` to Py2 +- Enable support for the ``surrogateescape`` error handler for ``newstr`` and ``newbytes`` objects on Py2.x (issue #116). This feature is currently in alpha. Bug fixes: diff --git a/src/future/types/newbytes.py b/src/future/types/newbytes.py index 2f96be6d..fb8bee53 100644 --- a/src/future/types/newbytes.py +++ b/src/future/types/newbytes.py @@ -201,6 +201,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: diff --git a/src/future/utils/surrogateescape.py b/src/future/utils/surrogateescape.py index a0d8d44a..398c3531 100644 --- a/src/future/utils/surrogateescape.py +++ b/src/future/utils/surrogateescape.py @@ -186,14 +186,15 @@ 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/tests/test_future/test_bytes.py b/tests/test_future/test_bytes.py index e307beff..b932b4fe 100644 --- a/tests/test_future/test_bytes.py +++ b/tests/test_future/test_bytes.py @@ -627,6 +627,19 @@ class MetaClass(type): class TestClass(with_metaclass(MetaClass, bytes)): pass + def test_surrogateescape_decoding(self): + """ + Tests whether surrogateescape decoding works correctly. + """ + pairs = [(u'\udcc3', b'\xc3'), + (u'\udcff', b'\xff')] + + for (s, b) in pairs: + decoded = bytes(b).decode('utf-8', 'surrogateescape') + self.assertEqual(s, decoded) + self.assertTrue(isinstance(decoded, str)) + self.assertEqual(b, decoded.encode('utf-8', 'surrogateescape')) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_future/test_str.py b/tests/test_future/test_str.py index bcc5e594..7e37a62f 100644 --- a/tests/test_future/test_str.py +++ b/tests/test_future/test_str.py @@ -551,6 +551,19 @@ class MetaClass(type): class TestClass(with_metaclass(MetaClass, str)): pass + def test_surrogateescape_encoding(self): + """ + Tests whether surrogateescape encoding works correctly. + """ + pairs = [(u'\udcc3', b'\xc3'), + (u'\udcff', b'\xff')] + + for (s, b) in pairs: + encoded = str(s).encode('utf-8', 'surrogateescape') + self.assertEqual(b, encoded) + self.assertTrue(isinstance(encoded, bytes)) + self.assertEqual(s, encoded.decode('utf-8', 'surrogateescape')) + if __name__ == '__main__': unittest.main() From ae19794982466bda827e8e0475a93418a9f94397 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 12:09:49 +1000 Subject: [PATCH 088/425] Update docs --- README.rst | 19 +++++++++---------- docs/bytes_object.rst | 3 --- docs/credits.rst | 3 ++- docs/faq.rst | 4 ++-- docs/str_object.rst | 2 -- docs/whatsnew.rst | 2 +- 6 files changed, 14 insertions(+), 19 deletions(-) diff --git a/README.rst b/README.rst index adfe5915..c79aa830 100644 --- a/README.rst +++ b/README.rst @@ -53,9 +53,9 @@ Features ``past.utils`` selected from Py2/3 compatibility interfaces from projects like ``six``, ``IPython``, ``Jinja2``, ``Django``, and ``Pandas``. -- partial support for the ``surrogateescape`` error handler when encoding and - decoding the backported ``str`` and ``bytes`` objects. (This is currently - in alpha.) +- support for the ``surrogateescape`` error handler when encoding and + decoding the backported ``str`` and ``bytes`` objects. [This feature is + currently in alpha.] .. _code-examples: @@ -94,7 +94,7 @@ 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') @@ -155,9 +155,10 @@ interface works like this: # Then, for example: from itertools import filterfalse, zip_longest from urllib.request import urlopen - from collections import Counter, OrderedDict, ChainMap # backported to Py2.6 + 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 @@ -190,7 +191,6 @@ For example, running ``futurize -w mymodule.py`` turns this Python 2 code: import Queue from urllib2 import urlopen - def greet(name): print 'Hello', print name @@ -210,7 +210,6 @@ into this code which runs on both Py2 and Py3: import queue from urllib.request import urlopen - def greet(name): print('Hello', end=' ') print(name) @@ -225,7 +224,7 @@ 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 @@ -257,8 +256,8 @@ 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`. diff --git a/docs/bytes_object.rst b/docs/bytes_object.rst index cca43d2c..1c4a7cc7 100644 --- a/docs/bytes_object.rst +++ b/docs/bytes_object.rst @@ -66,9 +66,6 @@ code incompatibilities caused by the many differences between Py3 bytes and Py2 strings. -surrogateescape -~~~~~~~~~~~~~~~ - 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:: diff --git a/docs/credits.rst b/docs/credits.rst index 26f56ec3..32cf6604 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -41,7 +41,8 @@ http://pythoncharmers.com Authors ------- -Python-Future is written and maintained by Ed Schofield and various contributors: +Python-Future is written and maintained by Ed Schofield with the help of +various contributors: Development Lead ~~~~~~~~~~~~~~~~ diff --git a/docs/faq.rst b/docs/faq.rst index fc14945c..5e159144 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -123,8 +123,8 @@ How well has it been tested? currently being used to help with porting 800,000 lines of Python 2 code in `Sage `_ 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 990+ 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. diff --git a/docs/str_object.rst b/docs/str_object.rst index ae32ccd7..722d7747 100644 --- a/docs/str_object.rst +++ b/docs/str_object.rst @@ -84,8 +84,6 @@ same behaviours as Python 3's :class:`str`:: >>> assert list(s) == ['A', 'B', 'C', 'D'] >>> assert s.split('B') == ['A', 'CD'] -surrogateescape -~~~~~~~~~~~~~~~ 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 diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index a582672c..1545e103 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -23,9 +23,9 @@ New features: - ``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 -- Enable support for the ``surrogateescape`` error handler for ``newstr`` and ``newbytes`` objects on Py2.x (issue #116). This feature is currently in alpha. Bug fixes: From c865899555553a5c313cc9ee21e2549f007ac659 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 12:11:00 +1000 Subject: [PATCH 089/425] Enable conservative basestring fixer from past.builtins (issues #127 and #156) --- docs/whatsnew.rst | 3 + src/libfuturize/fixes/__init__.py | 2 +- src/libfuturize/fixes/fix_basestring.py | 18 ++++ tests/test_future/test_futurize.py | 118 ++++++++++++++++-------- 4 files changed, 99 insertions(+), 42 deletions(-) create mode 100644 src/libfuturize/fixes/fix_basestring.py diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 1545e103..45d2cf36 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -37,6 +37,9 @@ Bug fixes: - 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) What's new in version 0.14.3 (2014-12-15) diff --git a/src/libfuturize/fixes/__init__.py b/src/libfuturize/fixes/__init__.py index 556abe8b..443d0db5 100644 --- a/src/libfuturize/fixes/__init__.py +++ b/src/libfuturize/fixes/__init__.py @@ -40,7 +40,6 @@ # 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 @@ -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', diff --git a/src/libfuturize/fixes/fix_basestring.py b/src/libfuturize/fixes/fix_basestring.py new file mode 100644 index 00000000..8c6ec6ce --- /dev/null +++ b/src/libfuturize/fixes/fix_basestring.py @@ -0,0 +1,18 @@ +""" +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/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index 22b01cf1..a50e662c 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -1144,14 +1144,10 @@ def test_range_necessary_list_calls(self): """ self.convert_check(before, after) - -class TestConservativeFuturize(CodeHandler): - @unittest.expectedFailure def test_basestring(self): """ - In conservative mode, futurize would not modify "basestring" - but merely import it, and the following code would still run on - both Py2 and Py3. + The 2to3 basestring fixer breaks working Py2 code that uses basestring. + This tests whether something sensible is done instead. """ before = """ assert isinstance('hello', basestring) @@ -1164,41 +1160,7 @@ def test_basestring(self): assert isinstance(u'hello', basestring) assert isinstance(b'hello', basestring) """ - self.convert_check(before, after, conservative=True) - - @unittest.expectedFailure - def test_open(self): - """ - In conservative mode, futurize would not import io.open because - this changes the default return type from bytes to text. - """ - before = """ - filename = 'temp_file_open.test' - contents = 'Temporary file contents. Delete me.' - with open(filename, 'w') as f: - f.write(contents) - - with open(filename, 'r') as f: - data = f.read() - assert isinstance(data, str) - assert data == contents - """ - after = """ - from past.builtins import open, str as oldbytes, unicode - filename = oldbytes(b'temp_file_open.test') - contents = oldbytes(b'Temporary file contents. Delete me.') - with open(filename, oldbytes(b'w')) as f: - f.write(contents) - - with open(filename, oldbytes(b'r')) as f: - data = f.read() - assert isinstance(data, oldbytes) - assert data == contents - assert isinstance(oldbytes(b'hello'), basestring) - assert isinstance(unicode(u'hello'), basestring) - assert isinstance(oldbytes(b'hello'), basestring) - """ - self.convert_check(before, after, conservative=True) + self.convert_check(before, after) def test_safe_division(self): """ @@ -1255,6 +1217,80 @@ def __truediv__(self, other): """ self.convert_check(before, after) + def test_basestring_issue_156(self): + before = """ + x = str(3) + allowed_types = basestring, int + assert isinstance('', allowed_types) + assert isinstance(u'', allowed_types) + assert isinstance(u'foo', basestring) + """ + after = """ + from builtins import str + from past.builtins import basestring + x = str(3) + allowed_types = basestring, int + assert isinstance('', allowed_types) + assert isinstance(u'', allowed_types) + assert isinstance(u'foo', basestring) + """ + self.convert_check(before, after) + + +class TestConservativeFuturize(CodeHandler): + @unittest.expectedFailure + def test_basestring(self): + """ + In conservative mode, futurize would not modify "basestring" + but merely import it from ``past``, and the following code would still + run on both Py2 and Py3. + """ + before = """ + assert isinstance('hello', basestring) + assert isinstance(u'hello', basestring) + assert isinstance(b'hello', basestring) + """ + after = """ + from past.builtins import basestring + assert isinstance('hello', basestring) + assert isinstance(u'hello', basestring) + assert isinstance(b'hello', basestring) + """ + self.convert_check(before, after, conservative=True) + + @unittest.expectedFailure + def test_open(self): + """ + In conservative mode, futurize would not import io.open because + this changes the default return type from bytes to text. + """ + before = """ + filename = 'temp_file_open.test' + contents = 'Temporary file contents. Delete me.' + with open(filename, 'w') as f: + f.write(contents) + + with open(filename, 'r') as f: + data = f.read() + assert isinstance(data, str) + assert data == contents + """ + after = """ + from past.builtins import open, str as oldbytes, unicode + filename = oldbytes(b'temp_file_open.test') + contents = oldbytes(b'Temporary file contents. Delete me.') + with open(filename, oldbytes(b'w')) as f: + f.write(contents) + + with open(filename, oldbytes(b'r')) as f: + data = f.read() + assert isinstance(data, oldbytes) + assert data == contents + assert isinstance(oldbytes(b'hello'), basestring) + assert isinstance(unicode(u'hello'), basestring) + assert isinstance(oldbytes(b'hello'), basestring) + """ + self.convert_check(before, after, conservative=True) class TestFuturizeAllImports(CodeHandler): """ From 3b9c2115aa38b516c82ff6bf370eeac31b9b6a62 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 12:27:23 +1000 Subject: [PATCH 090/425] future.utils: add string_types etc. and clean up docs (issue #126) --- docs/whatsnew.rst | 1 + src/future/utils/__init__.py | 90 +++++++++++++++++++++--------------- 2 files changed, 54 insertions(+), 37 deletions(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 45d2cf36..e39e1c85 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -40,6 +40,7 @@ Bug fixes: - ``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) What's new in version 0.14.3 (2014-12-15) diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index 046fb54d..eb6916ef 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -1,17 +1,19 @@ """ 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: @@ -26,39 +28,15 @@ * tobytes(s) 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: @@ -87,10 +65,35 @@ 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. """ @@ -135,7 +138,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]) @@ -146,6 +149,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): @@ -155,6 +165,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: From 87c46f416ca93be62de4e556dec78219e173a0c0 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 12:32:45 +1000 Subject: [PATCH 091/425] Bump version to v0.15.0 --- src/future/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/__init__.py b/src/future/__init__.py index 1bdd43a0..95699041 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -89,6 +89,6 @@ __ver_major__ = 0 __ver_minor__ = 15 __ver_patch__ = 0 -__ver_sub__ = '-dev' +__ver_sub__ = '' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From 03390b7a4cb91c5c26c762ed51d6235bede45cdd Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 23:18:36 +1000 Subject: [PATCH 092/425] Bump version to v0.15.1-dev and update cheat-sheet and upload docs --- .../Writing Python 2-3 compatible code.ipynb | 6099 +++++++++-------- docs/other/upload_future_docs.sh | 5 +- src/future/__init__.py | 4 +- 3 files changed, 3102 insertions(+), 3006 deletions(-) diff --git a/docs/notebooks/Writing Python 2-3 compatible code.ipynb b/docs/notebooks/Writing Python 2-3 compatible code.ipynb index d7c6bdc4..c5f8c9ea 100644 --- a/docs/notebooks/Writing Python 2-3 compatible code.ipynb +++ b/docs/notebooks/Writing Python 2-3 compatible code.ipynb @@ -1,3007 +1,3104 @@ { - "metadata": { - "name": "", - "signature": "sha256:fa152cbedcb4d5d5c255d3456863fe07938d6196acf812a42c7c9ea3869c9f1b" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Cheat Sheet: Writing Python 2-3 compatible code" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- **Copyright (c):** 2013-2015 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 future.utils import viewitems\n", - "\n", - "for (key, value) in viewitems(heights): # also behaves like a set\n", - " ..." - ], - "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", - "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" + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Cheat Sheet: Writing Python 2-3 compatible code" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Copyright (c):** 2013-2015 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": "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 http://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 http://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 builtins 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", + " 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]" + ] + }, + { + "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", + " import itertools.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": [ + "### 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 (http://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", + "\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\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" } - ] + }, + "nbformat": 4, + "nbformat_minor": 0 } diff --git a/docs/other/upload_future_docs.sh b/docs/other/upload_future_docs.sh index dc07f6f1..9fed43e4 100644 --- a/docs/other/upload_future_docs.sh +++ b/docs/other/upload_future_docs.sh @@ -10,9 +10,8 @@ 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: -scp cheatsheet.pdf python-future.org: +scp -i ~/.ssh/pythoncharmers_2015.pem /shared/python-future-html-docs.zip python-future.org: +scp -i ~/.ssh/pythoncharmers_2015.pem /shared/cheatsheet.pdf python-future.org: ssh python-future.org diff --git a/src/future/__init__.py b/src/future/__init__.py index 95699041..dfbce17e 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -88,7 +88,7 @@ __copyright__ = 'Copyright 2013-2015 Python Charmers Pty Ltd' __ver_major__ = 0 __ver_minor__ = 15 -__ver_patch__ = 0 -__ver_sub__ = '' +__ver_patch__ = 1 +__ver_sub__ = '-dev' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From 6ee67277ac89d87d753431a412b99fb27842ad1a Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 25 Jul 2015 23:23:58 +1000 Subject: [PATCH 093/425] Update What's New and Changelog pages --- docs/changelog.rst | 142 ++++++++++++++++++++++++++++++++++++++++++++- docs/whatsnew.rst | 132 +---------------------------------------- 2 files changed, 141 insertions(+), 133 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 240d5614..20c39078 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,135 @@ Changes in previous versions Changes in the most recent major version are here: :ref:`whats-new`. +.. _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) @@ -19,8 +148,8 @@ This is a bug-fix release: - Doc formatting fix (issues #98, 100) -Changes in version 0.13 (2014-08-13) -==================================== +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. @@ -765,6 +894,13 @@ deprecated. Summary of all changes ====================== +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 @@ -774,7 +910,7 @@ v0.14.2: v0.14.1: * Bug fixes -v0.14: +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: diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index e39e1c85..55cf5224 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -3,7 +3,7 @@ What's New ********** -.. _whats-new-0.14.x: +.. _whats-new-0.15.x: What's new in version 0.15.0 (2015-07-25) ========================================= @@ -42,135 +42,7 @@ Bug fixes: ``past.builtins`` (issues #127 and #156) - ``future.utils``: add ``string_types`` etc. and update docs (issue #126) - -What's new 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 - -What's new 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) - -What's new 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) - - -What's new in version 0.14 (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`` - - Previous versions ================= -See :ref:`whats-old` for versions prior to v0.14. +See :ref:`whats-old` for versions prior to v0.15. From 9bc3ac32151dfeae29e2c419629f63b6f2bae0b3 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Mon, 3 Aug 2015 13:18:07 +1000 Subject: [PATCH 094/425] Docs: update cheatsheet to include exec --- docs/compatible_idioms.rst | 27 ++++++++ .../Writing Python 2-3 compatible code.ipynb | 62 +++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index a8e72850..a34693ef 100644 --- a/docs/compatible_idioms.rst +++ b/docs/compatible_idioms.rst @@ -952,6 +952,33 @@ file() 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() ~~~~~~~~~~ diff --git a/docs/notebooks/Writing Python 2-3 compatible code.ipynb b/docs/notebooks/Writing Python 2-3 compatible code.ipynb index c5f8c9ea..d2c115f8 100644 --- a/docs/notebooks/Writing Python 2-3 compatible code.ipynb +++ b/docs/notebooks/Writing Python 2-3 compatible code.ipynb @@ -2064,6 +2064,68 @@ "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": {}, From c185b9be516cf0ba3bb404e257f77a4ec7894cf1 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Mon, 3 Aug 2015 16:10:49 +1000 Subject: [PATCH 095/425] Docs: add tkinter.ttk to Py2/3 cheatsheet --- docs/compatible_idioms.rst | 2 ++ docs/notebooks/Writing Python 2-3 compatible code.ipynb | 1 + 2 files changed, 3 insertions(+) diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index a34693ef..68edf475 100644 --- a/docs/compatible_idioms.rst +++ b/docs/compatible_idioms.rst @@ -1367,6 +1367,7 @@ Tkinter import tkFont import tkMessageBox import tkSimpleDialog + import ttk # Python 2 and 3 (after ``pip install future``): import tkinter @@ -1383,6 +1384,7 @@ Tkinter import tkinter.font import tkinter.messagebox import tkinter.simpledialog + import tkinter.ttk socketserver ~~~~~~~~~~~~ diff --git a/docs/notebooks/Writing Python 2-3 compatible code.ipynb b/docs/notebooks/Writing Python 2-3 compatible code.ipynb index d2c115f8..190bc2c5 100644 --- a/docs/notebooks/Writing Python 2-3 compatible code.ipynb +++ b/docs/notebooks/Writing Python 2-3 compatible code.ipynb @@ -2944,6 +2944,7 @@ "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", From f5114368a0a6411a7a09db21ad1748e7162ea436 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 19 Aug 2015 09:42:57 +1000 Subject: [PATCH 096/425] futurize: move exec fixer to stage1 --- docs/whatsnew.rst | 6 ++++++ src/libfuturize/fixes/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 55cf5224..760773d3 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -5,6 +5,12 @@ What's New .. _whats-new-0.15.x: +What's new in version 0.15.1 (in development) +============================================= + +- ``futurize``: Moved exec fixer to stage1. 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. + + What's new in version 0.15.0 (2015-07-25) ========================================= diff --git a/src/libfuturize/fixes/__init__.py b/src/libfuturize/fixes/__init__.py index 443d0db5..a059c949 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', @@ -43,7 +44,6 @@ # '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 From 7959135e44a35de7dff4b56dcaf86d9b74d88329 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 20 Aug 2015 21:33:54 +1000 Subject: [PATCH 097/425] Disable two assertions in builtins tests for Py3.5 compatibility Py3.5 seems to have changed two TypeErrors to ValueErrors in two builtins functions: pow() and compile(). We'll keep future.builtins.pow() to the <= Py3.4 behaviour, and compile() is not implemented. --- tests/test_future/test_builtins.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_future/test_builtins.py b/tests/test_future/test_builtins.py index 715d7bd9..a6db4fda 100644 --- a/tests/test_future/test_builtins.py +++ b/tests/test_future/test_builtins.py @@ -524,7 +524,8 @@ def test_compile(self): self.assertRaises(TypeError, compile) self.assertRaises(ValueError, compile, 'print(42)\n', '', 'badmode') self.assertRaises(ValueError, compile, 'print(42)\n', '', 'single', 0xff) - self.assertRaises(TypeError, compile, chr(0), 'f', 'exec') + # Raises TypeError in Python < v3.5, ValueError in v3.5: + # self.assertRaises(TypeError, compile, chr(0), 'f', 'exec') self.assertRaises(TypeError, compile, 'pass', '?', 'exec', mode='eval', source='0', filename='tmp') compile('print("\xe5")\n', '', 'exec') @@ -1285,7 +1286,8 @@ def test_pow(self): self.assertAlmostEqual(pow(-1, 0.5), 1j) self.assertAlmostEqual(pow(-1, 1/3), 0.5 + 0.8660254037844386j) - self.assertRaises(TypeError, pow, -1, -2, 3) + # Raises TypeError in Python < v3.5, ValueError in v3.5: + # self.assertRaises(TypeError, pow, -1, -2, 3) self.assertRaises(ValueError, pow, 1, 2, 0) self.assertRaises(TypeError, pow) From 12bc4f9b7d38843e03be9d02e6032fc7fc48f7dd Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 20 Aug 2015 21:53:17 +1000 Subject: [PATCH 098/425] Enable Py3.5 tests on Travis and add Py3.5 trove classifier to setup.py --- .travis.yml | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6e20573f..da167c1a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: python python: + - "3.5" - "3.4" - "3.3" - "2.7" diff --git a/setup.py b/setup.py index 7c81a099..79b71254 100755 --- a/setup.py +++ b/setup.py @@ -106,6 +106,7 @@ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", "License :: OSI Approved", "License :: OSI Approved :: MIT License", "Development Status :: 4 - Beta", From eaf8a12ebf5a25ef4765ce5b1f67eaa953b6567c Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 20 Aug 2015 22:15:08 +1000 Subject: [PATCH 099/425] Add bytes.__mod__ tests (test_mod and test_imod) from Py3.5's test_bytes.py --- tests/test_future/test_bytes.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/tests/test_future/test_bytes.py b/tests/test_future/test_bytes.py index b932b4fe..9e4b2b2f 100644 --- a/tests/test_future/test_bytes.py +++ b/tests/test_future/test_bytes.py @@ -534,8 +534,38 @@ def test_maketrans(self): self.assertRaises(ValueError, bytes.maketrans, b'abc', b'xyzq') self.assertRaises(TypeError, bytes.maketrans, 'abc', 'def') - @unittest.expectedFailure def test_mod(self): + """ + From Py3.5 test suite (post-PEP 461). + + The bytes mod code is in _PyBytes_Format() in bytesobject.c in Py3.5. + """ + b = b'hello, %b!' + orig = b + b = b % b'world' + self.assertEqual(b, b'hello, world!') + self.assertEqual(orig, b'hello, %b!') + self.assertFalse(b is orig) + b = b'%s / 100 = %d%%' + a = b % (b'seventy-nine', 79) + self.assertEqual(a, b'seventy-nine / 100 = 79%') + + def test_imod(self): + """ + From Py3.5 test suite (post-PEP 461) + """ + b = b'hello, %b!' + orig = b + b %= b'world' + self.assertEqual(b, b'hello, world!') + self.assertEqual(orig, b'hello, %b!') + self.assertFalse(b is orig) + b = b'%s / 100 = %d%%' + b %= (b'seventy-nine', 79) + self.assertEqual(b, b'seventy-nine / 100 = 79%') + + @unittest.expectedFailure + def test_mod_pep_461(self): """ Test for the PEP 461 functionality (resurrection of %s formatting for bytes). From d141ee8953e9995d0bc6ed84d073db2fa2a0eee8 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 9 Sep 2015 09:35:01 +1000 Subject: [PATCH 100/425] Mark bytes mod tests as currently failing (PEP 461): not implemented yet --- tests/test_future/test_bytes.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_future/test_bytes.py b/tests/test_future/test_bytes.py index 9e4b2b2f..f30e7406 100644 --- a/tests/test_future/test_bytes.py +++ b/tests/test_future/test_bytes.py @@ -534,6 +534,7 @@ def test_maketrans(self): self.assertRaises(ValueError, bytes.maketrans, b'abc', b'xyzq') self.assertRaises(TypeError, bytes.maketrans, 'abc', 'def') + @unittest.expectedFailure def test_mod(self): """ From Py3.5 test suite (post-PEP 461). @@ -550,17 +551,20 @@ def test_mod(self): a = b % (b'seventy-nine', 79) self.assertEqual(a, b'seventy-nine / 100 = 79%') + @unittest.expectedFailure def test_imod(self): """ From Py3.5 test suite (post-PEP 461) """ - b = b'hello, %b!' + # if (3, 0) <= sys.version_info[:2] < (3, 5): + # raise unittest.SkipTest('bytes % not yet implemented on Py3.0-3.4') + b = bytes(b'hello, %b!') orig = b b %= b'world' self.assertEqual(b, b'hello, world!') self.assertEqual(orig, b'hello, %b!') self.assertFalse(b is orig) - b = b'%s / 100 = %d%%' + b = bytes(b'%s / 100 = %d%%') b %= (b'seventy-nine', 79) self.assertEqual(b, b'seventy-nine / 100 = 79%') From 6caac864d578c378479e0f2b8d78d6b3ba115075 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 9 Sep 2015 09:15:27 +1000 Subject: [PATCH 101/425] Revert "Enable Py3.5 tests on Travis and add Py3.5 trove classifier to setup.py" Travis-CI doesn't support Py3.5 yet. This reverts commit 12bc4f9b7d38843e03be9d02e6032fc7fc48f7dd. --- .travis.yml | 1 - setup.py | 1 - 2 files changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index da167c1a..6e20573f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: python python: - - "3.5" - "3.4" - "3.3" - "2.7" diff --git a/setup.py b/setup.py index 79b71254..7c81a099 100755 --- a/setup.py +++ b/setup.py @@ -106,7 +106,6 @@ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", "License :: OSI Approved", "License :: OSI Approved :: MIT License", "Development Status :: 4 - Beta", From e7e3d40c4be8ce8cb681c4677c0bbe634605e543 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 9 Sep 2015 09:23:35 +1000 Subject: [PATCH 102/425] Fix _count compatibility import on Py2.6 --- src/future/types/newrange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/types/newrange.py b/src/future/types/newrange.py index 322a9704..ec9e2245 100644 --- a/src/future/types/newrange.py +++ b/src/future/types/newrange.py @@ -23,7 +23,7 @@ from future.backports.misc import count # with step parameter on Py2.6 # For backward compatibility with python-future versions < 0.14.4: -from future.backports.misc import _count +_count = count class newrange(Sequence): From edc321ac50d3d5c2d3728adf0ebed800d4a04d2f Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 9 Sep 2015 09:13:39 +1000 Subject: [PATCH 103/425] Py2.6 compatibility: use 3-argument create_connection() from future.backports.socket (issue #162) --- docs/whatsnew.rst | 1 + src/future/backports/__init__.py | 9 ++++++++- src/future/backports/http/client.py | 7 ++++--- src/future/backports/misc.py | 1 + src/future/tests/base.py | 5 ++++- src/future/types/newrange.py | 1 + 6 files changed, 19 insertions(+), 5 deletions(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 760773d3..5f238e11 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -9,6 +9,7 @@ What's new in version 0.15.1 (in development) ============================================= - ``futurize``: Moved exec fixer to stage1. 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. +- Use 3-argument socket.create_connection() backport to restore Py2.6 compatibility in ``urllib.request.urlopen()`` (issue #162) What's new in version 0.15.0 (2015-07-25) diff --git a/src/future/backports/__init__.py b/src/future/backports/__init__.py index 396e9496..a8c95e27 100644 --- a/src/future/backports/__init__.py +++ b/src/future/backports/__init__.py @@ -1,12 +1,19 @@ -# 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: import_top_level_modules() + from .misc import (ceil, OrderedDict, Counter, diff --git a/src/future/backports/http/client.py b/src/future/backports/http/client.py index 6cde7833..994992b1 100644 --- a/src/future/backports/http/client.py +++ b/src/future/backports/http/client.py @@ -75,6 +75,7 @@ from future.backports.email import parser as email_parser from future.backports.email import message as email_message +from future.backports.socket import create_connection as socket_create_connection import io import os import socket @@ -843,7 +844,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() @@ -1226,7 +1227,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 +1267,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/misc.py b/src/future/backports/misc.py index 178c5564..fee662dd 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -10,6 +10,7 @@ - subprocess.check_output (for Python 2.6) - reprlib.recursive_repr (for Python 2.6+) """ +from __future__ import absolute_import import subprocess from math import ceil as oldceil diff --git a/src/future/tests/base.py b/src/future/tests/base.py index 279b4f0f..be634539 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 @@ -362,6 +362,9 @@ def _run_test_script(self, filename='mytestscript.py', 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 diff --git a/src/future/types/newrange.py b/src/future/types/newrange.py index ec9e2245..c190ba9e 100644 --- a/src/future/types/newrange.py +++ b/src/future/types/newrange.py @@ -17,6 +17,7 @@ Read more at https://late.am/post/2012/06/18/what-the-heck-is-an-xrange """ +from __future__ import absolute_import from collections import Sequence, Iterator from itertools import islice From 9a71d70746aa93b862aead8d60c4ee8169984e24 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 9 Sep 2015 10:56:56 +1000 Subject: [PATCH 104/425] Use the same sentinel as in the stdlib ``socket.py`` for ``create_connection()`` in ``future.backports.socket`` --- src/future/backports/socket.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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): From d100404fdc005335f01c7519341997bebe5bf737 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 9 Sep 2015 10:44:04 +1000 Subject: [PATCH 105/425] Add 3-argument socket.create_connection() backport for Py2.6 (issue #162) This function is also in ``future.backports.socket``, but that module needs more testing. This function is simple enough to include separately in ``future.backports.misc`` for now. --- src/future/backports/http/client.py | 2 +- src/future/backports/misc.py | 48 +++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/future/backports/http/client.py b/src/future/backports/http/client.py index 994992b1..980f1ed9 100644 --- a/src/future/backports/http/client.py +++ b/src/future/backports/http/client.py @@ -75,7 +75,7 @@ from future.backports.email import parser as email_parser from future.backports.email import message as email_message -from future.backports.socket import create_connection as socket_create_connection +from future.backports.misc import create_connection as socket_create_connection import io import os import socket diff --git a/src/future/backports/misc.py b/src/future/backports/misc.py index fee662dd..c5b9a8f6 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -828,6 +828,52 @@ def clear(self): 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") + + # Back up our definitions above in case they're useful _OrderedDict = OrderedDict _Counter = Counter @@ -837,6 +883,7 @@ def clear(self): __count_elements = _count_elements _recursive_repr = recursive_repr _ChainMap = ChainMap +_create_connection = create_connection # Overwrite the definitions above with the usual ones # from the standard library: @@ -844,6 +891,7 @@ def clear(self): from collections import OrderedDict, Counter from subprocess import check_output from itertools import count + from socket import create_connection if sys.version_info >= (3, 0): from math import ceil From b83cac30626326301ca73545bee77069c46b297d Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 9 Sep 2015 10:50:46 +1000 Subject: [PATCH 106/425] Remove incorrect call to pdb in ``backports.http.client`` (issue #164) Previously there was a breakpoint triggered when the first two bytes of the data were "b'" for debugging purposes. This was being triggered by real data, e.g. http://data.openaddresses.io/runs/11158/au-queensland.zip. The data was retrieved correctly, however. --- docs/whatsnew.rst | 9 +++++++-- src/future/backports/http/client.py | 10 ---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 5f238e11..a6119461 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -8,8 +8,13 @@ What's New What's new in version 0.15.1 (in development) ============================================= -- ``futurize``: Moved exec fixer to stage1. 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. -- Use 3-argument socket.create_connection() backport to restore Py2.6 compatibility in ``urllib.request.urlopen()`` (issue #162) +- ``futurize``: Moved exec fixer to stage1. 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. +- 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) What's new in version 0.15.0 (2015-07-25) diff --git a/src/future/backports/http/client.py b/src/future/backports/http/client.py index 980f1ed9..5dd983d8 100644 --- a/src/future/backports/http/client.py +++ b/src/future/backports/http/client.py @@ -554,19 +554,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) From 4aafe2c9dda2af189ad26b5a39c34229a7b6d32d Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 9 Sep 2015 10:01:05 +1000 Subject: [PATCH 107/425] Remove some more pdb.set_trace() calls from unused code --- src/libfuturize/fixes/fix_UserDict.py | 1 - src/libfuturize/fixes/fix_oldstr_wrap.py | 2 -- src/libfuturize/fixes/fix_order___future__imports.py | 2 -- 3 files changed, 5 deletions(-) diff --git a/src/libfuturize/fixes/fix_UserDict.py b/src/libfuturize/fixes/fix_UserDict.py index d028b316..73b1cfb8 100644 --- a/src/libfuturize/fixes/fix_UserDict.py +++ b/src/libfuturize/fixes/fix_UserDict.py @@ -96,7 +96,6 @@ 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: diff --git a/src/libfuturize/fixes/fix_oldstr_wrap.py b/src/libfuturize/fixes/fix_oldstr_wrap.py index 4258f6aa..575292d8 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): diff --git a/src/libfuturize/fixes/fix_order___future__imports.py b/src/libfuturize/fixes/fix_order___future__imports.py index 120665a4..14312639 100644 --- a/src/libfuturize/fixes/fix_order___future__imports.py +++ b/src/libfuturize/fixes/fix_order___future__imports.py @@ -28,8 +28,6 @@ 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 From a3cfca244dc896739671e1c31602c0f967e7f9f6 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 9 Sep 2015 11:11:09 +1000 Subject: [PATCH 108/425] Define ``orig_meta_prefix`` in libfuturize.fixes.fix_metaclass. This needs more testing. --- src/libfuturize/fixes/fix_metaclass.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libfuturize/fixes/fix_metaclass.py b/src/libfuturize/fixes/fix_metaclass.py index a917341d..2ac41c97 100644 --- a/src/libfuturize/fixes/fix_metaclass.py +++ b/src/libfuturize/fixes/fix_metaclass.py @@ -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) From 466d47211db30f4c8de2ca04f8f92c8f5fa989e1 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 9 Sep 2015 11:21:34 +1000 Subject: [PATCH 109/425] Update What's New doc for v0.15.1 --- docs/whatsnew.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index a6119461..98c05cc9 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -5,16 +5,18 @@ What's New .. _whats-new-0.15.x: -What's new in version 0.15.1 (in development) -============================================= +What's new in version 0.15.1 (2015-09-09) +========================================= + +This is a minor bug-fix release: -- ``futurize``: Moved exec fixer to stage1. 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. - 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. What's new in version 0.15.0 (2015-07-25) From 042414fcdc828511c2e8e2bce5703c41fa8a699a Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 9 Sep 2015 11:22:13 +1000 Subject: [PATCH 110/425] Bump version to v0.15.1 --- src/future/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/__init__.py b/src/future/__init__.py index dfbce17e..607ac934 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -89,6 +89,6 @@ __ver_major__ = 0 __ver_minor__ = 15 __ver_patch__ = 1 -__ver_sub__ = '-dev' +__ver_sub__ = '' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From d7d6e016291676bda32da5f68767e1f167f870fa Mon Sep 17 00:00:00 2001 From: Satoshi KOBAYASHI Date: Fri, 4 Sep 2015 15:22:34 +0900 Subject: [PATCH 111/425] Fix typo s/scolledtext/scrolledtext/ --- docs/compatible_idioms.rst | 2 +- docs/notebooks/Writing Python 2-3 compatible code.ipynb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index 68edf475..0a1e02e1 100644 --- a/docs/compatible_idioms.rst +++ b/docs/compatible_idioms.rst @@ -1373,7 +1373,7 @@ Tkinter import tkinter import tkinter.dialog import tkinter.filedialog - import tkinter.scolledtext + import tkinter.scrolledtext import tkinter.simpledialog import tkinter.tix import tkinter.constants diff --git a/docs/notebooks/Writing Python 2-3 compatible code.ipynb b/docs/notebooks/Writing Python 2-3 compatible code.ipynb index 190bc2c5..960ce21f 100644 --- a/docs/notebooks/Writing Python 2-3 compatible code.ipynb +++ b/docs/notebooks/Writing Python 2-3 compatible code.ipynb @@ -2950,7 +2950,7 @@ "import tkinter\n", "import tkinter.dialog\n", "import tkinter.filedialog\n", - "import tkinter.scolledtext\n", + "import tkinter.scrolledtext\n", "import tkinter.simpledialog\n", "import tkinter.tix\n", "import tkinter.constants\n", From ff6cf4e6c801dda539fe65837a6a910d2c4ee5eb Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 11 Sep 2015 16:37:14 +1000 Subject: [PATCH 112/425] Fix ``future.backports.misc.create_connection()`` on Py26 --- src/future/backports/misc.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/future/backports/misc.py b/src/future/backports/misc.py index c5b9a8f6..4265cefb 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -21,7 +21,7 @@ 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, itervalues, PY26, PY3 @@ -39,10 +39,18 @@ def ceil(x): ######################################################################## from itertools import islice -try: - from _thread import get_ident -except ImportError: - from _dummy_thread import get_ident + +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 + def recursive_repr(fillvalue='...'): 'Decorator to make a repr function return fillvalue for a recursive call' From ca225ce25fe5c625f2da18c6981ba0de328412d7 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 11 Sep 2015 16:38:09 +1000 Subject: [PATCH 113/425] Add some more ``urllib`` tests under the ``install_aliases()`` assumption This tests the modules ``urllib.requests`` etc. --- tests/test_future/test_standard_library.py | 13 +- tests/test_future/test_urllib_toplevel.py | 1401 ++++++++++++++++++++ 2 files changed, 1413 insertions(+), 1 deletion(-) create mode 100644 tests/test_future/test_urllib_toplevel.py diff --git a/tests/test_future/test_standard_library.py b/tests/test_future/test_standard_library.py index fc564a59..399d587f 100644 --- a/tests/test_future/test_standard_library.py +++ b/tests/test_future/test_standard_library.py @@ -332,7 +332,7 @@ def test_urllib_request_ssl_redirect(self): # pprint(r.read().decode('utf-8')) self.assertTrue(True) - def test_urllib_request_http(self): + def test_moves_urllib_request_http(self): """ This site (python-future.org) uses plain http (as of 2014-09-23). """ @@ -343,6 +343,17 @@ def test_urllib_request_http(self): data = r.read() self.assertTrue(b'' in data) + def test_urllib_request_http(self): + """ + This site (python-future.org) uses plain http (as of 2014-09-23). + """ + import urllib.request as urllib_request + from pprint import pprint + URL = 'http://python-future.org' + r = urllib_request.urlopen(URL) + data = r.read() + self.assertTrue(b'' in data) + def test_html_import(self): import html import html.entities diff --git a/tests/test_future/test_urllib_toplevel.py b/tests/test_future/test_urllib_toplevel.py new file mode 100644 index 00000000..11e77201 --- /dev/null +++ b/tests/test_future/test_urllib_toplevel.py @@ -0,0 +1,1401 @@ +"""Regresssion tests for urllib""" +from __future__ import absolute_import, division, unicode_literals + +import io +import os +import sys +import tempfile +from nturl2path import url2pathname, pathname2url +from base64 import b64encode +import collections + +PY2 = sys.version_info[0] == 2 + +from future.builtins import bytes, chr, hex, open, range, str, int +from future.standard_library import install_aliases +install_aliases() +from urllib import parse as urllib_parse +from urllib import request as urllib_request +from urllib import error as urllib_error +from http import client as http_client +try: + from future.moves.test import support +except ImportError: + from future.backports.test import support +if PY2: + from future.backports.email import message as email_message +else: + from email import message as email_message +# from future.backports.email import message as email_message +from future.tests.base import unittest, skip26, expectedFailurePY26, expectedFailurePY2 + + +def hexescape(char): + """Escape char as RFC 2396 specifies""" + hex_repr = hex(ord(char))[2:].upper() + if len(hex_repr) == 1: + hex_repr = "0%s" % hex_repr + return "%" + hex_repr + +# Shortcut for testing FancyURLopener +_urlopener = None + + +def urlopen(url, data=None, proxies=None): + """urlopen(url [, data]) -> open file-like object""" + global _urlopener + if proxies is not None: + opener = urllib_request.FancyURLopener(proxies=proxies) + elif not _urlopener: + with support.check_warnings( + ('FancyURLopener style of invoking requests is deprecated.', + DeprecationWarning)): + opener = urllib_request.FancyURLopener() + _urlopener = opener + else: + opener = _urlopener + if data is None: + return opener.open(url) + else: + return opener.open(url, data) + + +class FakeHTTPMixin(object): + def fakehttp(self, fakedata): + class FakeSocket(io.BytesIO): + io_refs = 1 + + def sendall(self, data): + FakeHTTPConnection.buf = data + + def makefile(self, *args, **kwds): + self.io_refs += 1 + return self + + def read(self, amt=None): + if self.closed: + return b"" + return io.BytesIO.read(self, amt) + + def readline(self, length=None): + if self.closed: + return b"" + return io.BytesIO.readline(self, length) + + def close(self): + self.io_refs -= 1 + if self.io_refs == 0: + io.BytesIO.close(self) + + class FakeHTTPConnection(http_client.HTTPConnection): + + # buffer to store data for verification in urlopen tests. + buf = None + + def connect(self): + self.sock = FakeSocket(fakedata) + + self._connection_class = http_client.HTTPConnection + http_client.HTTPConnection = FakeHTTPConnection + + def unfakehttp(self): + http_client.HTTPConnection = self._connection_class + + +class urlopen_FileTests(unittest.TestCase): + """Test urlopen() opening a temporary file. + + Try to test as much functionality as possible so as to cut down on reliance + on connecting to the Net for testing. + + """ + + def setUp(self): + # Create a temp file to use for testing + self.text = bytes("test_urllib: %s\n" % self.__class__.__name__, + "ascii") + f = open(support.TESTFN, 'wb') + try: + f.write(self.text) + finally: + f.close() + self.pathname = support.TESTFN + self.returned_obj = urlopen("file:%s" % self.pathname) + + def tearDown(self): + """Shut down the open object""" + self.returned_obj.close() + os.remove(support.TESTFN) + + def test_interface(self): + # Make sure object returned by urlopen() has the specified methods + for attr in ("read", "readline", "readlines", "fileno", + "close", "info", "geturl", "getcode", "__iter__"): + self.assertTrue(hasattr(self.returned_obj, attr), + "object returned by urlopen() lacks %s attribute" % + attr) + + def test_read(self): + self.assertEqual(self.text, self.returned_obj.read()) + + def test_readline(self): + self.assertEqual(self.text, self.returned_obj.readline()) + self.assertEqual(b'', self.returned_obj.readline(), + "calling readline() after exhausting the file did not" + " return an empty string") + + def test_readlines(self): + lines_list = self.returned_obj.readlines() + self.assertEqual(len(lines_list), 1, + "readlines() returned the wrong number of lines") + self.assertEqual(lines_list[0], self.text, + "readlines() returned improper text") + + def test_fileno(self): + file_num = self.returned_obj.fileno() + self.assertIsInstance(file_num, int, "fileno() did not return an int") + self.assertEqual(os.read(file_num, len(self.text)), self.text, + "Reading on the file descriptor returned by fileno() " + "did not return the expected text") + + def test_close(self): + # Test close() by calling it here and then having it be called again + # by the tearDown() method for the test + self.returned_obj.close() + + def test_info(self): + self.assertIsInstance(self.returned_obj.info(), email_message.Message) + + def test_geturl(self): + self.assertEqual(self.returned_obj.geturl(), self.pathname) + + def test_getcode(self): + self.assertIsNone(self.returned_obj.getcode()) + + def test_iter(self): + # Test iterator + # Don't need to count number of iterations since test would fail the + # instant it returned anything beyond the first line from the + # comparison. + # Use the iterator in the usual implicit way to test for ticket #4608. + for line in self.returned_obj: + self.assertEqual(line, self.text) + + def test_relativelocalfile(self): + self.assertRaises(ValueError,urllib_request.urlopen,'./' + self.pathname) + +class ProxyTests(unittest.TestCase): + + def setUp(self): + # Records changes to env vars + self.env = support.EnvironmentVarGuard() + # Delete all proxy related env vars + for k in list(os.environ): + if 'proxy' in k.lower(): + self.env.unset(k) + + def tearDown(self): + # Restore all proxy related env vars + self.env.__exit__() + del self.env + + def test_getproxies_environment_keep_no_proxies(self): + self.env.set('NO_PROXY', 'localhost') + proxies = urllib_request.getproxies_environment() + # getproxies_environment use lowered case truncated (no '_proxy') keys + self.assertEqual('localhost', proxies['no']) + # List of no_proxies with space. + self.env.set('NO_PROXY', 'localhost, anotherdomain.com, newdomain.com') + self.assertTrue(urllib_request.proxy_bypass_environment('anotherdomain.com')) + +class urlopen_HttpTests(unittest.TestCase, FakeHTTPMixin): + """Test urlopen() opening a fake http connection.""" + + def check_read(self, ver): + self.fakehttp(b"HTTP/" + ver + b" 200 OK\r\n\r\nHello!") + try: + fp = urlopen("http://python.org/") + self.assertEqual(fp.readline(), b"Hello!") + self.assertEqual(fp.readline(), b"") + self.assertEqual(fp.geturl(), 'http://python.org/') + self.assertEqual(fp.getcode(), 200) + finally: + self.unfakehttp() + + @unittest.skip('skipping test that uses https') + def test_url_fragment(self): + # Issue #11703: geturl() omits fragments in the original URL. + url = 'http://docs.python.org/library/urllib.html#OK' + self.fakehttp(b"HTTP/1.1 200 OK\r\n\r\nHello!") + try: + fp = urllib_request.urlopen(url) + self.assertEqual(fp.geturl(), url) + finally: + self.unfakehttp() + + @unittest.skip('skipping test that uses https') + def test_willclose(self): + self.fakehttp(b"HTTP/1.1 200 OK\r\n\r\nHello!") + try: + resp = urlopen("http://www.python.org") + self.assertTrue(resp.fp.will_close) + finally: + self.unfakehttp() + + @expectedFailurePY2 + def test_read_0_9(self): + # "0.9" response accepted (but not "simple responses" without + # a status line) + self.check_read(b"0.9") + + @expectedFailurePY2 + def test_read_1_0(self): + self.check_read(b"1.0") + + @expectedFailurePY2 + def test_read_1_1(self): + self.check_read(b"1.1") + + @expectedFailurePY2 + def test_read_bogus(self): + # urlopen() should raise IOError for many error codes. + self.fakehttp(b'''HTTP/1.1 401 Authentication Required +Date: Wed, 02 Jan 2008 03:03:54 GMT +Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e +Connection: close +Content-Type: text/html; charset=iso-8859-1 +''') + try: + self.assertRaises(OSError, urlopen, "http://python.org/") + finally: + self.unfakehttp() + + @unittest.skip('skipping test that uses https') + def test_invalid_redirect(self): + # urlopen() should raise IOError for many error codes. + self.fakehttp(b'''HTTP/1.1 302 Found +Date: Wed, 02 Jan 2008 03:03:54 GMT +Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e +Location: file://guidocomputer.athome.com:/python/license +Connection: close +Content-Type: text/html; charset=iso-8859-1 +''') + try: + self.assertRaises(urllib_error.HTTPError, urlopen, + "http://python.org/") + finally: + self.unfakehttp() + + def test_empty_socket(self): + # urlopen() raises IOError if the underlying socket does not send any + # data. (#1680230) + self.fakehttp(b'') + try: + self.assertRaises(IOError, urlopen, "http://something") + finally: + self.unfakehttp() + + def test_missing_localfile(self): + # Test for #10836 + # 3.3 - URLError is not captured, explicit IOError is raised. + with self.assertRaises(IOError): + urlopen('file://localhost/a/file/which/doesnot/exists.py') + + def test_file_notexists(self): + fd, tmp_file = tempfile.mkstemp() + tmp_fileurl = 'file://localhost/' + tmp_file.replace(os.path.sep, '/') + try: + self.assertTrue(os.path.exists(tmp_file)) + with urlopen(tmp_fileurl) as fobj: + self.assertTrue(fobj) + finally: + os.close(fd) + os.unlink(tmp_file) + self.assertFalse(os.path.exists(tmp_file)) + # 3.3 - IOError instead of URLError + with self.assertRaises(IOError): + urlopen(tmp_fileurl) + + def test_ftp_nohost(self): + test_ftp_url = 'ftp:///path' + # 3.3 - IOError instead of URLError + with self.assertRaises(IOError): + urlopen(test_ftp_url) + + def test_ftp_nonexisting(self): + # 3.3 - IOError instead of URLError + with self.assertRaises(IOError): + urlopen('ftp://localhost/a/file/which/doesnot/exists.py') + + + @expectedFailurePY2 + def test_userpass_inurl(self): + self.fakehttp(b"HTTP/1.0 200 OK\r\n\r\nHello!") + try: + fp = urlopen("http://user:pass@python.org/") + self.assertEqual(fp.readline(), b"Hello!") + self.assertEqual(fp.readline(), b"") + self.assertEqual(fp.geturl(), 'http://user:pass@python.org/') + self.assertEqual(fp.getcode(), 200) + finally: + self.unfakehttp() + + @expectedFailurePY2 + def test_userpass_inurl_w_spaces(self): + self.fakehttp(b"HTTP/1.0 200 OK\r\n\r\nHello!") + try: + userpass = "a b:c d" + url = "http://{0}@python.org/".format(userpass) + fakehttp_wrapper = http_client.HTTPConnection + authorization = ("Authorization: Basic %s\r\n" % + b64encode(userpass.encode("ASCII")).decode("ASCII")) + fp = urlopen(url) + # The authorization header must be in place + self.assertIn(authorization, fakehttp_wrapper.buf.decode("UTF-8")) + self.assertEqual(fp.readline(), b"Hello!") + self.assertEqual(fp.readline(), b"") + # the spaces are quoted in URL so no match + self.assertNotEqual(fp.geturl(), url) + self.assertEqual(fp.getcode(), 200) + finally: + self.unfakehttp() + + def test_URLopener_deprecation(self): + with support.check_warnings(('',DeprecationWarning)): + urllib_request.URLopener() + +class urlretrieve_FileTests(unittest.TestCase): + """Test urllib.urlretrieve() on local files""" + + def setUp(self): + # Create a list of temporary files. Each item in the list is a file + # name (absolute path or relative to the current working directory). + # All files in this list will be deleted in the tearDown method. Note, + # this only helps to makes sure temporary files get deleted, but it + # does nothing about trying to close files that may still be open. It + # is the responsibility of the developer to properly close files even + # when exceptional conditions occur. + self.tempFiles = [] + + # Create a temporary file. + self.registerFileForCleanUp(support.TESTFN) + self.text = b'testing urllib.urlretrieve' + try: + FILE = open(support.TESTFN, 'wb') + FILE.write(self.text) + FILE.close() + finally: + try: FILE.close() + except: pass + + def tearDown(self): + # Delete the temporary files. + for each in self.tempFiles: + try: os.remove(each) + except: pass + + def constructLocalFileUrl(self, filePath): + filePath = os.path.abspath(filePath) + try: + filePath.encode("utf-8") + except UnicodeEncodeError: + raise unittest.SkipTest("filePath is not encodable to utf8") + return "file://%s" % urllib_request.pathname2url(filePath) + + def createNewTempFile(self, data=b""): + """Creates a new temporary file containing the specified data, + registers the file for deletion during the test fixture tear down, and + returns the absolute path of the file.""" + + newFd, newFilePath = tempfile.mkstemp() + try: + self.registerFileForCleanUp(newFilePath) + newFile = os.fdopen(newFd, "wb") + newFile.write(data) + newFile.close() + finally: + try: newFile.close() + except: pass + return newFilePath + + def registerFileForCleanUp(self, fileName): + self.tempFiles.append(fileName) + + def test_basic(self): + # Make sure that a local file just gets its own location returned and + # a headers value is returned. + result = urllib_request.urlretrieve("file:%s" % support.TESTFN) + self.assertEqual(result[0], support.TESTFN) + self.assertIsInstance(result[1], email_message.Message, + "did not get a email.message.Message instance " + "as second returned value") + + def test_copy(self): + # Test that setting the filename argument works. + second_temp = "%s.2" % support.TESTFN + self.registerFileForCleanUp(second_temp) + result = urllib_request.urlretrieve(self.constructLocalFileUrl( + support.TESTFN), second_temp) + self.assertEqual(second_temp, result[0]) + self.assertTrue(os.path.exists(second_temp), "copy of the file was not " + "made") + FILE = open(second_temp, 'rb') + try: + text = FILE.read() + FILE.close() + finally: + try: FILE.close() + except: pass + self.assertEqual(self.text, text) + + def test_reporthook(self): + # Make sure that the reporthook works. + def hooktester(block_count, block_read_size, file_size, count_holder=[0]): + self.assertIsInstance(block_count, int) + self.assertIsInstance(block_read_size, int) + self.assertIsInstance(file_size, int) + self.assertEqual(block_count, count_holder[0]) + count_holder[0] = count_holder[0] + 1 + second_temp = "%s.2" % support.TESTFN + self.registerFileForCleanUp(second_temp) + urllib_request.urlretrieve( + self.constructLocalFileUrl(support.TESTFN), + second_temp, hooktester) + + def test_reporthook_0_bytes(self): + # Test on zero length file. Should call reporthook only 1 time. + report = [] + def hooktester(block_count, block_read_size, file_size, _report=report): + _report.append((block_count, block_read_size, file_size)) + srcFileName = self.createNewTempFile() + urllib_request.urlretrieve(self.constructLocalFileUrl(srcFileName), + support.TESTFN, hooktester) + self.assertEqual(len(report), 1) + self.assertEqual(report[0][2], 0) + + def test_reporthook_5_bytes(self): + # Test on 5 byte file. Should call reporthook only 2 times (once when + # the "network connection" is established and once when the block is + # read). + report = [] + def hooktester(block_count, block_read_size, file_size, _report=report): + _report.append((block_count, block_read_size, file_size)) + srcFileName = self.createNewTempFile(b"x" * 5) + urllib_request.urlretrieve(self.constructLocalFileUrl(srcFileName), + support.TESTFN, hooktester) + self.assertEqual(len(report), 2) + self.assertEqual(report[0][2], 5) + self.assertEqual(report[1][2], 5) + + def test_reporthook_8193_bytes(self): + # Test on 8193 byte file. Should call reporthook only 3 times (once + # when the "network connection" is established, once for the next 8192 + # bytes, and once for the last byte). + report = [] + def hooktester(block_count, block_read_size, file_size, _report=report): + _report.append((block_count, block_read_size, file_size)) + srcFileName = self.createNewTempFile(b"x" * 8193) + urllib_request.urlretrieve(self.constructLocalFileUrl(srcFileName), + support.TESTFN, hooktester) + self.assertEqual(len(report), 3) + self.assertEqual(report[0][2], 8193) + self.assertEqual(report[0][1], 8192) + self.assertEqual(report[1][1], 8192) + self.assertEqual(report[2][1], 8192) + + +class urlretrieve_HttpTests(unittest.TestCase, FakeHTTPMixin): + """Test urllib.urlretrieve() using fake http connections""" + + @expectedFailurePY2 + def test_short_content_raises_ContentTooShortError(self): + self.fakehttp(b'''HTTP/1.1 200 OK +Date: Wed, 02 Jan 2008 03:03:54 GMT +Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e +Connection: close +Content-Length: 100 +Content-Type: text/html; charset=iso-8859-1 + +FF +''') + + def _reporthook(par1, par2, par3): + pass + + with self.assertRaises(urllib_error.ContentTooShortError): + try: + urllib_request.urlretrieve('http://example.com/', + reporthook=_reporthook) + finally: + self.unfakehttp() + + @expectedFailurePY2 + def test_short_content_raises_ContentTooShortError_without_reporthook(self): + self.fakehttp(b'''HTTP/1.1 200 OK +Date: Wed, 02 Jan 2008 03:03:54 GMT +Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e +Connection: close +Content-Length: 100 +Content-Type: text/html; charset=iso-8859-1 + +FF +''') + with self.assertRaises(urllib_error.ContentTooShortError): + try: + urllib_request.urlretrieve('http://example.com/') + finally: + self.unfakehttp() + + +class QuotingTests(unittest.TestCase): + """Tests for urllib.quote() and urllib.quote_plus() + + According to RFC 2396 (Uniform Resource Identifiers), to escape a + character you write it as '%' + <2 character US-ASCII hex value>. + The Python code of ``'%' + hex(ord())[2:]`` escapes a + character properly. Case does not matter on the hex letters. + + The various character sets specified are: + + Reserved characters : ";/?:@&=+$," + Have special meaning in URIs and must be escaped if not being used for + their special meaning + Data characters : letters, digits, and "-_.!~*'()" + Unreserved and do not need to be escaped; can be, though, if desired + Control characters : 0x00 - 0x1F, 0x7F + Have no use in URIs so must be escaped + space : 0x20 + Must be escaped + Delimiters : '<>#%"' + Must be escaped + Unwise : "{}|\^[]`" + Must be escaped + + """ + + def test_never_quote(self): + # Make sure quote() does not quote letters, digits, and "_,.-" + do_not_quote = '' .join(["ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "abcdefghijklmnopqrstuvwxyz", + "0123456789", + "_.-"]) + result = urllib_parse.quote(do_not_quote) + self.assertEqual(do_not_quote, result, + "using quote(): %r != %r" % (do_not_quote, result)) + result = urllib_parse.quote_plus(do_not_quote) + self.assertEqual(do_not_quote, result, + "using quote_plus(): %r != %r" % (do_not_quote, result)) + + def test_default_safe(self): + # Test '/' is default value for 'safe' parameter + self.assertEqual(urllib_parse.quote.__defaults__[0], '/') + + def test_safe(self): + # Test setting 'safe' parameter does what it should do + quote_by_default = "<>" + result = urllib_parse.quote(quote_by_default, safe=quote_by_default) + self.assertEqual(quote_by_default, result, + "using quote(): %r != %r" % (quote_by_default, result)) + result = urllib_parse.quote_plus(quote_by_default, + safe=quote_by_default) + self.assertEqual(quote_by_default, result, + "using quote_plus(): %r != %r" % + (quote_by_default, result)) + # Safe expressed as bytes rather than str + result = urllib_parse.quote(quote_by_default, safe=b"<>") + self.assertEqual(quote_by_default, result, + "using quote(): %r != %r" % (quote_by_default, result)) + # "Safe" non-ASCII characters should have no effect + # (Since URIs are not allowed to have non-ASCII characters) + result = urllib_parse.quote("a\xfcb", encoding="latin-1", safe="\xfc") + expect = urllib_parse.quote("a\xfcb", encoding="latin-1", safe="") + self.assertEqual(expect, result, + "using quote(): %r != %r" % + (expect, result)) + # Same as above, but using a bytes rather than str + result = urllib_parse.quote("a\xfcb", encoding="latin-1", safe=b"\xfc") + expect = urllib_parse.quote("a\xfcb", encoding="latin-1", safe="") + self.assertEqual(expect, result, + "using quote(): %r != %r" % + (expect, result)) + + def test_default_quoting(self): + # Make sure all characters that should be quoted are by default sans + # space (separate test for that). + should_quote = [chr(num) for num in range(32)] # For 0x00 - 0x1F + should_quote.append('<>#%"{}|\^[]`') + should_quote.append(chr(127)) # For 0x7F + should_quote = ''.join(should_quote) + for char in should_quote: + result = urllib_parse.quote(char) + self.assertEqual(hexescape(char), result, + "using quote(): " + "%s should be escaped to %s, not %s" % + (char, hexescape(char), result)) + result = urllib_parse.quote_plus(char) + self.assertEqual(hexescape(char), result, + "using quote_plus(): " + "%s should be escapes to %s, not %s" % + (char, hexescape(char), result)) + del should_quote + partial_quote = "ab[]cd" + expected = "ab%5B%5Dcd" + result = urllib_parse.quote(partial_quote) + self.assertEqual(expected, result, + "using quote(): %r != %r" % (expected, result)) + result = urllib_parse.quote_plus(partial_quote) + self.assertEqual(expected, result, + "using quote_plus(): %r != %r" % (expected, result)) + + def test_quoting_space(self): + # Make sure quote() and quote_plus() handle spaces as specified in + # their unique way + result = urllib_parse.quote(' ') + self.assertEqual(result, hexescape(' '), + "using quote(): %r != %r" % (result, hexescape(' '))) + result = urllib_parse.quote_plus(' ') + self.assertEqual(result, '+', + "using quote_plus(): %r != +" % result) + given = "a b cd e f" + expect = given.replace(' ', hexescape(' ')) + result = urllib_parse.quote(given) + self.assertEqual(expect, result, + "using quote(): %r != %r" % (expect, result)) + expect = given.replace(' ', '+') + result = urllib_parse.quote_plus(given) + self.assertEqual(expect, result, + "using quote_plus(): %r != %r" % (expect, result)) + + def test_quoting_plus(self): + self.assertEqual(urllib_parse.quote_plus('alpha+beta gamma'), + 'alpha%2Bbeta+gamma') + self.assertEqual(urllib_parse.quote_plus('alpha+beta gamma', '+'), + 'alpha+beta+gamma') + # Test with bytes + self.assertEqual(urllib_parse.quote_plus(b'alpha+beta gamma'), + 'alpha%2Bbeta+gamma') + # Test with safe bytes + self.assertEqual(urllib_parse.quote_plus('alpha+beta gamma', b'+'), + 'alpha+beta+gamma') + + def test_quote_bytes(self): + # Bytes should quote directly to percent-encoded values + given = b"\xa2\xd8ab\xff" + expect = "%A2%D8ab%FF" + result = urllib_parse.quote(given) + self.assertEqual(expect, result, + "using quote(): %r != %r" % (expect, result)) + # Encoding argument should raise type error on bytes input + self.assertRaises(TypeError, urllib_parse.quote, given, + encoding="latin-1") + # quote_from_bytes should work the same + result = urllib_parse.quote_from_bytes(given) + self.assertEqual(expect, result, + "using quote_from_bytes(): %r != %r" + % (expect, result)) + + def test_quote_with_unicode(self): + # Characters in Latin-1 range, encoded by default in UTF-8 + given = "\xa2\xd8ab\xff" + expect = "%C2%A2%C3%98ab%C3%BF" + result = urllib_parse.quote(given) + self.assertEqual(expect, result, + "using quote(): %r != %r" % (expect, result)) + # Characters in Latin-1 range, encoded by with None (default) + result = urllib_parse.quote(given, encoding=None, errors=None) + self.assertEqual(expect, result, + "using quote(): %r != %r" % (expect, result)) + # Characters in Latin-1 range, encoded with Latin-1 + given = "\xa2\xd8ab\xff" + expect = "%A2%D8ab%FF" + result = urllib_parse.quote(given, encoding="latin-1") + self.assertEqual(expect, result, + "using quote(): %r != %r" % (expect, result)) + # Characters in BMP, encoded by default in UTF-8 + given = "\u6f22\u5b57" # "Kanji" + expect = "%E6%BC%A2%E5%AD%97" + result = urllib_parse.quote(given) + self.assertEqual(expect, result, + "using quote(): %r != %r" % (expect, result)) + # Characters in BMP, encoded with Latin-1 + given = "\u6f22\u5b57" + self.assertRaises(UnicodeEncodeError, urllib_parse.quote, given, + encoding="latin-1") + # Characters in BMP, encoded with Latin-1, with replace error handling + given = "\u6f22\u5b57" + expect = "%3F%3F" # "??" + result = urllib_parse.quote(given, encoding="latin-1", + errors="replace") + self.assertEqual(expect, result, + "using quote(): %r != %r" % (expect, result)) + # Characters in BMP, Latin-1, with xmlcharref error handling + given = "\u6f22\u5b57" + expect = "%26%2328450%3B%26%2323383%3B" # "漢字" + result = urllib_parse.quote(given, encoding="latin-1", + errors="xmlcharrefreplace") + self.assertEqual(expect, result, + "using quote(): %r != %r" % (expect, result)) + + def test_quote_plus_with_unicode(self): + # Encoding (latin-1) test for quote_plus + given = "\xa2\xd8 \xff" + expect = "%A2%D8+%FF" + result = urllib_parse.quote_plus(given, encoding="latin-1") + self.assertEqual(expect, result, + "using quote_plus(): %r != %r" % (expect, result)) + # Errors test for quote_plus + given = "ab\u6f22\u5b57 cd" + expect = "ab%3F%3F+cd" + result = urllib_parse.quote_plus(given, encoding="latin-1", + errors="replace") + self.assertEqual(expect, result, + "using quote_plus(): %r != %r" % (expect, result)) + + +class UnquotingTests(unittest.TestCase): + """Tests for unquote() and unquote_plus() + + See the doc string for quoting_Tests for details on quoting and such. + + """ + + def test_unquoting(self): + # Make sure unquoting of all ASCII values works + escape_list = [] + for num in range(128): + given = hexescape(chr(num)) + expect = chr(num) + result = urllib_parse.unquote(given) + self.assertEqual(expect, result, + "using unquote(): %r != %r" % (expect, result)) + result = urllib_parse.unquote_plus(given) + self.assertEqual(expect, result, + "using unquote_plus(): %r != %r" % + (expect, result)) + escape_list.append(given) + escape_string = ''.join(escape_list) + del escape_list + result = urllib_parse.unquote(escape_string) + self.assertEqual(result.count('%'), 1, + "using unquote(): not all characters escaped: " + "%s" % result) + self.assertRaises((TypeError, AttributeError), urllib_parse.unquote, None) + self.assertRaises((TypeError, AttributeError), urllib_parse.unquote, ()) + with support.check_warnings(('', BytesWarning), quiet=True): + self.assertRaises((TypeError, AttributeError), urllib_parse.unquote, bytes(b'')) + + def test_unquoting_badpercent(self): + # Test unquoting on bad percent-escapes + given = '%xab' + expect = given + result = urllib_parse.unquote(given) + self.assertEqual(expect, result, "using unquote(): %r != %r" + % (expect, result)) + given = '%x' + expect = given + result = urllib_parse.unquote(given) + self.assertEqual(expect, result, "using unquote(): %r != %r" + % (expect, result)) + given = '%' + expect = given + result = urllib_parse.unquote(given) + self.assertEqual(expect, result, "using unquote(): %r != %r" + % (expect, result)) + # unquote_to_bytes + given = '%xab' + expect = bytes(given, 'ascii') + result = urllib_parse.unquote_to_bytes(given) + self.assertEqual(expect, result, "using unquote_to_bytes(): %r != %r" + % (expect, result)) + given = '%x' + expect = bytes(given, 'ascii') + result = urllib_parse.unquote_to_bytes(given) + self.assertEqual(expect, result, "using unquote_to_bytes(): %r != %r" + % (expect, result)) + given = '%' + expect = bytes(given, 'ascii') + result = urllib_parse.unquote_to_bytes(given) + self.assertEqual(expect, result, "using unquote_to_bytes(): %r != %r" + % (expect, result)) + self.assertRaises((TypeError, AttributeError), urllib_parse.unquote_to_bytes, None) + self.assertRaises((TypeError, AttributeError), urllib_parse.unquote_to_bytes, ()) + + def test_unquoting_mixed_case(self): + # Test unquoting on mixed-case hex digits in the percent-escapes + given = '%Ab%eA' + expect = b'\xab\xea' + result = urllib_parse.unquote_to_bytes(given) + self.assertEqual(expect, result, + "using unquote_to_bytes(): %r != %r" + % (expect, result)) + + def test_unquoting_parts(self): + # Make sure unquoting works when have non-quoted characters + # interspersed + given = 'ab%sd' % hexescape('c') + expect = "abcd" + result = urllib_parse.unquote(given) + self.assertEqual(expect, result, + "using quote(): %r != %r" % (expect, result)) + result = urllib_parse.unquote_plus(given) + self.assertEqual(expect, result, + "using unquote_plus(): %r != %r" % (expect, result)) + + def test_unquoting_plus(self): + # Test difference between unquote() and unquote_plus() + given = "are+there+spaces..." + expect = given + result = urllib_parse.unquote(given) + self.assertEqual(expect, result, + "using unquote(): %r != %r" % (expect, result)) + expect = given.replace('+', ' ') + result = urllib_parse.unquote_plus(given) + self.assertEqual(expect, result, + "using unquote_plus(): %r != %r" % (expect, result)) + + def test_unquote_to_bytes(self): + given = 'br%C3%BCckner_sapporo_20050930.doc' + expect = b'br\xc3\xbcckner_sapporo_20050930.doc' + result = urllib_parse.unquote_to_bytes(given) + self.assertEqual(expect, result, + "using unquote_to_bytes(): %r != %r" + % (expect, result)) + # Test on a string with unescaped non-ASCII characters + # (Technically an invalid URI; expect those characters to be UTF-8 + # encoded). + result = urllib_parse.unquote_to_bytes("\u6f22%C3%BC") + expect = b'\xe6\xbc\xa2\xc3\xbc' # UTF-8 for "\u6f22\u00fc" + self.assertEqual(expect, result, + "using unquote_to_bytes(): %r != %r" + % (expect, result)) + # Test with a bytes as input + given = b'%A2%D8ab%FF' + expect = b'\xa2\xd8ab\xff' + result = urllib_parse.unquote_to_bytes(given) + self.assertEqual(expect, result, + "using unquote_to_bytes(): %r != %r" + % (expect, result)) + # Test with a bytes as input, with unescaped non-ASCII bytes + # (Technically an invalid URI; expect those bytes to be preserved) + given = b'%A2\xd8ab%FF' + expect = b'\xa2\xd8ab\xff' + result = urllib_parse.unquote_to_bytes(given) + self.assertEqual(expect, result, + "using unquote_to_bytes(): %r != %r" + % (expect, result)) + + def test_unquote_with_unicode(self): + # Characters in the Latin-1 range, encoded with UTF-8 + given = 'br%C3%BCckner_sapporo_20050930.doc' + expect = 'br\u00fcckner_sapporo_20050930.doc' + result = urllib_parse.unquote(given) + self.assertEqual(expect, result, + "using unquote(): %r != %r" % (expect, result)) + # Characters in the Latin-1 range, encoded with None (default) + result = urllib_parse.unquote(given, encoding=None, errors=None) + self.assertEqual(expect, result, + "using unquote(): %r != %r" % (expect, result)) + + # Characters in the Latin-1 range, encoded with Latin-1 + result = urllib_parse.unquote('br%FCckner_sapporo_20050930.doc', + encoding="latin-1") + expect = 'br\u00fcckner_sapporo_20050930.doc' + self.assertEqual(expect, result, + "using unquote(): %r != %r" % (expect, result)) + + # Characters in BMP, encoded with UTF-8 + given = "%E6%BC%A2%E5%AD%97" + expect = "\u6f22\u5b57" # "Kanji" + result = urllib_parse.unquote(given) + self.assertEqual(expect, result, + "using unquote(): %r != %r" % (expect, result)) + + # Decode with UTF-8, invalid sequence + given = "%F3%B1" + expect = "\ufffd" # Replacement character + result = urllib_parse.unquote(given) + self.assertEqual(expect, result, + "using unquote(): %r != %r" % (expect, result)) + + # Decode with UTF-8, invalid sequence, replace errors + result = urllib_parse.unquote(given, errors="replace") + self.assertEqual(expect, result, + "using unquote(): %r != %r" % (expect, result)) + + # Decode with UTF-8, invalid sequence, ignoring errors + given = "%F3%B1" + expect = "" + result = urllib_parse.unquote(given, errors="ignore") + self.assertEqual(expect, result, + "using unquote(): %r != %r" % (expect, result)) + + # A mix of non-ASCII and percent-encoded characters, UTF-8 + result = urllib_parse.unquote("\u6f22%C3%BC") + expect = '\u6f22\u00fc' + self.assertEqual(expect, result, + "using unquote(): %r != %r" % (expect, result)) + + # A mix of non-ASCII and percent-encoded characters, Latin-1 + # (Note, the string contains non-Latin-1-representable characters) + result = urllib_parse.unquote("\u6f22%FC", encoding="latin-1") + expect = '\u6f22\u00fc' + self.assertEqual(expect, result, + "using unquote(): %r != %r" % (expect, result)) + +class urlencode_Tests(unittest.TestCase): + """Tests for urlencode()""" + + def help_inputtype(self, given, test_type): + """Helper method for testing different input types. + + 'given' must lead to only the pairs: + * 1st, 1 + * 2nd, 2 + * 3rd, 3 + + Test cannot assume anything about order. Docs make no guarantee and + have possible dictionary input. + + """ + expect_somewhere = ["1st=1", "2nd=2", "3rd=3"] + result = urllib_parse.urlencode(given) + for expected in expect_somewhere: + self.assertIn(expected, result, + "testing %s: %s not found in %s" % + (test_type, expected, result)) + self.assertEqual(result.count('&'), 2, + "testing %s: expected 2 '&'s; got %s" % + (test_type, result.count('&'))) + amp_location = result.index('&') + on_amp_left = result[amp_location - 1] + on_amp_right = result[amp_location + 1] + self.assertTrue(on_amp_left.isdigit() and on_amp_right.isdigit(), + "testing %s: '&' not located in proper place in %s" % + (test_type, result)) + self.assertEqual(len(result), (5 * 3) + 2, #5 chars per thing and amps + "testing %s: " + "unexpected number of characters: %s != %s" % + (test_type, len(result), (5 * 3) + 2)) + + def test_using_mapping(self): + # Test passing in a mapping object as an argument. + self.help_inputtype({"1st":'1', "2nd":'2', "3rd":'3'}, + "using dict as input type") + + def test_using_sequence(self): + # Test passing in a sequence of two-item sequences as an argument. + self.help_inputtype([('1st', '1'), ('2nd', '2'), ('3rd', '3')], + "using sequence of two-item tuples as input") + + def test_quoting(self): + # Make sure keys and values are quoted using quote_plus() + given = {"&":"="} + expect = "%s=%s" % (hexescape('&'), hexescape('=')) + result = urllib_parse.urlencode(given) + self.assertEqual(expect, result) + given = {"key name":"A bunch of pluses"} + expect = "key+name=A+bunch+of+pluses" + result = urllib_parse.urlencode(given) + self.assertEqual(expect, result) + + def test_doseq(self): + # Test that passing True for 'doseq' parameter works correctly + given = {'sequence':['1', '2', '3']} + expect = "sequence=%s" % urllib_parse.quote_plus(str(['1', '2', '3'])) + result = urllib_parse.urlencode(given) + self.assertEqual(expect, result) + result = urllib_parse.urlencode(given, True) + for value in given["sequence"]: + expect = "sequence=%s" % value + self.assertIn(expect, result) + self.assertEqual(result.count('&'), 2, + "Expected 2 '&'s, got %s" % result.count('&')) + + def test_empty_sequence(self): + self.assertEqual("", urllib_parse.urlencode({})) + self.assertEqual("", urllib_parse.urlencode([])) + + def test_nonstring_values(self): + self.assertEqual("a=1", urllib_parse.urlencode({"a": 1})) + self.assertEqual("a=None", urllib_parse.urlencode({"a": None})) + + def test_nonstring_seq_values(self): + from future.backports import OrderedDict # for Py2.6 + self.assertEqual("a=1&a=2", urllib_parse.urlencode({"a": [1, 2]}, True)) + self.assertEqual("a=None&a=a", + urllib_parse.urlencode({"a": [None, "a"]}, True)) + data = OrderedDict([("a", 1), ("b", 1)]) + self.assertEqual("a=a&a=b", + urllib_parse.urlencode({"a": data}, True)) + + def test_urlencode_encoding(self): + # ASCII encoding. Expect %3F with errors="replace' + given = (('\u00a0', '\u00c1'),) + expect = '%3F=%3F' + result = urllib_parse.urlencode(given, encoding="ASCII", errors="replace") + self.assertEqual(expect, result) + + # Default is UTF-8 encoding. + given = (('\u00a0', '\u00c1'),) + expect = '%C2%A0=%C3%81' + result = urllib_parse.urlencode(given) + self.assertEqual(expect, result) + + # Latin-1 encoding. + given = (('\u00a0', '\u00c1'),) + expect = '%A0=%C1' + result = urllib_parse.urlencode(given, encoding="latin-1") + self.assertEqual(expect, result) + + def test_urlencode_encoding_doseq(self): + # ASCII Encoding. Expect %3F with errors="replace' + given = (('\u00a0', '\u00c1'),) + expect = '%3F=%3F' + result = urllib_parse.urlencode(given, doseq=True, + encoding="ASCII", errors="replace") + self.assertEqual(expect, result) + + # ASCII Encoding. On a sequence of values. + given = (("\u00a0", (1, "\u00c1")),) + expect = '%3F=1&%3F=%3F' + result = urllib_parse.urlencode(given, True, + encoding="ASCII", errors="replace") + self.assertEqual(expect, result) + + # Utf-8 + given = (("\u00a0", "\u00c1"),) + expect = '%C2%A0=%C3%81' + result = urllib_parse.urlencode(given, True) + self.assertEqual(expect, result) + + given = (("\u00a0", (42, "\u00c1")),) + expect = '%C2%A0=42&%C2%A0=%C3%81' + result = urllib_parse.urlencode(given, True) + self.assertEqual(expect, result) + + # latin-1 + given = (("\u00a0", "\u00c1"),) + expect = '%A0=%C1' + result = urllib_parse.urlencode(given, True, encoding="latin-1") + self.assertEqual(expect, result) + + given = (("\u00a0", (42, "\u00c1")),) + expect = '%A0=42&%A0=%C1' + result = urllib_parse.urlencode(given, True, encoding="latin-1") + self.assertEqual(expect, result) + + def test_urlencode_bytes(self): + given = ((b'\xa0\x24', b'\xc1\x24'),) + expect = '%A0%24=%C1%24' + result = urllib_parse.urlencode(given) + self.assertEqual(expect, result) + result = urllib_parse.urlencode(given, True) + self.assertEqual(expect, result) + + # Sequence of values + given = ((b'\xa0\x24', (42, b'\xc1\x24')),) + expect = '%A0%24=42&%A0%24=%C1%24' + result = urllib_parse.urlencode(given, True) + self.assertEqual(expect, result) + + def test_urlencode_encoding_safe_parameter(self): + + # Send '$' (\x24) as safe character + # Default utf-8 encoding + + given = ((b'\xa0\x24', b'\xc1\x24'),) + result = urllib_parse.urlencode(given, safe=":$") + expect = '%A0$=%C1$' + self.assertEqual(expect, result) + + given = ((b'\xa0\x24', b'\xc1\x24'),) + result = urllib_parse.urlencode(given, doseq=True, safe=":$") + expect = '%A0$=%C1$' + self.assertEqual(expect, result) + + # Safe parameter in sequence + given = ((b'\xa0\x24', (b'\xc1\x24', 0xd, 42)),) + expect = '%A0$=%C1$&%A0$=13&%A0$=42' + result = urllib_parse.urlencode(given, True, safe=":$") + self.assertEqual(expect, result) + + # Test all above in latin-1 encoding + + given = ((b'\xa0\x24', b'\xc1\x24'),) + result = urllib_parse.urlencode(given, safe=":$", + encoding="latin-1") + expect = '%A0$=%C1$' + self.assertEqual(expect, result) + + given = ((b'\xa0\x24', b'\xc1\x24'),) + expect = '%A0$=%C1$' + result = urllib_parse.urlencode(given, doseq=True, safe=":$", + encoding="latin-1") + + given = ((b'\xa0\x24', (b'\xc1\x24', 0xd, 42)),) + expect = '%A0$=%C1$&%A0$=13&%A0$=42' + result = urllib_parse.urlencode(given, True, safe=":$", + encoding="latin-1") + self.assertEqual(expect, result) + +class Pathname_Tests(unittest.TestCase): + """Test pathname2url() and url2pathname()""" + + def test_basic(self): + # Make sure simple tests pass + expected_path = os.path.join("parts", "of", "a", "path") + expected_url = "parts/of/a/path" + result = urllib_request.pathname2url(expected_path) + self.assertEqual(expected_url, result, + "pathname2url() failed; %s != %s" % + (result, expected_url)) + result = urllib_request.url2pathname(expected_url) + self.assertEqual(expected_path, result, + "url2pathame() failed; %s != %s" % + (result, expected_path)) + + def test_quoting(self): + # Test automatic quoting and unquoting works for pathnam2url() and + # url2pathname() respectively + given = os.path.join("needs", "quot=ing", "here") + expect = "needs/%s/here" % urllib_parse.quote("quot=ing") + result = urllib_request.pathname2url(given) + self.assertEqual(expect, result, + "pathname2url() failed; %s != %s" % + (expect, result)) + expect = given + result = urllib_request.url2pathname(result) + self.assertEqual(expect, result, + "url2pathname() failed; %s != %s" % + (expect, result)) + given = os.path.join("make sure", "using_quote") + expect = "%s/using_quote" % urllib_parse.quote("make sure") + result = urllib_request.pathname2url(given) + self.assertEqual(expect, result, + "pathname2url() failed; %s != %s" % + (expect, result)) + given = "make+sure/using_unquote" + expect = os.path.join("make+sure", "using_unquote") + result = urllib_request.url2pathname(given) + self.assertEqual(expect, result, + "url2pathname() failed; %s != %s" % + (expect, result)) + + @unittest.skipUnless(sys.platform == 'win32', + 'test specific to the urllib.url2path function.') + def test_ntpath(self): + given = ('/C:/', '///C:/', '/C|//') + expect = 'C:\\' + for url in given: + result = urllib_request.url2pathname(url) + self.assertEqual(expect, result, + 'urllib_request..url2pathname() failed; %s != %s' % + (expect, result)) + given = '///C|/path' + expect = 'C:\\path' + result = urllib_request.url2pathname(given) + self.assertEqual(expect, result, + 'urllib_request.url2pathname() failed; %s != %s' % + (expect, result)) + +class Utility_Tests(unittest.TestCase): + """Testcase to test the various utility functions in the urllib.""" + + def test_splitpasswd(self): + """Some of password examples are not sensible, but it is added to + confirming to RFC2617 and addressing issue4675. + """ + self.assertEqual(('user', 'ab'),urllib_parse.splitpasswd('user:ab')) + self.assertEqual(('user', 'a\nb'),urllib_parse.splitpasswd('user:a\nb')) + self.assertEqual(('user', 'a\tb'),urllib_parse.splitpasswd('user:a\tb')) + self.assertEqual(('user', 'a\rb'),urllib_parse.splitpasswd('user:a\rb')) + self.assertEqual(('user', 'a\fb'),urllib_parse.splitpasswd('user:a\fb')) + self.assertEqual(('user', 'a\vb'),urllib_parse.splitpasswd('user:a\vb')) + self.assertEqual(('user', 'a:b'),urllib_parse.splitpasswd('user:a:b')) + self.assertEqual(('user', 'a b'),urllib_parse.splitpasswd('user:a b')) + self.assertEqual(('user 2', 'ab'),urllib_parse.splitpasswd('user 2:ab')) + self.assertEqual(('user+1', 'a+b'),urllib_parse.splitpasswd('user+1:a+b')) + + def test_thishost(self): + """Test the urllib_request.thishost utility function returns a tuple""" + self.assertIsInstance(urllib_request.thishost(), tuple) + + +class URLopener_Tests(unittest.TestCase): + """Testcase to test the open method of URLopener class.""" + + def test_quoted_open(self): + class DummyURLopener(urllib_request.URLopener): + def open_spam(self, url): + return url + with support.check_warnings( + ('DummyURLopener style of invoking requests is deprecated.', + DeprecationWarning)): + self.assertEqual(DummyURLopener().open( + 'spam://example/ /'),'//example/%20/') + + # test the safe characters are not quoted by urlopen + self.assertEqual(DummyURLopener().open( + "spam://c:|windows%/:=&?~#+!$,;'@()*[]|/path/"), + "//c:|windows%/:=&?~#+!$,;'@()*[]|/path/") + +# Just commented them out. +# Can't really tell why keep failing in windows and sparc. +# Everywhere else they work ok, but on those machines, sometimes +# fail in one of the tests, sometimes in other. I have a linux, and +# the tests go ok. +# If anybody has one of the problematic enviroments, please help! +# . Facundo +# +# def server(evt): +# import socket, time +# serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +# serv.settimeout(3) +# serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +# serv.bind(("", 9093)) +# serv.listen(5) +# try: +# conn, addr = serv.accept() +# conn.send("1 Hola mundo\n") +# cantdata = 0 +# while cantdata < 13: +# data = conn.recv(13-cantdata) +# cantdata += len(data) +# time.sleep(.3) +# conn.send("2 No more lines\n") +# conn.close() +# except socket.timeout: +# pass +# finally: +# serv.close() +# evt.set() +# +# class FTPWrapperTests(unittest.TestCase): +# +# def setUp(self): +# import ftplib, time, threading +# ftplib.FTP.port = 9093 +# self.evt = threading.Event() +# threading.Thread(target=server, args=(self.evt,)).start() +# time.sleep(.1) +# +# def tearDown(self): +# self.evt.wait() +# +# def testBasic(self): +# # connects +# ftp = urllib.ftpwrapper("myuser", "mypass", "localhost", 9093, []) +# ftp.close() +# +# def testTimeoutNone(self): +# # global default timeout is ignored +# import socket +# self.assertTrue(socket.getdefaulttimeout() is None) +# socket.setdefaulttimeout(30) +# try: +# ftp = urllib.ftpwrapper("myuser", "mypass", "localhost", 9093, []) +# finally: +# socket.setdefaulttimeout(None) +# self.assertEqual(ftp.ftp.sock.gettimeout(), 30) +# ftp.close() +# +# def testTimeoutDefault(self): +# # global default timeout is used +# import socket +# self.assertTrue(socket.getdefaulttimeout() is None) +# socket.setdefaulttimeout(30) +# try: +# ftp = urllib.ftpwrapper("myuser", "mypass", "localhost", 9093, []) +# finally: +# socket.setdefaulttimeout(None) +# self.assertEqual(ftp.ftp.sock.gettimeout(), 30) +# ftp.close() +# +# def testTimeoutValue(self): +# ftp = urllib.ftpwrapper("myuser", "mypass", "localhost", 9093, [], +# timeout=30) +# self.assertEqual(ftp.ftp.sock.gettimeout(), 30) +# ftp.close() + +class RequestTests(unittest.TestCase): + """Unit tests for urllib_request.Request.""" + + def test_default_values(self): + Request = urllib_request.Request + request = Request("http://www.python.org") + self.assertEqual(request.get_method(), 'GET') + request = Request("http://www.python.org", {}) + self.assertEqual(request.get_method(), 'POST') + + def test_with_method_arg(self): + Request = urllib_request.Request + request = Request("http://www.python.org", method='HEAD') + self.assertEqual(request.method, 'HEAD') + self.assertEqual(request.get_method(), 'HEAD') + request = Request("http://www.python.org", {}, method='HEAD') + self.assertEqual(request.method, 'HEAD') + self.assertEqual(request.get_method(), 'HEAD') + request = Request("http://www.python.org", method='GET') + self.assertEqual(request.get_method(), 'GET') + request.method = 'HEAD' + self.assertEqual(request.get_method(), 'HEAD') + + +class URL2PathNameTests(unittest.TestCase): + + @expectedFailurePY26 + def test_converting_drive_letter(self): + self.assertEqual(url2pathname("///C|"), 'C:') + self.assertEqual(url2pathname("///C:"), 'C:') + self.assertEqual(url2pathname("///C|/"), 'C:\\') + + def test_converting_when_no_drive_letter(self): + # cannot end a raw string in \ + self.assertEqual(url2pathname("///C/test/"), r'\\\C\test' '\\') + self.assertEqual(url2pathname("////C/test/"), r'\\C\test' '\\') + + def test_simple_compare(self): + self.assertEqual(url2pathname("///C|/foo/bar/spam.foo"), + r'C:\foo\bar\spam.foo') + + def test_non_ascii_drive_letter(self): + self.assertRaises(IOError, url2pathname, "///\u00e8|/") + + def test_roundtrip_url2pathname(self): + list_of_paths = ['C:', + r'\\\C\test\\', + r'C:\foo\bar\spam.foo' + ] + for path in list_of_paths: + self.assertEqual(url2pathname(pathname2url(path)), path) + +class PathName2URLTests(unittest.TestCase): + + def test_converting_drive_letter(self): + self.assertEqual(pathname2url("C:"), '///C:') + self.assertEqual(pathname2url("C:\\"), '///C:') + + def test_converting_when_no_drive_letter(self): + self.assertEqual(pathname2url(r"\\\folder\test" "\\"), + '/////folder/test/') + self.assertEqual(pathname2url(r"\\folder\test" "\\"), + '////folder/test/') + self.assertEqual(pathname2url(r"\folder\test" "\\"), + '/folder/test/') + + def test_simple_compare(self): + self.assertEqual(pathname2url(r'C:\foo\bar\spam.foo'), + "///C:/foo/bar/spam.foo" ) + + def test_long_drive_letter(self): + self.assertRaises(IOError, pathname2url, "XX:\\") + + def test_roundtrip_pathname2url(self): + list_of_paths = ['///C:', + '/////folder/test/', + '///C:/foo/bar/spam.foo'] + for path in list_of_paths: + self.assertEqual(pathname2url(url2pathname(path)), path) + +if __name__ == '__main__': + unittest.main() From 6e8cfa0994701163f999cea3cda01a2a04d7e04d Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 11 Sep 2015 16:46:31 +1000 Subject: [PATCH 114/425] Add note to docs about missing SSL support in backports currently --- docs/standard_library_imports.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/standard_library_imports.rst b/docs/standard_library_imports.rst index 42503ebe..95cde9c0 100644 --- a/docs/standard_library_imports.rst +++ b/docs/standard_library_imports.rst @@ -116,10 +116,16 @@ follows:: import test.support -The newly exposed ``urllib`` submodules are full backports of those from Py3.x. +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. +**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:: + + from future.moves.urllib.request import urlopen + Backports also exist of the following features from Python 3.4: - ``math.ceil`` returns an int on Py3 From e6b7f3ddcbe86fa6c8d58f2f4baf35744b3ac7ad Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 11 Sep 2015 16:47:34 +1000 Subject: [PATCH 115/425] Update What's New --- docs/whatsnew.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 98c05cc9..647908f9 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -5,12 +5,20 @@ What's New .. _whats-new-0.15.x: +What's new in version 0.15.2 (2015-09-11) +========================================= + +This is a minor bug-fix release: + +- Fix ``socket.create_connection()`` backport on Py2.6 +- Add more tests of ``urllib.request`` etc. + What's new 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 +- 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) From d1d2ae79219bd46177c934e9cf0fda8e8059c85b Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 11 Sep 2015 16:51:36 +1000 Subject: [PATCH 116/425] Note issue number in What's New page for v0.15.2 --- docs/whatsnew.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 647908f9..03a5e071 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -10,7 +10,7 @@ What's new in version 0.15.2 (2015-09-11) This is a minor bug-fix release: -- Fix ``socket.create_connection()`` backport on Py2.6 +- Fix ``socket.create_connection()`` backport on Py2.6 (issue #162) - Add more tests of ``urllib.request`` etc. What's new in version 0.15.1 (2015-09-09) From ee0522ebedd8ab23357538bdd5d1486beef470f8 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 11 Sep 2015 17:00:50 +1000 Subject: [PATCH 117/425] Apply Christopher Arndt's ``newsuper`` fix for PyQt subclasses (issue #160) --- src/future/builtins/newsuper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/builtins/newsuper.py b/src/future/builtins/newsuper.py index 5190a2ac..a787f4bb 100644 --- a/src/future/builtins/newsuper.py +++ b/src/future/builtins/newsuper.py @@ -62,7 +62,7 @@ def newsuper(typ=_SENTINEL, type_or_obj=_SENTINEL, framedepth=1): try: # Get the MRO so we can crawl it. mro = type_or_obj.__mro__ - except AttributeError: + except (AttributeError, RuntimeError): # see issue #160 try: mro = type_or_obj.__class__.__mro__ except AttributeError: From 32ddf32a1d735e45259469621e2337c844be57cf Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 11 Sep 2015 17:07:20 +1000 Subject: [PATCH 118/425] What's New doc: mention fix for issue #160 --- docs/whatsnew.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 03a5e071..5f1a6a4e 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -12,6 +12,8 @@ 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) What's new in version 0.15.1 (2015-09-09) ========================================= From b9786935eac44c7e6d939bcda331a689e10146cb Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 11 Sep 2015 17:13:43 +1000 Subject: [PATCH 119/425] Bump version to v0.15.2 --- src/future/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/__init__.py b/src/future/__init__.py index 607ac934..1295771c 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -88,7 +88,7 @@ __copyright__ = 'Copyright 2013-2015 Python Charmers Pty Ltd' __ver_major__ = 0 __ver_minor__ = 15 -__ver_patch__ = 1 +__ver_patch__ = 2 __ver_sub__ = '' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From 9f60ee008464e0b4128b7806d83a48ee31b75004 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 11 Sep 2015 17:16:20 +1000 Subject: [PATCH 120/425] Update docs: 1000+ tests now :o --- README.rst | 2 +- docs/faq.rst | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index c79aa830..da8539f2 100644 --- a/README.rst +++ b/README.rst @@ -42,7 +42,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 diff --git a/docs/faq.rst b/docs/faq.rst index 5e159144..57fef25e 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -123,13 +123,13 @@ How well has it been tested? currently being used to help with porting 800,000 lines of Python 2 code in `Sage `_ to Python 2/3. -Currently ``python-future`` has 990+ unit tests. Many of these are straight +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? From 0922f8d595d24df3d7127522f506110e841a4e87 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Tue, 15 Sep 2015 15:26:32 +1000 Subject: [PATCH 121/425] Don't add imports above a comment like a shebang line --- src/libfuturize/fixer_util.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/libfuturize/fixer_util.py b/src/libfuturize/fixer_util.py index 0013437b..cf6b5de4 100644 --- a/src/libfuturize/fixer_util.py +++ b/src/libfuturize/fixer_util.py @@ -293,7 +293,8 @@ 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() @@ -376,10 +377,12 @@ def touch_import_top(package, name_to_import, node): 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)) From 55e8227267a4ebbbef89a2d2ae67ceb43c1dcc60 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Tue, 15 Sep 2015 15:29:48 +1000 Subject: [PATCH 122/425] Refactor and clean up fixer_util a bit --- src/libfuturize/fixer_util.py | 57 +++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/src/libfuturize/fixer_util.py b/src/libfuturize/fixer_util.py index cf6b5de4..ce1e9753 100644 --- a/src/libfuturize/fixer_util.py +++ b/src/libfuturize/fixer_util.py @@ -17,6 +17,38 @@ 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,7 +61,7 @@ def Minus(prefix=None): return Leaf(token.MINUS, u'-', prefix=prefix) def commatize(leafs): - u""" + """ Accepts/turns: (Name, Name, ..., Name, Name) Returns/into: (Name, Comma, Name, Comma, ..., Name, Comma, Name) """ @@ -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,7 +110,7 @@ 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 """ @@ -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 @@ -203,8 +243,7 @@ def future_import(feature, node): # Is it a shebang or encoding line? if is_shebang_comment(node) or is_encoding_comment(node): shebang_encoding_idx = idx - if node.type == syms.simple_stmt and \ - len(node.children) > 0 and node.children[0].type == token.STRING: + if is_docstring(node): # skip over docstring continue names = check_future_import(node) @@ -346,7 +385,7 @@ 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 From 39a1186ea3a7a7ad1e8225a151e682d02bafdfa9 Mon Sep 17 00:00:00 2001 From: Evangelos Foutras Date: Sun, 20 Sep 2015 10:17:44 +0300 Subject: [PATCH 123/425] Remove duplicate assertion in builtins tests The other instance of this assertion is commented out because it fails on Python 3.5; see commit 7959135e44a35de7dff4b56dcaf86d9b74d88329 (Disable two assertions in builtins tests for Py3.5 compatibility). --- tests/test_future/test_builtins.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_future/test_builtins.py b/tests/test_future/test_builtins.py index a6db4fda..84db01e9 100644 --- a/tests/test_future/test_builtins.py +++ b/tests/test_future/test_builtins.py @@ -529,7 +529,6 @@ def test_compile(self): self.assertRaises(TypeError, compile, 'pass', '?', 'exec', mode='eval', source='0', filename='tmp') compile('print("\xe5")\n', '', 'exec') - self.assertRaises(TypeError, compile, chr(0), 'f', 'exec') self.assertRaises(ValueError, compile, str('a = 1'), 'f', 'bad') # test the optimize argument From 41f2344dbfe9ccff553bf6e54c1e47fd4c51928b Mon Sep 17 00:00:00 2001 From: Zearin Date: Fri, 6 Nov 2015 09:35:59 -0500 Subject: [PATCH 124/425] Minor formatting and phrasing tweaks --- docs/changelog.rst | 178 ++++++++++++++++++++++----------------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 20c39078..d4fdf55d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -12,10 +12,10 @@ 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) +- 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) @@ -23,15 +23,15 @@ Changes in version 0.14.2 (2014-11-21) This is a bug-fix release: -- Speed up importing of ``past.translation`` (issue #117) +- 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) +- ``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) +- 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) @@ -39,10 +39,10 @@ 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) +- 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) @@ -62,7 +62,7 @@ Instead of this interface:: ... import tkinter.dialog ... # etc. -you can now use the following interface for much Python 2/3 compatible code:: +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 @@ -109,10 +109,10 @@ 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). + (Issue #104) - This release also removes the broken ``--doctests_only`` option from the ``futurize`` - and ``pasteurize`` scripts for now (issue #103). + and ``pasteurize`` scripts for now. (Issue #103) Internal cleanups ----------------- @@ -121,7 +121,7 @@ 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): +The following deprecated internal modules have been removed (Issue #80): - ``future.utils.encoding`` and ``future.utils.six``. @@ -141,11 +141,11 @@ 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 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) +- 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) @@ -170,9 +170,9 @@ 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 ``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). + ...`` 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()``. @@ -181,18 +181,18 @@ 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). + ``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 + (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). +- 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). +- Fix for 3-argument ``pow(x, y, z)`` with ``newint`` arguments. (Thanks to @str4d.) + (Issue #87) .. _whats-new-0.12.4: @@ -200,7 +200,7 @@ Bug fixes 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: @@ -209,29 +209,29 @@ 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: @@ -239,10 +239,10 @@ Changes in version 0.12.3 (2014-06-19) 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: @@ -353,7 +353,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``. @@ -381,7 +381,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 @@ -410,7 +410,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 @@ -434,7 +434,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 @@ -462,9 +462,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. @@ -482,12 +482,12 @@ 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``. @@ -497,7 +497,7 @@ 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. @@ -529,7 +529,7 @@ 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. @@ -564,10 +564,10 @@ 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: @@ -641,16 +641,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 @@ -675,18 +675,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. @@ -703,8 +703,8 @@ types but not their use. 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:: @@ -727,13 +727,13 @@ If not using this context manager, it is now encouraged to add an explicit call 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. @@ -778,7 +778,7 @@ 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 @@ -825,8 +825,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 @@ -873,13 +873,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 ``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. @@ -926,7 +926,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 @@ -1001,12 +1001,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 @@ -1017,7 +1017,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 @@ -1030,7 +1030,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 @@ -1061,23 +1061,23 @@ 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 @@ -1102,7 +1102,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` @@ -1113,7 +1113,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! From 3bf80a650044b6ba882682aa360346af21322e53 Mon Sep 17 00:00:00 2001 From: Matt Bogosian Date: Sat, 21 Nov 2015 14:21:54 -0800 Subject: [PATCH 125/425] Augment tests for #185 (appropriately failing at this point). --- tests/test_future/test_utils.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/test_future/test_utils.py b/tests/test_future/test_utils.py index e2240ea6..080f5826 100644 --- a/tests/test_future/test_utils.py +++ b/tests/test_future/test_utils.py @@ -62,8 +62,8 @@ def test_native_str(self): for s in inputs: self.assertEqual(native_str(s), builtin_str(s)) self.assertTrue(isinstance(native_str(s), builtin_str)) - - def test_native(self): + + def test_native(self): a = int(10**20) # long int b = native(a) self.assertEqual(a, b) @@ -71,7 +71,7 @@ def test_native(self): self.assertEqual(type(b), long) else: self.assertEqual(type(b), int) - + c = bytes(b'ABC') d = native(c) self.assertEqual(c, d) @@ -79,7 +79,7 @@ def test_native(self): self.assertEqual(type(d), type(b'Py2 byte-string')) else: self.assertEqual(type(d), bytes) - + s = str(u'ABC') t = native(s) self.assertEqual(s, t) @@ -151,7 +151,7 @@ def test_raise_from_None(self): except ValueError as e: self.assertTrue(isinstance(e.__context__, TypeError)) self.assertIsNone(e.__cause__) - + @skip26 def test_as_native_str(self): """ @@ -161,9 +161,9 @@ class MyClass(object): @as_native_str() def __repr__(self): return u'abc' - + obj = MyClass() - + self.assertEqual(repr(obj), 'abc') if PY2: self.assertEqual(repr(obj), b'abc') @@ -186,6 +186,9 @@ def test_ensure_new_type(self): self.assertEqual(ensure_new_type(i), i2) self.assertEqual(type(ensure_new_type(i)), int) + l = [] + self.assertIs(ensure_new_type(l), l) + def test_bytes_to_native_str(self): """ Test for issue #47 @@ -215,7 +218,7 @@ class DatabaseError(Exception): # Python 2 and 3: from future.utils import raise_from - + class FileDatabase: def __init__(self, filename): try: @@ -228,7 +231,7 @@ def __init__(self, filename): fd = FileDatabase('non_existent_file.txt') except Exception as e: assert isinstance(e.__cause__, IOError) # FileNotFoundError on - # Py3.3+ inherits from IOError + # Py3.3+ inherits from IOError def testCauseSyntax(self): try: From 2fcbb5afc757ab28e08eebf0fbe146582bdd3836 Mon Sep 17 00:00:00 2001 From: Matt Bogosian Date: Sat, 21 Nov 2015 14:25:01 -0800 Subject: [PATCH 126/425] Fix #185. Return argument unmolested from ensure_new_type if conversion to new type does not exist. --- src/future/utils/__init__.py | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index eb6916ef..3a1e7c4c 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -28,7 +28,7 @@ * tobytes(s) Take a text string, a byte string, or a sequence of characters taken from a byte string, and make a byte string. - + * raise_from() * raise_with_traceback() @@ -66,32 +66,32 @@ def python_2_unicode_compatible(cls): """ A decorator that defines __unicode__ and __str__ methods under Python 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, 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)) @@ -108,13 +108,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 @@ -124,7 +124,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. """ @@ -480,7 +480,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): @@ -489,7 +489,7 @@ def __iter__(self): return self def __next__(self): return next(self._iter).upper() - + ''' if PY3: return cls @@ -520,7 +520,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 @@ -587,7 +587,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')) @@ -656,7 +656,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): @@ -717,7 +717,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] @@ -738,4 +738,3 @@ def ensure_new_type(obj): 'tobytes', 'viewitems', 'viewkeys', 'viewvalues', 'with_metaclass' ] - From 956817cfbda7b3188abeacc81f62d0e4c2c07feb Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 10 Feb 2016 09:08:10 +1100 Subject: [PATCH 127/425] Add a test exposing newbytes constructor bug (issue #193) --- tests/test_future/test_bytes.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_future/test_bytes.py b/tests/test_future/test_bytes.py index f30e7406..ed56e5c0 100644 --- a/tests/test_future/test_bytes.py +++ b/tests/test_future/test_bytes.py @@ -29,6 +29,16 @@ def test_bytes_encoding_arg(self): b = bytes(u, encoding='utf-8') self.assertEqual(b, u.encode('utf-8')) + def test_bytes_encoding_arg_issue_193(self): + """ + This used to be True: bytes(str(u'abc'), 'utf8') == b"b'abc'" + """ + u = u'abc' + b = bytes(str(u), 'utf8') + self.assertNotEqual(b, b"b'abc'") + self.assertEqual(b, b'abc') + self.assertEqual(b, bytes(b'abc')) + def test_bytes_encoding_arg_non_kwarg(self): """ As above, but with a positional argument From abf19bbe002cdf24e42a6c9a2aab0e64fee9fd22 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 10 Feb 2016 09:51:51 +1100 Subject: [PATCH 128/425] Putative fix for issue #193 --- src/future/types/newbytes.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/future/types/newbytes.py b/src/future/types/newbytes.py index fb8bee53..4bbd51f5 100644 --- a/src/future/types/newbytes.py +++ b/src/future/types/newbytes.py @@ -8,6 +8,7 @@ from collections import Iterable from numbers import Integral import string +import copy from future.utils import istext, isbytes, PY3, with_metaclass from future.types import no, issubset @@ -49,7 +50,6 @@ def __new__(cls, *args, **kwargs): - any object implementing the buffer API. - an integer """ - encoding = None errors = None @@ -112,7 +112,15 @@ 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__() From bd8f4949161a3a2b143a495a9e4c10726b50e041 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 10 Feb 2016 10:00:56 +1100 Subject: [PATCH 129/425] Tweak script to upload docs --- docs/other/upload_future_docs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/other/upload_future_docs.sh b/docs/other/upload_future_docs.sh index 9fed43e4..5d1d560a 100644 --- a/docs/other/upload_future_docs.sh +++ b/docs/other/upload_future_docs.sh @@ -12,7 +12,7 @@ zip -r /shared/python-future-html-docs.zip * scp -i ~/.ssh/pythoncharmers_2015.pem /shared/python-future-html-docs.zip python-future.org: scp -i ~/.ssh/pythoncharmers_2015.pem /shared/cheatsheet.pdf python-future.org: -ssh python-future.org +ssh -i ~/.ssh/pythoncharmers_2015.pem python-future.org On the remote machine: From 088cd981e6e3c2b005bc2a3cff15de4281f651b4 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 10 Feb 2016 10:01:07 +1100 Subject: [PATCH 130/425] Update copyright year to 2016 --- LICENSE.txt | 2 +- README.rst | 2 +- docs/compatible_idioms.rst | 2 +- docs/conf.py | 2 +- docs/credits.rst | 2 +- docs/notebooks/Writing Python 2-3 compatible code.ipynb | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 19572a0b..c4dfd4b0 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2013-2015 Python Charmers Pty Ltd, Australia +Copyright (c) 2013-2016 Python Charmers Pty Ltd, Australia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.rst b/README.rst index da8539f2..868ed14b 100644 --- a/README.rst +++ b/README.rst @@ -266,7 +266,7 @@ Licensing :Author: Ed Schofield -:Copyright: 2013-2015 Python Charmers Pty Ltd, Australia. +:Copyright: 2013-2016 Python Charmers Pty Ltd, Australia. :Sponsor: Python Charmers Pty Ltd, Australia, and Python Charmers Pte Ltd, Singapore. http://pythoncharmers.com diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index 0a1e02e1..b523349f 100644 --- a/docs/compatible_idioms.rst +++ b/docs/compatible_idioms.rst @@ -3,7 +3,7 @@ Cheat Sheet: Writing Python 2-3 compatible code =============================================== -- **Copyright (c):** 2013-2015 Python Charmers Pty Ltd, Australia. +- **Copyright (c):** 2013-2016 Python Charmers Pty Ltd, Australia. - **Author:** Ed Schofield. - **Licence:** Creative Commons Attribution. diff --git a/docs/conf.py b/docs/conf.py index 67e92a80..78183b99 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,7 +51,7 @@ # General information about the project. project = u'Python-Future' -copyright = u'2013-2015, Python Charmers Pty Ltd, Australia' +copyright = u'2013-2016, Python Charmers Pty Ltd, Australia' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/credits.rst b/docs/credits.rst index 32cf6604..fd6dd559 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -8,7 +8,7 @@ Licence The software is distributed under an MIT licence. The text is as follows (from ``LICENSE.txt``):: - Copyright (c) 2013-2015 Python Charmers Pty Ltd, Australia + Copyright (c) 2013-2016 Python Charmers Pty Ltd, Australia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/docs/notebooks/Writing Python 2-3 compatible code.ipynb b/docs/notebooks/Writing Python 2-3 compatible code.ipynb index 960ce21f..f33a9204 100644 --- a/docs/notebooks/Writing Python 2-3 compatible code.ipynb +++ b/docs/notebooks/Writing Python 2-3 compatible code.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "- **Copyright (c):** 2013-2015 Python Charmers Pty Ltd, Australia.\n", + "- **Copyright (c):** 2013-2016 Python Charmers Pty Ltd, Australia.\n", "- **Author:** Ed Schofield.\n", "- **Licence:** Creative Commons Attribution.\n", "\n", From 9a24fbdf5ff2cd8775bb40b94a8fdb39f114f61d Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 10 Feb 2016 10:03:20 +1100 Subject: [PATCH 131/425] Add Val Markovic to credits page --- docs/credits.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/credits.rst b/docs/credits.rst index fd6dd559..2bccfc45 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -82,6 +82,7 @@ Suggestions and Feedback - Martijn Faassen - Lion Krischer - Danielle Madeley +- Val Markovic - wluebbe (GitHub) From 181b4f5691900b393da31c5e8c519ec8c68107b4 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 10 Feb 2016 10:05:41 +1100 Subject: [PATCH 132/425] Bump version to v0.15.3-dev and update What's New page --- docs/whatsnew.rst | 8 ++++++++ src/future/__init__.py | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 5f1a6a4e..589c2c8f 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -5,6 +5,14 @@ What's New .. _whats-new-0.15.x: +What's new in version 0.15.3 (2015-02-...) +========================================== + +This is a minor bug-fix release: + +- Fix ``newbytes`` constructor bug (issue #163) + + What's new in version 0.15.2 (2015-09-11) ========================================= diff --git a/src/future/__init__.py b/src/future/__init__.py index 1295771c..9aa3af09 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -88,7 +88,7 @@ __copyright__ = 'Copyright 2013-2015 Python Charmers Pty Ltd' __ver_major__ = 0 __ver_minor__ = 15 -__ver_patch__ = 2 -__ver_sub__ = '' +__ver_patch__ = 3 +__ver_sub__ = '-dev' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From 5e81a4fe8949efa44e2f1b9be10047b6679e9738 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 10 Feb 2016 10:10:58 +1100 Subject: [PATCH 133/425] Add Python 3.5 to list of Trove classifiers in `setup.py` --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 7c81a099..79b71254 100755 --- a/setup.py +++ b/setup.py @@ -106,6 +106,7 @@ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", "License :: OSI Approved", "License :: OSI Approved :: MIT License", "Development Status :: 4 - Beta", From 148d5f21d0fbf73f8f3543c64f4338cc36783ed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiziano=20M=C3=BCller?= Date: Tue, 8 Mar 2016 19:50:48 +0100 Subject: [PATCH 134/425] tests: properly fix assertRaises for pow and compile on python-3.5 --- tests/test_future/test_builtins.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_future/test_builtins.py b/tests/test_future/test_builtins.py index a6db4fda..05d597a5 100644 --- a/tests/test_future/test_builtins.py +++ b/tests/test_future/test_builtins.py @@ -525,11 +525,10 @@ def test_compile(self): self.assertRaises(ValueError, compile, 'print(42)\n', '', 'badmode') self.assertRaises(ValueError, compile, 'print(42)\n', '', 'single', 0xff) # Raises TypeError in Python < v3.5, ValueError in v3.5: - # self.assertRaises(TypeError, compile, chr(0), 'f', 'exec') + self.assertRaises((TypeError, ValueError), compile, chr(0), 'f', 'exec') self.assertRaises(TypeError, compile, 'pass', '?', 'exec', mode='eval', source='0', filename='tmp') compile('print("\xe5")\n', '', 'exec') - self.assertRaises(TypeError, compile, chr(0), 'f', 'exec') self.assertRaises(ValueError, compile, str('a = 1'), 'f', 'bad') # test the optimize argument @@ -1287,7 +1286,7 @@ def test_pow(self): self.assertAlmostEqual(pow(-1, 1/3), 0.5 + 0.8660254037844386j) # Raises TypeError in Python < v3.5, ValueError in v3.5: - # self.assertRaises(TypeError, pow, -1, -2, 3) + self.assertRaises((TypeError, ValueError), pow, -1, -2, 3) self.assertRaises(ValueError, pow, 1, 2, 0) self.assertRaises(TypeError, pow) From 193f63841987469022acf5d3c20d048d8a25c1c6 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 12 Apr 2016 03:45:35 -0700 Subject: [PATCH 135/425] Fixes standard_library.install_aliases() on pypy --- src/future/types/newbytes.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/future/types/newbytes.py b/src/future/types/newbytes.py index fb8bee53..b3dd0bc3 100644 --- a/src/future/types/newbytes.py +++ b/src/future/types/newbytes.py @@ -29,6 +29,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 +49,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 +98,7 @@ def __new__(cls, *args, **kwargs): if errors is not None: newargs.append(errors) value = args[0].encode(*newargs) - ### + ### 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 +109,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): @@ -113,7 +119,7 @@ def __new__(cls, *args, **kwargs): else: value = args[0] return super(newbytes, cls).__new__(cls, value) - + def __repr__(self): return 'b' + super(newbytes, self).__repr__() @@ -140,7 +146,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 +154,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)) @@ -371,7 +377,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) @@ -379,24 +385,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 From 89b95cf1b11826eb364eb709ec3d84fa07d2d44e Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 22 May 2016 17:01:14 +1000 Subject: [PATCH 136/425] Add test for issue #211 (thanks to Tadeusz Sznuk) --- tests/test_future/test_object.py | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/test_future/test_object.py b/tests/test_future/test_object.py index 04392074..07715029 100644 --- a/tests/test_future/test_object.py +++ b/tests/test_future/test_object.py @@ -230,6 +230,47 @@ class MetaClass(type): class TestClass(with_metaclass(MetaClass, object)): pass + def test_bool(self): + """ + Issue #211 + """ + from builtins import object + + class ResultSet(object): + def __len__(self): + return 0 + + self.assertTrue(bool(ResultSet()) is False) + + class ResultSet(object): + def __len__(self): + return 2 + + self.assertTrue(bool(ResultSet()) is True) + + def test_bool2(self): + """ + If __bool__ is defined, the presence or absence of __len__ should + be irrelevant. + """ + from builtins import object + + class TrueThing(object): + def __bool__(self): + return True + def __len__(self): + raise RuntimeError('__len__ should not be called') + + self.assertTrue(bool(TrueThing())) + + class FalseThing(object): + def __bool__(self): + return False + def __len__(self): + raise RuntimeError('__len__ should not be called') + + self.assertFalse(bool(FalseThing())) + if __name__ == '__main__': unittest.main() From 511359a1b203106056ff4d727ad1086eb34c9fdd Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 22 May 2016 17:11:43 +1000 Subject: [PATCH 137/425] Putative fix for issue #211 --- src/future/types/newobject.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/future/types/newobject.py b/src/future/types/newobject.py index 59c19ea5..1ec09ff5 100644 --- a/src/future/types/newobject.py +++ b/src/future/types/newobject.py @@ -86,6 +86,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 From 4ee470afe04cad2b8d33d264799664630d7b3e1e Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 22 May 2016 17:55:55 +1000 Subject: [PATCH 138/425] Issue #190: clean up unused `libfuturize` fixer `FixOldstrWrap` --- src/libfuturize/fixes/fix_oldstr_wrap.py | 39 ------------------------ 1 file changed, 39 deletions(-) diff --git a/src/libfuturize/fixes/fix_oldstr_wrap.py b/src/libfuturize/fixes/fix_oldstr_wrap.py index 575292d8..ad58771d 100644 --- a/src/libfuturize/fixes/fix_oldstr_wrap.py +++ b/src/libfuturize/fixes/fix_oldstr_wrap.py @@ -37,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) - From 4b3a58162bdaf00211350c33aaff61c4eed3cc2d Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 22 May 2016 18:57:44 +1000 Subject: [PATCH 139/425] Add tests for issue #171. The first part seems to be passing. --- tests/test_future/test_bytes.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/tests/test_future/test_bytes.py b/tests/test_future/test_bytes.py index ed56e5c0..e84fde3b 100644 --- a/tests/test_future/test_bytes.py +++ b/tests/test_future/test_bytes.py @@ -29,6 +29,10 @@ def test_bytes_encoding_arg(self): b = bytes(u, encoding='utf-8') self.assertEqual(b, u.encode('utf-8')) + nu = str(u) + b = bytes(nu, encoding='utf-8') + self.assertEqual(b, u.encode('utf-8')) + def test_bytes_encoding_arg_issue_193(self): """ This used to be True: bytes(str(u'abc'), 'utf8') == b"b'abc'" @@ -47,6 +51,10 @@ def test_bytes_encoding_arg_non_kwarg(self): b = bytes(u, 'utf-8') self.assertEqual(b, u.encode('utf-8')) + nu = str(u) + b = bytes(nu, 'utf-8') + self.assertEqual(b, u.encode('utf-8')) + def test_bytes_string_no_encoding(self): with self.assertRaises(TypeError): bytes(u'ABC') @@ -300,7 +308,7 @@ def test_endswith(self): exc = str(cm.exception) # self.assertIn('bytes', exc) # self.assertIn('tuple', exc) - + def test_decode(self): b = bytes(b'abcd') s = b.decode('utf-8') @@ -367,7 +375,7 @@ def test_hash(self): d[s] = s self.assertEqual(len(d), 2) self.assertEqual(set(d.keys()), set([s, b])) - + @unittest.expectedFailure def test_hash_with_native_types(self): # Warning: initializing the dict with native Py2 types throws the @@ -488,7 +496,7 @@ def test_bytes_within_range(self): ValueError ... ValueError: bytes must be in range(0, 256) - + Ensure our bytes() constructor has the same behaviour """ b1 = bytes([254, 255]) @@ -684,6 +692,23 @@ def test_surrogateescape_decoding(self): self.assertTrue(isinstance(decoded, str)) self.assertEqual(b, decoded.encode('utf-8', 'surrogateescape')) + def test_issue_171_part_a(self): + b1 = str(u'abc \u0123 do re mi').encode(u'utf_8') + b2 = bytes(u'abc \u0123 do re mi', u'utf_8') + b3 = bytes(str(u'abc \u0123 do re mi'), u'utf_8') + + @expectedFailurePY2 + def test_issue_171_part_b(self): + """ + Tests whether: + >>> nativebytes = bytes ; nativestr = str ; from builtins import * + >>> nativebytes(bytes(b'asdf'))[0] == b'a' == b'asdf' + """ + nativebytes = type(b'') + nativestr = type('') + b = nativebytes(bytes(b'asdf')) + self.assertEqual(b, b'asdf') + if __name__ == '__main__': unittest.main() From bd4381db4c9ff63479e475e6757dc39ceb66e397 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 22 May 2016 20:29:45 +1000 Subject: [PATCH 140/425] Remove configparser alias package (issue #118) --- docs/compatible_idioms.rst | 2 +- docs/pasteurize.rst | 10 +++++++- docs/quickstart.rst | 1 - docs/standard_library_imports.rst | 6 ++--- docs/whatsnew.rst | 27 +++++++++++++++++----- setup.py | 6 ++--- src/configparser/__init__.py | 13 ----------- src/future/__init__.py | 1 - src/future/standard_library/__init__.py | 6 ++--- tests/test_future/test_futurize.py | 4 +++- tests/test_future/test_standard_library.py | 7 +++--- 11 files changed, 47 insertions(+), 36 deletions(-) delete mode 100644 src/configparser/__init__.py diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index b523349f..e48ac6d1 100644 --- a/docs/compatible_idioms.rst +++ b/docs/compatible_idioms.rst @@ -1413,7 +1413,7 @@ 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 ~~~~~ diff --git a/docs/pasteurize.rst b/docs/pasteurize.rst index 9ed6cb1d..66c5d2f6 100644 --- a/docs/pasteurize.rst +++ b/docs/pasteurize.rst @@ -6,6 +6,7 @@ Running ``pasteurize -w mypy3module.py`` turns this Python 3 code:: import configparser + import copyreg class Blah: pass @@ -18,6 +19,7 @@ into this code which runs on both Py2 and Py3:: standard_library.install_hooks() import configparser + import copyreg class Blah(object): pass @@ -27,6 +29,13 @@ Notice that both ``futurize`` and ``pasteurize`` create explicit new-style 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.python.org/pypi/configparser), so, as +of v0.18.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 @@ -36,4 +45,3 @@ refer to stdlib modules (as well as builtins) under their Py3 names. To handle function annotations (PEP 3107), see :ref:`func_annotations`. - diff --git a/docs/quickstart.rst b/docs/quickstart.rst index e241d8ae..b0d5900f 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -100,7 +100,6 @@ to be accessed under their Python 3 names and locations in Python 2:: # 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, ChainMap # even on Py2.6 from itertools import filterfalse, zip_longest diff --git a/docs/standard_library_imports.rst b/docs/standard_library_imports.rst index 95cde9c0..a8299cff 100644 --- a/docs/standard_library_imports.rst +++ b/docs/standard_library_imports.rst @@ -24,7 +24,6 @@ Python 3 code runs unchanged on Python 2 after installing ``future``:: >>> # Top-level packages with Py3 names provided on Py2: >>> import queue - >>> import configparser >>> import tkinter.dialog >>> etc. @@ -32,14 +31,13 @@ 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 @@ -79,6 +77,8 @@ complete list is here:: import _markupbase import _thread +Note that, as of v0.18.0, ``python-future`` no longer includes an alias for the +``configparser`` module because a full backport exists (see https://pypi.python.org/pypi/configparser). .. _list-standard-library-refactored: diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 589c2c8f..79adc9f3 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -3,15 +3,30 @@ What's New ********** -.. _whats-new-0.15.x: +.. _whats-new-0.16.x: -What's new in version 0.15.3 (2015-02-...) +What's new in version 0.16.0 (2016-05-...) ========================================== -This is a minor bug-fix release: - -- Fix ``newbytes`` constructor bug (issue #163) - +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 +confusion and various compatibility issues. (Issue #118) + +This warrants a new major version number for ``python-future`` because Py2/3 +code that uses `configparser` will no longer run on Py2 systems without the +`configparser` backport installed. As an upgrade path, run ``pip install +configparser`` or add ``configparser`` to your ``requirements.txt`` file. + +This releases fixes these bugs: + +- Fix ``newbytes`` constructor bug. (Issue #163) +- 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) +- Allow the `old_div` fixer to be disabled. (Issue #190) What's new in version 0.15.2 (2015-09-11) ========================================= diff --git a/setup.py b/setup.py index 79b71254..ef85e4da 100755 --- a/setup.py +++ b/setup.py @@ -58,7 +58,7 @@ if sys.version_info[:2] < (3, 0): PACKAGES += [ "builtins", - "configparser", + # "configparser", # removed in v0.18.0 "copyreg", "html", "http", @@ -125,7 +125,7 @@ # If the user happens to run: # python2 setup.py build # python3 setup.py install - # then folders like "configparser" will be in build/lib. + # 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 configparser" etc. may pick up our Py2 @@ -135,7 +135,7 @@ '_markupbase', '_thread', 'builtins', - 'configparser', + # 'configparser', 'copyreg', 'html', 'http', diff --git a/src/configparser/__init__.py b/src/configparser/__init__.py deleted file mode 100644 index 3642c5ec..00000000 --- a/src/configparser/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import absolute_import -import sys - -if sys.version_info[0] < 3: - from ConfigParser import * - try: - from ConfigParser import (_Chainmap, Error, InterpolationMissingOptionError) - except ImportError: - pass -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 9aa3af09..cbea9b63 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -31,7 +31,6 @@ following Py3 interfaces: >>> # Top-level packages with Py3 names provided on Py2: - >>> import configparser >>> import html.parser >>> import queue >>> import tkinter.dialog diff --git a/src/future/standard_library/__init__.py b/src/future/standard_library/__init__.py index 9e22c59d..4cd1c7c9 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -11,7 +11,6 @@ And then these normal Py3 imports work on both Py3 and Py2:: import builtins - import configparser import copyreg import queue import reprlib @@ -739,8 +738,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.18.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 diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index a50e662c..f3fe0b84 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -631,7 +631,9 @@ def test_renamed_modules(self): import pickle import io """ - self.convert_check(before, after) + # We can't run the converted code because configparser may + # not be there. + self.convert_check(before, after, run=False) @unittest.skip('Not working yet ...') def test_urllib_refactor(self): diff --git a/tests/test_future/test_standard_library.py b/tests/test_future/test_standard_library.py index 399d587f..e4d6ed8a 100644 --- a/tests/test_future/test_standard_library.py +++ b/tests/test_future/test_standard_library.py @@ -134,7 +134,7 @@ def test_disable_hooks(self): # Imports should succeed again now: import builtins - import configparser + import html if utils.PY2: self.assertTrue(standard_library.detect_hooks()) self.assertTrue(len(old_meta_path) == len(sys.meta_path)) @@ -271,8 +271,9 @@ def test_import_failure_from_module(self): with self.assertRaises(CalledProcessError): output = self._run_test_script('importme1.py') - def test_configparser(self): - import configparser + # Disabled since v0.18.0: + # def test_configparser(self): + # import configparser def test_copyreg(self): import copyreg From 1e3c96cf541193cedec6cd5eab3c0b48abde5063 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 22 May 2016 20:34:34 +1000 Subject: [PATCH 141/425] Add a note about removing ``configparser`` to the What's New doc --- docs/whatsnew.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 79adc9f3..fa2b6b65 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -19,7 +19,12 @@ code that uses `configparser` will no longer run on Py2 systems without the `configparser` backport installed. As an upgrade path, run ``pip install configparser`` or add ``configparser`` to your ``requirements.txt`` file. -This releases fixes these bugs: +Note that, if you are upgrading ``future`` with ``pip``, you will need to +uninstall the old version of future or manually remove the +``site-packages/future-0.15.3.dev0-py2.7.egg/configparser/`` folder for this +change to take effect on your system. + +This releases fixes also these bugs: - Fix ``newbytes`` constructor bug. (Issue #163) - Fix semantics of `bool()` with `newobject`. (Issue #211) From 62be9a0974961fe26ab5ba65d146d857e47f7061 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sun, 22 May 2016 20:48:48 +1000 Subject: [PATCH 142/425] Fix docs/comments to refer to the correct version number --- docs/pasteurize.rst | 2 +- docs/standard_library_imports.rst | 2 +- setup.py | 2 +- src/future/standard_library/__init__.py | 2 +- tests/test_future/test_standard_library.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/pasteurize.rst b/docs/pasteurize.rst index 66c5d2f6..dc434759 100644 --- a/docs/pasteurize.rst +++ b/docs/pasteurize.rst @@ -31,7 +31,7 @@ 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.python.org/pypi/configparser), so, as -of v0.18.0, ``python-future`` no longer provides a ``configparser`` package +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. diff --git a/docs/standard_library_imports.rst b/docs/standard_library_imports.rst index a8299cff..e0c87746 100644 --- a/docs/standard_library_imports.rst +++ b/docs/standard_library_imports.rst @@ -77,7 +77,7 @@ complete list is here:: import _markupbase import _thread -Note that, as of v0.18.0, ``python-future`` no longer includes an alias for the +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.python.org/pypi/configparser). .. _list-standard-library-refactored: diff --git a/setup.py b/setup.py index ef85e4da..f2cf1074 100755 --- a/setup.py +++ b/setup.py @@ -58,7 +58,7 @@ if sys.version_info[:2] < (3, 0): PACKAGES += [ "builtins", - # "configparser", # removed in v0.18.0 + # "configparser", # removed in v0.16.0 "copyreg", "html", "http", diff --git a/src/future/standard_library/__init__.py b/src/future/standard_library/__init__.py index 4cd1c7c9..1047e583 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -738,7 +738,7 @@ 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. - (This was need prior to v0.18.0 because the presence of a configparser + (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?) """ diff --git a/tests/test_future/test_standard_library.py b/tests/test_future/test_standard_library.py index e4d6ed8a..647da550 100644 --- a/tests/test_future/test_standard_library.py +++ b/tests/test_future/test_standard_library.py @@ -271,7 +271,7 @@ def test_import_failure_from_module(self): with self.assertRaises(CalledProcessError): output = self._run_test_script('importme1.py') - # Disabled since v0.18.0: + # Disabled since v0.16.0: # def test_configparser(self): # import configparser From ca957091c842abbc2cbd707556f17d9118ce90cc Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 24 Jun 2016 22:26:55 +1000 Subject: [PATCH 143/425] Update copyright dates to 2016 --- futurize.py | 2 +- pasteurize.py | 2 +- src/future/__init__.py | 4 ++-- src/past/__init__.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/futurize.py b/futurize.py index b8b734d4..5f1a7732 100755 --- a/futurize.py +++ b/futurize.py @@ -13,7 +13,7 @@ Licensing --------- -Copyright 2013-2015 Python Charmers Pty Ltd, Australia. +Copyright 2013-2016 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ diff --git a/pasteurize.py b/pasteurize.py index 591c1414..db7931ab 100755 --- a/pasteurize.py +++ b/pasteurize.py @@ -12,7 +12,7 @@ Licensing --------- -Copyright 2013-2015 Python Charmers Pty Ltd, Australia. +Copyright 2013-2016 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ diff --git a/src/future/__init__.py b/src/future/__init__.py index cbea9b63..61158b54 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -76,7 +76,7 @@ Licensing --------- -Copyright 2013-2015 Python Charmers Pty Ltd, Australia. +Copyright 2013-2016 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ @@ -84,7 +84,7 @@ __title__ = 'future' __author__ = 'Ed Schofield' __license__ = 'MIT' -__copyright__ = 'Copyright 2013-2015 Python Charmers Pty Ltd' +__copyright__ = 'Copyright 2013-2016 Python Charmers Pty Ltd' __ver_major__ = 0 __ver_minor__ = 15 __ver_patch__ = 3 diff --git a/src/past/__init__.py b/src/past/__init__.py index e31678b3..08eeb58d 100644 --- a/src/past/__init__.py +++ b/src/past/__init__.py @@ -80,7 +80,7 @@ Licensing --------- -Copyright 2013-2015 Python Charmers Pty Ltd, Australia. +Copyright 2013-2016 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ From 54d0e17f52aa74e5b7f0e0b84ab14216708ef69c Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 24 Jun 2016 22:46:29 +1000 Subject: [PATCH 144/425] Update upload instructions for docs --- docs/other/upload_future_docs.sh | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/other/upload_future_docs.sh b/docs/other/upload_future_docs.sh index 5d1d560a..006a45a9 100644 --- a/docs/other/upload_future_docs.sh +++ b/docs/other/upload_future_docs.sh @@ -1,7 +1,6 @@ -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/ @@ -10,15 +9,14 @@ touch /shared/python-future-html-docs.zip rm /shared/python-future-html-docs.zip zip -r /shared/python-future-html-docs.zip * -scp -i ~/.ssh/pythoncharmers_2015.pem /shared/python-future-html-docs.zip python-future.org: -scp -i ~/.ssh/pythoncharmers_2015.pem /shared/cheatsheet.pdf python-future.org: -ssh -i ~/.ssh/pythoncharmers_2015.pem python-future.org +scp /shared/python-future-html-docs.zip python-future.org: +scp /shared/cheatsheet.pdf python-future.org: +ssh 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 From ba01cca6dcb5c353d9f11315c0bac92b984184c1 Mon Sep 17 00:00:00 2001 From: Matthew Parnell Date: Wed, 29 Jun 2016 18:51:23 -0400 Subject: [PATCH 145/425] Add ChainMap into `install_aliases` Your website notes that the `futures.standard_library.install_aliases()` adds support for `collections.ChainMap`. I found out it didn't, it seemed this line was missing. I have tested on Python 2.7.11 and it works fine. --- src/future/standard_library/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/future/standard_library/__init__.py b/src/future/standard_library/__init__.py index 9e22c59d..3fc69560 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -31,7 +31,7 @@ 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 @@ -196,6 +196,7 @@ ('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'), From 47255c6e7c5d5c5bd0992a081d165803bd9fd63d Mon Sep 17 00:00:00 2001 From: Nate Bogdanowicz Date: Thu, 30 Jun 2016 18:14:49 -0700 Subject: [PATCH 146/425] Add more robust support for --nofix --- src/libfuturize/main.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/libfuturize/main.py b/src/libfuturize/main.py index 18f33ec0..02808786 100644 --- a/src/libfuturize/main.py +++ b/src/libfuturize/main.py @@ -205,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: From ccc4955f0d9b100e599ffb24bdbba84efcf92df2 Mon Sep 17 00:00:00 2001 From: Martijn Jacobs Date: Wed, 27 Jul 2016 15:12:48 +0200 Subject: [PATCH 147/425] 'map' is also in six.moves --- docs/compatible_idioms.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index 0a1e02e1..cb5b8a37 100644 --- a/docs/compatible_idioms.rst +++ b/docs/compatible_idioms.rst @@ -860,6 +860,14 @@ imap 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 ~~~~~~~~~ From 7655484a57171110cc65fb74c4b8b8ff3b01d2c1 Mon Sep 17 00:00:00 2001 From: Grant Bakker Date: Thu, 4 Aug 2016 11:29:09 -0700 Subject: [PATCH 148/425] Added type to __get__ call to prevent some c modules from segfaulting. --- src/future/builtins/newsuper.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/future/builtins/newsuper.py b/src/future/builtins/newsuper.py index a787f4bb..5d3402bd 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,14 +51,14 @@ 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__ @@ -67,9 +67,9 @@ def newsuper(typ=_SENTINEL, type_or_obj=_SENTINEL, framedepth=1): mro = type_or_obj.__class__.__mro__ 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: + # If unfamiliar with for...else, see: # # http://psung.blogspot.com/2007/12/for-else-in-python.html for typ in mro: @@ -88,7 +88,7 @@ def newsuper(typ=_SENTINEL, type_or_obj=_SENTINEL, framedepth=1): try: meth = meth.__func__ except AttributeError: - meth = meth.__get__(type_or_obj) + meth = meth.__get__(type_or_obj, typ) except (AttributeError, TypeError): continue if meth.func_code is f.f_code: @@ -98,7 +98,7 @@ def newsuper(typ=_SENTINEL, type_or_obj=_SENTINEL, framedepth=1): break # Found! Break out of the search loop. else: 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) @@ -112,4 +112,3 @@ def superm(*args, **kwds): __all__ = ['newsuper'] - From 5b25b778b63353df0f97e538ea28e44b3dce53e2 Mon Sep 17 00:00:00 2001 From: Fergus Symon Date: Tue, 16 Aug 2016 13:40:41 +1000 Subject: [PATCH 149/425] Add IntelliJ (PyCharm) folder to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index e211af6a..3cdec3ca 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,5 @@ nosetests.xml .project .pydevproject +# IntelliJ +.idea From 88b621fe4ff6cd1f977df0fa79b74c560498cfd2 Mon Sep 17 00:00:00 2001 From: Fergus Symon Date: Tue, 16 Aug 2016 15:12:45 +1000 Subject: [PATCH 150/425] Add multiprocessing's SimpleQueue to those fixed by future --- src/future/moves/multiprocessing.py | 7 +++++++ src/future/standard_library/__init__.py | 3 +++ src/libpasteurize/fixes/fix_imports.py | 1 + tests/test_future/test_standard_library.py | 9 +++++++++ 4 files changed, 20 insertions(+) create mode 100644 src/future/moves/multiprocessing.py 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/standard_library/__init__.py b/src/future/standard_library/__init__.py index 9e22c59d..9aa654bd 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -34,6 +34,7 @@ from collections import OrderedDict, Counter # 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.) @@ -109,6 +110,7 @@ 'future.moves.socketserver': 'socketserver', 'ConfigParser': 'configparser', 'repr': 'reprlib', + 'multiprocessing.queues': 'multiprocessing', # 'FileDialog': 'tkinter.filedialog', # 'tkFileDialog': 'tkinter.filedialog', # 'SimpleDialog': 'tkinter.simpledialog', @@ -184,6 +186,7 @@ ('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+). diff --git a/src/libpasteurize/fixes/fix_imports.py b/src/libpasteurize/fixes/fix_imports.py index 4db0d548..d5b21009 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", diff --git a/tests/test_future/test_standard_library.py b/tests/test_future/test_standard_library.py index 399d587f..d7a10294 100644 --- a/tests/test_future/test_standard_library.py +++ b/tests/test_future/test_standard_library.py @@ -295,6 +295,15 @@ def test_bytesio(self): for method in ['tell', 'read', 'seek', 'close', 'flush', 'getvalue']: self.assertTrue(hasattr(s, method)) + def test_SimpleQueue(self): + from multiprocessing import SimpleQueue + sq = SimpleQueue() + self.assertTrue(sq.empty()) + sq.put('thing') + self.assertFalse(sq.empty()) + self.assertEqual(sq.get(), 'thing') + self.assertTrue(sq.empty()) + def test_queue(self): import queue q = queue.Queue() From efe07a0c237483e023e74df82c617a79f14789ce Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 21 Sep 2016 12:39:29 +0200 Subject: [PATCH 151/425] Add test for issue #235 --- tests/test_future/test_utils.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/test_future/test_utils.py b/tests/test_future/test_utils.py index 080f5826..124c6ebd 100644 --- a/tests/test_future/test_utils.py +++ b/tests/test_future/test_utils.py @@ -152,6 +152,26 @@ def test_raise_from_None(self): self.assertTrue(isinstance(e.__context__, TypeError)) self.assertIsNone(e.__cause__) + def test_issue_235(self): + class MyException(Exception): + def __init__(self, a, b): + super(MyException, self).__init__('{0}: {1}'.format(a, 7)) + + def foo(): + raise MyException(3, 7) + + def bar(): + try: + foo() + except Exception as err: + raise_from(ValueError('blue'), err) + + try: + bar() + except ValueError as e: + pass + # incorrectly raises a TypeError on Py3 as of v0.15.2. + @skip26 def test_as_native_str(self): """ From 7d311e4d111f167fa9cea0120838a51818e7c60e Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 21 Sep 2016 13:04:16 +0200 Subject: [PATCH 152/425] Add missing cmp_to_key for Py2.6 (issue #189) --- docs/whatsnew.rst | 1 + src/future/__init__.py | 4 ++-- src/future/backports/__init__.py | 1 + src/future/backports/misc.py | 26 +++++++++++++++++++++++++ src/future/standard_library/__init__.py | 1 + 5 files changed, 31 insertions(+), 2 deletions(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index fa2b6b65..6776d964 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -31,6 +31,7 @@ This releases fixes also these bugs: - 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) What's new in version 0.15.2 (2015-09-11) diff --git a/src/future/__init__.py b/src/future/__init__.py index 61158b54..83617aea 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -86,8 +86,8 @@ __license__ = 'MIT' __copyright__ = 'Copyright 2013-2016 Python Charmers Pty Ltd' __ver_major__ = 0 -__ver_minor__ = 15 -__ver_patch__ = 3 +__ver_minor__ = 16 +__ver_patch__ = 0 __ver_sub__ = '-dev' __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 a8c95e27..68291141 100644 --- a/src/future/backports/__init__.py +++ b/src/future/backports/__init__.py @@ -22,4 +22,5 @@ count, recursive_repr, _count_elements, + cmp_to_key ) diff --git a/src/future/backports/misc.py b/src/future/backports/misc.py index 4265cefb..513391b5 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -9,7 +9,9 @@ - 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 __future__ import absolute_import import subprocess @@ -881,6 +883,28 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT, 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 @@ -892,6 +916,7 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT, _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: @@ -900,6 +925,7 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT, from subprocess import check_output from itertools import count from socket import create_connection + from functools import cmp_to_key if sys.version_info >= (3, 0): from math import ceil diff --git a/src/future/standard_library/__init__.py b/src/future/standard_library/__init__.py index 1047e583..57b14121 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -197,6 +197,7 @@ ('collections', 'Counter', 'future.backports.misc', 'Counter'), ('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'), From 62829227c50d5f91ff973b23af88955d64c36533 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 21 Sep 2016 13:12:25 +0200 Subject: [PATCH 153/425] Update What's New doc --- docs/whatsnew.rst | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 6776d964..754b8414 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -12,19 +12,18 @@ 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 -confusion and various compatibility issues. (Issue #118) +various compatibility issues. (Issue #118) -This warrants a new major version number for ``python-future`` because Py2/3 -code that uses `configparser` will no longer run on Py2 systems without the -`configparser` backport installed. As an upgrade path, run ``pip install +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 will need to +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.3.dev0-py2.7.egg/configparser/`` folder for this -change to take effect on your system. +``site-packages/future-0.15.2-py2.7.egg`` folder for this change to take +effect on your system. -This releases fixes also these bugs: +This releases also fixes these bugs: - Fix ``newbytes`` constructor bug. (Issue #163) - Fix semantics of `bool()` with `newobject`. (Issue #211) From c75378e4a8e925b44102a0f604f45cc97f6c0933 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 21 Sep 2016 13:25:58 +0200 Subject: [PATCH 154/425] Remove attempt to import top-level configparser --- src/future/standard_library/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/future/standard_library/__init__.py b/src/future/standard_library/__init__.py index 57b14121..bfe6e67d 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -787,7 +787,6 @@ def __exit__(self, *args): sys.modules[m] = self.old_sys_modules[m] TOP_LEVEL_MODULES = ['builtins', - 'configparser', 'copyreg', 'html', 'http', From a6ed514a4b17cf03a46bcdb49f85f30f9eb42bcc Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 21 Sep 2016 13:39:36 +0200 Subject: [PATCH 155/425] Add _flatten and _cnfmerge to tkinter package (issue #233) --- src/future/moves/tkinter/__init__.py | 1 + src/tkinter/__init__.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/future/moves/tkinter/__init__.py b/src/future/moves/tkinter/__init__.py index 09442e46..96937450 100644 --- a/src/future/moves/tkinter/__init__.py +++ b/src/future/moves/tkinter/__init__.py @@ -4,5 +4,6 @@ if not PY3: from Tkinter import * + from Tkinter import _flatten, _cnfmerge else: from tkinter import * diff --git a/src/tkinter/__init__.py b/src/tkinter/__init__.py index cff06c0e..73ebaa6e 100644 --- a/src/tkinter/__init__.py +++ b/src/tkinter/__init__.py @@ -3,6 +3,7 @@ if sys.version_info[0] < 3: from Tkinter import * + from Tkinter import _flatten, _cnfmerge else: raise ImportError('This package should not be accessible on Python 3. ' 'Either you are trying to run from the python-future src folder ' From 5cd9587d21d04443ded94244caf0474cef0c6a21 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 22 Sep 2016 05:33:33 +0200 Subject: [PATCH 156/425] Add more underscore-prefixed imports to `tkinter` (issue #233) --- src/future/moves/tkinter/__init__.py | 4 +++- src/tkinter/__init__.py | 4 +++- src/tkinter/filedialog.py | 6 +++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/future/moves/tkinter/__init__.py b/src/future/moves/tkinter/__init__.py index 96937450..bc50b4c6 100644 --- a/src/future/moves/tkinter/__init__.py +++ b/src/future/moves/tkinter/__init__.py @@ -4,6 +4,8 @@ if not PY3: from Tkinter import * - from Tkinter import _flatten, _cnfmerge + from Tkinter import (_cnfmerge, _default_root, _flatten, _join, _setit, + _splitdict, _stringify, _support_default_root, _test, + _tkinter) else: from tkinter import * diff --git a/src/tkinter/__init__.py b/src/tkinter/__init__.py index 73ebaa6e..d0e1b0af 100644 --- a/src/tkinter/__init__.py +++ b/src/tkinter/__init__.py @@ -3,7 +3,9 @@ if sys.version_info[0] < 3: from Tkinter import * - from Tkinter import _flatten, _cnfmerge + from Tkinter import (_cnfmerge, _default_root, _flatten, _join, _setit, + _splitdict, _stringify, _support_default_root, _test, + _tkinter) else: raise ImportError('This package should not be accessible on Python 3. ' 'Either you are trying to run from the python-future src folder ' 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?') From 7f10e746ac5354c9dfe96239c8212615f4a169ea Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 22 Sep 2016 05:42:44 +0200 Subject: [PATCH 157/425] Discourage use of `future.standard_library.hooks` in docs (issue #238) --- docs/older_interfaces.rst | 50 ++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/docs/older_interfaces.rst b/docs/older_interfaces.rst index bbf07f16..463d5775 100644 --- a/docs/older_interfaces.rst +++ b/docs/older_interfaces.rst @@ -9,28 +9,6 @@ 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 __________________________ @@ -92,8 +70,32 @@ functions from ``future.standard_library`` as follows:: This interface also works with two-level imports. -install_hooks() call -____________________ +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 0f34dd009704be1523c5becd205330840e5e9147 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 22 Sep 2016 05:54:50 +0200 Subject: [PATCH 158/425] Ignore ImportError with `subprocess.check_output` for GAE compatibility (issue #231) --- src/future/backports/misc.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/future/backports/misc.py b/src/future/backports/misc.py index 513391b5..31e713ae 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -922,10 +922,14 @@ def __hash__(self): # from the standard library: if sys.version_info >= (2, 7): from collections import OrderedDict, Counter - from subprocess import check_output from itertools import count - from socket import create_connection 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 44c83fef5505ec863e62eeffed92e240fa397010 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 22 Sep 2016 06:03:22 +0200 Subject: [PATCH 159/425] Update What's New and other docs --- docs/whatsnew.rst | 6 ++++-- setup.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 754b8414..c91655ec 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -5,14 +5,14 @@ What's New .. _whats-new-0.16.x: -What's new in version 0.16.0 (2016-05-...) +What's new in version 0.16.0 (2016-09-22) ========================================== 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. (Issue #118) +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 @@ -32,6 +32,8 @@ This releases also fixes these bugs: - 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) What's new in version 0.15.2 (2015-09-11) ========================================= diff --git a/setup.py b/setup.py index f2cf1074..9665c5eb 100755 --- a/setup.py +++ b/setup.py @@ -128,7 +128,7 @@ # 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 configparser" etc. may pick up our Py2 + # 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', From a7447b02cf0f834d0094b7c6cd0bdbad876cb637 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 22 Sep 2016 06:06:22 +0200 Subject: [PATCH 160/425] Bump version to v0.16.0 --- src/future/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/__init__.py b/src/future/__init__.py index 83617aea..8139aa33 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -88,6 +88,6 @@ __ver_major__ = 0 __ver_minor__ = 16 __ver_patch__ = 0 -__ver_sub__ = '-dev' +__ver_sub__ = '' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From 25a86feb774bd6b2ec80be224c07400d7dd6fb54 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 27 Oct 2016 21:47:19 +0200 Subject: [PATCH 161/425] Fix tests for issue #235 --- tests/test_future/test_utils.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_future/test_utils.py b/tests/test_future/test_utils.py index 124c6ebd..19dc11d3 100644 --- a/tests/test_future/test_utils.py +++ b/tests/test_future/test_utils.py @@ -18,6 +18,11 @@ TEST_UNICODE_STR = u'ℝεα∂@ßʟ℮ ☂ℯṧт υηḯ¢☺ḓ℮' +class MyExceptionIssue235(Exception): + def __init__(self, a, b): + super(MyExceptionIssue235, self).__init__('{0}: {1}'.format(a, b)) + + class TestUtils(unittest.TestCase): def setUp(self): self.s = TEST_UNICODE_STR @@ -153,12 +158,8 @@ def test_raise_from_None(self): self.assertIsNone(e.__cause__) def test_issue_235(self): - class MyException(Exception): - def __init__(self, a, b): - super(MyException, self).__init__('{0}: {1}'.format(a, 7)) - def foo(): - raise MyException(3, 7) + raise MyExceptionIssue235(3, 7) def bar(): try: @@ -288,7 +289,6 @@ def test_class_cause(self): else: self.fail("No exception raised") - @expectedFailurePY3 def test_instance_cause(self): cause = KeyError('blah') try: From d78983763f6de1c375ee9356ea6e21db723314d5 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 27 Oct 2016 21:48:40 +0200 Subject: [PATCH 162/425] Add fix for issues #235 and (probably) #213 --- src/future/utils/__init__.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index 3a1e7c4c..cb4ade35 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -387,15 +387,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): From 5cbeb4b2c48cda871648a0470b2f5768b61298dd Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 27 Oct 2016 21:49:50 +0200 Subject: [PATCH 163/425] Update docs re issues #235, #213 --- docs/credits.rst | 3 +++ docs/whatsnew.rst | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/credits.rst b/docs/credits.rst index 2bccfc45..011936d5 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -74,6 +74,7 @@ Patches - Mystic-Mirage (GitHub) - str4d (GitHub) - 9seconds (GitHub) +- Varriount (GitHub) Suggestions and Feedback ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -104,6 +105,8 @@ 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. diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index c91655ec..582b506b 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -29,11 +29,16 @@ This releases also fixes these bugs: - 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) +- 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) +- Add some missing imports to the `tkinter` and `tkinter.filedialog` + package namespaces. (Issues #212 and #233) +- Fix ``raise_from`` on PY3 when the exception cannot be recreated from + its repr. (Issues #213 and #235, fix provided by Varriount) + What's new in version 0.15.2 (2015-09-11) ========================================= From d25f4f8f8e13fd1d5cf959c24e4a4b50520c0590 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 27 Oct 2016 21:50:44 +0200 Subject: [PATCH 164/425] Use the presence of `builtins` rather than `configparser` to indicate the future/src folder --- setup.py | 4 +++- src/future/standard_library/__init__.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 9665c5eb..f168e90f 100755 --- a/setup.py +++ b/setup.py @@ -135,7 +135,9 @@ '_markupbase', '_thread', 'builtins', - # 'configparser', + # Catch the case that configparser is in the build folder + # from a previous version of `future`: + 'configparser', 'copyreg', 'html', 'http', diff --git a/src/future/standard_library/__init__.py b/src/future/standard_library/__init__.py index bfe6e67d..aca58c3e 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -755,7 +755,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: From 8ab0ae01f4bfd495e36e63fa2bf07ed1dd1a420f Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 27 Oct 2016 21:51:33 +0200 Subject: [PATCH 165/425] Mark PEP 461 tests as expected to pass on Py3.5 --- src/future/tests/base.py | 5 ++++- tests/test_future/test_bytes.py | 23 ++++++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/future/tests/base.py b/src/future/tests/base.py index be634539..dc735557 100644 --- a/src/future/tests/base.py +++ b/src/future/tests/base.py @@ -6,7 +6,6 @@ import re import warnings import io -import functools from textwrap import dedent from future.utils import bind_method, PY26, PY3, PY2, PY27 @@ -378,6 +377,10 @@ def expectedFailurePY3(func): return func return unittest.expectedFailure(func) +def expectedFailurePY33_and_PY34(func): + if sys.version_info[:2] not in {(3, 3), (3, 4)}: + return func + return unittest.expectedFailure(func) def expectedFailurePY26(func): if not PY26: diff --git a/tests/test_future/test_bytes.py b/tests/test_future/test_bytes.py index e84fde3b..8d424abd 100644 --- a/tests/test_future/test_bytes.py +++ b/tests/test_future/test_bytes.py @@ -8,7 +8,7 @@ from future import utils from numbers import Integral -from future.tests.base import unittest, expectedFailurePY2 +from future.tests.base import unittest, expectedFailurePY2, expectedFailurePY33_and_PY34 TEST_UNICODE_STR = u'ℝεα∂@ßʟ℮ ☂ℯṧт υηḯ¢☺ḓ℮' @@ -552,7 +552,7 @@ def test_maketrans(self): self.assertRaises(ValueError, bytes.maketrans, b'abc', b'xyzq') self.assertRaises(TypeError, bytes.maketrans, 'abc', 'def') - @unittest.expectedFailure + @expectedFailurePY33_and_PY34 def test_mod(self): """ From Py3.5 test suite (post-PEP 461). @@ -569,7 +569,7 @@ def test_mod(self): a = b % (b'seventy-nine', 79) self.assertEqual(a, b'seventy-nine / 100 = 79%') - @unittest.expectedFailure + @expectedFailurePY33_and_PY34 def test_imod(self): """ From Py3.5 test suite (post-PEP 461) @@ -586,7 +586,7 @@ def test_imod(self): b %= (b'seventy-nine', 79) self.assertEqual(b, b'seventy-nine / 100 = 79%') - @unittest.expectedFailure + @expectedFailurePY33_and_PY34 def test_mod_pep_461(self): """ Test for the PEP 461 functionality (resurrection of %s formatting for @@ -621,9 +621,10 @@ def test_mod_pep_461(self): # is supposed to be equivalent to # ("%x" % val).encode("ascii") for code in b'xdiouxXeEfFgG': - pct_str = u"%" + code.decode('ascii') + bytechar = bytes([code]) + pct_str = u"%" + bytechar.decode('ascii') for val in range(300): - self.assertEqual(bytes(b"%" + code) % val, + self.assertEqual(bytes(b"%" + bytechar) % val, (pct_str % val).encode("ascii")) with self.assertRaises(TypeError): @@ -645,12 +646,12 @@ def test_mod_pep_461(self): self.assertEqual(bytes(b'%a') % 'def', b"'def'") - # PEP 461 specifes that %r is not supported. - with self.assertRaises(TypeError): - bytes(b'%r' % b'abc') + # PEP 461 was updated after an Py3.5 alpha release to specify that %r is now supported + # for compatibility: http://legacy.python.org/dev/peps/pep-0461/#id16 + assert bytes(b'%r' % b'abc') == bytes(b'%a' % b'abc') - with self.assertRaises(TypeError): - bytes(b'%r' % 'abc') + # with self.assertRaises(TypeError): + # bytes(b'%r' % 'abc') @expectedFailurePY2 def test_multiple_inheritance(self): From ecf112163ce9aaa7715c1d777a79e1c66d509e8f Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 27 Oct 2016 21:58:41 +0200 Subject: [PATCH 166/425] Comment out PEP 461 tests for now Python 2.x doesn't support bytes % characters like '%b' --- src/future/tests/base.py | 5 - tests/test_future/test_bytes.py | 199 ++++++++++++++++---------------- 2 files changed, 98 insertions(+), 106 deletions(-) diff --git a/src/future/tests/base.py b/src/future/tests/base.py index dc735557..546c779b 100644 --- a/src/future/tests/base.py +++ b/src/future/tests/base.py @@ -377,11 +377,6 @@ def expectedFailurePY3(func): return func return unittest.expectedFailure(func) -def expectedFailurePY33_and_PY34(func): - if sys.version_info[:2] not in {(3, 3), (3, 4)}: - return func - return unittest.expectedFailure(func) - def expectedFailurePY26(func): if not PY26: return func diff --git a/tests/test_future/test_bytes.py b/tests/test_future/test_bytes.py index 8d424abd..5254a9be 100644 --- a/tests/test_future/test_bytes.py +++ b/tests/test_future/test_bytes.py @@ -8,7 +8,7 @@ from future import utils from numbers import Integral -from future.tests.base import unittest, expectedFailurePY2, expectedFailurePY33_and_PY34 +from future.tests.base import unittest, expectedFailurePY2 TEST_UNICODE_STR = u'ℝεα∂@ßʟ℮ ☂ℯṧт υηḯ¢☺ḓ℮' @@ -552,106 +552,103 @@ def test_maketrans(self): self.assertRaises(ValueError, bytes.maketrans, b'abc', b'xyzq') self.assertRaises(TypeError, bytes.maketrans, 'abc', 'def') - @expectedFailurePY33_and_PY34 - def test_mod(self): - """ - From Py3.5 test suite (post-PEP 461). - - The bytes mod code is in _PyBytes_Format() in bytesobject.c in Py3.5. - """ - b = b'hello, %b!' - orig = b - b = b % b'world' - self.assertEqual(b, b'hello, world!') - self.assertEqual(orig, b'hello, %b!') - self.assertFalse(b is orig) - b = b'%s / 100 = %d%%' - a = b % (b'seventy-nine', 79) - self.assertEqual(a, b'seventy-nine / 100 = 79%') - - @expectedFailurePY33_and_PY34 - def test_imod(self): - """ - From Py3.5 test suite (post-PEP 461) - """ - # if (3, 0) <= sys.version_info[:2] < (3, 5): - # raise unittest.SkipTest('bytes % not yet implemented on Py3.0-3.4') - b = bytes(b'hello, %b!') - orig = b - b %= b'world' - self.assertEqual(b, b'hello, world!') - self.assertEqual(orig, b'hello, %b!') - self.assertFalse(b is orig) - b = bytes(b'%s / 100 = %d%%') - b %= (b'seventy-nine', 79) - self.assertEqual(b, b'seventy-nine / 100 = 79%') - - @expectedFailurePY33_and_PY34 - def test_mod_pep_461(self): - """ - Test for the PEP 461 functionality (resurrection of %s formatting for - bytes). - """ - b1 = bytes(b'abc%b') - b2 = b1 % b'def' - self.assertEqual(b2, b'abcdef') - self.assertTrue(isinstance(b2, bytes)) - self.assertEqual(type(b2), bytes) - b3 = b1 % bytes(b'def') - self.assertEqual(b3, b'abcdef') - self.assertTrue(isinstance(b3, bytes)) - self.assertEqual(type(b3), bytes) - - # %s is supported for backwards compatibility with Py2's str - b4 = bytes(b'abc%s') - b5 = b4 % b'def' - self.assertEqual(b5, b'abcdef') - self.assertTrue(isinstance(b5, bytes)) - self.assertEqual(type(b5), bytes) - b6 = b4 % bytes(b'def') - self.assertEqual(b6, b'abcdef') - self.assertTrue(isinstance(b6, bytes)) - self.assertEqual(type(b6), bytes) - - self.assertEqual(bytes(b'%c') % 48, b'0') - self.assertEqual(bytes(b'%c') % b'a', b'a') - - # For any numeric code %x, formatting of - # b"%x" % val - # is supposed to be equivalent to - # ("%x" % val).encode("ascii") - for code in b'xdiouxXeEfFgG': - bytechar = bytes([code]) - pct_str = u"%" + bytechar.decode('ascii') - for val in range(300): - self.assertEqual(bytes(b"%" + bytechar) % val, - (pct_str % val).encode("ascii")) - - with self.assertRaises(TypeError): - bytes(b'%b') % 3.14 - # Traceback (most recent call last): - # ... - # TypeError: b'%b' does not accept 'float' - - with self.assertRaises(TypeError): - bytes(b'%b') % 'hello world!' - # Traceback (most recent call last): - # ... - # TypeError: b'%b' does not accept 'str' - - self.assertEqual(bytes(b'%a') % 3.14, b'3.14') - - self.assertEqual(bytes(b'%a') % b'abc', b"b'abc'") - self.assertEqual(bytes(b'%a') % bytes(b'abc'), b"b'abc'") - - self.assertEqual(bytes(b'%a') % 'def', b"'def'") - - # PEP 461 was updated after an Py3.5 alpha release to specify that %r is now supported - # for compatibility: http://legacy.python.org/dev/peps/pep-0461/#id16 - assert bytes(b'%r' % b'abc') == bytes(b'%a' % b'abc') - - # with self.assertRaises(TypeError): - # bytes(b'%r' % 'abc') + # def test_mod(self): + # """ + # From Py3.5 test suite (post-PEP 461). + # + # The bytes mod code is in _PyBytes_Format() in bytesobject.c in Py3.5. + # """ + # b = b'hello, %b!' + # orig = b + # b = b % b'world' + # self.assertEqual(b, b'hello, world!') + # self.assertEqual(orig, b'hello, %b!') + # self.assertFalse(b is orig) + # b = b'%s / 100 = %d%%' + # a = b % (b'seventy-nine', 79) + # self.assertEqual(a, b'seventy-nine / 100 = 79%') + + # def test_imod(self): + # """ + # From Py3.5 test suite (post-PEP 461) + # """ + # # if (3, 0) <= sys.version_info[:2] < (3, 5): + # # raise unittest.SkipTest('bytes % not yet implemented on Py3.0-3.4') + # b = bytes(b'hello, %b!') + # orig = b + # b %= b'world' + # self.assertEqual(b, b'hello, world!') + # self.assertEqual(orig, b'hello, %b!') + # self.assertFalse(b is orig) + # b = bytes(b'%s / 100 = %d%%') + # b %= (b'seventy-nine', 79) + # self.assertEqual(b, b'seventy-nine / 100 = 79%') + + # def test_mod_pep_461(self): + # """ + # Test for the PEP 461 functionality (resurrection of %s formatting for + # bytes). + # """ + # b1 = bytes(b'abc%b') + # b2 = b1 % b'def' + # self.assertEqual(b2, b'abcdef') + # self.assertTrue(isinstance(b2, bytes)) + # self.assertEqual(type(b2), bytes) + # b3 = b1 % bytes(b'def') + # self.assertEqual(b3, b'abcdef') + # self.assertTrue(isinstance(b3, bytes)) + # self.assertEqual(type(b3), bytes) + # + # # %s is supported for backwards compatibility with Py2's str + # b4 = bytes(b'abc%s') + # b5 = b4 % b'def' + # self.assertEqual(b5, b'abcdef') + # self.assertTrue(isinstance(b5, bytes)) + # self.assertEqual(type(b5), bytes) + # b6 = b4 % bytes(b'def') + # self.assertEqual(b6, b'abcdef') + # self.assertTrue(isinstance(b6, bytes)) + # self.assertEqual(type(b6), bytes) + # + # self.assertEqual(bytes(b'%c') % 48, b'0') + # self.assertEqual(bytes(b'%c') % b'a', b'a') + # + # # For any numeric code %x, formatting of + # # b"%x" % val + # # is supposed to be equivalent to + # # ("%x" % val).encode("ascii") + # for code in b'xdiouxXeEfFgG': + # bytechar = bytes([code]) + # pct_str = u"%" + bytechar.decode('ascii') + # for val in range(300): + # self.assertEqual(bytes(b"%" + bytechar) % val, + # (pct_str % val).encode("ascii")) + # + # with self.assertRaises(TypeError): + # bytes(b'%b') % 3.14 + # # Traceback (most recent call last): + # # ... + # # TypeError: b'%b' does not accept 'float' + # + # with self.assertRaises(TypeError): + # bytes(b'%b') % 'hello world!' + # # Traceback (most recent call last): + # # ... + # # TypeError: b'%b' does not accept 'str' + # + # self.assertEqual(bytes(b'%a') % 3.14, b'3.14') + # + # self.assertEqual(bytes(b'%a') % b'abc', b"b'abc'") + # self.assertEqual(bytes(b'%a') % bytes(b'abc'), b"b'abc'") + # + # self.assertEqual(bytes(b'%a') % 'def', b"'def'") + # + # # PEP 461 was updated after an Py3.5 alpha release to specify that %r is now supported + # # for compatibility: http://legacy.python.org/dev/peps/pep-0461/#id16 + # assert bytes(b'%r' % b'abc') == bytes(b'%a' % b'abc') + # + # # with self.assertRaises(TypeError): + # # bytes(b'%r' % 'abc') @expectedFailurePY2 def test_multiple_inheritance(self): From 03f86d23f903615f9920dd238de7e5fd8d9b2206 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 27 Oct 2016 22:34:35 +0200 Subject: [PATCH 167/425] Docs: update v0.16.x release date --- docs/whatsnew.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 582b506b..cb86d005 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -5,7 +5,7 @@ What's New .. _whats-new-0.16.x: -What's new in version 0.16.0 (2016-09-22) +What's new in version 0.16.0 (2016-10-27) ========================================== This release removes the ``configparser`` package as an alias for From ba19436d4691fbfc409244538ff89c0c7abdfc84 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 27 Oct 2016 22:45:26 +0200 Subject: [PATCH 168/425] Tweak What's New page for v0.16.0 --- docs/whatsnew.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index cb86d005..ed606f17 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -9,10 +9,10 @@ What's new 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) +``ConfigParser`` on Py2 to improve compatibility with Lukasz Langa's +backported `configparser `_ package. +Previously ``python-future`` and the ``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 @@ -26,15 +26,15 @@ effect on your system. This releases also fixes these bugs: - Fix ``newbytes`` constructor bug. (Issue #163) -- 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 +- 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) +- 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` +- Add some missing imports to the ``tkinter`` and ``tkinter.filedialog`` package namespaces. (Issues #212 and #233) - Fix ``raise_from`` on PY3 when the exception cannot be recreated from its repr. (Issues #213 and #235, fix provided by Varriount) From dd4f27ec99eec1ef9049f93db1176da0ea0d2e84 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 27 Oct 2016 23:02:09 +0200 Subject: [PATCH 169/425] Update What's New doc to mention that issue #141 is fixed in v0.16.0 --- docs/whatsnew.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index ed606f17..f349ffef 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -36,8 +36,8 @@ This releases also fixes these bugs: - Improve compatibility with Google App Engine. (Issue #231) - Add some missing imports to the ``tkinter`` and ``tkinter.filedialog`` package namespaces. (Issues #212 and #233) -- Fix ``raise_from`` on PY3 when the exception cannot be recreated from - its repr. (Issues #213 and #235, fix provided by Varriount) +- More complete implementation of ``raise_from`` on PY3. (Issues #141, + #213 and #235, fix provided by Varriount) What's new in version 0.15.2 (2015-09-11) From a12f9b9901a5da711fe4bd21a181540f99bb9b97 Mon Sep 17 00:00:00 2001 From: Calum Lind Date: Mon, 28 Nov 2016 23:29:59 +0000 Subject: [PATCH 170/425] Add py2.7.11 unicode fix info for array.array() to stdlib_incompatibilities.rst --- docs/stdlib_incompatibilities.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/stdlib_incompatibilities.rst b/docs/stdlib_incompatibilities.rst index e5129253..38eeaa88 100644 --- a/docs/stdlib_incompatibilities.rst +++ b/docs/stdlib_incompatibilities.rst @@ -57,6 +57,9 @@ You can use the following code on both Python 3 and Python 2:: 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: From cf56246de3d1026f5e2aeb851cd6fb66f81f48f6 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Tue, 13 Dec 2016 14:20:11 +1100 Subject: [PATCH 171/425] Bump version number to 0.17.0-dev --- src/future/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/future/__init__.py b/src/future/__init__.py index 8139aa33..e1b7fe08 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -86,8 +86,8 @@ __license__ = 'MIT' __copyright__ = 'Copyright 2013-2016 Python Charmers Pty Ltd' __ver_major__ = 0 -__ver_minor__ = 16 +__ver_minor__ = 17 __ver_patch__ = 0 -__ver_sub__ = '' +__ver_sub__ = '-dev' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From ad654c5ec0de7d5ff0a475a66de3c778606306b5 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Tue, 13 Dec 2016 15:02:04 +1100 Subject: [PATCH 172/425] Add Py3.5 to Travis-CI tests --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 6e20573f..da167c1a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: python python: + - "3.5" - "3.4" - "3.3" - "2.7" From a6bd30510feb5b988633647eed0015589caefcc1 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Tue, 13 Dec 2016 15:29:47 +1100 Subject: [PATCH 173/425] Fix for install_aliases() omitting ChainMap (issue #226) --- docs/whatsnew.rst | 8 ++++++++ src/future/standard_library/__init__.py | 1 + 2 files changed, 9 insertions(+) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index f349ffef..0e89646a 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -5,6 +5,14 @@ What's New .. _whats-new-0.16.x: +What's new in version 0.16.1 (unreleased) +========================================= + +This is a minor bug-fix release: + +- Fix `from collections import ChainMap` after install_aliases() (issue #226) + + What's new in version 0.16.0 (2016-10-27) ========================================== diff --git a/src/future/standard_library/__init__.py b/src/future/standard_library/__init__.py index aca58c3e..c2d35bdc 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -180,6 +180,7 @@ 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'), From edc3f277c6cc074734401a57a2ac8952ba56c0ca Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Tue, 13 Dec 2016 15:41:44 +1100 Subject: [PATCH 174/425] Add tests for ChainMap (issue #226) --- tests/test_future/test_chainmap.py | 160 +++++++++++++++++++++ tests/test_future/test_standard_library.py | 7 + 2 files changed, 167 insertions(+) create mode 100644 tests/test_future/test_chainmap.py diff --git a/tests/test_future/test_chainmap.py b/tests/test_future/test_chainmap.py new file mode 100644 index 00000000..2440401b --- /dev/null +++ b/tests/test_future/test_chainmap.py @@ -0,0 +1,160 @@ +""" +Tests for the future.standard_library module +""" + +from __future__ import absolute_import, print_function +from future import standard_library +from future import utils +from future.tests.base import unittest, CodeHandler, expectedFailurePY2 + +import sys +import tempfile +import os +import copy +import textwrap +from subprocess import CalledProcessError + + +class TestChainMap(CodeHandler): + + def setUp(self): + self.interpreter = sys.executable + standard_library.install_aliases() + super(TestChainMap, self).setUp() + + def tearDown(self): + # standard_library.remove_hooks() + pass + + @staticmethod + def simple_cm(): + from collections import ChainMap + c = ChainMap() + c['one'] = 1 + c['two'] = 2 + + cc = c.new_child() + cc['one'] = 'one' + + return c, cc + + + def test_repr(self): + c, cc = TestChainMap.simple_cm() + + order1 = "ChainMap({'one': 'one'}, {'one': 1, 'two': 2})" + order2 = "ChainMap({'one': 'one'}, {'two': 2, 'one': 1})" + assert repr(cc) in [order1, order2] + + + def test_recursive_repr(self): + """ + Test for degnerative recursive cases. Very unlikely in + ChainMaps. But all must bow before the god of testing coverage. + """ + from collections import ChainMap + c = ChainMap() + c['one'] = c + assert repr(c) == "ChainMap({'one': ...})" + + + def test_get(self): + c, cc = TestChainMap.simple_cm() + + assert cc.get('two') == 2 + assert cc.get('three') == None + assert cc.get('three', 'notthree') == 'notthree' + + + def test_bool(self): + from collections import ChainMap + c = ChainMap() + assert not(bool(c)) + + c['one'] = 1 + c['two'] = 2 + assert bool(c) + + cc = c.new_child() + cc['one'] = 'one' + assert cc + + + def test_fromkeys(self): + from collections import ChainMap + keys = 'a b c'.split() + c = ChainMap.fromkeys(keys) + assert len(c) == 3 + assert c['a'] == None + assert c['b'] == None + assert c['c'] == None + + + def test_copy(self): + c, cc = TestChainMap.simple_cm() + new_cc = cc.copy() + assert new_cc is not cc + assert sorted(new_cc.items()) == sorted(cc.items()) + + + def test_parents(self): + c, cc = TestChainMap.simple_cm() + + new_c = cc.parents + assert c is not new_c + assert len(new_c) == 2 + assert new_c['one'] == c['one'] + assert new_c['two'] == c['two'] + + + def test_delitem(self): + c, cc = TestChainMap.simple_cm() + + with self.assertRaises(KeyError): + del cc['two'] + + del cc['one'] + assert len(cc) == 2 + assert cc['one'] == 1 + assert cc['two'] == 2 + + + def test_popitem(self): + c, cc = TestChainMap.simple_cm() + + assert cc.popitem() == ('one', 'one') + + with self.assertRaises(KeyError): + cc.popitem() + + + def test_pop(self): + c, cc = TestChainMap.simple_cm() + + assert cc.pop('one') == 'one' + + with self.assertRaises(KeyError): + cc.pop('two') + + assert len(cc) == 2 + + + def test_clear(self): + c, cc = TestChainMap.simple_cm() + + cc.clear() + assert len(cc) == 2 + assert cc['one'] == 1 + assert cc['two'] == 2 + + + def test_missing(self): + + c, cc = TestChainMap.simple_cm() + + with self.assertRaises(KeyError): + cc['clown'] + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_future/test_standard_library.py b/tests/test_future/test_standard_library.py index 647da550..7da6b75b 100644 --- a/tests/test_future/test_standard_library.py +++ b/tests/test_future/test_standard_library.py @@ -247,6 +247,13 @@ def test_itertools_zip_longest(self): self.assertEqual(list(zip_longest(a, b)), [(1, 2), (2, 4), (None, 6)]) + def test_ChainMap(self): + """ + Tests whether collections.ChainMap is available. + """ + from collections import ChainMap + cm = ChainMap() + @unittest.expectedFailure @unittest.skipIf(utils.PY3, 'generic import tests are for Py2 only') def test_import_failure_from_module(self): From b4666394e7cc574f7396f4ff70b7a6ee226ef6ad Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Tue, 13 Dec 2016 15:02:04 +1100 Subject: [PATCH 175/425] Add Py3.5 to Travis-CI tests --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 6e20573f..da167c1a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: python python: + - "3.5" - "3.4" - "3.3" - "2.7" From 2093a6b46875694b4b4de807fdca2c6994ba4838 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 8 Jan 2017 11:08:37 +0100 Subject: [PATCH 176/425] fix typo --- docs/faq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.rst b/docs/faq.rst index 57fef25e..edd27c94 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -220,7 +220,7 @@ to Python 2's ``str`` object) and several standard library modules. ``python-future`` supports only Python 2.6+ and Python 3.3+, 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 `_. From cd971b8a35abbb6358644852ced41cfb136280c0 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 6 Feb 2017 17:38:26 -0800 Subject: [PATCH 177/425] Add universal wheel support See http://pythonwheels.com/ When uploading to PyPI use: $ python setup.py sdist bdist_wheel upload Refs #250 --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..5e409001 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[wheel] +universal = 1 From 9b51ec531198b8ee0b805c3526b02bb3bc57130d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Tue, 7 Mar 2017 09:38:32 +0100 Subject: [PATCH 178/425] Allow inequalities with newstr and newbytes. --- src/future/types/newbytes.py | 24 ++++++++++++------------ src/future/types/newstr.py | 28 ++++++++++++++++------------ tests/test_future/test_str.py | 4 ---- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/future/types/newbytes.py b/src/future/types/newbytes.py index 85e6501c..24ff5f90 100644 --- a/src/future/types/newbytes.py +++ b/src/future/types/newbytes.py @@ -348,24 +348,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 diff --git a/src/future/types/newstr.py b/src/future/types/newstr.py index fd8615af..7bb1d915 100644 --- a/src/future/types/newstr.py +++ b/src/future/types/newstr.py @@ -302,24 +302,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/tests/test_future/test_str.py b/tests/test_future/test_str.py index 7e37a62f..d8da4d0e 100644 --- a/tests/test_future/test_str.py +++ b/tests/test_future/test_str.py @@ -382,10 +382,6 @@ def test_cmp(self): s > 3 with self.assertRaises(TypeError): s < 1000 - with self.assertRaises(TypeError): - s > b'XYZ' - with self.assertRaises(TypeError): - s < b'XYZ' with self.assertRaises(TypeError): s <= 3 with self.assertRaises(TypeError): From 9642c74ab65dff3ab9521159ae30b2208793a30c Mon Sep 17 00:00:00 2001 From: Simon Gomizelj Date: Sun, 26 Mar 2017 01:57:50 +0000 Subject: [PATCH 179/425] Teach newbytes to call __bytes__ if available Currently, the newbytes implementation ignores __bytes__ on classes and acts like python2's str. --- src/future/types/newbytes.py | 2 ++ tests/test_future/test_bytes.py | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/future/types/newbytes.py b/src/future/types/newbytes.py index 85e6501c..8a937a6d 100644 --- a/src/future/types/newbytes.py +++ b/src/future/types/newbytes.py @@ -100,6 +100,8 @@ def __new__(cls, *args, **kwargs): 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. diff --git a/tests/test_future/test_bytes.py b/tests/test_future/test_bytes.py index 5254a9be..e852a0bd 100644 --- a/tests/test_future/test_bytes.py +++ b/tests/test_future/test_bytes.py @@ -707,6 +707,31 @@ def test_issue_171_part_b(self): b = nativebytes(bytes(b'asdf')) self.assertEqual(b, b'asdf') + def test_cast_to_bytes(self): + """ + Tests whether __bytes__ method is called + """ + + class TestObject: + def __bytes__(self): + return b'asdf' + + self.assertEqual(bytes(TestObject()), b'asdf') + + def test_cast_to_bytes_iter_precedence(self): + """ + Tests that call to __bytes__ is preferred to iteration + """ + + class TestObject: + def __bytes__(self): + return b'asdf' + + def __iter__(self): + return iter(b'hjkl') + + self.assertEqual(bytes(TestObject()), b'asdf') + if __name__ == '__main__': unittest.main() From da5e6d241bb6a58a5a1e2e941602037d47115781 Mon Sep 17 00:00:00 2001 From: John-Mark Gurney Date: Sun, 9 Apr 2017 00:20:51 -0700 Subject: [PATCH 180/425] exc not defined in this function, raise the correct error... --- src/future/utils/surrogateescape.py | 2 +- tests/test_future/test_surrogateescape.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/future/utils/surrogateescape.py b/src/future/utils/surrogateescape.py index 398c3531..78ac11cb 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)] diff --git a/tests/test_future/test_surrogateescape.py b/tests/test_future/test_surrogateescape.py index 0057ba36..7789ce9d 100644 --- a/tests/test_future/test_surrogateescape.py +++ b/tests/test_future/test_surrogateescape.py @@ -33,6 +33,12 @@ def test_encode_ascii_surrogateescape(self): b = payload.encode('ascii', 'surrogateescape') self.assertEqual(b, b'cMO2c3RhbA\xc3\xa1=\n') + def test_encode_ascii_unicode(self): + """ + Verify that exceptions are raised properly. + """ + self.assertRaises(UnicodeEncodeError, u'\N{SNOWMAN}'.encode, 'US-ASCII', 'surrogateescape') + @expectedFailurePY2 def test_encode_ascii_surrogateescape_non_newstr(self): """ From 0a5b8730fc3ed50f1f28f2f644ea8de7cfd01390 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 16 May 2017 22:01:19 -0700 Subject: [PATCH 181/425] Change section to [bdist_wheel] as [wheel] is considered legacy See: https://bitbucket.org/pypa/wheel/src/54ddbcc9cec25e1f4d111a142b8bfaa163130a61/wheel/bdist_wheel.py?fileviewer=file-view-default#bdist_wheel.py-119:125 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 5e409001..2a9acf13 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ -[wheel] +[bdist_wheel] universal = 1 From 99030ec81309164fefc0cd6b5d090eeb08b4a2f7 Mon Sep 17 00:00:00 2001 From: Mital Ashok Date: Mon, 31 Jul 2017 20:18:15 +0100 Subject: [PATCH 182/425] Fixed newdict checking version every time --- src/future/types/newdict.py | 63 +++++++++---------------------------- 1 file changed, 14 insertions(+), 49 deletions(-) diff --git a/src/future/types/newdict.py b/src/future/types/newdict.py index 5dbcc4b7..7e9cdbc1 100644 --- a/src/future/types/newdict.py +++ b/src/future/types/newdict.py @@ -23,7 +23,7 @@ _builtin_dict = dict -ver = sys.version_info[:2] +ver = sys.version_info class BaseNewDict(type): @@ -38,47 +38,18 @@ class newdict(with_metaclass(BaseNewDict, _builtin_dict)): """ A backport of the Python 3 dict object to Py2 """ - def items(self): - """ - On Python 2.7+: - D.items() -> a set-like object providing a view on D's items - On Python 2.6: - D.items() -> an iterator over D's items - """ - if ver == (2, 7): - return self.viewitems() - elif ver == (2, 6): - return self.iteritems() - elif ver >= (3, 0): - return self.items() - - def keys(self): - """ - On Python 2.7+: - D.keys() -> a set-like object providing a view on D's keys - On Python 2.6: - D.keys() -> an iterator over D's keys - """ - if ver == (2, 7): - return self.viewkeys() - elif ver == (2, 6): - return self.iterkeys() - elif ver >= (3, 0): - return self.keys() - - def values(self): - """ - On Python 2.7+: - D.values() -> a set-like object providing a view on D's values - On Python 2.6: - D.values() -> an iterator over D's values - """ - if ver == (2, 7): - return self.viewvalues() - elif ver == (2, 6): - return self.itervalues() - elif ver >= (3, 0): - return self.values() + + if ver >= (3,): + # Inherit items, keys and values from `dict` in 3.x + pass + elif ver >= (2, 7): + items = dict.viewitems + keys = dict.viewkeys + values = dict.viewvalues + else: + items = dict.iteritems + keys = dict.iterkeys + values = dict.itervalues def __new__(cls, *args, **kwargs): """ @@ -93,13 +64,7 @@ def __new__(cls, *args, **kwargs): in the keyword argument list. For example: dict(one=1, two=2) """ - if len(args) == 0: - return super(newdict, cls).__new__(cls) - elif type(args[0]) == newdict: - value = args[0] - else: - value = args[0] - return super(newdict, cls).__new__(cls, value) + return super(newdict, cls).__new__(cls, *args) def __native__(self): """ From 1cf58c31cceb26f11b8fcfcfd188fd817ce95a94 Mon Sep 17 00:00:00 2001 From: cclauss Date: Sat, 19 Aug 2017 00:22:14 +0200 Subject: [PATCH 183/425] remove the parens on the cmp lambda definition See #297 Before this change, the code in question generates a SyntaxError in Python 3 --- docs/compatible_idioms.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index e48ac6d1..7eb0cfb6 100644 --- a/docs/compatible_idioms.rst +++ b/docs/compatible_idioms.rst @@ -1120,7 +1120,7 @@ cmp() .. code:: python # Python 2 and 3: alternative 2 - cmp = lambda(x, y): (x > y) - (x < y) + cmp = lambda x, y: (x > y) - (x < y) assert cmp('a', 'b') < 0 and cmp('b', 'a') > 0 and cmp('c', 'c') == 0 reload() ~~~~~~~~ From 19a60e7fd18967b3340be318aaac58f191855155 Mon Sep 17 00:00:00 2001 From: lisongmin Date: Wed, 23 Aug 2017 00:04:00 +0800 Subject: [PATCH 184/425] fixed standard_library.install_aliases() failed under SLES11SP1 --- src/future/types/newrange.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/future/types/newrange.py b/src/future/types/newrange.py index c190ba9e..89cc8bdf 100644 --- a/src/future/types/newrange.py +++ b/src/future/types/newrange.py @@ -152,6 +152,9 @@ def __init__(self, range_): def __iter__(self): return self + def __next__(self): + return next(self._stepper) + def next(self): return next(self._stepper) From d87713e5165b6907895ebbd21d98a8a8a739e5c3 Mon Sep 17 00:00:00 2001 From: Austin Marshall Date: Tue, 10 Oct 2017 10:14:50 -0700 Subject: [PATCH 185/425] Fixup broken link to external django documentation re: porting to Python 3 and unicode_literals --- docs/unicode_literals.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/unicode_literals.rst b/docs/unicode_literals.rst index 7af644c8..d17c8a4f 100644 --- a/docs/unicode_literals.rst +++ b/docs/unicode_literals.rst @@ -145,7 +145,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 From cf4e6ae96370dde24e2f92d5f2508766ad10df8e Mon Sep 17 00:00:00 2001 From: cclauss Date: Fri, 20 Oct 2017 09:46:20 +0200 Subject: [PATCH 186/425] Stop recommending Python 3.3 because it is now EOL https://docs.python.org/devguide/index.html#branchstatus --- docs/futurize_cheatsheet.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/futurize_cheatsheet.rst b/docs/futurize_cheatsheet.rst index 5f4d853c..bc6bc13e 100644 --- a/docs/futurize_cheatsheet.rst +++ b/docs/futurize_cheatsheet.rst @@ -14,10 +14,10 @@ Step 0 goal: set up and see the tests passing on Python 2 and failing on Python 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.:: +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 py33 python=3.3 pip + conda create -n py36 python=3.6 pip .. _porting-step1: From 15ee1a8b55001a09cae5aee8d4c48d412b391215 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 21 Oct 2017 11:56:25 -0700 Subject: [PATCH 187/425] Include license file in the generated wheel package The wheel package format supports including the license file. This is done using the [metadata] section in the setup.cfg file. For additional information on this feature, see: https://wheel.readthedocs.io/en/stable/index.html#including-the-license-in-the-generated-wheel-file --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index 5e409001..5f465ef6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,5 @@ [wheel] universal = 1 + +[metadata] +license_file = LICENSE.txt From b56f0afa66384e838dca66ed1de903a92cbcb1a9 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 21 Oct 2017 12:02:36 -0700 Subject: [PATCH 188/425] Enable pip cache in Travis CI Can speed up builds and reduce load on PyPI servers. For more information, see: https://docs.travis-ci.com/user/caching/#pip-cache --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6e20573f..cb1de26c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: python - +cache: pip python: - "3.4" - "3.3" From 04dd9ba0678dd475b4de9f0c35474164d0b4acad Mon Sep 17 00:00:00 2001 From: santi Date: Fri, 8 Dec 2017 10:27:43 +0100 Subject: [PATCH 189/425] Export and document types in future.utils --- src/future/utils/__init__.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index cb4ade35..53d07276 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -18,8 +18,10 @@ * types: * text_type: unicode in Python 2, str in Python 3 - * binary_type: str in Python 2, 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 @@ -724,16 +726,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' + ] From 3e1bc8a3d650bdf8515cb5f84306bf0ece6e96b9 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 6 Jan 2018 08:49:01 -0800 Subject: [PATCH 190/425] Prefer readthedocs.io instead of readthedocs.org for doc links Read the Docs moved hosting to readthedocs.io instead of readthedocs.org. Fix all links in the project. For additional details, see: https://blog.readthedocs.com/securing-subdomains/ > Starting today, Read the Docs will start hosting projects from subdomains on > the domain readthedocs.io, instead of on readthedocs.org. This change > addresses some security concerns around site cookies while hosting user > generated data on the same domain as our dashboard. --- docs/conf.py | 2 +- docs/other/useful_links.txt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 78183b99..cce52ff6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,7 +33,7 @@ 'sphinx.ext.intersphinx', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', - # 'sphinxcontrib.napoleon' # see https://readthedocs.org/projects/sphinxcontrib-napoleon/ + # 'sphinxcontrib.napoleon' # see https://sphinxcontrib-napoleon.readthedocs.io/ # 'sphinx.ext.napoleon' # use this in Sphinx 1.3+ ] diff --git a/docs/other/useful_links.txt b/docs/other/useful_links.txt index 8dec2f9b..e9ff3774 100644 --- a/docs/other/useful_links.txt +++ b/docs/other/useful_links.txt @@ -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 @@ -108,4 +108,3 @@ https://pypi.python.org/pypi/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 - From 1ba02ca419b6d9061bdca0650902df7915278cbd Mon Sep 17 00:00:00 2001 From: Markus Gerstel Date: Fri, 27 Apr 2018 09:43:28 +0100 Subject: [PATCH 191/425] Fix direct attribute assignment to newobject and add test. Also resolves #336. --- src/future/types/newobject.py | 1 + tests/test_future/test_object.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/future/types/newobject.py b/src/future/types/newobject.py index 1ec09ff5..6331a8e9 100644 --- a/src/future/types/newobject.py +++ b/src/future/types/newobject.py @@ -130,5 +130,6 @@ def __native__(self): """ return object(self) + __slots__ = [] __all__ = ['newobject'] diff --git a/tests/test_future/test_object.py b/tests/test_future/test_object.py index 07715029..fe14f8d5 100644 --- a/tests/test_future/test_object.py +++ b/tests/test_future/test_object.py @@ -271,6 +271,16 @@ def __len__(self): self.assertFalse(bool(FalseThing())) + def test_cannot_assign_new_attributes_to_object(self): + """ + New attributes cannot be assigned to object() instances in Python. + The same should apply to newobject. + """ + from builtins import object + + with self.assertRaises(AttributeError): + object().arbitrary_attribute_name = True + if __name__ == '__main__': unittest.main() From 9fcde90f29b6e278417dac243170ebb79ce65283 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 18 May 2018 09:45:41 +1000 Subject: [PATCH 192/425] Fix small doc bug: location of itervalues --- docs/compatible_idioms.rst | 2 +- docs/notebooks/Writing Python 2-3 compatible code.ipynb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index e48ac6d1..37a7135f 100644 --- a/docs/compatible_idioms.rst +++ b/docs/compatible_idioms.rst @@ -512,7 +512,7 @@ Iterable dict values: .. code:: python # Python 2 and 3: option 2 - from builtins import itervalues + from future.utils import itervalues # or from six import itervalues diff --git a/docs/notebooks/Writing Python 2-3 compatible code.ipynb b/docs/notebooks/Writing Python 2-3 compatible code.ipynb index f33a9204..8ac0bd80 100644 --- a/docs/notebooks/Writing Python 2-3 compatible code.ipynb +++ b/docs/notebooks/Writing Python 2-3 compatible code.ipynb @@ -1129,7 +1129,7 @@ "outputs": [], "source": [ "# Python 2 and 3: option 2\n", - "from builtins import itervalues\n", + "from future.utils import itervalues\n", "# or\n", "from six import itervalues\n", "\n", From cce97e2fa6fb4096b7599f455f0e00dc2c4dff74 Mon Sep 17 00:00:00 2001 From: Nate Bogdanowicz Date: Thu, 30 Jun 2016 18:14:49 -0700 Subject: [PATCH 193/425] Add more robust support for --nofix --- src/libfuturize/main.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/libfuturize/main.py b/src/libfuturize/main.py index 18f33ec0..02808786 100644 --- a/src/libfuturize/main.py +++ b/src/libfuturize/main.py @@ -205,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: From 66ca231e897bb06656c314aa0c4c9ffb4a1f41d3 Mon Sep 17 00:00:00 2001 From: Lucas Cimon Date: Tue, 12 Jun 2018 10:50:14 +0200 Subject: [PATCH 194/425] Setting the __traceback__ attribute in future.utils.raise_from - close #340 --- TESTING.txt | 8 +++++ src/future/utils/__init__.py | 2 ++ tests/test_future/test_utils.py | 57 ++++++++++++++++++++++++++++++++- 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/TESTING.txt b/TESTING.txt index 13aeca83..6e31d22f 100644 --- a/TESTING.txt +++ b/TESTING.txt @@ -7,3 +7,11 @@ The test suite can be run either with: which uses the unittest module's test discovery mechanism, or with: $ py.test + +To execute a single test: + + $ python setup.py test -s tests.test_future.test_utils.TestCause.test_chained_exceptions_stacktrace + +or with: + + $ pytest -k test_chained_exceptions_stacktrace diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index cb4ade35..3f55df5c 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -441,12 +441,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 + e.__cause__.__traceback__ = sys.exc_info()[2] e.__suppress_context__ = True else: raise TypeError("exception causes must derive from BaseException") diff --git a/tests/test_future/test_utils.py b/tests/test_future/test_utils.py index 19dc11d3..b80e8c17 100644 --- a/tests/test_future/test_utils.py +++ b/tests/test_future/test_utils.py @@ -4,7 +4,7 @@ """ from __future__ import absolute_import, unicode_literals, print_function -import sys +import sys, traceback from future.builtins import * from future.utils import (old_div, istext, isbytes, native, PY2, PY3, native_str, raise_, as_native_str, ensure_new_type, @@ -314,6 +314,61 @@ def __init__(self): else: self.fail("No exception raised") + def test_single_exception_stacktrace(self): + expected = '''Traceback (most recent call last): + File "/opt/python-future/tests/test_future/test_utils.py", line 328, in test_single_exception_stacktrace + raise CustomException('ERROR') +''' + if PY2: + expected += 'CustomException: ERROR\n' + else: + expected += 'tests.test_future.test_utils.CustomException: ERROR\n' + + try: + raise CustomException('ERROR') + except: + self.assertEqual(expected, traceback.format_exc()) + else: + self.fail('No exception raised') + + if PY2: + def test_chained_exceptions_stacktrace(self): + expected = '''Traceback (most recent call last): + File "/opt/python-future/tests/test_future/test_utils.py", line 354, in test_chained_exceptions_stacktrace + raise_from(CustomException('ERROR'), val_err) + File "/opt/python-future/src/future/utils/__init__.py", line 456, in raise_from + raise e +CustomException: ERROR + +The above exception was the direct cause of the following exception: + + File "/opt/python-future/tests/test_future/test_utils.py", line 352, in test_chained_exceptions_stacktrace + raise ValueError('Wooops') +ValueError: Wooops +''' + + try: + try: + raise ValueError('Wooops') + except ValueError as val_err: + raise_from(CustomException('ERROR'), val_err) + except Exception as err: + self.assertEqual(expected.splitlines(), traceback.format_exc().splitlines()) + else: + self.fail('No exception raised') + + +class CustomException(Exception): + if PY2: + def __str__(self): + out = Exception.__str__(self) + if hasattr(self, '__cause__') and self.__cause__ and hasattr(self.__cause__, '__traceback__') and self.__cause__.__traceback__: + out += '\n\nThe above exception was the direct cause of the following exception:\n\n' + out += ''.join(traceback.format_tb(self.__cause__.__traceback__) + ['{}: {}'.format(self.__cause__.__class__.__name__, self.__cause__)]) + return out + else: + pass + if __name__ == '__main__': unittest.main() From 7cf0f9db449297a99211133651544ee412a2d805 Mon Sep 17 00:00:00 2001 From: Johann-Michael Thiebaut Date: Thu, 5 Jul 2018 09:43:46 +0200 Subject: [PATCH 195/425] os.makedirs can fail --- src/past/translation/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/past/translation/__init__.py b/src/past/translation/__init__.py index 7b21d9f5..6ea94536 100644 --- a/src/past/translation/__init__.py +++ b/src/past/translation/__init__.py @@ -402,9 +402,9 @@ def load_module(self, fullname): code = compile(source, self.pathname, 'exec') dirname = os.path.dirname(cachename) - if not os.path.exists(dirname): - os.makedirs(dirname) try: + if not os.path.exists(dirname): + os.makedirs(dirname) with open(cachename, 'wb') as f: data = marshal.dumps(code) f.write(data) From 8209539e0f4c20c47ce455957c7b2f00bad30b69 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 13 Jul 2018 15:46:29 -0700 Subject: [PATCH 196/425] Skip float divisions in fix_division_safe --- src/libfuturize/fixes/fix_division_safe.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/libfuturize/fixes/fix_division_safe.py b/src/libfuturize/fixes/fix_division_safe.py index 5e16b0a5..5cdd6268 100644 --- a/src/libfuturize/fixes/fix_division_safe.py +++ b/src/libfuturize/fixes/fix_division_safe.py @@ -13,6 +13,9 @@ nothing. """ +import re +import lib2to3.pytree as pytree +from lib2to3.fixer_util import Leaf, Node 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, @@ -28,6 +31,18 @@ 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.]+$') + + +def _is_floaty(expr): + 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 + return expr.children[0].value == u'float' + return False + class FixDivisionSafe(fixer_base.BaseFix): # BM_compatible = True @@ -68,5 +83,10 @@ def transform(self, node, results): expr1, expr2 = results[0].clone(), results[1].clone() # Strip any leading space for the first number: expr1.prefix = u'' + # if expr1 or expr2 are obviously floats, we don't need to wrap in + # old_div, as the behavior of division between any number and a float + # should be the same in 2 or 3 + if _is_floaty(expr1) or _is_floaty(expr2): + return return wrap_in_fn_call("old_div", (expr1, expr2), prefix=node.prefix) From fb786929748b1fe80f57bead0b783a27c2a26cda Mon Sep 17 00:00:00 2001 From: tst Date: Mon, 16 Jul 2018 13:20:30 +0200 Subject: [PATCH 197/425] fixed undefined variable children_hooks + imports cleanup --- src/libfuturize/fixer_util.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/libfuturize/fixer_util.py b/src/libfuturize/fixer_util.py index ce1e9753..afc22510 100644 --- a/src/libfuturize/fixer_util.py +++ b/src/libfuturize/fixer_util.py @@ -9,11 +9,11 @@ """ 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 @@ -62,7 +62,7 @@ def Minus(prefix=None): def commatize(leafs): """ - Accepts/turns: (Name, Name, ..., Name, Name) + Accepts/turns: (Name, Name, ..., Name, Name) Returns/into: (Name, Comma, Name, Comma, ..., Name, Comma, Name) """ new_leafs = [] @@ -272,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 @@ -304,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): @@ -338,7 +338,7 @@ def touch_import_top(package, name_to_import, node): 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. """ @@ -390,6 +390,7 @@ def touch_import_top(package, name_to_import, node): break insert_pos = idx + children_hooks = [] if package is None: import_ = Node(syms.import_name, [ Leaf(token.NAME, u"import"), @@ -413,9 +414,7 @@ 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" ")]) children_import = [import_, Newline()] @@ -445,7 +444,6 @@ def check_future_import(node): return set() 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: From 0881f37e6ac3eac73aac4e231a740566dcab99c0 Mon Sep 17 00:00:00 2001 From: jenssss <37403847+jenssss@users.noreply.github.com> Date: Tue, 31 Jul 2018 14:23:15 +0900 Subject: [PATCH 198/425] Fixed import statements Changed `import itertools.imap as map` to `from itertools import imap as map` in two places --- docs/notebooks/Writing Python 2-3 compatible code.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/notebooks/Writing Python 2-3 compatible code.ipynb b/docs/notebooks/Writing Python 2-3 compatible code.ipynb index f33a9204..c1af15e7 100644 --- a/docs/notebooks/Writing Python 2-3 compatible code.ipynb +++ b/docs/notebooks/Writing Python 2-3 compatible code.ipynb @@ -1747,7 +1747,7 @@ "source": [ "# Python 2 and 3: option 3\n", "try:\n", - " import itertools.imap as map\n", + " from itertools import imap as map\n", "except ImportError:\n", " pass\n", "\n", @@ -1845,7 +1845,7 @@ "source": [ "# Python 2 and 3: option 2\n", "try:\n", - " import itertools.imap as map\n", + " from itertools import imap as map\n", "except ImportError:\n", " pass\n", "\n", From 03770dbc4494433fa711c9a4b4bce091efa0c6e9 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 3 Aug 2018 15:35:01 -0700 Subject: [PATCH 199/425] bugfix --- src/libfuturize/fixes/fix_division_safe.py | 2 +- tests/test_future/test_futurize.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libfuturize/fixes/fix_division_safe.py b/src/libfuturize/fixes/fix_division_safe.py index 5cdd6268..a2ef0984 100644 --- a/src/libfuturize/fixes/fix_division_safe.py +++ b/src/libfuturize/fixes/fix_division_safe.py @@ -31,7 +31,7 @@ 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.]+$') +const_re = re.compile('^[0-9]*[.][0-9]*$') def _is_floaty(expr): diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index f3fe0b84..5943f8a4 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -1179,7 +1179,7 @@ def test_safe_division(self): from __future__ import division from past.utils import old_div x = old_div(3, 2) - y = old_div(3., 2) + y = 3. / 2 assert x == 1 and isinstance(x, int) assert y == 1.5 and isinstance(y, float) """ From ef625b060ebef20f50999f319b466789d50df295 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Wed, 8 Aug 2018 14:44:43 -0700 Subject: [PATCH 200/425] Only import past.utils.old_div() if needed; Verify the expression isnt a nested expression when doing a floaty check --- src/libfuturize/fixes/fix_division_safe.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libfuturize/fixes/fix_division_safe.py b/src/libfuturize/fixes/fix_division_safe.py index a2ef0984..3a0d9aef 100644 --- a/src/libfuturize/fixes/fix_division_safe.py +++ b/src/libfuturize/fixes/fix_division_safe.py @@ -40,7 +40,8 @@ def _is_floaty(expr): 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 - return expr.children[0].value == u'float' + if isinstance(expr.children[0], Leaf): + return expr.children[0].value == u'float' return False @@ -79,7 +80,6 @@ def transform(self, node, results): 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'' @@ -88,5 +88,6 @@ def transform(self, node, results): # should be the same in 2 or 3 if _is_floaty(expr1) or _is_floaty(expr2): return + touch_import_top(u'past.utils', u'old_div', node) return wrap_in_fn_call("old_div", (expr1, expr2), prefix=node.prefix) From 05270673dd1e3e353454dbe12a61bc982eb094c6 Mon Sep 17 00:00:00 2001 From: Louis Sautier Date: Mon, 20 Aug 2018 11:17:59 +0200 Subject: [PATCH 201/425] Make test_bad_status_repr work for Python 3.7 Allow a different representation for the exception. --- tests/test_future/test_httplib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_future/test_httplib.py b/tests/test_future/test_httplib.py index 968e9339..a1e2b19a 100644 --- a/tests/test_future/test_httplib.py +++ b/tests/test_future/test_httplib.py @@ -197,7 +197,7 @@ def test_bad_status_repr(self): if not utils.PY3: self.assertEqual(repr(exc), '''BadStatusLine("u\'\'",)''') else: - self.assertEqual(repr(exc), '''BadStatusLine("\'\'",)''') + self.assertIn(repr(exc), ('''BadStatusLine("''",)''', '''BadStatusLine("''")''')) def test_partial_reads(self): # if we have a length, the system knows when to close itself From 4332803bd1b9093d372a2ccf55c4af5ec45eda9a Mon Sep 17 00:00:00 2001 From: Flaviu Tamas Date: Mon, 3 Sep 2018 16:15:50 -0400 Subject: [PATCH 202/425] Fix #113 --- src/libfuturize/fixer_util.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libfuturize/fixer_util.py b/src/libfuturize/fixer_util.py index ce1e9753..a10184c7 100644 --- a/src/libfuturize/fixer_util.py +++ b/src/libfuturize/fixer_util.py @@ -243,6 +243,7 @@ def future_import(feature, node): # Is it a shebang or encoding line? if is_shebang_comment(node) or is_encoding_comment(node): shebang_encoding_idx = idx + continue if is_docstring(node): # skip over docstring continue @@ -443,7 +444,10 @@ 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: From 1533854b4d36eb3854c795b4785e711dd99c4c22 Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Fri, 7 Sep 2018 16:33:54 -0400 Subject: [PATCH 203/425] Add support for proper %s formatting of newbytes --- src/future/types/newbytes.py | 16 +++++++++++ tests/test_future/test_bytes.py | 47 ++++++++++++++++++++++----------- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/future/types/newbytes.py b/src/future/types/newbytes.py index 8a937a6d..f0aa419f 100644 --- a/src/future/types/newbytes.py +++ b/src/future/types/newbytes.py @@ -174,6 +174,22 @@ 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, dict): + for k, v in vals.items(): + if isinstance(v, newbytes): + vals[k] = _builtin_bytes.__str__(v) + elif isinstance(vals, tuple): + newvals = [] + for v in vals: + if isinstance(v, newbytes): + v = _builtin_bytes.__str__(v) + newvals.append(v) + vals = tuple(newvals) + return _builtin_bytes.__mod__(self, vals) + def join(self, iterable_of_bytes): errmsg = 'sequence item {0}: expected bytes, {1} found' if isbytes(iterable_of_bytes) or istext(iterable_of_bytes): diff --git a/tests/test_future/test_bytes.py b/tests/test_future/test_bytes.py index e852a0bd..2f47abc2 100644 --- a/tests/test_future/test_bytes.py +++ b/tests/test_future/test_bytes.py @@ -552,21 +552,38 @@ def test_maketrans(self): self.assertRaises(ValueError, bytes.maketrans, b'abc', b'xyzq') self.assertRaises(TypeError, bytes.maketrans, 'abc', 'def') - # def test_mod(self): - # """ - # From Py3.5 test suite (post-PEP 461). - # - # The bytes mod code is in _PyBytes_Format() in bytesobject.c in Py3.5. - # """ - # b = b'hello, %b!' - # orig = b - # b = b % b'world' - # self.assertEqual(b, b'hello, world!') - # self.assertEqual(orig, b'hello, %b!') - # self.assertFalse(b is orig) - # b = b'%s / 100 = %d%%' - # a = b % (b'seventy-nine', 79) - # self.assertEqual(a, b'seventy-nine / 100 = 79%') + def test_mod_more(self): + self.assertEqual(b'%s' % b'aaa', b'aaa') + self.assertEqual(bytes(b'%s') % b'aaa', b'aaa') + self.assertEqual(bytes(b'%s') % bytes(b'aaa'), b'aaa') + + self.assertEqual(b'%s' % (b'aaa',), b'aaa') + self.assertEqual(bytes(b'%s') % (b'aaa',), b'aaa') + self.assertEqual(bytes(b'%s') % (bytes(b'aaa'),), b'aaa') + + self.assertEqual(bytes(b'%(x)s') % {'x': b'aaa'}, b'aaa') + self.assertEqual(bytes(b'%(x)s') % {'x': bytes(b'aaa')}, b'aaa') + + def test_mod(self): + """ + From Py3.5 test suite (post-PEP 461). + + The bytes mod code is in _PyBytes_Format() in bytesobject.c in Py3.5. + """ + # b = bytes(b'hello, %b!') + # orig = b + # b = b % b'world' + # self.assertEqual(b, b'hello, world!') + # self.assertEqual(orig, b'hello, %b!') + # self.assertFalse(b is orig) + + b = bytes(b'%s / 100 = %d%%') + a = b % (b'seventy-nine', 79) + self.assertEqual(a, b'seventy-nine / 100 = 79%') + + b = bytes(b'%s / 100 = %d%%') + a = b % (bytes(b'seventy-nine'), 79) + self.assertEqual(a, b'seventy-nine / 100 = 79%') # def test_imod(self): # """ From 0b4839fbbc673c9ec62302529fec18fcd16bfbc7 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 7 Sep 2018 13:39:52 -0700 Subject: [PATCH 204/425] Remove use-mirrors flag from pip invocation in travis --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6e20573f..89082e95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,12 +8,12 @@ python: sudo: false -# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors +# command to install dependencies, e.g. pip install -r requirements.txt # These packages only exist on Ubuntu 13.04 and newer: # 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 + - if [[ $TRAVIS_PYTHON_VERSION == 2.6* ]]; then pip install -r requirements_py26.txt; fi - python setup.py install # command to run tests, e.g. python setup.py test From d379c5fe9259b379c107e15b1a68f4645bb16496 Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Fri, 7 Sep 2018 16:40:44 -0400 Subject: [PATCH 205/425] Ignore bytes.__mod__ tests on Python < 3.5 --- src/future/utils/__init__.py | 1 + tests/test_future/test_bytes.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index cb4ade35..e374a81e 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -56,6 +56,7 @@ PY3 = sys.version_info[0] == 3 +PY35 = sys.version_info[0:2] >= (3, 5) PY2 = sys.version_info[0] == 2 PY26 = sys.version_info[0:2] == (2, 6) PY27 = sys.version_info[0:2] == (2, 7) diff --git a/tests/test_future/test_bytes.py b/tests/test_future/test_bytes.py index 2f47abc2..ccbe0c01 100644 --- a/tests/test_future/test_bytes.py +++ b/tests/test_future/test_bytes.py @@ -552,6 +552,8 @@ def test_maketrans(self): self.assertRaises(ValueError, bytes.maketrans, b'abc', b'xyzq') self.assertRaises(TypeError, bytes.maketrans, 'abc', 'def') + @unittest.skipUnless(utils.PY35 or utils.PY2, + 'test requires Python 2 or 3.5+') def test_mod_more(self): self.assertEqual(b'%s' % b'aaa', b'aaa') self.assertEqual(bytes(b'%s') % b'aaa', b'aaa') @@ -564,6 +566,8 @@ def test_mod_more(self): self.assertEqual(bytes(b'%(x)s') % {'x': b'aaa'}, b'aaa') self.assertEqual(bytes(b'%(x)s') % {'x': bytes(b'aaa')}, b'aaa') + @unittest.skipUnless(utils.PY35 or utils.PY2, + 'test requires Python 2 or 3.5+') def test_mod(self): """ From Py3.5 test suite (post-PEP 461). From 3e20f05f82c7270a0980b0d7f991ae7fb8e13d3b Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 7 Sep 2018 13:51:06 -0700 Subject: [PATCH 206/425] skip broken ttest --- tests/test_future/test_standard_library.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_future/test_standard_library.py b/tests/test_future/test_standard_library.py index 647da550..f4dd7942 100644 --- a/tests/test_future/test_standard_library.py +++ b/tests/test_future/test_standard_library.py @@ -319,7 +319,7 @@ def test_builtins(self): import builtins self.assertTrue(hasattr(builtins, 'tuple')) - # @unittest.skip("ssl support has been stripped out for now ...") + @unittest.skip("ssl redirect support on pypi isn't working as expected for now ...") def test_urllib_request_ssl_redirect(self): """ This site redirects to https://... From 7a1f48ee1fc357692a8e2fb292b093480fdbd7b1 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 7 Sep 2018 13:58:33 -0700 Subject: [PATCH 207/425] Add 3.5, 3.6, 3.7 to CI tests --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 89082e95..c2ec1032 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,9 @@ language: python python: + - "3.7" + - "3.6" + - "3.5" - "3.4" - "3.3" - "2.7" From 77b303769c10a80dd6d9014f9f510c20bc0f3bcb Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Fri, 7 Sep 2018 16:58:44 -0400 Subject: [PATCH 208/425] Enable custom mappings support --- src/future/types/newbytes.py | 12 ++++++++---- tests/test_future/test_bytes.py | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/future/types/newbytes.py b/src/future/types/newbytes.py index f0aa419f..42bb087f 100644 --- a/src/future/types/newbytes.py +++ b/src/future/types/newbytes.py @@ -177,10 +177,7 @@ def __rmul__(self, other): def __mod__(self, vals): if isinstance(vals, newbytes): vals = _builtin_bytes.__str__(vals) - elif isinstance(vals, dict): - for k, v in vals.items(): - if isinstance(v, newbytes): - vals[k] = _builtin_bytes.__str__(v) + elif isinstance(vals, tuple): newvals = [] for v in vals: @@ -188,6 +185,13 @@ def __mod__(self, vals): 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 join(self, iterable_of_bytes): diff --git a/tests/test_future/test_bytes.py b/tests/test_future/test_bytes.py index ccbe0c01..00f1c904 100644 --- a/tests/test_future/test_bytes.py +++ b/tests/test_future/test_bytes.py @@ -552,6 +552,20 @@ def test_maketrans(self): self.assertRaises(ValueError, bytes.maketrans, b'abc', b'xyzq') self.assertRaises(TypeError, bytes.maketrans, 'abc', 'def') + @unittest.skipUnless(utils.PY2, 'test requires Python 2') + def test_mod_custom_dict(self): + import UserDict + + class MyDict(UserDict.UserDict): + pass + + d = MyDict() + d['foo'] = bytes(b'bar') + self.assertFalse(isinstance(d, dict)) + self.assertTrue(isinstance(d, UserDict.UserDict)) + + self.assertEqual(bytes(b'%(foo)s') % d, b'bar') + @unittest.skipUnless(utils.PY35 or utils.PY2, 'test requires Python 2 or 3.5+') def test_mod_more(self): From 2ff07863857e10cfdfe3fee7479d132ec68d39ac Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 7 Sep 2018 14:03:03 -0700 Subject: [PATCH 209/425] Drop 3.7 as it's not on the TravisCI's agent's ubuntu version yet --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c2ec1032..78e9ae47 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: python python: - - "3.7" - "3.6" - "3.5" - "3.4" From 2b1f62daa3b342f042305f35ba80e99288d40b01 Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Fri, 7 Sep 2018 17:03:49 -0400 Subject: [PATCH 210/425] Clarify comment --- tests/test_future/test_bytes.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_future/test_bytes.py b/tests/test_future/test_bytes.py index 00f1c904..a699b9c0 100644 --- a/tests/test_future/test_bytes.py +++ b/tests/test_future/test_bytes.py @@ -588,6 +588,9 @@ def test_mod(self): The bytes mod code is in _PyBytes_Format() in bytesobject.c in Py3.5. """ + + # XXX Add support for %b! + # # b = bytes(b'hello, %b!') # orig = b # b = b % b'world' From ae7b3c7e262e61c9f4d9f4517ff927d570cef4fe Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Fri, 7 Sep 2018 17:10:36 -0400 Subject: [PATCH 211/425] Uncomment test_imod --- tests/test_future/test_bytes.py | 38 ++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/tests/test_future/test_bytes.py b/tests/test_future/test_bytes.py index a699b9c0..2a623d34 100644 --- a/tests/test_future/test_bytes.py +++ b/tests/test_future/test_bytes.py @@ -606,21 +606,29 @@ def test_mod(self): a = b % (bytes(b'seventy-nine'), 79) self.assertEqual(a, b'seventy-nine / 100 = 79%') - # def test_imod(self): - # """ - # From Py3.5 test suite (post-PEP 461) - # """ - # # if (3, 0) <= sys.version_info[:2] < (3, 5): - # # raise unittest.SkipTest('bytes % not yet implemented on Py3.0-3.4') - # b = bytes(b'hello, %b!') - # orig = b - # b %= b'world' - # self.assertEqual(b, b'hello, world!') - # self.assertEqual(orig, b'hello, %b!') - # self.assertFalse(b is orig) - # b = bytes(b'%s / 100 = %d%%') - # b %= (b'seventy-nine', 79) - # self.assertEqual(b, b'seventy-nine / 100 = 79%') + @unittest.skipUnless(utils.PY35 or utils.PY2, + 'test requires Python 2 or 3.5+') + def test_imod(self): + """ + From Py3.5 test suite (post-PEP 461) + """ + # if (3, 0) <= sys.version_info[:2] < (3, 5): + # raise unittest.SkipTest('bytes % not yet implemented on Py3.0-3.4') + + # b = bytes(b'hello, %b!') + # orig = b + # b %= b'world' + # self.assertEqual(b, b'hello, world!') + # self.assertEqual(orig, b'hello, %b!') + # self.assertFalse(b is orig) + + b = bytes(b'%s / 100 = %d%%') + b %= (b'seventy-nine', 79) + self.assertEqual(b, b'seventy-nine / 100 = 79%') + + b = bytes(b'%s / 100 = %d%%') + b %= (bytes(b'seventy-nine'), 79) + self.assertEqual(b, b'seventy-nine / 100 = 79%') # def test_mod_pep_461(self): # """ From 70b61bc5aa4ba7c4cd01743f9308ab5ec4f44e99 Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Fri, 7 Sep 2018 17:11:45 -0400 Subject: [PATCH 212/425] Make __imod__ explicitly call __mod__ --- src/future/types/newbytes.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/future/types/newbytes.py b/src/future/types/newbytes.py index 42bb087f..70d12687 100644 --- a/src/future/types/newbytes.py +++ b/src/future/types/newbytes.py @@ -194,6 +194,9 @@ def __mod__(self, vals): 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): From 2218d6fb139294a73b00abeb894d31bfc799f07a Mon Sep 17 00:00:00 2001 From: Louis Sautier Date: Mon, 20 Aug 2018 12:42:29 +0200 Subject: [PATCH 213/425] Skip int(x) keyword tests with Python 3.7 The first argument must be positional since https://github.com/python/cpython/commit/2e5642422f6234fd8d0c082142b27340e588f96e --- tests/test_future/test_int.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_future/test_int.py b/tests/test_future/test_int.py index f1d9c5d8..9acbd23a 100644 --- a/tests/test_future/test_int.py +++ b/tests/test_future/test_int.py @@ -265,12 +265,17 @@ def test_small_ints(self): def test_no_args(self): self.assertEqual(int(), 0) - def test_keyword_args(self): + @unittest.skipIf(sys.version_info >= (3, 7), + "The first parameter must be positional with Python >= 3.7" + ) + def test_x_keyword_arg(self): # Test invoking int() using keyword arguments. self.assertEqual(int(x=1.2), 1) - self.assertEqual(int('100', base=2), 4) self.assertEqual(int(x='100', base=2), 4) + def text_base_keyword_arg(self): + self.assertEqual(int('100', base=2), 4) + def test_newint_plus_float(self): minutes = int(100) second = 0.0 From 359b749d808a248073919047fdb58bb0d8dcf515 Mon Sep 17 00:00:00 2001 From: Flaviu Tamas Date: Mon, 10 Sep 2018 00:44:43 -0400 Subject: [PATCH 214/425] Add test for #113 --- tests/test_future/test_futurize.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index f3fe0b84..d46f9bc4 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -123,6 +123,17 @@ def test_encoding_comments_kept_at_top(self): """ self.convert_check(before, after) + def test_multiline_future_import(self): + """ + Issue #113: don't crash if a future import has multiple lines + """ + text = """ + from __future__ import ( + division + ) + """ + self.convert(text) + def test_shebang_blank_with_future_division_import(self): """ Issue #43: Is shebang line preserved as the first From dfc4f30d19cf5d9b9d5863c232d12e9f4300dcd7 Mon Sep 17 00:00:00 2001 From: Flaviu Tamas Date: Mon, 3 Sep 2018 16:15:50 -0400 Subject: [PATCH 215/425] Fix PR #357 Turns out that comments are associated with a specific AST node in python, and are not AST nodes on their own. Therefore, "continue" after detecting the shebang comment in fact causes the future import to be placed after the first AST node, which might be anything. --- src/libfuturize/fixer_util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libfuturize/fixer_util.py b/src/libfuturize/fixer_util.py index a10184c7..345bb3a6 100644 --- a/src/libfuturize/fixer_util.py +++ b/src/libfuturize/fixer_util.py @@ -243,7 +243,6 @@ def future_import(feature, node): # Is it a shebang or encoding line? if is_shebang_comment(node) or is_encoding_comment(node): shebang_encoding_idx = idx - continue if is_docstring(node): # skip over docstring continue From 923d473162974657f4c6a0912c40d3c33c30a0a5 Mon Sep 17 00:00:00 2001 From: "Jordan M. Adler" Date: Mon, 10 Sep 2018 11:45:55 -0700 Subject: [PATCH 216/425] Revert "Fix `past.translation` on read-only file systems" --- src/past/translation/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/past/translation/__init__.py b/src/past/translation/__init__.py index 6ea94536..7b21d9f5 100644 --- a/src/past/translation/__init__.py +++ b/src/past/translation/__init__.py @@ -402,9 +402,9 @@ def load_module(self, fullname): code = compile(source, self.pathname, 'exec') dirname = os.path.dirname(cachename) + if not os.path.exists(dirname): + os.makedirs(dirname) try: - if not os.path.exists(dirname): - os.makedirs(dirname) with open(cachename, 'wb') as f: data = marshal.dumps(code) f.write(data) From 970e7e41bdcb55cbc0ce5f711364127586389c32 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Mon, 10 Sep 2018 11:49:24 -0700 Subject: [PATCH 217/425] Merge #344 into master --- src/past/translation/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/past/translation/__init__.py b/src/past/translation/__init__.py index 7b21d9f5..6ea94536 100644 --- a/src/past/translation/__init__.py +++ b/src/past/translation/__init__.py @@ -402,9 +402,9 @@ def load_module(self, fullname): code = compile(source, self.pathname, 'exec') dirname = os.path.dirname(cachename) - if not os.path.exists(dirname): - os.makedirs(dirname) try: + if not os.path.exists(dirname): + os.makedirs(dirname) with open(cachename, 'wb') as f: data = marshal.dumps(code) f.write(data) From a6afa49318533390062035e19b790d54d89531d0 Mon Sep 17 00:00:00 2001 From: "Jordan M. Adler" Date: Mon, 10 Sep 2018 12:05:10 -0700 Subject: [PATCH 218/425] Revert "remove the parens from the cmp() lambda definition" --- docs/compatible_idioms.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index 7eb0cfb6..e48ac6d1 100644 --- a/docs/compatible_idioms.rst +++ b/docs/compatible_idioms.rst @@ -1120,7 +1120,7 @@ cmp() .. code:: python # Python 2 and 3: alternative 2 - cmp = lambda x, y: (x > y) - (x < y) + cmp = lambda(x, y): (x > y) - (x < y) assert cmp('a', 'b') < 0 and cmp('b', 'a') > 0 and cmp('c', 'c') == 0 reload() ~~~~~~~~ From 99cd678279e4a3e167a994a87844f0a2e63c7bd7 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 10 Sep 2018 13:55:14 -0700 Subject: [PATCH 219/425] Trim trailing white space throughout the project Many editors clean up trailing white space on save. By removing it all in one go, it helps keep future diffs cleaner by avoiding spurious white space changes on unrelated lines. --- .gitignore | 1 - MANIFEST.in | 1 - README.rst | 21 +- discover_tests.py | 5 +- .../ipython_py3compat.py | 52 +- docs/_themes/future/static/future.css_t | 50 +- docs/automatic_conversion.rst | 1 - docs/bind_method.rst | 6 +- docs/bytes_object.rst | 7 +- docs/changelog.rst | 26 +- docs/compatible_idioms.rst | 218 +-- docs/contents.rst.inc | 1 - docs/credits.rst | 7 +- docs/custom_iterators.rst | 3 +- docs/dev_notes.rst | 2 - docs/development.rst | 7 +- docs/dict_object.rst | 15 +- docs/faq.rst | 13 +- docs/future-builtins.rst | 1 - docs/futurize.rst | 2 - docs/futurize_cheatsheet.rst | 10 +- docs/hindsight.rst | 1 - docs/imports.rst | 6 +- docs/index.rst | 1 - docs/int_object.rst | 1 - docs/isinstance.rst | 7 +- docs/limitations.rst | 5 +- docs/metaclasses.rst | 8 +- docs/older_interfaces.rst | 12 +- docs/open_function.rst | 3 +- docs/other/lessons.txt | 4 +- docs/other/upload_future_docs.sh | 1 - docs/overview.rst | 1 - docs/pasteurize.rst | 12 +- docs/quickstart.rst | 6 +- docs/reference.rst | 9 +- docs/roadmap.rst | 3 +- docs/standard_library_imports.rst | 5 +- docs/stdlib_incompatibilities.rst | 9 +- docs/str_object.rst | 7 +- docs/translation.rst | 14 +- docs/unicode_literals.rst | 27 +- docs/upgrading.rst | 1 - docs/utilities.rst | 1 - docs/what_else.rst | 1 - docs/why_python3.rst | 4 +- requirements_py26.txt | 1 - src/future/backports/html/__init__.py | 1 - src/future/backports/html/entities.py | 1 - src/future/backports/html/parser.py | 1 - src/future/backports/misc.py | 2 +- src/future/backports/test/support.py | 22 +- src/future/backports/urllib/parse.py | 4 +- src/future/builtins/__init__.py | 4 +- src/future/builtins/iterators.py | 3 +- src/future/builtins/newnext.py | 3 +- src/future/builtins/newround.py | 12 +- src/future/moves/test/support.py | 1 - src/future/moves/tkinter/colorchooser.py | 1 - src/future/moves/tkinter/commondialog.py | 1 - src/future/moves/tkinter/constants.py | 1 - src/future/moves/tkinter/dialog.py | 1 - src/future/moves/tkinter/dnd.py | 1 - src/future/moves/tkinter/filedialog.py | 1 - src/future/moves/tkinter/font.py | 1 - src/future/moves/tkinter/messagebox.py | 1 - src/future/moves/tkinter/scrolledtext.py | 1 - src/future/moves/tkinter/simpledialog.py | 1 - src/future/moves/tkinter/tix.py | 1 - src/future/moves/tkinter/ttk.py | 1 - src/future/moves/urllib/__init__.py | 1 - src/future/moves/urllib/error.py | 4 +- src/future/moves/urllib/parse.py | 4 +- src/future/moves/urllib/request.py | 4 +- src/future/moves/urllib/response.py | 1 - src/future/standard_library/__init__.py | 2 +- src/future/tests/base.py | 6 +- src/future/types/__init__.py | 9 +- src/future/types/newdict.py | 2 +- src/future/types/newobject.py | 12 +- src/future/types/newopen.py | 1 - src/future/types/newstr.py | 10 +- src/future/utils/surrogateescape.py | 2 - src/libfuturize/fixer_util.py | 12 +- src/libfuturize/fixes/__init__.py | 1 - src/libfuturize/fixes/fix_UserDict.py | 9 +- src/libfuturize/fixes/fix_absolute_import.py | 1 - ...future__imports_except_unicode_literals.py | 1 - src/libfuturize/fixes/fix_basestring.py | 1 - src/libfuturize/fixes/fix_cmp.py | 1 - src/libfuturize/fixes/fix_division.py | 1 - src/libfuturize/fixes/fix_division_safe.py | 3 +- src/libfuturize/fixes/fix_execfile.py | 1 - src/libfuturize/fixes/fix_future_builtins.py | 1 - .../fixes/fix_future_standard_library.py | 2 - .../fix_future_standard_library_urllib.py | 2 - .../fixes/fix_order___future__imports.py | 1 - .../fixes/fix_print_with_import.py | 1 - .../fixes/fix_remove_old__future__imports.py | 1 - src/libfuturize/fixes/fix_unicode_keep_u.py | 1 - .../fixes/fix_unicode_literals_import.py | 3 +- src/libfuturize/main.py | 2 +- src/libpasteurize/fixes/__init__.py | 1 - src/libpasteurize/fixes/feature_base.py | 2 +- .../fixes/fix_add_all__future__imports.py | 1 - .../fixes/fix_add_all_future_builtins.py | 1 - src/libpasteurize/fixes/fix_annotations.py | 2 +- src/libpasteurize/fixes/fix_features.py | 2 +- src/libpasteurize/fixes/fix_fullargspec.py | 2 +- .../fixes/fix_future_builtins.py | 1 - src/libpasteurize/fixes/fix_imports.py | 1 - src/libpasteurize/fixes/fix_imports2.py | 5 +- src/libpasteurize/fixes/fix_kwargs.py | 3 +- src/libpasteurize/fixes/fix_metaclass.py | 2 +- src/libpasteurize/fixes/fix_unpacking.py | 2 +- src/libpasteurize/main.py | 1 - src/past/__init__.py | 3 +- src/past/builtins/__init__.py | 4 +- src/past/builtins/misc.py | 1 - src/past/builtins/noniterators.py | 47 +- src/past/translation/__init__.py | 19 +- src/past/types/__init__.py | 1 - src/past/types/basestring.py | 1 - src/past/types/olddict.py | 5 +- src/past/types/oldstr.py | 12 +- src/past/utils/__init__.py | 10 +- src/tkinter/colorchooser.py | 1 - src/tkinter/commondialog.py | 1 - src/tkinter/constants.py | 1 - src/tkinter/dialog.py | 1 - src/tkinter/dnd.py | 1 - src/tkinter/font.py | 1 - src/tkinter/messagebox.py | 1 - src/tkinter/scrolledtext.py | 1 - src/tkinter/simpledialog.py | 1 - src/tkinter/tix.py | 1 - src/tkinter/ttk.py | 1 - tests/test_future/test_decorators.py | 4 +- tests/test_future/test_futurize.py | 14 +- tests/test_future/test_libfuturize_fixers.py | 1544 ++++++++--------- tests/test_future/test_magicsuper.py | 1 - tests/test_future/test_object.py | 10 +- tests/test_future/test_pasteurize.py | 10 +- tests/test_future/test_standard_library.py | 6 +- tests/test_future/test_str.py | 2 +- tests/test_past/test_builtins.py | 6 +- tests/test_past/test_olddict.py | 2 +- tests/test_past/test_translation.py | 129 +- 148 files changed, 1256 insertions(+), 1380 deletions(-) diff --git a/.gitignore b/.gitignore index e211af6a..8c52a551 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,3 @@ nosetests.xml .mr.developer.cfg .project .pydevproject - diff --git a/MANIFEST.in b/MANIFEST.in index fcdb9c63..d0e9f3d1 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -25,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 868ed14b..e6801d90 100644 --- a/README.rst +++ b/README.rst @@ -68,7 +68,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) @@ -93,7 +93,7 @@ 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 zero-argument super() function: class VerboseList(list): def append(self, item): @@ -103,15 +103,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) @@ -187,7 +187,7 @@ 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 @@ -202,14 +202,14 @@ 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=' ') print(name) @@ -233,14 +233,14 @@ 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 @@ -284,4 +284,3 @@ 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. - diff --git a/discover_tests.py b/discover_tests.py index 9f2e581d..1d289418 100644 --- a/discover_tests.py +++ b/discover_tests.py @@ -33,14 +33,14 @@ def skeleton_run_test(self): 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'): @@ -55,4 +55,3 @@ def exclude_tests(suite, blacklist): 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/_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 fc3428bf..5c718da5 100644 --- a/docs/automatic_conversion.rst +++ b/docs/automatic_conversion.rst @@ -27,4 +27,3 @@ mostly unchanged on both Python 2 and Python 3. .. 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 1c4a7cc7..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 @@ -52,9 +52,9 @@ behaviours to Python 3's :class:`bytes`:: 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') @@ -78,4 +78,3 @@ identically on Python 2.x and 3.x:: 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 d4fdf55d..aa317b96 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -319,7 +319,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): @@ -451,7 +451,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.*`` @@ -530,7 +530,7 @@ 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) - + 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. @@ -560,7 +560,7 @@ 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 @@ -575,7 +575,7 @@ The ``pasteurize`` script for converting from Py3 to Py2/3 still adds 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 @@ -615,10 +615,10 @@ it like this:: $ pip3 install plotrique==0.2.5-7 --no-compile # to ignore SyntaxErrors $ python3 - + Then pass in a whitelist of module name prefixes to the ``past.autotranslate()`` function. Example:: - + >>> from past import autotranslate >>> autotranslate(['plotrique']) >>> import plotrique @@ -678,7 +678,7 @@ compatibility. 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() @@ -721,7 +721,7 @@ 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 @@ -754,7 +754,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) @@ -876,7 +876,7 @@ The unused ``hacks`` module has also been removed from the source tree. ``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. @@ -992,7 +992,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 @@ -1080,7 +1080,7 @@ v0.3.2: * 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 diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index a911e9d1..5ed60e2a 100644 --- a/docs/compatible_idioms.rst +++ b/docs/compatible_idioms.rst @@ -1,5 +1,5 @@ .. _compatible-idioms: - + Cheat Sheet: Writing Python 2-3 compatible code =============================================== @@ -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,7 +334,7 @@ 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 @@ -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,7 +505,7 @@ 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 ... @@ -515,7 +515,7 @@ Iterable dict values: from builtins import itervalues # or from six import itervalues - + for key in itervalues(heights): ... Iterable dict items: @@ -528,13 +528,13 @@ 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 future.utils import viewitems - + for (key, value) in viewitems(heights): # also behaves like a set ... .. code:: python @@ -543,7 +543,7 @@ Iterable dict items: from future.utils import iteritems # or from six import iteritems - + for (key, value) in iteritems(heights): ... dict keys/values/items as a list @@ -577,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 @@ -592,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: @@ -604,7 +604,7 @@ dict items as a list: # Python 2 and 3: option 2 from future.utils import listitems - + itemlist = listitems(heights) .. code:: python @@ -612,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 ---------------------- @@ -630,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') @@ -638,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) @@ -646,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') @@ -654,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): @@ -663,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') @@ -678,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 @@ -710,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 @@ -766,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 @@ -801,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 @@ -811,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 @@ -835,7 +835,7 @@ imap # Python 2 only: from itertools import imap - + myiter = imap(func, myoldlist) assert isinstance(myiter, iter) .. code:: python @@ -847,7 +847,7 @@ imap # Python 2 and 3: option 1 from builtins import map - + myiter = map(func, myoldlist) assert isinstance(myiter, iter) .. code:: python @@ -857,14 +857,14 @@ 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) @@ -890,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') @@ -912,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() ~~~~~~~~~~~~ @@ -926,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() @@ -954,7 +954,7 @@ 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 @@ -998,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 @@ -1098,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 @@ -1110,7 +1110,7 @@ chr() # Python 2 and 3: option 2 from builtins import bytes - + assert bytes([64]) == b'@' assert bytes([0xc8]) == b'\xc8' cmp() @@ -1156,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) @@ -1182,11 +1182,11 @@ 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() ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1195,14 +1195,14 @@ subprocess.check\_output() # 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 from future import standard_library standard_library.install_aliases() - + from subprocess import check_output collections: Counter and OrderedDict ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1211,14 +1211,14 @@ collections: Counter and OrderedDict # 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 StringIO module ~~~~~~~~~~~~~~~ @@ -1245,7 +1245,7 @@ http module import BaseHTTPServer import SimpleHTTPServer import CGIHttpServer - + # Python 2 and 3 (after ``pip install future``): import http.client import http.cookies @@ -1259,14 +1259,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 @@ -1276,13 +1276,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 @@ -1292,10 +1292,10 @@ 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 @@ -1321,7 +1321,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 @@ -1329,7 +1329,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 @@ -1366,9 +1366,9 @@ Tkinter import FileDialog import ScrolledText import SimpleDialog - import Tix + import Tix import Tkconstants - import Tkdnd + import Tkdnd import tkColorChooser import tkCommonDialog import tkFileDialog @@ -1376,7 +1376,7 @@ Tkinter import tkMessageBox import tkSimpleDialog import ttk - + # Python 2 and 3 (after ``pip install future``): import tkinter import tkinter.dialog @@ -1400,7 +1400,7 @@ socketserver # Python 2 only: import SocketServer - + # Python 2 and 3 (after ``pip install future``): import socketserver copy\_reg, copyreg @@ -1410,7 +1410,7 @@ copy\_reg, copyreg # Python 2 only: import copy_reg - + # Python 2 and 3 (after ``pip install future``): import copyreg configparser @@ -1420,7 +1420,7 @@ configparser # Python 2 only: from ConfigParser import ConfigParser - + # Python 2 and 3 (after ``pip install configparser``): from configparser import ConfigParser queue @@ -1430,7 +1430,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 @@ -1440,7 +1440,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 @@ -1452,16 +1452,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() @@ -1473,16 +1473,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/contents.rst.inc b/docs/contents.rst.inc index cedf7bb7..7c9bbf2f 100644 --- a/docs/contents.rst.inc +++ b/docs/contents.rst.inc @@ -24,4 +24,3 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` - diff --git a/docs/credits.rst b/docs/credits.rst index 011936d5..c713d36d 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -9,17 +9,17 @@ The software is distributed under an MIT licence. The text is as follows (from ``LICENSE.txt``):: Copyright (c) 2013-2016 Python Charmers Pty Ltd, Australia - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal 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 @@ -112,4 +112,3 @@ Other Credits - ``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..674d83b3 100644 --- a/docs/dev_notes.rst +++ b/docs/dev_notes.rst @@ -19,5 +19,3 @@ 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..06ff7798 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -11,12 +11,9 @@ The easiest way to start developing ``python-future`` is as follows: conda install -n future2 python=2.7 pip conda install -n future3 python=3.3 pip - - git clone https://github.com/PythonCharmers/python-future + + 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 c967d6bb..883beaa8 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,7 +18,7 @@ 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 @@ -34,12 +34,12 @@ 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()) @@ -82,11 +82,11 @@ 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) @@ -94,4 +94,3 @@ 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. - diff --git a/docs/faq.rst b/docs/faq.rst index 57fef25e..6ba443c8 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -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 @@ -143,10 +143,10 @@ version 2.0. .. 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 +189,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 @@ -237,7 +237,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, @@ -314,4 +314,3 @@ 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 3e1e278d..c98848d4 100644 --- a/docs/futurize.rst +++ b/docs/futurize.rst @@ -317,5 +317,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 bc6bc13e..b14fa8c2 100644 --- a/docs/futurize_cheatsheet.rst +++ b/docs/futurize_cheatsheet.rst @@ -16,7 +16,7 @@ a. Clone the package from github/bitbucket. Optionally rename your repo to ``pac 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.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: @@ -27,9 +27,9 @@ 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:: @@ -49,7 +49,7 @@ 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 @@ -110,7 +110,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/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 50f97995..cd1acb14 100644 --- a/docs/imports.rst +++ b/docs/imports.rst @@ -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,7 +59,7 @@ 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 builtins import (ascii, bytes, chr, dict, filter, hex, input, int, map, next, oct, open, pow, range, round, str, super, zip) @@ -84,7 +84,7 @@ The internal API is currently as follows:: 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. +builtins on Python 2, see the docs for these modules. For more information on what the backported types provide, 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 9f49250d..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) @@ -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/older_interfaces.rst b/docs/older_interfaces.rst index 463d5775..546f92b9 100644 --- a/docs/older_interfaces.rst +++ b/docs/older_interfaces.rst @@ -22,7 +22,7 @@ robust, at the cost of less idiomatic code. Use it as follows:: If you wish to achieve the effect of a two-level import such as this:: - import http.client + import http.client portably on both Python 2 and Python 3, note that Python currently does not support syntax like this:: @@ -61,7 +61,7 @@ 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') @@ -120,12 +120,12 @@ active for the life of a process unless removed.) .. 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. @@ -133,11 +133,9 @@ active for the life of a process unless removed.) .. 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 656c9127..88066050 100644 --- a/docs/open_function.rst +++ b/docs/open_function.rst @@ -5,7 +5,7 @@ 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. @@ -37,4 +37,3 @@ cast it explicitly as follows:: assert data[4] == 13 # integer # Raises TypeError: # data + u'' - 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 006a45a9..d5c272d2 100644 --- a/docs/other/upload_future_docs.sh +++ b/docs/other/upload_future_docs.sh @@ -21,4 +21,3 @@ 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/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 dc434759..5cdffa3e 100644 --- a/docs/pasteurize.rst +++ b/docs/pasteurize.rst @@ -4,20 +4,20 @@ ---------------------------- 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 @@ -26,7 +26,7 @@ into this code which runs on both Py2 and Py3:: 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 @@ -43,5 +43,3 @@ file. - 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 b0d5900f..dbc474da 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -66,7 +66,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. @@ -93,7 +93,7 @@ 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.install_aliases() @@ -136,7 +136,7 @@ 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) 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/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 e0c87746..5e9115a0 100644 --- a/docs/standard_library_imports.rst +++ b/docs/standard_library_imports.rst @@ -78,7 +78,7 @@ complete list is here:: 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.python.org/pypi/configparser). +``configparser`` module because a full backport exists (see https://pypi.python.org/pypi/configparser). .. _list-standard-library-refactored: @@ -187,11 +187,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 38eeaa88..5f2217d2 100644 --- a/docs/stdlib_incompatibilities.rst +++ b/docs/stdlib_incompatibilities.rst @@ -33,14 +33,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,7 +54,7 @@ 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 @@ -96,7 +96,7 @@ 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) + >>> pack('<4H2I', version, rec_type, build, year, file_hist_flags, ver_can_read) raised ``TypeError: Struct() argument 1 must be string, not unicode``. @@ -104,4 +104,3 @@ 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 722d7747..4c5257a0 100644 --- a/docs/str_object.rst +++ b/docs/str_object.rst @@ -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 @@ -97,4 +97,3 @@ identically on Python 2.x and 3.x:: 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 e59c6676..49f558b0 100644 --- a/docs/translation.rst +++ b/docs/translation.rst @@ -19,10 +19,10 @@ 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 >>> 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 @@ -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 7af644c8..7252e4d6 100644 --- a/docs/unicode_literals.rst +++ b/docs/unicode_literals.rst @@ -1,4 +1,3 @@ - .. _unicode-literals: Should I import unicode_literals? @@ -28,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 @@ -57,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 @@ -82,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 @@ -122,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 @@ -156,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!" @@ -182,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/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/requirements_py26.txt b/requirements_py26.txt index 5b618903..a671277f 100644 --- a/requirements_py26.txt +++ b/requirements_py26.txt @@ -1,4 +1,3 @@ unittest2 argparse # for the http.server module importlib - 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/misc.py b/src/future/backports/misc.py index 31e713ae..e883d335 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -779,7 +779,7 @@ def __bool__(self): # Py2 compatibility: __nonzero__ = __bool__ - + @recursive_repr() def __repr__(self): return '{0.__class__.__name__}({1})'.format( diff --git a/src/future/backports/test/support.py b/src/future/backports/test/support.py index b59c4ff7..1999e208 100644 --- a/src/future/backports/test/support.py +++ b/src/future/backports/test/support.py @@ -666,7 +666,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 @@ -685,11 +685,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) @@ -702,7 +702,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': @@ -712,7 +712,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. @@ -745,7 +745,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 @@ -775,7 +775,7 @@ def _is_ipv6_enabled(): # except UnicodeDecodeError: # TESTFN_UNDECODABLE = os.fsencode(TESTFN) + name # break -# +# # if FS_NONASCII: # TESTFN_NONASCII = TESTFN + '-' + FS_NONASCII # else: @@ -1667,15 +1667,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/builtins/__init__.py b/src/future/builtins/__init__.py index 94011f97..216465a1 100644 --- a/src/future/builtins/__init__.py +++ b/src/future/builtins/__init__.py @@ -38,9 +38,9 @@ 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', 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/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..3943ebb6 100644 --- a/src/future/builtins/newround.py +++ b/src/future/builtins/newround.py @@ -1,7 +1,7 @@ """ ``python-future``: pure Python implementation of Python 3 round(). """ - + from future.utils import PYPY, PY26, bind_method # Use the decimal module for simplicity of implementation (and @@ -12,13 +12,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,7 +28,7 @@ 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) @@ -48,7 +48,7 @@ def newround(number, ndigits=None): return int(d) else: return float(d) - + ### From Python 2.7's decimal.py. Only needed to support Py2.6: diff --git a/src/future/moves/test/support.py b/src/future/moves/test/support.py index ab189f40..e9aa0f48 100644 --- a/src/future/moves/test/support.py +++ b/src/future/moves/test/support.py @@ -8,4 +8,3 @@ __future_module__ = True with suspend_hooks(): from test.test_support 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..973923e2 100644 --- a/src/future/moves/tkinter/filedialog.py +++ b/src/future/moves/tkinter/filedialog.py @@ -10,4 +10,3 @@ except ImportError: raise ImportError('The FileDialog 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 index 22ac9774..081c1b49 100644 --- a/src/future/moves/tkinter/ttk.py +++ b/src/future/moves/tkinter/ttk.py @@ -10,4 +10,3 @@ 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..60e440a7 100644 --- a/src/future/moves/urllib/request.py +++ b/src/future/moves/urllib/request.py @@ -51,7 +51,7 @@ # URLopener, # FancyURLopener, # proxy_bypass) - + # from urllib2 import ( # AbstractBasicAuthHandler, # AbstractDigestAuthHandler, @@ -80,7 +80,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 6246eb05..05ac4ec7 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -397,7 +397,7 @@ def scrub_future_sys_modules(): """ Deprecated. """ - return {} + return {} class suspend_hooks(object): """ diff --git a/src/future/tests/base.py b/src/future/tests/base.py index 546c779b..9f4607b6 100644 --- a/src/future/tests/base.py +++ b/src/future/tests/base.py @@ -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, 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/newdict.py b/src/future/types/newdict.py index 5dbcc4b7..3f3a559d 100644 --- a/src/future/types/newdict.py +++ b/src/future/types/newdict.py @@ -100,7 +100,7 @@ def __new__(cls, *args, **kwargs): else: value = args[0] return super(newdict, cls).__new__(cls, value) - + def __native__(self): """ Hook for the future.utils.native() function diff --git a/src/future/types/newobject.py b/src/future/types/newobject.py index 1ec09ff5..35ec05ec 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,7 +32,7 @@ def __next__(self): # note the Py3 interface return next(self._iter).upper() def __iter__(self): return self - + assert list(Upper('hello')) == list('HELLO') """ @@ -62,7 +62,7 @@ class newobject(object): next __unicode__ __nonzero__ - + Subclasses of this class can merely define the Python 3 methods (__next__, __str__, and __bool__). """ @@ -70,7 +70,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. @@ -123,7 +123,7 @@ def __long__(self): # else: # value = args[0] # return super(newdict, cls).__new__(cls, value) - + def __native__(self): """ Hook for the future.utils.native() function 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/newstr.py b/src/future/types/newstr.py index fd8615af..def42e94 100644 --- a/src/future/types/newstr.py +++ b/src/future/types/newstr.py @@ -37,7 +37,7 @@ ``__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 @@ -73,7 +73,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 +81,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,7 +100,7 @@ def __new__(cls, *args, **kwargs): else: value = args[0] return super(newstr, cls).__new__(cls, value) - + def __repr__(self): """ Without the u prefix @@ -128,7 +128,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)) diff --git a/src/future/utils/surrogateescape.py b/src/future/utils/surrogateescape.py index 78ac11cb..0dcc9fa6 100644 --- a/src/future/utils/surrogateescape.py +++ b/src/future/utils/surrogateescape.py @@ -196,5 +196,3 @@ def register_surrogateescape(): # c = encodefilename(b) # assert c == fn, '%r != %r' % (c, fn) # # print("ok") - - diff --git a/src/libfuturize/fixer_util.py b/src/libfuturize/fixer_util.py index 345bb3a6..28ec76a0 100644 --- a/src/libfuturize/fixer_util.py +++ b/src/libfuturize/fixer_util.py @@ -62,7 +62,7 @@ def Minus(prefix=None): def commatize(leafs): """ - Accepts/turns: (Name, Name, ..., Name, Name) + Accepts/turns: (Name, Name, ..., Name, Name) Returns/into: (Name, Comma, Name, Comma, ..., Name, Comma, Name) """ new_leafs = [] @@ -272,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 @@ -304,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): @@ -338,7 +338,7 @@ def touch_import_top(package, name_to_import, node): 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. """ @@ -415,7 +415,7 @@ 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" ")]) children_import = [import_, Newline()] @@ -517,5 +517,3 @@ def wrap_in_fn_call(fn_name, args, prefix=None): else: assert NotImplementedError('write me') return Call(Name(fn_name), newargs, prefix=prefix) - - diff --git a/src/libfuturize/fixes/__init__.py b/src/libfuturize/fixes/__init__.py index a059c949..7de304da 100644 --- a/src/libfuturize/fixes/__init__.py +++ b/src/libfuturize/fixes/__init__.py @@ -94,4 +94,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 73b1cfb8..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 @@ -100,4 +100,3 @@ def transform(self, node, results): 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..1d419a1c 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 @@ -24,4 +24,3 @@ def transform(self, node, results): future_import(u"print_function", node) future_import(u"division", node) future_import(u"absolute_import", node) - diff --git a/src/libfuturize/fixes/fix_basestring.py b/src/libfuturize/fixes/fix_basestring.py index 8c6ec6ce..5676d08f 100644 --- a/src/libfuturize/fixes/fix_basestring.py +++ b/src/libfuturize/fixes/fix_basestring.py @@ -15,4 +15,3 @@ class FixBasestring(fixer_base.BaseFix): 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 3a0d9aef..a3adfa67 100644 --- a/src/libfuturize/fixes/fix_division_safe.py +++ b/src/libfuturize/fixes/fix_division_safe.py @@ -67,7 +67,7 @@ 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 + 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] @@ -90,4 +90,3 @@ def transform(self, node, results): return touch_import_top(u'past.utils', u'old_div', node) return wrap_in_fn_call("old_div", (expr1, expr2), prefix=node.prefix) - 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_order___future__imports.py b/src/libfuturize/fixes/fix_order___future__imports.py index 14312639..00d7ef60 100644 --- a/src/libfuturize/fixes/fix_order___future__imports.py +++ b/src/libfuturize/fixes/fix_order___future__imports.py @@ -34,4 +34,3 @@ class FixOrderFutureImports(fixer_base.BaseFix): def transform(self, node, results): # TODO # write me pass - diff --git a/src/libfuturize/fixes/fix_print_with_import.py b/src/libfuturize/fixes/fix_print_with_import.py index 5308d925..34490461 100644 --- a/src/libfuturize/fixes/fix_print_with_import.py +++ b/src/libfuturize/fixes/fix_print_with_import.py @@ -20,4 +20,3 @@ def transform(self, node, results): future_import(u'print_function', node) n_stmt = super(FixPrintWithImport, self).transform(node, results) return n_stmt - 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 02808786..13e24314 100644 --- a/src/libfuturize/main.py +++ b/src/libfuturize/main.py @@ -91,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", 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..37897946 100644 --- a/src/libpasteurize/fixes/fix_add_all__future__imports.py +++ b/src/libpasteurize/fixes/fix_add_all__future__imports.py @@ -22,4 +22,3 @@ def transform(self, node, results): future_import(u"print_function", node) future_import(u"division", node) future_import(u"absolute_import", 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 4db0d548..2d6718f1 100644 --- a/src/libpasteurize/fixes/fix_imports.py +++ b/src/libpasteurize/fixes/fix_imports.py @@ -110,4 +110,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..c2d3207a 100644 --- a/src/libpasteurize/fixes/fix_unpacking.py +++ b/src/libpasteurize/fixes/fix_unpacking.py @@ -60,7 +60,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 cc0e6ec3..8eca78da 100644 --- a/src/libpasteurize/main.py +++ b/src/libpasteurize/main.py @@ -146,4 +146,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 08eeb58d..06293a89 100644 --- a/src/past/__init__.py +++ b/src/past/__init__.py @@ -67,7 +67,7 @@ until the authors of the Python 2 modules have upgraded their code. Then, for example:: - + >>> mypy2module.func_taking_py2_string(oldstr(b'abcd')) @@ -90,4 +90,3 @@ __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..06fbb92d 100644 --- a/src/past/builtins/misc.py +++ b/src/past/builtins/misc.py @@ -87,4 +87,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..5826b97c 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'] @@ -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/translation/__init__.py b/src/past/translation/__init__.py index 6ea94536..c7ae2b7a 100644 --- a/src/past/translation/__init__.py +++ b/src/past/translation/__init__.py @@ -28,7 +28,7 @@ >>> from past.translation import remove_hooks >>> remove_hooks() -Author: Ed Schofield. +Author: Ed Schofield. Inspired by and based on ``uprefix`` by Vinay M. Sajip. """ @@ -220,16 +220,16 @@ def detect_python2(source, pathname): # 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' % + 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' % + 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' % + f.write('### Original code (detected as py3): %s\n%s' % (pathname, source)) try: os.remove('/tmp/futurize_code.py') @@ -359,7 +359,7 @@ def load_module(self, fullname): # 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 @@ -367,7 +367,7 @@ def load_module(self, fullname): #else, regular module mod.__path__ = [] mod.__package__ = fullname.rpartition('.')[0] - + try: cachename = imp.cache_from_source(self.pathname) if not os.path.exists(cachename): @@ -396,7 +396,7 @@ def load_module(self, fullname): if detect_python2(source, self.pathname): source = self.transform(source) with open('/tmp/futurized_code.py', 'w') as f: - f.write('### Futurized code (from %s)\n%s' % + f.write('### Futurized code (from %s)\n%s' % (self.pathname, source)) code = compile(source, self.pathname, 'exec') @@ -457,7 +457,7 @@ def detect_hooks(): class hooks(object): """ Acts as a context manager. Use like this: - + >>> from past import translation >>> with translation.hooks(): ... import mypy2module @@ -477,7 +477,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 @@ -495,4 +495,3 @@ def __enter__(self): def __exit__(self, *args): if self.hooks_were_installed: 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..1cab22f6 100644 --- a/src/past/types/basestring.py +++ b/src/past/types/basestring.py @@ -37,4 +37,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..7768d328 100644 --- a/src/past/types/oldstr.py +++ b/src/past/types/oldstr.py @@ -32,7 +32,7 @@ def unescape(s): def """ return s.encode().decode('unicode_escape') - + class oldstr(with_metaclass(BaseOldStr, _builtin_bytes)): """ @@ -55,14 +55,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 +84,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 +101,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 +124,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..c6606d0b 100644 --- a/src/past/utils/__init__.py +++ b/src/past/utils/__init__.py @@ -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/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/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 index 22ac9774..081c1b49 100644 --- a/src/tkinter/ttk.py +++ b/src/tkinter/ttk.py @@ -10,4 +10,3 @@ except ImportError: raise ImportError('The ttk module is missing. Does your Py2 ' 'installation include tkinter?') - diff --git a/tests/test_future/test_decorators.py b/tests/test_future/test_decorators.py index 428fb8a3..9ec2bb37 100644 --- a/tests/test_future/test_decorators.py +++ b/tests/test_future/test_decorators.py @@ -36,14 +36,14 @@ def __str__(self): assert str(a) == str(b) def test_implements_iterator(self): - + @implements_iterator class MyIter(object): def __next__(self): return 'Next!' def __iter__(self): return self - + itr = MyIter() self.assertEqual(next(itr), 'Next!') diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index d4df5375..49d210f5 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -452,7 +452,7 @@ def test_xrange(self): pass """ self.convert_check(before, after, ignore_imports=False) - + def test_source_coding_utf8(self): """ Tests to ensure that the source coding line is not corrupted or @@ -517,13 +517,13 @@ def test_apply(self): before = ''' def addup(*x): return sum(x) - + assert apply(addup, (10,20)) == 30 ''' after = """ def addup(*x): return sum(x) - + assert addup(*(10,20)) == 30 """ self.convert_check(before, after) @@ -662,7 +662,7 @@ def test_urllib_refactor(self): from future import standard_library standard_library.install_aliases() import urllib.request - + URL = 'http://pypi.python.org/pypi/future/json' package = 'future' r = urllib.request.urlopen(URL.format(package)) @@ -1045,13 +1045,13 @@ def test___future___import_position(self): # # another comment # - + CONSTANTS = [ 0, 01, 011, 0111, 012, 02, 021, 0211, 02111, 013 ] _RN_LETTERS = "IVXLCDM" - + def my_func(value): pass - + ''' Docstring-like comment here ''' """ self.convert(code) diff --git a/tests/test_future/test_libfuturize_fixers.py b/tests/test_future/test_libfuturize_fixers.py index c7fa8ddc..8c4a9a3e 100644 --- a/tests/test_future/test_libfuturize_fixers.py +++ b/tests/test_future/test_libfuturize_fixers.py @@ -25,7 +25,7 @@ # grammar_path = os.path.join(test_dir, "..", "Grammar.txt") # grammar = driver.load_grammar(grammar_path) # driver = driver.Driver(grammar, convert=pytree.convert) -# +# # def parse_string(string): # return driver.parse_string(reformat(string), debug=True) @@ -118,118 +118,118 @@ def assert_runs_after(self, *names): ############### EDIT the tests below ... -# +# # class Test_ne(FixerTestCase): # fixer = "ne" -# +# # def test_basic(self): # b = """if x <> y: # pass""" -# +# # a = """if x != y: # pass""" # self.check(b, a) -# -# +# +# # class Test_print(FixerTestCase): # fixer = "print_" -# +# # def test_print(self): # b = """print 'Hello world'""" # a = """from __future__ import print_function\nprint('Hello world')""" # self.check(b, a) -# -# +# +# # class Test_apply(FixerTestCase): # fixer = "apply" -# +# # def test_1(self): # b = """x = apply(f, g + h)""" # a = """x = f(*g + h)""" # self.check(b, a) -# -# +# +# # class Test_intern(FixerTestCase): # fixer = "intern" -# +# # def test_prefix_preservation(self): # b = """x = intern( a )""" # a = """import sys\nx = sys.intern( a )""" # self.check(b, a) -# +# # b = """y = intern("b" # test # )""" # a = """import sys\ny = sys.intern("b" # test # )""" # self.check(b, a) -# +# # b = """z = intern(a+b+c.d, )""" # a = """import sys\nz = sys.intern(a+b+c.d, )""" # self.check(b, a) -# +# # def test(self): # b = """x = intern(a)""" # a = """import sys\nx = sys.intern(a)""" # self.check(b, a) -# +# # b = """z = intern(a+b+c.d,)""" # a = """import sys\nz = sys.intern(a+b+c.d,)""" # self.check(b, a) -# +# # b = """intern("y%s" % 5).replace("y", "")""" # a = """import sys\nsys.intern("y%s" % 5).replace("y", "")""" # self.check(b, a) -# +# # # These should not be refactored -# +# # def test_unchanged(self): # s = """intern(a=1)""" # self.unchanged(s) -# +# # s = """intern(f, g)""" # self.unchanged(s) -# +# # s = """intern(*h)""" # self.unchanged(s) -# +# # s = """intern(**i)""" # self.unchanged(s) -# +# # s = """intern()""" # self.unchanged(s) -# +# # class Test_reduce(FixerTestCase): # fixer = "reduce" -# +# # def test_simple_call(self): # b = "reduce(a, b, c)" # a = "from functools import reduce\nreduce(a, b, c)" # self.check(b, a) -# +# # def test_bug_7253(self): # # fix_tuple_params was being bad and orphaning nodes in the tree. # b = "def x(arg): reduce(sum, [])" # a = "from functools import reduce\ndef x(arg): reduce(sum, [])" # self.check(b, a) -# +# # def test_call_with_lambda(self): # b = "reduce(lambda x, y: x + y, seq)" # a = "from functools import reduce\nreduce(lambda x, y: x + y, seq)" # self.check(b, a) -# +# # def test_unchanged(self): # s = "reduce(a)" # self.unchanged(s) -# +# # s = "reduce(a, b=42)" # self.unchanged(s) -# +# # s = "reduce(a, b, c, d)" # self.unchanged(s) -# +# # s = "reduce(**c)" # self.unchanged(s) -# +# # s = "reduce()" # self.unchanged(s) @@ -341,96 +341,96 @@ def test_with_future_print_function(self): # class Test_exec(FixerTestCase): # fixer = "exec" -# +# # def test_prefix_preservation(self): # b = """ exec code in ns1, ns2""" # a = """ exec(code, ns1, ns2)""" # self.check(b, a) -# +# # def test_basic(self): # b = """exec code""" # a = """exec(code)""" # self.check(b, a) -# +# # def test_with_globals(self): # b = """exec code in ns""" # a = """exec(code, ns)""" # self.check(b, a) -# +# # def test_with_globals_locals(self): # b = """exec code in ns1, ns2""" # a = """exec(code, ns1, ns2)""" # self.check(b, a) -# +# # def test_complex_1(self): # b = """exec (a.b()) in ns""" # a = """exec((a.b()), ns)""" # self.check(b, a) -# +# # def test_complex_2(self): # b = """exec a.b() + c in ns""" # a = """exec(a.b() + c, ns)""" # self.check(b, a) -# +# # # These should not be touched -# +# # def test_unchanged_1(self): # s = """exec(code)""" # self.unchanged(s) -# +# # def test_unchanged_2(self): # s = """exec (code)""" # self.unchanged(s) -# +# # def test_unchanged_3(self): # s = """exec(code, ns)""" # self.unchanged(s) -# +# # def test_unchanged_4(self): # s = """exec(code, ns1, ns2)""" # self.unchanged(s) -# +# # class Test_repr(FixerTestCase): # fixer = "repr" -# +# # def test_prefix_preservation(self): # b = """x = `1 + 2`""" # a = """x = repr(1 + 2)""" # self.check(b, a) -# +# # def test_simple_1(self): # b = """x = `1 + 2`""" # a = """x = repr(1 + 2)""" # self.check(b, a) -# +# # def test_simple_2(self): # b = """y = `x`""" # a = """y = repr(x)""" # self.check(b, a) -# +# # def test_complex(self): # b = """z = `y`.__repr__()""" # a = """z = repr(y).__repr__()""" # self.check(b, a) -# +# # def test_tuple(self): # b = """x = `1, 2, 3`""" # a = """x = repr((1, 2, 3))""" # self.check(b, a) -# +# # def test_nested(self): # b = """x = `1 + `2``""" # a = """x = repr(1 + repr(2))""" # self.check(b, a) -# +# # def test_nested_tuples(self): # b = """x = `1, 2 + `3, 4``""" # a = """x = repr((1, 2 + repr((3, 4))))""" # self.check(b, a) -# +# # class Test_except(FixerTestCase): # fixer = "except" -# +# # def test_prefix_preservation(self): # b = """ # try: @@ -443,7 +443,7 @@ def test_with_future_print_function(self): # except (RuntimeError, ImportError) as e: # pass""" # self.check(b, a) -# +# # def test_simple(self): # b = """ # try: @@ -456,7 +456,7 @@ def test_with_future_print_function(self): # except Foo as e: # pass""" # self.check(b, a) -# +# # def test_simple_no_space_before_target(self): # b = """ # try: @@ -469,7 +469,7 @@ def test_with_future_print_function(self): # except Foo as e: # pass""" # self.check(b, a) -# +# # def test_tuple_unpack(self): # b = """ # def foo(): @@ -479,7 +479,7 @@ def test_with_future_print_function(self): # pass # except ImportError, e: # pass""" -# +# # a = """ # def foo(): # try: @@ -490,28 +490,28 @@ def test_with_future_print_function(self): # except ImportError as e: # pass""" # self.check(b, a) -# +# # def test_multi_class(self): # b = """ # try: # pass # except (RuntimeError, ImportError), e: # pass""" -# +# # a = """ # try: # pass # except (RuntimeError, ImportError) as e: # pass""" # self.check(b, a) -# +# # def test_list_unpack(self): # b = """ # try: # pass # except Exception, [a, b]: # pass""" -# +# # a = """ # try: # pass @@ -519,14 +519,14 @@ def test_with_future_print_function(self): # [a, b] = xxx_todo_changeme.args # pass""" # self.check(b, a) -# +# # def test_weird_target_1(self): # b = """ # try: # pass # except Exception, d[5]: # pass""" -# +# # a = """ # try: # pass @@ -534,14 +534,14 @@ def test_with_future_print_function(self): # d[5] = xxx_todo_changeme # pass""" # self.check(b, a) -# +# # def test_weird_target_2(self): # b = """ # try: # pass # except Exception, a.foo: # pass""" -# +# # a = """ # try: # pass @@ -549,14 +549,14 @@ def test_with_future_print_function(self): # a.foo = xxx_todo_changeme # pass""" # self.check(b, a) -# +# # def test_weird_target_3(self): # b = """ # try: # pass # except Exception, a().foo: # pass""" -# +# # a = """ # try: # pass @@ -564,7 +564,7 @@ def test_with_future_print_function(self): # a().foo = xxx_todo_changeme # pass""" # self.check(b, a) -# +# # def test_bare_except(self): # b = """ # try: @@ -573,7 +573,7 @@ def test_with_future_print_function(self): # pass # except: # pass""" -# +# # a = """ # try: # pass @@ -582,7 +582,7 @@ def test_with_future_print_function(self): # except: # pass""" # self.check(b, a) -# +# # def test_bare_except_and_else_finally(self): # b = """ # try: @@ -595,7 +595,7 @@ def test_with_future_print_function(self): # pass # finally: # pass""" -# +# # a = """ # try: # pass @@ -608,7 +608,7 @@ def test_with_future_print_function(self): # finally: # pass""" # self.check(b, a) -# +# # def test_multi_fixed_excepts_before_bare_except(self): # b = """ # try: @@ -619,7 +619,7 @@ def test_with_future_print_function(self): # pass # except: # pass""" -# +# # a = """ # try: # pass @@ -630,7 +630,7 @@ def test_with_future_print_function(self): # except: # pass""" # self.check(b, a) -# +# # def test_one_line_suites(self): # b = """ # try: raise TypeError @@ -676,9 +676,9 @@ def test_with_future_print_function(self): # finally: done() # """ # self.check(b, a) -# +# # # These should not be touched: -# +# # def test_unchanged_1(self): # s = """ # try: @@ -686,7 +686,7 @@ def test_with_future_print_function(self): # except: # pass""" # self.unchanged(s) -# +# # def test_unchanged_2(self): # s = """ # try: @@ -694,7 +694,7 @@ def test_with_future_print_function(self): # except Exception: # pass""" # self.unchanged(s) -# +# # def test_unchanged_3(self): # s = """ # try: @@ -702,87 +702,87 @@ def test_with_future_print_function(self): # except (Exception, SystemExit): # pass""" # self.unchanged(s) -# +# # class Test_raise(FixerTestCase): # fixer = "raise" -# +# # def test_basic(self): # b = """raise Exception, 5""" # a = """raise Exception(5)""" # self.check(b, a) -# +# # def test_prefix_preservation(self): # b = """raise Exception,5""" # a = """raise Exception(5)""" # self.check(b, a) -# +# # b = """raise Exception, 5""" # a = """raise Exception(5)""" # self.check(b, a) -# +# # def test_with_comments(self): # b = """raise Exception, 5 # foo""" # a = """raise Exception(5) # foo""" # self.check(b, a) -# +# # b = """raise E, (5, 6) % (a, b) # foo""" # a = """raise E((5, 6) % (a, b)) # foo""" # self.check(b, a) -# +# # b = """def foo(): # raise Exception, 5, 6 # foo""" # a = """def foo(): # raise Exception(5).with_traceback(6) # foo""" # self.check(b, a) -# +# # def test_None_value(self): # b = """raise Exception(5), None, tb""" # a = """raise Exception(5).with_traceback(tb)""" # self.check(b, a) -# +# # def test_tuple_value(self): # b = """raise Exception, (5, 6, 7)""" # a = """raise Exception(5, 6, 7)""" # self.check(b, a) -# +# # def test_tuple_detection(self): # b = """raise E, (5, 6) % (a, b)""" # a = """raise E((5, 6) % (a, b))""" # self.check(b, a) -# +# # def test_tuple_exc_1(self): # b = """raise (((E1, E2), E3), E4), V""" # a = """raise E1(V)""" # self.check(b, a) -# +# # def test_tuple_exc_2(self): # b = """raise (E1, (E2, E3), E4), V""" # a = """raise E1(V)""" # self.check(b, a) -# +# # # These should produce a warning -# +# # def test_string_exc(self): # s = """raise 'foo'""" # self.warns_unchanged(s, "Python 3 does not support string exceptions") -# +# # def test_string_exc_val(self): # s = """raise "foo", 5""" # self.warns_unchanged(s, "Python 3 does not support string exceptions") -# +# # def test_string_exc_val_tb(self): # s = """raise "foo", 5, 6""" # self.warns_unchanged(s, "Python 3 does not support string exceptions") -# +# # # These should result in traceback-assignment -# +# # def test_tb_1(self): # b = """def foo(): # raise Exception, 5, 6""" # a = """def foo(): # raise Exception(5).with_traceback(6)""" # self.check(b, a) -# +# # def test_tb_2(self): # b = """def foo(): # a = 5 @@ -793,14 +793,14 @@ def test_with_future_print_function(self): # raise Exception(5).with_traceback(6) # b = 6""" # self.check(b, a) -# +# # def test_tb_3(self): # b = """def foo(): # raise Exception,5,6""" # a = """def foo(): # raise Exception(5).with_traceback(6)""" # self.check(b, a) -# +# # def test_tb_4(self): # b = """def foo(): # a = 5 @@ -811,14 +811,14 @@ def test_with_future_print_function(self): # raise Exception(5).with_traceback(6) # b = 6""" # self.check(b, a) -# +# # def test_tb_5(self): # b = """def foo(): # raise Exception, (5, 6, 7), 6""" # a = """def foo(): # raise Exception(5, 6, 7).with_traceback(6)""" # self.check(b, a) -# +# # def test_tb_6(self): # b = """def foo(): # a = 5 @@ -829,67 +829,67 @@ def test_with_future_print_function(self): # raise Exception(5, 6, 7).with_traceback(6) # b = 6""" # self.check(b, a) -# +# # class Test_throw(FixerTestCase): # fixer = "throw" -# +# # def test_1(self): # b = """g.throw(Exception, 5)""" # a = """g.throw(Exception(5))""" # self.check(b, a) -# +# # def test_2(self): # b = """g.throw(Exception,5)""" # a = """g.throw(Exception(5))""" # self.check(b, a) -# +# # def test_3(self): # b = """g.throw(Exception, (5, 6, 7))""" # a = """g.throw(Exception(5, 6, 7))""" # self.check(b, a) -# +# # def test_4(self): # b = """5 + g.throw(Exception, 5)""" # a = """5 + g.throw(Exception(5))""" # self.check(b, a) -# +# # # These should produce warnings -# +# # def test_warn_1(self): # s = """g.throw("foo")""" # self.warns_unchanged(s, "Python 3 does not support string exceptions") -# +# # def test_warn_2(self): # s = """g.throw("foo", 5)""" # self.warns_unchanged(s, "Python 3 does not support string exceptions") -# +# # def test_warn_3(self): # s = """g.throw("foo", 5, 6)""" # self.warns_unchanged(s, "Python 3 does not support string exceptions") -# +# # # These should not be touched -# +# # def test_untouched_1(self): # s = """g.throw(Exception)""" # self.unchanged(s) -# +# # def test_untouched_2(self): # s = """g.throw(Exception(5, 6))""" # self.unchanged(s) -# +# # def test_untouched_3(self): # s = """5 + g.throw(Exception(5, 6))""" # self.unchanged(s) -# +# # # These should result in traceback-assignment -# +# # def test_tb_1(self): # b = """def foo(): # g.throw(Exception, 5, 6)""" # a = """def foo(): # g.throw(Exception(5).with_traceback(6))""" # self.check(b, a) -# +# # def test_tb_2(self): # b = """def foo(): # a = 5 @@ -900,14 +900,14 @@ def test_with_future_print_function(self): # g.throw(Exception(5).with_traceback(6)) # b = 6""" # self.check(b, a) -# +# # def test_tb_3(self): # b = """def foo(): # g.throw(Exception,5,6)""" # a = """def foo(): # g.throw(Exception(5).with_traceback(6))""" # self.check(b, a) -# +# # def test_tb_4(self): # b = """def foo(): # a = 5 @@ -918,14 +918,14 @@ def test_with_future_print_function(self): # g.throw(Exception(5).with_traceback(6)) # b = 6""" # self.check(b, a) -# +# # def test_tb_5(self): # b = """def foo(): # g.throw(Exception, (5, 6, 7), 6)""" # a = """def foo(): # g.throw(Exception(5, 6, 7).with_traceback(6))""" # self.check(b, a) -# +# # def test_tb_6(self): # b = """def foo(): # a = 5 @@ -936,14 +936,14 @@ def test_with_future_print_function(self): # g.throw(Exception(5, 6, 7).with_traceback(6)) # b = 6""" # self.check(b, a) -# +# # def test_tb_7(self): # b = """def foo(): # a + g.throw(Exception, 5, 6)""" # a = """def foo(): # a + g.throw(Exception(5).with_traceback(6))""" # self.check(b, a) -# +# # def test_tb_8(self): # b = """def foo(): # a = 5 @@ -954,596 +954,596 @@ def test_with_future_print_function(self): # a + g.throw(Exception(5).with_traceback(6)) # b = 6""" # self.check(b, a) -# +# # class Test_long(FixerTestCase): # fixer = "long" -# +# # def test_1(self): # b = """x = long(x)""" # a = """x = int(x)""" # self.check(b, a) -# +# # def test_2(self): # b = """y = isinstance(x, long)""" # a = """y = isinstance(x, int)""" # self.check(b, a) -# +# # def test_3(self): # b = """z = type(x) in (int, long)""" # a = """z = type(x) in (int, int)""" # self.check(b, a) -# +# # def test_unchanged(self): # s = """long = True""" # self.unchanged(s) -# +# # s = """s.long = True""" # self.unchanged(s) -# +# # s = """def long(): pass""" # self.unchanged(s) -# +# # s = """class long(): pass""" # self.unchanged(s) -# +# # s = """def f(long): pass""" # self.unchanged(s) -# +# # s = """def f(g, long): pass""" # self.unchanged(s) -# +# # s = """def f(x, long=True): pass""" # self.unchanged(s) -# +# # def test_prefix_preservation(self): # b = """x = long( x )""" # a = """x = int( x )""" # self.check(b, a) -# -# +# +# # class Test_execfile(FixerTestCase): # fixer = "execfile" -# +# # def test_conversion(self): # b = """execfile("fn")""" # a = """exec(compile(open("fn").read(), "fn", 'exec'))""" # self.check(b, a) -# +# # b = """execfile("fn", glob)""" # a = """exec(compile(open("fn").read(), "fn", 'exec'), glob)""" # self.check(b, a) -# +# # b = """execfile("fn", glob, loc)""" # a = """exec(compile(open("fn").read(), "fn", 'exec'), glob, loc)""" # self.check(b, a) -# +# # b = """execfile("fn", globals=glob)""" # a = """exec(compile(open("fn").read(), "fn", 'exec'), globals=glob)""" # self.check(b, a) -# +# # b = """execfile("fn", locals=loc)""" # a = """exec(compile(open("fn").read(), "fn", 'exec'), locals=loc)""" # self.check(b, a) -# +# # b = """execfile("fn", globals=glob, locals=loc)""" # a = """exec(compile(open("fn").read(), "fn", 'exec'), globals=glob, locals=loc)""" # self.check(b, a) -# +# # def test_spacing(self): # b = """execfile( "fn" )""" # a = """exec(compile(open( "fn" ).read(), "fn", 'exec'))""" # self.check(b, a) -# +# # b = """execfile("fn", globals = glob)""" # a = """exec(compile(open("fn").read(), "fn", 'exec'), globals = glob)""" # self.check(b, a) -# -# +# +# # class Test_isinstance(FixerTestCase): # fixer = "isinstance" -# +# # def test_remove_multiple_items(self): # b = """isinstance(x, (int, int, int))""" # a = """isinstance(x, int)""" # self.check(b, a) -# +# # b = """isinstance(x, (int, float, int, int, float))""" # a = """isinstance(x, (int, float))""" # self.check(b, a) -# +# # b = """isinstance(x, (int, float, int, int, float, str))""" # a = """isinstance(x, (int, float, str))""" # self.check(b, a) -# +# # b = """isinstance(foo() + bar(), (x(), y(), x(), int, int))""" # a = """isinstance(foo() + bar(), (x(), y(), x(), int))""" # self.check(b, a) -# +# # def test_prefix_preservation(self): # b = """if isinstance( foo(), ( bar, bar, baz )) : pass""" # a = """if isinstance( foo(), ( bar, baz )) : pass""" # self.check(b, a) -# +# # def test_unchanged(self): # self.unchanged("isinstance(x, (str, int))") -# +# # class Test_dict(FixerTestCase): # fixer = "dict" -# +# # def test_prefix_preservation(self): # b = "if d. keys ( ) : pass" # a = "if list(d. keys ( )) : pass" # self.check(b, a) -# +# # b = "if d. items ( ) : pass" # a = "if list(d. items ( )) : pass" # self.check(b, a) -# +# # b = "if d. iterkeys ( ) : pass" # a = "if iter(d. keys ( )) : pass" # self.check(b, a) -# +# # b = "[i for i in d. iterkeys( ) ]" # a = "[i for i in d. keys( ) ]" # self.check(b, a) -# +# # b = "if d. viewkeys ( ) : pass" # a = "if d. keys ( ) : pass" # self.check(b, a) -# +# # b = "[i for i in d. viewkeys( ) ]" # a = "[i for i in d. keys( ) ]" # self.check(b, a) -# +# # def test_trailing_comment(self): # b = "d.keys() # foo" # a = "list(d.keys()) # foo" # self.check(b, a) -# +# # b = "d.items() # foo" # a = "list(d.items()) # foo" # self.check(b, a) -# +# # b = "d.iterkeys() # foo" # a = "iter(d.keys()) # foo" # self.check(b, a) -# +# # b = """[i for i in d.iterkeys() # foo # ]""" # a = """[i for i in d.keys() # foo # ]""" # self.check(b, a) -# +# # b = """[i for i in d.iterkeys() # foo # ]""" # a = """[i for i in d.keys() # foo # ]""" # self.check(b, a) -# +# # b = "d.viewitems() # foo" # a = "d.items() # foo" # self.check(b, a) -# +# # def test_unchanged(self): # for wrapper in fixer_util.consuming_calls: # s = "s = %s(d.keys())" % wrapper # self.unchanged(s) -# +# # s = "s = %s(d.values())" % wrapper # self.unchanged(s) -# +# # s = "s = %s(d.items())" % wrapper # self.unchanged(s) -# +# # def test_01(self): # b = "d.keys()" # a = "list(d.keys())" # self.check(b, a) -# +# # b = "a[0].foo().keys()" # a = "list(a[0].foo().keys())" # self.check(b, a) -# +# # def test_02(self): # b = "d.items()" # a = "list(d.items())" # self.check(b, a) -# +# # def test_03(self): # b = "d.values()" # a = "list(d.values())" # self.check(b, a) -# +# # def test_04(self): # b = "d.iterkeys()" # a = "iter(d.keys())" # self.check(b, a) -# +# # def test_05(self): # b = "d.iteritems()" # a = "iter(d.items())" # self.check(b, a) -# +# # def test_06(self): # b = "d.itervalues()" # a = "iter(d.values())" # self.check(b, a) -# +# # def test_07(self): # s = "list(d.keys())" # self.unchanged(s) -# +# # def test_08(self): # s = "sorted(d.keys())" # self.unchanged(s) -# +# # def test_09(self): # b = "iter(d.keys())" # a = "iter(list(d.keys()))" # self.check(b, a) -# +# # def test_10(self): # b = "foo(d.keys())" # a = "foo(list(d.keys()))" # self.check(b, a) -# +# # def test_11(self): # b = "for i in d.keys(): print i" # a = "for i in list(d.keys()): print i" # self.check(b, a) -# +# # def test_12(self): # b = "for i in d.iterkeys(): print i" # a = "for i in d.keys(): print i" # self.check(b, a) -# +# # def test_13(self): # b = "[i for i in d.keys()]" # a = "[i for i in list(d.keys())]" # self.check(b, a) -# +# # def test_14(self): # b = "[i for i in d.iterkeys()]" # a = "[i for i in d.keys()]" # self.check(b, a) -# +# # def test_15(self): # b = "(i for i in d.keys())" # a = "(i for i in list(d.keys()))" # self.check(b, a) -# +# # def test_16(self): # b = "(i for i in d.iterkeys())" # a = "(i for i in d.keys())" # self.check(b, a) -# +# # def test_17(self): # b = "iter(d.iterkeys())" # a = "iter(d.keys())" # self.check(b, a) -# +# # def test_18(self): # b = "list(d.iterkeys())" # a = "list(d.keys())" # self.check(b, a) -# +# # def test_19(self): # b = "sorted(d.iterkeys())" # a = "sorted(d.keys())" # self.check(b, a) -# +# # def test_20(self): # b = "foo(d.iterkeys())" # a = "foo(iter(d.keys()))" # self.check(b, a) -# +# # def test_21(self): # b = "print h.iterkeys().next()" # a = "print iter(h.keys()).next()" # self.check(b, a) -# +# # def test_22(self): # b = "print h.keys()[0]" # a = "print list(h.keys())[0]" # self.check(b, a) -# +# # def test_23(self): # b = "print list(h.iterkeys().next())" # a = "print list(iter(h.keys()).next())" # self.check(b, a) -# +# # def test_24(self): # b = "for x in h.keys()[0]: print x" # a = "for x in list(h.keys())[0]: print x" # self.check(b, a) -# +# # def test_25(self): # b = "d.viewkeys()" # a = "d.keys()" # self.check(b, a) -# +# # def test_26(self): # b = "d.viewitems()" # a = "d.items()" # self.check(b, a) -# +# # def test_27(self): # b = "d.viewvalues()" # a = "d.values()" # self.check(b, a) -# +# # def test_14(self): # b = "[i for i in d.viewkeys()]" # a = "[i for i in d.keys()]" # self.check(b, a) -# +# # def test_15(self): # b = "(i for i in d.viewkeys())" # a = "(i for i in d.keys())" # self.check(b, a) -# +# # def test_17(self): # b = "iter(d.viewkeys())" # a = "iter(d.keys())" # self.check(b, a) -# +# # def test_18(self): # b = "list(d.viewkeys())" # a = "list(d.keys())" # self.check(b, a) -# +# # def test_19(self): # b = "sorted(d.viewkeys())" # a = "sorted(d.keys())" # self.check(b, a) -# +# # class Test_xrange(FixerTestCase): # fixer = "xrange" -# +# # def test_prefix_preservation(self): # b = """x = xrange( 10 )""" # a = """x = range( 10 )""" # self.check(b, a) -# +# # b = """x = xrange( 1 , 10 )""" # a = """x = range( 1 , 10 )""" # self.check(b, a) -# +# # b = """x = xrange( 0 , 10 , 2 )""" # a = """x = range( 0 , 10 , 2 )""" # self.check(b, a) -# +# # def test_single_arg(self): # b = """x = xrange(10)""" # a = """x = range(10)""" # self.check(b, a) -# +# # def test_two_args(self): # b = """x = xrange(1, 10)""" # a = """x = range(1, 10)""" # self.check(b, a) -# +# # def test_three_args(self): # b = """x = xrange(0, 10, 2)""" # a = """x = range(0, 10, 2)""" # self.check(b, a) -# +# # def test_wrap_in_list(self): # b = """x = range(10, 3, 9)""" # a = """x = list(range(10, 3, 9))""" # self.check(b, a) -# +# # b = """x = foo(range(10, 3, 9))""" # a = """x = foo(list(range(10, 3, 9)))""" # self.check(b, a) -# +# # b = """x = range(10, 3, 9) + [4]""" # a = """x = list(range(10, 3, 9)) + [4]""" # self.check(b, a) -# +# # b = """x = range(10)[::-1]""" # a = """x = list(range(10))[::-1]""" # self.check(b, a) -# +# # b = """x = range(10) [3]""" # a = """x = list(range(10)) [3]""" # self.check(b, a) -# +# # def test_xrange_in_for(self): # b = """for i in xrange(10):\n j=i""" # a = """for i in range(10):\n j=i""" # self.check(b, a) -# +# # b = """[i for i in xrange(10)]""" # a = """[i for i in range(10)]""" # self.check(b, a) -# +# # def test_range_in_for(self): # self.unchanged("for i in range(10): pass") # self.unchanged("[i for i in range(10)]") -# +# # def test_in_contains_test(self): # self.unchanged("x in range(10, 3, 9)") -# +# # def test_in_consuming_context(self): # for call in fixer_util.consuming_calls: # self.unchanged("a = %s(range(10))" % call) -# +# # class Test_xrange_with_reduce(FixerTestCase): -# +# # def setUp(self): # super(Test_xrange_with_reduce, self).setUp(["xrange", "reduce"]) -# +# # def test_double_transform(self): # b = """reduce(x, xrange(5))""" # a = """from functools import reduce # reduce(x, range(5))""" # self.check(b, a) -# +# # class Test_raw_input(FixerTestCase): # fixer = "raw_input" -# +# # def test_prefix_preservation(self): # b = """x = raw_input( )""" # a = """x = input( )""" # self.check(b, a) -# +# # b = """x = raw_input( '' )""" # a = """x = input( '' )""" # self.check(b, a) -# +# # def test_1(self): # b = """x = raw_input()""" # a = """x = input()""" # self.check(b, a) -# +# # def test_2(self): # b = """x = raw_input('')""" # a = """x = input('')""" # self.check(b, a) -# +# # def test_3(self): # b = """x = raw_input('prompt')""" # a = """x = input('prompt')""" # self.check(b, a) -# +# # def test_4(self): # b = """x = raw_input(foo(a) + 6)""" # a = """x = input(foo(a) + 6)""" # self.check(b, a) -# +# # def test_5(self): # b = """x = raw_input(invite).split()""" # a = """x = input(invite).split()""" # self.check(b, a) -# +# # def test_6(self): # b = """x = raw_input(invite) . split ()""" # a = """x = input(invite) . split ()""" # self.check(b, a) -# +# # def test_8(self): # b = "x = int(raw_input())" # a = "x = int(input())" # self.check(b, a) -# +# # class Test_funcattrs(FixerTestCase): # fixer = "funcattrs" -# +# # attrs = ["closure", "doc", "name", "defaults", "code", "globals", "dict"] -# +# # def test(self): # for attr in self.attrs: # b = "a.func_%s" % attr # a = "a.__%s__" % attr # self.check(b, a) -# +# # b = "self.foo.func_%s.foo_bar" % attr # a = "self.foo.__%s__.foo_bar" % attr # self.check(b, a) -# +# # def test_unchanged(self): # for attr in self.attrs: # s = "foo(func_%s + 5)" % attr # self.unchanged(s) -# +# # s = "f(foo.__%s__)" % attr # self.unchanged(s) -# +# # s = "f(foo.__%s__.foo)" % attr # self.unchanged(s) -# +# # class Test_xreadlines(FixerTestCase): # fixer = "xreadlines" -# +# # def test_call(self): # b = "for x in f.xreadlines(): pass" # a = "for x in f: pass" # self.check(b, a) -# +# # b = "for x in foo().xreadlines(): pass" # a = "for x in foo(): pass" # self.check(b, a) -# +# # b = "for x in (5 + foo()).xreadlines(): pass" # a = "for x in (5 + foo()): pass" # self.check(b, a) -# +# # def test_attr_ref(self): # b = "foo(f.xreadlines + 5)" # a = "foo(f.__iter__ + 5)" # self.check(b, a) -# +# # b = "foo(f().xreadlines + 5)" # a = "foo(f().__iter__ + 5)" # self.check(b, a) -# +# # b = "foo((5 + f()).xreadlines + 5)" # a = "foo((5 + f()).__iter__ + 5)" # self.check(b, a) -# +# # def test_unchanged(self): # s = "for x in f.xreadlines(5): pass" # self.unchanged(s) -# +# # s = "for x in f.xreadlines(k=5): pass" # self.unchanged(s) -# +# # s = "for x in f.xreadlines(*k, **v): pass" # self.unchanged(s) -# +# # s = "foo(xreadlines)" # self.unchanged(s) -# -# +# +# # class ImportsFixerTests: -# +# # def test_import_module(self): # for old, new in self.modules.items(): # b = "import %s" % old # a = "import %s" % new # self.check(b, a) -# +# # b = "import foo, %s, bar" % old # a = "import foo, %s, bar" % new # self.check(b, a) -# +# # def test_import_from(self): # for old, new in self.modules.items(): # b = "from %s import foo" % old # a = "from %s import foo" % new # self.check(b, a) -# +# # b = "from %s import foo, bar" % old # a = "from %s import foo, bar" % new # self.check(b, a) -# +# # b = "from %s import (yes, no)" % old # a = "from %s import (yes, no)" % new # self.check(b, a) -# +# # def test_import_module_as(self): # for old, new in self.modules.items(): # b = "import %s as foo_bar" % old # a = "import %s as foo_bar" % new # self.check(b, a) -# +# # b = "import %s as foo_bar" % old # a = "import %s as foo_bar" % new # self.check(b, a) -# +# # def test_import_from_as(self): # for old, new in self.modules.items(): # b = "from %s import foo as bar" % old # a = "from %s import foo as bar" % new # self.check(b, a) -# +# # def test_star(self): # for old, new in self.modules.items(): # b = "from %s import *" % old # a = "from %s import *" % new # self.check(b, a) -# +# # def test_import_module_usage(self): # for old, new in self.modules.items(): # b = """ @@ -1555,7 +1555,7 @@ def test_with_future_print_function(self): # foo(%s.bar) # """ % (new, new) # self.check(b, a) -# +# # b = """ # from %s import x # %s = 23 @@ -1565,13 +1565,13 @@ def test_with_future_print_function(self): # %s = 23 # """ % (new, old) # self.check(b, a) -# +# # s = """ # def f(): # %s.method() # """ % (old,) # self.unchanged(s) -# +# # # test nested usage # b = """ # import %s @@ -1582,7 +1582,7 @@ def test_with_future_print_function(self): # %s.bar(%s.foo) # """ % (new, new, new) # self.check(b, a) -# +# # b = """ # import %s # x.%s @@ -1592,16 +1592,16 @@ def test_with_future_print_function(self): # x.%s # """ % (new, old) # self.check(b, a) -# -# +# +# # class Test_imports(FixerTestCase, ImportsFixerTests): # fixer = "imports" -# +# # def test_multiple_imports(self): # b = """import urlparse, cStringIO""" # a = """import urllib.parse, io""" # self.check(b, a) -# +# # def test_multiple_imports_as(self): # b = """ # import copy_reg as bar, HTMLParser as foo, urlparse @@ -1612,14 +1612,14 @@ def test_with_future_print_function(self): # s = urllib.parse.spam(bar.foo()) # """ # self.check(b, a) -# -# +# +# # class Test_imports2(FixerTestCase, ImportsFixerTests): # fixer = "imports2" -# -# +# +# # class Test_imports_fixer_order(FixerTestCase, ImportsFixerTests): -# +# # def setUp(self): # super(Test_imports_fixer_order, self).setUp(['imports', 'imports2']) # from ..fixes.fix_imports2 import MAPPING as mapping2 @@ -1627,23 +1627,23 @@ def test_with_future_print_function(self): # from ..fixes.fix_imports import MAPPING as mapping1 # for key in ('dbhash', 'dumbdbm', 'dbm', 'gdbm'): # self.modules[key] = mapping1[key] -# +# # def test_after_local_imports_refactoring(self): # for fix in ("imports", "imports2"): # self.fixer = fix # self.assert_runs_after("import") -# -# +# +# # class Test_urllib(FixerTestCase): # fixer = "urllib" # from ..fixes.fix_urllib import MAPPING as modules -# +# # def test_import_module(self): # for old, changes in self.modules.items(): # b = "import %s" % old # a = "import %s" % ", ".join(map(itemgetter(0), changes)) # self.check(b, a) -# +# # def test_import_from(self): # for old, changes in self.modules.items(): # all_members = [] @@ -1653,28 +1653,28 @@ def test_with_future_print_function(self): # b = "from %s import %s" % (old, member) # a = "from %s import %s" % (new, member) # self.check(b, a) -# +# # s = "from foo import %s" % member # self.unchanged(s) -# +# # b = "from %s import %s" % (old, ", ".join(members)) # a = "from %s import %s" % (new, ", ".join(members)) # self.check(b, a) -# +# # s = "from foo import %s" % ", ".join(members) # self.unchanged(s) -# +# # # test the breaking of a module into multiple replacements # b = "from %s import %s" % (old, ", ".join(all_members)) # a = "\n".join(["from %s import %s" % (new, ", ".join(members)) # for (new, members) in changes]) # self.check(b, a) -# +# # def test_import_module_as(self): # for old in self.modules: # s = "import %s as foo" % old # self.warns_unchanged(s, "This module is now multiple modules") -# +# # def test_import_from_as(self): # for old, changes in self.modules.items(): # for new, members in changes: @@ -1685,12 +1685,12 @@ def test_with_future_print_function(self): # b = "from %s import %s as blah, %s" % (old, member, member) # a = "from %s import %s as blah, %s" % (new, member, member) # self.check(b, a) -# +# # def test_star(self): # for old in self.modules: # s = "from %s import *" % old # self.warns_unchanged(s, "Cannot handle star imports") -# +# # def test_indented(self): # b = """ # def foo(): @@ -1702,7 +1702,7 @@ def test_with_future_print_function(self): # from urllib.request import urlopen # """ # self.check(b, a) -# +# # b = """ # def foo(): # other() @@ -1715,9 +1715,9 @@ def test_with_future_print_function(self): # from urllib.request import urlopen # """ # self.check(b, a) -# -# -# +# +# +# # def test_import_module_usage(self): # for old, changes in self.modules.items(): # for new, members in changes: @@ -1742,163 +1742,163 @@ def test_with_future_print_function(self): # %s.%s(%s.%s) # """ % (new_import, new, member, new, member) # self.check(b, a) -# -# +# +# # class Test_input(FixerTestCase): # fixer = "input" -# +# # def test_prefix_preservation(self): # b = """x = input( )""" # a = """x = eval(input( ))""" # self.check(b, a) -# +# # b = """x = input( '' )""" # a = """x = eval(input( '' ))""" # self.check(b, a) -# +# # def test_trailing_comment(self): # b = """x = input() # foo""" # a = """x = eval(input()) # foo""" # self.check(b, a) -# +# # def test_idempotency(self): # s = """x = eval(input())""" # self.unchanged(s) -# +# # s = """x = eval(input(''))""" # self.unchanged(s) -# +# # s = """x = eval(input(foo(5) + 9))""" # self.unchanged(s) -# +# # def test_1(self): # b = """x = input()""" # a = """x = eval(input())""" # self.check(b, a) -# +# # def test_2(self): # b = """x = input('')""" # a = """x = eval(input(''))""" # self.check(b, a) -# +# # def test_3(self): # b = """x = input('prompt')""" # a = """x = eval(input('prompt'))""" # self.check(b, a) -# +# # def test_4(self): # b = """x = input(foo(5) + 9)""" # a = """x = eval(input(foo(5) + 9))""" # self.check(b, a) -# +# # class Test_tuple_params(FixerTestCase): # fixer = "tuple_params" -# +# # def test_unchanged_1(self): # s = """def foo(): pass""" # self.unchanged(s) -# +# # def test_unchanged_2(self): # s = """def foo(a, b, c): pass""" # self.unchanged(s) -# +# # def test_unchanged_3(self): # s = """def foo(a=3, b=4, c=5): pass""" # self.unchanged(s) -# +# # def test_1(self): # b = """ # def foo(((a, b), c)): # x = 5""" -# +# # a = """ # def foo(xxx_todo_changeme): # ((a, b), c) = xxx_todo_changeme # x = 5""" # self.check(b, a) -# +# # def test_2(self): # b = """ # def foo(((a, b), c), d): # x = 5""" -# +# # a = """ # def foo(xxx_todo_changeme, d): # ((a, b), c) = xxx_todo_changeme # x = 5""" # self.check(b, a) -# +# # def test_3(self): # b = """ # def foo(((a, b), c), d) -> e: # x = 5""" -# +# # a = """ # def foo(xxx_todo_changeme, d) -> e: # ((a, b), c) = xxx_todo_changeme # x = 5""" # self.check(b, a) -# +# # def test_semicolon(self): # b = """ # def foo(((a, b), c)): x = 5; y = 7""" -# +# # a = """ # def foo(xxx_todo_changeme): ((a, b), c) = xxx_todo_changeme; x = 5; y = 7""" # self.check(b, a) -# +# # def test_keywords(self): # b = """ # def foo(((a, b), c), d, e=5) -> z: # x = 5""" -# +# # a = """ # def foo(xxx_todo_changeme, d, e=5) -> z: # ((a, b), c) = xxx_todo_changeme # x = 5""" # self.check(b, a) -# +# # def test_varargs(self): # b = """ # def foo(((a, b), c), d, *vargs, **kwargs) -> z: # x = 5""" -# +# # a = """ # def foo(xxx_todo_changeme, d, *vargs, **kwargs) -> z: # ((a, b), c) = xxx_todo_changeme # x = 5""" # self.check(b, a) -# +# # def test_multi_1(self): # b = """ # def foo(((a, b), c), (d, e, f)) -> z: # x = 5""" -# +# # a = """ # def foo(xxx_todo_changeme, xxx_todo_changeme1) -> z: # ((a, b), c) = xxx_todo_changeme # (d, e, f) = xxx_todo_changeme1 # x = 5""" # self.check(b, a) -# +# # def test_multi_2(self): # b = """ # def foo(x, ((a, b), c), d, (e, f, g), y) -> z: # x = 5""" -# +# # a = """ # def foo(x, xxx_todo_changeme, d, xxx_todo_changeme1, y) -> z: # ((a, b), c) = xxx_todo_changeme # (e, f, g) = xxx_todo_changeme1 # x = 5""" # self.check(b, a) -# +# # def test_docstring(self): # b = """ # def foo(((a, b), c), (d, e, f)) -> z: # "foo foo foo foo" # x = 5""" -# +# # a = """ # def foo(xxx_todo_changeme, xxx_todo_changeme1) -> z: # "foo foo foo foo" @@ -1906,83 +1906,83 @@ def test_with_future_print_function(self): # (d, e, f) = xxx_todo_changeme1 # x = 5""" # self.check(b, a) -# +# # def test_lambda_no_change(self): # s = """lambda x: x + 5""" # self.unchanged(s) -# +# # def test_lambda_parens_single_arg(self): # b = """lambda (x): x + 5""" # a = """lambda x: x + 5""" # self.check(b, a) -# +# # b = """lambda(x): x + 5""" # a = """lambda x: x + 5""" # self.check(b, a) -# +# # b = """lambda ((((x)))): x + 5""" # a = """lambda x: x + 5""" # self.check(b, a) -# +# # b = """lambda((((x)))): x + 5""" # a = """lambda x: x + 5""" # self.check(b, a) -# +# # def test_lambda_simple(self): # b = """lambda (x, y): x + f(y)""" # a = """lambda x_y: x_y[0] + f(x_y[1])""" # self.check(b, a) -# +# # b = """lambda(x, y): x + f(y)""" # a = """lambda x_y: x_y[0] + f(x_y[1])""" # self.check(b, a) -# +# # b = """lambda (((x, y))): x + f(y)""" # a = """lambda x_y: x_y[0] + f(x_y[1])""" # self.check(b, a) -# +# # b = """lambda(((x, y))): x + f(y)""" # a = """lambda x_y: x_y[0] + f(x_y[1])""" # self.check(b, a) -# +# # def test_lambda_one_tuple(self): # b = """lambda (x,): x + f(x)""" # a = """lambda x1: x1[0] + f(x1[0])""" # self.check(b, a) -# +# # b = """lambda (((x,))): x + f(x)""" # a = """lambda x1: x1[0] + f(x1[0])""" # self.check(b, a) -# +# # def test_lambda_simple_multi_use(self): # b = """lambda (x, y): x + x + f(x) + x""" # a = """lambda x_y: x_y[0] + x_y[0] + f(x_y[0]) + x_y[0]""" # self.check(b, a) -# +# # def test_lambda_simple_reverse(self): # b = """lambda (x, y): y + x""" # a = """lambda x_y: x_y[1] + x_y[0]""" # self.check(b, a) -# +# # def test_lambda_nested(self): # b = """lambda (x, (y, z)): x + y + z""" # a = """lambda x_y_z: x_y_z[0] + x_y_z[1][0] + x_y_z[1][1]""" # self.check(b, a) -# +# # b = """lambda (((x, (y, z)))): x + y + z""" # a = """lambda x_y_z: x_y_z[0] + x_y_z[1][0] + x_y_z[1][1]""" # self.check(b, a) -# +# # def test_lambda_nested_multi_use(self): # b = """lambda (x, (y, z)): x + y + f(y)""" # a = """lambda x_y_z: x_y_z[0] + x_y_z[1][0] + f(x_y_z[1][0])""" # self.check(b, a) -# +# # class Test_methodattrs(FixerTestCase): # fixer = "methodattrs" -# +# # attrs = ["func", "self", "class"] -# +# # def test(self): # for attr in self.attrs: # b = "a.im_%s" % attr @@ -1991,58 +1991,58 @@ def test_with_future_print_function(self): # else: # a = "a.__%s__" % attr # self.check(b, a) -# +# # b = "self.foo.im_%s.foo_bar" % attr # if attr == "class": # a = "self.foo.__self__.__class__.foo_bar" # else: # a = "self.foo.__%s__.foo_bar" % attr # self.check(b, a) -# +# # def test_unchanged(self): # for attr in self.attrs: # s = "foo(im_%s + 5)" % attr # self.unchanged(s) -# +# # s = "f(foo.__%s__)" % attr # self.unchanged(s) -# +# # s = "f(foo.__%s__.foo)" % attr # self.unchanged(s) -# +# # class Test_next(FixerTestCase): # fixer = "next" -# +# # def test_1(self): # b = """it.next()""" # a = """next(it)""" # self.check(b, a) -# +# # def test_2(self): # b = """a.b.c.d.next()""" # a = """next(a.b.c.d)""" # self.check(b, a) -# +# # def test_3(self): # b = """(a + b).next()""" # a = """next((a + b))""" # self.check(b, a) -# +# # def test_4(self): # b = """a().next()""" # a = """next(a())""" # self.check(b, a) -# +# # def test_5(self): # b = """a().next() + b""" # a = """next(a()) + b""" # self.check(b, a) -# +# # def test_6(self): # b = """c( a().next() + b)""" # a = """c( next(a()) + b)""" # self.check(b, a) -# +# # def test_prefix_preservation_1(self): # b = """ # for a in b: @@ -2055,7 +2055,7 @@ def test_with_future_print_function(self): # next(a) # """ # self.check(b, a) -# +# # def test_prefix_preservation_2(self): # b = """ # for a in b: @@ -2070,7 +2070,7 @@ def test_with_future_print_function(self): # next(a) # """ # self.check(b, a) -# +# # def test_prefix_preservation_3(self): # b = """ # next = 5 @@ -2085,7 +2085,7 @@ def test_with_future_print_function(self): # a.__next__() # """ # self.check(b, a, ignore_warnings=True) -# +# # def test_prefix_preservation_4(self): # b = """ # next = 5 @@ -2102,7 +2102,7 @@ def test_with_future_print_function(self): # a.__next__() # """ # self.check(b, a, ignore_warnings=True) -# +# # def test_prefix_preservation_5(self): # b = """ # next = 5 @@ -2117,7 +2117,7 @@ def test_with_future_print_function(self): # a.__next__()) # """ # self.check(b, a, ignore_warnings=True) -# +# # def test_prefix_preservation_6(self): # b = """ # for a in b: @@ -2130,7 +2130,7 @@ def test_with_future_print_function(self): # next(a)) # """ # self.check(b, a) -# +# # def test_method_1(self): # b = """ # class A: @@ -2143,7 +2143,7 @@ def test_with_future_print_function(self): # pass # """ # self.check(b, a) -# +# # def test_method_2(self): # b = """ # class A(object): @@ -2156,7 +2156,7 @@ def test_with_future_print_function(self): # pass # """ # self.check(b, a) -# +# # def test_method_3(self): # b = """ # class A: @@ -2169,16 +2169,16 @@ def test_with_future_print_function(self): # pass # """ # self.check(b, a) -# +# # def test_method_4(self): # b = """ # class A: # def __init__(self, foo): # self.foo = foo -# +# # def next(self): # pass -# +# # def __iter__(self): # return self # """ @@ -2186,15 +2186,15 @@ def test_with_future_print_function(self): # class A: # def __init__(self, foo): # self.foo = foo -# +# # def __next__(self): # pass -# +# # def __iter__(self): # return self # """ # self.check(b, a) -# +# # def test_method_unchanged(self): # s = """ # class A: @@ -2202,227 +2202,227 @@ def test_with_future_print_function(self): # pass # """ # self.unchanged(s) -# +# # def test_shadowing_assign_simple(self): # s = """ # next = foo -# +# # class A: # def next(self, a, b): # pass # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_shadowing_assign_tuple_1(self): # s = """ # (next, a) = foo -# +# # class A: # def next(self, a, b): # pass # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_shadowing_assign_tuple_2(self): # s = """ # (a, (b, (next, c)), a) = foo -# +# # class A: # def next(self, a, b): # pass # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_shadowing_assign_list_1(self): # s = """ # [next, a] = foo -# +# # class A: # def next(self, a, b): # pass # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_shadowing_assign_list_2(self): # s = """ # [a, [b, [next, c]], a] = foo -# +# # class A: # def next(self, a, b): # pass # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_builtin_assign(self): # s = """ # def foo(): # __builtin__.next = foo -# +# # class A: # def next(self, a, b): # pass # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_builtin_assign_in_tuple(self): # s = """ # def foo(): # (a, __builtin__.next) = foo -# +# # class A: # def next(self, a, b): # pass # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_builtin_assign_in_list(self): # s = """ # def foo(): # [a, __builtin__.next] = foo -# +# # class A: # def next(self, a, b): # pass # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_assign_to_next(self): # s = """ # def foo(): # A.next = foo -# +# # class A: # def next(self, a, b): # pass # """ # self.unchanged(s) -# +# # def test_assign_to_next_in_tuple(self): # s = """ # def foo(): # (a, A.next) = foo -# +# # class A: # def next(self, a, b): # pass # """ # self.unchanged(s) -# +# # def test_assign_to_next_in_list(self): # s = """ # def foo(): # [a, A.next] = foo -# +# # class A: # def next(self, a, b): # pass # """ # self.unchanged(s) -# +# # def test_shadowing_import_1(self): # s = """ # import foo.bar as next -# +# # class A: # def next(self, a, b): # pass # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_shadowing_import_2(self): # s = """ # import bar, bar.foo as next -# +# # class A: # def next(self, a, b): # pass # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_shadowing_import_3(self): # s = """ # import bar, bar.foo as next, baz -# +# # class A: # def next(self, a, b): # pass # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_shadowing_import_from_1(self): # s = """ # from x import next -# +# # class A: # def next(self, a, b): # pass # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_shadowing_import_from_2(self): # s = """ # from x.a import next -# +# # class A: # def next(self, a, b): # pass # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_shadowing_import_from_3(self): # s = """ # from x import a, next, b -# +# # class A: # def next(self, a, b): # pass # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_shadowing_import_from_4(self): # s = """ # from x.a import a, next, b -# +# # class A: # def next(self, a, b): # pass # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_shadowing_funcdef_1(self): # s = """ # def next(a): # pass -# +# # class A: # def next(self, a, b): # pass # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_shadowing_funcdef_2(self): # b = """ # def next(a): # pass -# +# # class A: # def next(self): # pass -# +# # it.next() # """ # a = """ # def next(a): # pass -# +# # class A: # def __next__(self): # pass -# +# # it.__next__() # """ # self.warns(b, a, "Calls to builtin next() possibly shadowed") -# +# # def test_shadowing_global_1(self): # s = """ # def f(): @@ -2430,7 +2430,7 @@ def test_with_future_print_function(self): # next = 5 # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_shadowing_global_2(self): # s = """ # def f(): @@ -2438,55 +2438,55 @@ def test_with_future_print_function(self): # next = 5 # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_shadowing_for_simple(self): # s = """ # for next in it(): # pass -# +# # b = 5 # c = 6 # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_shadowing_for_tuple_1(self): # s = """ # for next, b in it(): # pass -# +# # b = 5 # c = 6 # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_shadowing_for_tuple_2(self): # s = """ # for a, (next, c), b in it(): # pass -# +# # b = 5 # c = 6 # """ # self.warns_unchanged(s, "Calls to builtin next() possibly shadowed") -# +# # def test_noncall_access_1(self): # b = """gnext = g.next""" # a = """gnext = g.__next__""" # self.check(b, a) -# +# # def test_noncall_access_2(self): # b = """f(g.next + 5)""" # a = """f(g.__next__ + 5)""" # self.check(b, a) -# +# # def test_noncall_access_3(self): # b = """f(g().next + 5)""" # a = """f(g().__next__ + 5)""" # self.check(b, a) -# +# # class Test_nonzero(FixerTestCase): # fixer = "nonzero" -# +# # def test_1(self): # b = """ # class A: @@ -2499,7 +2499,7 @@ def test_with_future_print_function(self): # pass # """ # self.check(b, a) -# +# # def test_2(self): # b = """ # class A(object): @@ -2512,7 +2512,7 @@ def test_with_future_print_function(self): # pass # """ # self.check(b, a) -# +# # def test_unchanged_1(self): # s = """ # class A(object): @@ -2520,7 +2520,7 @@ def test_with_future_print_function(self): # pass # """ # self.unchanged(s) -# +# # def test_unchanged_2(self): # s = """ # class A(object): @@ -2528,101 +2528,101 @@ def test_with_future_print_function(self): # pass # """ # self.unchanged(s) -# +# # def test_unchanged_func(self): # s = """ # def __nonzero__(self): # pass # """ # self.unchanged(s) -# +# # class Test_numliterals(FixerTestCase): # fixer = "numliterals" -# +# # def test_octal_1(self): # b = """0755""" # a = """0o755""" # self.check(b, a) -# +# # def test_long_int_1(self): # b = """a = 12L""" # a = """a = 12""" # self.check(b, a) -# +# # def test_long_int_2(self): # b = """a = 12l""" # a = """a = 12""" # self.check(b, a) -# +# # def test_long_hex(self): # b = """b = 0x12l""" # a = """b = 0x12""" # self.check(b, a) -# +# # def test_comments_and_spacing(self): # b = """b = 0x12L""" # a = """b = 0x12""" # self.check(b, a) -# +# # b = """b = 0755 # spam""" # a = """b = 0o755 # spam""" # self.check(b, a) -# +# # def test_unchanged_int(self): # s = """5""" # self.unchanged(s) -# +# # def test_unchanged_float(self): # s = """5.0""" # self.unchanged(s) -# +# # def test_unchanged_octal(self): # s = """0o755""" # self.unchanged(s) -# +# # def test_unchanged_hex(self): # s = """0xABC""" # self.unchanged(s) -# +# # def test_unchanged_exp(self): # s = """5.0e10""" # self.unchanged(s) -# +# # def test_unchanged_complex_int(self): # s = """5 + 4j""" # self.unchanged(s) -# +# # def test_unchanged_complex_float(self): # s = """5.4 + 4.9j""" # self.unchanged(s) -# +# # def test_unchanged_complex_bare(self): # s = """4j""" # self.unchanged(s) # s = """4.4j""" # self.unchanged(s) -# +# # class Test_renames(FixerTestCase): # fixer = "renames" -# +# # modules = {"sys": ("maxint", "maxsize"), # } -# +# # def test_import_from(self): # for mod, (old, new) in self.modules.items(): # b = "from %s import %s" % (mod, old) # a = "from %s import %s" % (mod, new) # self.check(b, a) -# +# # s = "from foo import %s" % old # self.unchanged(s) -# +# # def test_import_from_as(self): # for mod, (old, new) in self.modules.items(): # b = "from %s import %s as foo_bar" % (mod, old) # a = "from %s import %s as foo_bar" % (mod, new) # self.check(b, a) -# +# # def test_import_module_usage(self): # for mod, (old, new) in self.modules.items(): # b = """ @@ -2634,7 +2634,7 @@ def test_with_future_print_function(self): # foo(%s, %s.%s) # """ % (mod, mod, mod, new) # self.check(b, a) -# +# # def XXX_test_from_import_usage(self): # # not implemented yet # for mod, (old, new) in self.modules.items(): @@ -2647,66 +2647,66 @@ def test_with_future_print_function(self): # foo(%s, %s) # """ % (mod, new, mod, new) # self.check(b, a) -# +# # class Test_unicode(FixerTestCase): # fixer = "unicode" -# +# # def test_whitespace(self): # b = """unicode( x)""" # a = """str( x)""" # self.check(b, a) -# +# # b = """ unicode(x )""" # a = """ str(x )""" # self.check(b, a) -# +# # b = """ u'h'""" # a = """ 'h'""" # self.check(b, a) -# +# # def test_unicode_call(self): # b = """unicode(x, y, z)""" # a = """str(x, y, z)""" # self.check(b, a) -# +# # def test_unichr(self): # b = """unichr(u'h')""" # a = """chr('h')""" # self.check(b, a) -# +# # def test_unicode_literal_1(self): # b = '''u"x"''' # a = '''"x"''' # self.check(b, a) -# +# # def test_unicode_literal_2(self): # b = """ur'x'""" # a = """r'x'""" # self.check(b, a) -# +# # def test_unicode_literal_3(self): # b = """UR'''x''' """ # a = """R'''x''' """ # self.check(b, a) -# +# # class Test_callable(FixerTestCase): # fixer = "callable" -# +# # def test_prefix_preservation(self): # b = """callable( x)""" # a = """import collections\nisinstance( x, collections.Callable)""" # self.check(b, a) -# +# # b = """if callable(x): pass""" # a = """import collections # if isinstance(x, collections.Callable): pass""" # self.check(b, a) -# +# # def test_callable_call(self): # b = """callable(x)""" # a = """import collections\nisinstance(x, collections.Callable)""" # self.check(b, a) -# +# # def test_global_import(self): # b = """ # def spam(foo): @@ -2716,14 +2716,14 @@ def test_with_future_print_function(self): # def spam(foo): # isinstance(foo, collections.Callable)"""[1:] # self.check(b, a) -# +# # b = """ # import collections # def spam(foo): # callable(foo)"""[1:] # # same output if it was already imported # self.check(b, a) -# +# # b = """ # from collections import * # def spam(foo): @@ -2734,7 +2734,7 @@ def test_with_future_print_function(self): # def spam(foo): # isinstance(foo, collections.Callable)"""[1:] # self.check(b, a) -# +# # b = """ # do_stuff() # do_some_other_stuff() @@ -2745,7 +2745,7 @@ def test_with_future_print_function(self): # do_some_other_stuff() # assert isinstance(do_stuff, collections.Callable)"""[1:] # self.check(b, a) -# +# # b = """ # if isinstance(do_stuff, Callable): # assert callable(do_stuff) @@ -2768,55 +2768,55 @@ def test_with_future_print_function(self): # else: # assert not isinstance(do_stuff, collections.Callable)"""[1:] # self.check(b, a) -# +# # def test_callable_should_not_change(self): # a = """callable(*x)""" # self.unchanged(a) -# +# # a = """callable(x, y)""" # self.unchanged(a) -# +# # a = """callable(x, kw=y)""" # self.unchanged(a) -# +# # a = """callable()""" # self.unchanged(a) -# +# # class Test_filter(FixerTestCase): # fixer = "filter" -# +# # def test_prefix_preservation(self): # b = """x = filter( foo, 'abc' )""" # a = """x = list(filter( foo, 'abc' ))""" # self.check(b, a) -# +# # b = """x = filter( None , 'abc' )""" # a = """x = [_f for _f in 'abc' if _f]""" # self.check(b, a) -# +# # def test_filter_basic(self): # b = """x = filter(None, 'abc')""" # a = """x = [_f for _f in 'abc' if _f]""" # self.check(b, a) -# +# # b = """x = len(filter(f, 'abc'))""" # a = """x = len(list(filter(f, 'abc')))""" # self.check(b, a) -# +# # b = """x = filter(lambda x: x%2 == 0, range(10))""" # a = """x = [x for x in range(10) if x%2 == 0]""" # self.check(b, a) -# +# # # Note the parens around x # b = """x = filter(lambda (x): x%2 == 0, range(10))""" # a = """x = [x for x in range(10) if x%2 == 0]""" # self.check(b, a) -# +# # # XXX This (rare) case is not supported # ## b = """x = filter(f, 'abc')[0]""" # ## a = """x = list(filter(f, 'abc'))[0]""" # ## self.check(b, a) -# +# # def test_filter_nochange(self): # a = """b.join(filter(f, 'abc'))""" # self.unchanged(a) @@ -2856,62 +2856,62 @@ def test_with_future_print_function(self): # self.unchanged(a) # a = """(x for x in filter(f, 'abc'))""" # self.unchanged(a) -# +# # def test_future_builtins(self): # a = "from future_builtins import spam, filter; filter(f, 'ham')" # self.unchanged(a) -# +# # b = """from future_builtins import spam; x = filter(f, 'abc')""" # a = """from future_builtins import spam; x = list(filter(f, 'abc'))""" # self.check(b, a) -# +# # a = "from future_builtins import *; filter(f, 'ham')" # self.unchanged(a) -# +# # class Test_map(FixerTestCase): # fixer = "map" -# +# # def check(self, b, a): # self.unchanged("from future_builtins import map; " + b, a) # super(Test_map, self).check(b, a) -# +# # def test_prefix_preservation(self): # b = """x = map( f, 'abc' )""" # a = """x = list(map( f, 'abc' ))""" # self.check(b, a) -# +# # def test_trailing_comment(self): # b = """x = map(f, 'abc') # foo""" # a = """x = list(map(f, 'abc')) # foo""" # self.check(b, a) -# +# # def test_None_with_multiple_arguments(self): # s = """x = map(None, a, b, c)""" # self.warns_unchanged(s, "cannot convert map(None, ...) with " # "multiple arguments") -# +# # def test_map_basic(self): # b = """x = map(f, 'abc')""" # a = """x = list(map(f, 'abc'))""" # self.check(b, a) -# +# # b = """x = len(map(f, 'abc', 'def'))""" # a = """x = len(list(map(f, 'abc', 'def')))""" # self.check(b, a) -# +# # b = """x = map(None, 'abc')""" # a = """x = list('abc')""" # self.check(b, a) -# +# # b = """x = map(lambda x: x+1, range(4))""" # a = """x = [x+1 for x in range(4)]""" # self.check(b, a) -# +# # # Note the parens around x # b = """x = map(lambda (x): x+1, range(4))""" # a = """x = [x+1 for x in range(4)]""" # self.check(b, a) -# +# # b = """ # foo() # # foo @@ -2923,12 +2923,12 @@ def test_with_future_print_function(self): # list(map(f, x)) # """ # self.warns(b, a, "You should use a for loop here") -# +# # # XXX This (rare) case is not supported # ## b = """x = map(f, 'abc')[0]""" # ## a = """x = list(map(f, 'abc'))[0]""" # ## self.check(b, a) -# +# # def test_map_nochange(self): # a = """b.join(map(f, 'abc'))""" # self.unchanged(a) @@ -2968,34 +2968,34 @@ def test_with_future_print_function(self): # self.unchanged(a) # a = """(x for x in map(f, 'abc'))""" # self.unchanged(a) -# +# # def test_future_builtins(self): # a = "from future_builtins import spam, map, eggs; map(f, 'ham')" # self.unchanged(a) -# +# # b = """from future_builtins import spam, eggs; x = map(f, 'abc')""" # a = """from future_builtins import spam, eggs; x = list(map(f, 'abc'))""" # self.check(b, a) -# +# # a = "from future_builtins import *; map(f, 'ham')" # self.unchanged(a) -# +# # class Test_zip(FixerTestCase): # fixer = "zip" -# +# # def check(self, b, a): # self.unchanged("from future_builtins import zip; " + b, a) # super(Test_zip, self).check(b, a) -# +# # def test_zip_basic(self): # b = """x = zip(a, b, c)""" # a = """x = list(zip(a, b, c))""" # self.check(b, a) -# +# # b = """x = len(zip(a, b))""" # a = """x = len(list(zip(a, b)))""" # self.check(b, a) -# +# # def test_zip_nochange(self): # a = """b.join(zip(a, b))""" # self.unchanged(a) @@ -3035,74 +3035,74 @@ def test_with_future_print_function(self): # self.unchanged(a) # a = """(x for x in zip(a, b))""" # self.unchanged(a) -# +# # def test_future_builtins(self): # a = "from future_builtins import spam, zip, eggs; zip(a, b)" # self.unchanged(a) -# +# # b = """from future_builtins import spam, eggs; x = zip(a, b)""" # a = """from future_builtins import spam, eggs; x = list(zip(a, b))""" # self.check(b, a) -# +# # a = "from future_builtins import *; zip(a, b)" # self.unchanged(a) -# +# # class Test_standarderror(FixerTestCase): # fixer = "standarderror" -# +# # def test(self): # b = """x = StandardError()""" # a = """x = Exception()""" # self.check(b, a) -# +# # b = """x = StandardError(a, b, c)""" # a = """x = Exception(a, b, c)""" # self.check(b, a) -# +# # b = """f(2 + StandardError(a, b, c))""" # a = """f(2 + Exception(a, b, c))""" # self.check(b, a) -# +# # class Test_types(FixerTestCase): # fixer = "types" -# +# # def test_basic_types_convert(self): # b = """types.StringType""" # a = """bytes""" # self.check(b, a) -# +# # b = """types.DictType""" # a = """dict""" # self.check(b, a) -# +# # b = """types . IntType""" # a = """int""" # self.check(b, a) -# +# # b = """types.ListType""" # a = """list""" # self.check(b, a) -# +# # b = """types.LongType""" # a = """int""" # self.check(b, a) -# +# # b = """types.NoneType""" # a = """type(None)""" # self.check(b, a) -# +# # class Test_idioms(FixerTestCase): # fixer = "idioms" -# +# # def test_while(self): # b = """while 1: foo()""" # a = """while True: foo()""" # self.check(b, a) -# +# # b = """while 1: foo()""" # a = """while True: foo()""" # self.check(b, a) -# +# # b = """ # while 1: # foo() @@ -3112,132 +3112,132 @@ def test_with_future_print_function(self): # foo() # """ # self.check(b, a) -# +# # def test_while_unchanged(self): # s = """while 11: foo()""" # self.unchanged(s) -# +# # s = """while 0: foo()""" # self.unchanged(s) -# +# # s = """while foo(): foo()""" # self.unchanged(s) -# +# # s = """while []: foo()""" # self.unchanged(s) -# +# # def test_eq_simple(self): # b = """type(x) == T""" # a = """isinstance(x, T)""" # self.check(b, a) -# +# # b = """if type(x) == T: pass""" # a = """if isinstance(x, T): pass""" # self.check(b, a) -# +# # def test_eq_reverse(self): # b = """T == type(x)""" # a = """isinstance(x, T)""" # self.check(b, a) -# +# # b = """if T == type(x): pass""" # a = """if isinstance(x, T): pass""" # self.check(b, a) -# +# # def test_eq_expression(self): # b = """type(x+y) == d.get('T')""" # a = """isinstance(x+y, d.get('T'))""" # self.check(b, a) -# +# # b = """type( x + y) == d.get('T')""" # a = """isinstance(x + y, d.get('T'))""" # self.check(b, a) -# +# # def test_is_simple(self): # b = """type(x) is T""" # a = """isinstance(x, T)""" # self.check(b, a) -# +# # b = """if type(x) is T: pass""" # a = """if isinstance(x, T): pass""" # self.check(b, a) -# +# # def test_is_reverse(self): # b = """T is type(x)""" # a = """isinstance(x, T)""" # self.check(b, a) -# +# # b = """if T is type(x): pass""" # a = """if isinstance(x, T): pass""" # self.check(b, a) -# +# # def test_is_expression(self): # b = """type(x+y) is d.get('T')""" # a = """isinstance(x+y, d.get('T'))""" # self.check(b, a) -# +# # b = """type( x + y) is d.get('T')""" # a = """isinstance(x + y, d.get('T'))""" # self.check(b, a) -# +# # def test_is_not_simple(self): # b = """type(x) is not T""" # a = """not isinstance(x, T)""" # self.check(b, a) -# +# # b = """if type(x) is not T: pass""" # a = """if not isinstance(x, T): pass""" # self.check(b, a) -# +# # def test_is_not_reverse(self): # b = """T is not type(x)""" # a = """not isinstance(x, T)""" # self.check(b, a) -# +# # b = """if T is not type(x): pass""" # a = """if not isinstance(x, T): pass""" # self.check(b, a) -# +# # def test_is_not_expression(self): # b = """type(x+y) is not d.get('T')""" # a = """not isinstance(x+y, d.get('T'))""" # self.check(b, a) -# +# # b = """type( x + y) is not d.get('T')""" # a = """not isinstance(x + y, d.get('T'))""" # self.check(b, a) -# +# # def test_ne_simple(self): # b = """type(x) != T""" # a = """not isinstance(x, T)""" # self.check(b, a) -# +# # b = """if type(x) != T: pass""" # a = """if not isinstance(x, T): pass""" # self.check(b, a) -# +# # def test_ne_reverse(self): # b = """T != type(x)""" # a = """not isinstance(x, T)""" # self.check(b, a) -# +# # b = """if T != type(x): pass""" # a = """if not isinstance(x, T): pass""" # self.check(b, a) -# +# # def test_ne_expression(self): # b = """type(x+y) != d.get('T')""" # a = """not isinstance(x+y, d.get('T'))""" # self.check(b, a) -# +# # b = """type( x + y) != d.get('T')""" # a = """not isinstance(x + y, d.get('T'))""" # self.check(b, a) -# +# # def test_type_unchanged(self): # a = """type(x).__name__""" # self.unchanged(a) -# +# # def test_sort_list_call(self): # b = """ # v = list(t) @@ -3249,7 +3249,7 @@ def test_with_future_print_function(self): # foo(v) # """ # self.check(b, a) -# +# # b = """ # v = list(foo(b) + d) # v.sort() @@ -3260,7 +3260,7 @@ def test_with_future_print_function(self): # foo(v) # """ # self.check(b, a) -# +# # b = """ # while x: # v = list(t) @@ -3273,7 +3273,7 @@ def test_with_future_print_function(self): # foo(v) # """ # self.check(b, a) -# +# # b = """ # v = list(t) # # foo @@ -3286,7 +3286,7 @@ def test_with_future_print_function(self): # foo(v) # """ # self.check(b, a) -# +# # b = r""" # v = list( t) # v.sort() @@ -3297,21 +3297,21 @@ def test_with_future_print_function(self): # foo(v) # """ # self.check(b, a) -# +# # b = r""" # try: # m = list(s) # m.sort() # except: pass # """ -# +# # a = r""" # try: # m = sorted(s) # except: pass # """ # self.check(b, a) -# +# # b = r""" # try: # m = list(s) @@ -3319,7 +3319,7 @@ def test_with_future_print_function(self): # m.sort() # except: pass # """ -# +# # a = r""" # try: # m = sorted(s) @@ -3327,17 +3327,17 @@ def test_with_future_print_function(self): # except: pass # """ # self.check(b, a) -# +# # b = r""" # m = list(s) # # more comments # m.sort()""" -# +# # a = r""" # m = sorted(s) # # more comments""" # self.check(b, a) -# +# # def test_sort_simple_expr(self): # b = """ # v = t @@ -3349,7 +3349,7 @@ def test_with_future_print_function(self): # foo(v) # """ # self.check(b, a) -# +# # b = """ # v = foo(b) # v.sort() @@ -3360,7 +3360,7 @@ def test_with_future_print_function(self): # foo(v) # """ # self.check(b, a) -# +# # b = """ # v = b.keys() # v.sort() @@ -3371,7 +3371,7 @@ def test_with_future_print_function(self): # foo(v) # """ # self.check(b, a) -# +# # b = """ # v = foo(b) + d # v.sort() @@ -3382,7 +3382,7 @@ def test_with_future_print_function(self): # foo(v) # """ # self.check(b, a) -# +# # b = """ # while x: # v = t @@ -3395,7 +3395,7 @@ def test_with_future_print_function(self): # foo(v) # """ # self.check(b, a) -# +# # b = """ # v = t # # foo @@ -3408,7 +3408,7 @@ def test_with_future_print_function(self): # foo(v) # """ # self.check(b, a) -# +# # b = r""" # v = t # v.sort() @@ -3419,7 +3419,7 @@ def test_with_future_print_function(self): # foo(v) # """ # self.check(b, a) -# +# # def test_sort_unchanged(self): # s = """ # v = list(t) @@ -3427,57 +3427,57 @@ def test_with_future_print_function(self): # foo(w) # """ # self.unchanged(s) -# +# # s = """ # v = list(t) # v.sort(u) # foo(v) # """ # self.unchanged(s) -# +# # class Test_basestring(FixerTestCase): # fixer = "basestring" -# +# # def test_basestring(self): # b = """isinstance(x, basestring)""" # a = """isinstance(x, str)""" # self.check(b, a) -# +# # class Test_buffer(FixerTestCase): # fixer = "buffer" -# +# # def test_buffer(self): # b = """x = buffer(y)""" # a = """x = memoryview(y)""" # self.check(b, a) -# +# # def test_slicing(self): # b = """buffer(y)[4:5]""" # a = """memoryview(y)[4:5]""" # self.check(b, a) -# +# # class Test_future(FixerTestCase): # fixer = "future" -# +# # def test_future(self): # b = """from __future__ import braces""" # a = """""" # self.check(b, a) -# +# # b = """# comment\nfrom __future__ import braces""" # a = """# comment\n""" # self.check(b, a) -# +# # b = """from __future__ import braces\n# comment""" # a = """\n# comment""" # self.check(b, a) -# +# # def test_run_order(self): # self.assert_runs_after('print') -# +# # class Test_itertools(FixerTestCase): # fixer = "itertools" -# +# # def checkall(self, before, after): # # Because we need to check with and without the itertools prefix # # and on each of the three functions, these loops make it all @@ -3487,132 +3487,132 @@ def test_with_future_print_function(self): # b = before %(i+'i'+f) # a = after %(f) # self.check(b, a) -# +# # def test_0(self): # # A simple example -- test_1 covers exactly the same thing, # # but it's not quite as clear. # b = "itertools.izip(a, b)" # a = "zip(a, b)" # self.check(b, a) -# +# # def test_1(self): # b = """%s(f, a)""" # a = """%s(f, a)""" # self.checkall(b, a) -# +# # def test_qualified(self): # b = """itertools.ifilterfalse(a, b)""" # a = """itertools.filterfalse(a, b)""" # self.check(b, a) -# +# # b = """itertools.izip_longest(a, b)""" # a = """itertools.zip_longest(a, b)""" # self.check(b, a) -# +# # def test_2(self): # b = """ifilterfalse(a, b)""" # a = """filterfalse(a, b)""" # self.check(b, a) -# +# # b = """izip_longest(a, b)""" # a = """zip_longest(a, b)""" # self.check(b, a) -# +# # def test_space_1(self): # b = """ %s(f, a)""" # a = """ %s(f, a)""" # self.checkall(b, a) -# +# # def test_space_2(self): # b = """ itertools.ifilterfalse(a, b)""" # a = """ itertools.filterfalse(a, b)""" # self.check(b, a) -# +# # b = """ itertools.izip_longest(a, b)""" # a = """ itertools.zip_longest(a, b)""" # self.check(b, a) -# +# # def test_run_order(self): # self.assert_runs_after('map', 'zip', 'filter') -# -# +# +# # class Test_itertools_imports(FixerTestCase): # fixer = 'itertools_imports' -# +# # def test_reduced(self): # b = "from itertools import imap, izip, foo" # a = "from itertools import foo" # self.check(b, a) -# +# # b = "from itertools import bar, imap, izip, foo" # a = "from itertools import bar, foo" # self.check(b, a) -# +# # b = "from itertools import chain, imap, izip" # a = "from itertools import chain" # self.check(b, a) -# +# # def test_comments(self): # b = "#foo\nfrom itertools import imap, izip" # a = "#foo\n" # self.check(b, a) -# +# # def test_none(self): # b = "from itertools import imap, izip" # a = "" # self.check(b, a) -# +# # b = "from itertools import izip" # a = "" # self.check(b, a) -# +# # def test_import_as(self): # b = "from itertools import izip, bar as bang, imap" # a = "from itertools import bar as bang" # self.check(b, a) -# +# # b = "from itertools import izip as _zip, imap, bar" # a = "from itertools import bar" # self.check(b, a) -# +# # b = "from itertools import imap as _map" # a = "" # self.check(b, a) -# +# # b = "from itertools import imap as _map, izip as _zip" # a = "" # self.check(b, a) -# +# # s = "from itertools import bar as bang" # self.unchanged(s) -# +# # def test_ifilter_and_zip_longest(self): # for name in "filterfalse", "zip_longest": # b = "from itertools import i%s" % (name,) # a = "from itertools import %s" % (name,) # self.check(b, a) -# +# # b = "from itertools import imap, i%s, foo" % (name,) # a = "from itertools import %s, foo" % (name,) # self.check(b, a) -# +# # b = "from itertools import bar, i%s, foo" % (name,) # a = "from itertools import bar, %s, foo" % (name,) # self.check(b, a) -# +# # def test_import_star(self): # s = "from itertools import *" # self.unchanged(s) -# -# +# +# # def test_unchanged(self): # s = "from itertools import foo" # self.unchanged(s) -# -# +# +# # class Test_import(FixerTestCase): # fixer = "import" -# +# # def setUp(self): # super(Test_import, self).setUp() # # Need to replace fix_import's exists method @@ -3623,145 +3623,145 @@ def test_with_future_print_function(self): # def fake_exists(name): # self.files_checked.append(name) # return self.always_exists or (name in self.present_files) -# +# # from lib2to3.fixes import fix_import # fix_import.exists = fake_exists -# +# # def tearDown(self): # from lib2to3.fixes import fix_import # fix_import.exists = os.path.exists -# +# # def check_both(self, b, a): # self.always_exists = True # super(Test_import, self).check(b, a) # self.always_exists = False # super(Test_import, self).unchanged(b) -# +# # def test_files_checked(self): # def p(path): # # Takes a unix path and returns a path with correct separators # return os.path.pathsep.join(path.split("/")) -# +# # self.always_exists = False # self.present_files = set(['__init__.py']) # expected_extensions = ('.py', os.path.sep, '.pyc', '.so', '.sl', '.pyd') # names_to_test = (p("/spam/eggs.py"), "ni.py", p("../../shrubbery.py")) -# +# # for name in names_to_test: # self.files_checked = [] # self.filename = name # self.unchanged("import jam") -# +# # if os.path.dirname(name): # name = os.path.dirname(name) + '/jam' # else: # name = 'jam' # expected_checks = set(name + ext for ext in expected_extensions) # expected_checks.add("__init__.py") -# +# # self.assertEqual(set(self.files_checked), expected_checks) -# +# # def test_not_in_package(self): # s = "import bar" # self.always_exists = False # self.present_files = set(["bar.py"]) # self.unchanged(s) -# +# # def test_with_absolute_import_enabled(self): # s = "from __future__ import absolute_import\nimport bar" # self.always_exists = False # self.present_files = set(["__init__.py", "bar.py"]) # self.unchanged(s) -# +# # def test_in_package(self): # b = "import bar" # a = "from . import bar" # self.always_exists = False # self.present_files = set(["__init__.py", "bar.py"]) # self.check(b, a) -# +# # def test_import_from_package(self): # b = "import bar" # a = "from . import bar" # self.always_exists = False # self.present_files = set(["__init__.py", "bar" + os.path.sep]) # self.check(b, a) -# +# # def test_already_relative_import(self): # s = "from . import bar" # self.unchanged(s) -# +# # def test_comments_and_indent(self): # b = "import bar # Foo" # a = "from . import bar # Foo" # self.check(b, a) -# +# # def test_from(self): # b = "from foo import bar, baz" # a = "from .foo import bar, baz" # self.check_both(b, a) -# +# # b = "from foo import bar" # a = "from .foo import bar" # self.check_both(b, a) -# +# # b = "from foo import (bar, baz)" # a = "from .foo import (bar, baz)" # self.check_both(b, a) -# +# # def test_dotted_from(self): # b = "from green.eggs import ham" # a = "from .green.eggs import ham" # self.check_both(b, a) -# +# # def test_from_as(self): # b = "from green.eggs import ham as spam" # a = "from .green.eggs import ham as spam" # self.check_both(b, a) -# +# # def test_import(self): # b = "import foo" # a = "from . import foo" # self.check_both(b, a) -# +# # b = "import foo, bar" # a = "from . import foo, bar" # self.check_both(b, a) -# +# # b = "import foo, bar, x" # a = "from . import foo, bar, x" # self.check_both(b, a) -# +# # b = "import x, y, z" # a = "from . import x, y, z" # self.check_both(b, a) -# +# # def test_import_as(self): # b = "import foo as x" # a = "from . import foo as x" # self.check_both(b, a) -# +# # b = "import a as b, b as c, c as d" # a = "from . import a as b, b as c, c as d" # self.check_both(b, a) -# +# # def test_local_and_absolute(self): # self.always_exists = False # self.present_files = set(["foo.py", "__init__.py"]) -# +# # s = "import foo, bar" # self.warns_unchanged(s, "absolute and local imports together") -# +# # def test_dotted_import(self): # b = "import foo.bar" # a = "from . import foo.bar" # self.check_both(b, a) -# +# # def test_dotted_import_as(self): # b = "import foo.bar as bang" # a = "from . import foo.bar as bang" # self.check_both(b, a) -# +# # def test_prefix(self): # b = """ # # prefix @@ -3772,101 +3772,101 @@ def test_with_future_print_function(self): # from . import foo.bar # """ # self.check_both(b, a) -# -# +# +# # class Test_set_literal(FixerTestCase): -# +# # fixer = "set_literal" -# +# # def test_basic(self): # b = """set([1, 2, 3])""" # a = """{1, 2, 3}""" # self.check(b, a) -# +# # b = """set((1, 2, 3))""" # a = """{1, 2, 3}""" # self.check(b, a) -# +# # b = """set((1,))""" # a = """{1}""" # self.check(b, a) -# +# # b = """set([1])""" # self.check(b, a) -# +# # b = """set((a, b))""" # a = """{a, b}""" # self.check(b, a) -# +# # b = """set([a, b])""" # self.check(b, a) -# +# # b = """set((a*234, f(args=23)))""" # a = """{a*234, f(args=23)}""" # self.check(b, a) -# +# # b = """set([a*23, f(23)])""" # a = """{a*23, f(23)}""" # self.check(b, a) -# +# # b = """set([a-234**23])""" # a = """{a-234**23}""" # self.check(b, a) -# +# # def test_listcomps(self): # b = """set([x for x in y])""" # a = """{x for x in y}""" # self.check(b, a) -# +# # b = """set([x for x in y if x == m])""" # a = """{x for x in y if x == m}""" # self.check(b, a) -# +# # b = """set([x for x in y for a in b])""" # a = """{x for x in y for a in b}""" # self.check(b, a) -# +# # b = """set([f(x) - 23 for x in y])""" # a = """{f(x) - 23 for x in y}""" # self.check(b, a) -# +# # def test_whitespace(self): # b = """set( [1, 2])""" # a = """{1, 2}""" # self.check(b, a) -# +# # b = """set([1 , 2])""" # a = """{1 , 2}""" # self.check(b, a) -# +# # b = """set([ 1 ])""" # a = """{ 1 }""" # self.check(b, a) -# +# # b = """set( [1] )""" # a = """{1}""" # self.check(b, a) -# +# # b = """set([ 1, 2 ])""" # a = """{ 1, 2 }""" # self.check(b, a) -# +# # b = """set([x for x in y ])""" # a = """{x for x in y }""" # self.check(b, a) -# +# # b = """set( # [1, 2] # ) # """ # a = """{1, 2}\n""" # self.check(b, a) -# +# # def test_comments(self): # b = """set((1, 2)) # Hi""" # a = """{1, 2} # Hi""" # self.check(b, a) -# +# # # This isn't optimal behavior, but the fixer is optional. # b = """ # # Foo @@ -3879,124 +3879,124 @@ def test_with_future_print_function(self): # {1, 2} # """ # self.check(b, a) -# +# # def test_unchanged(self): # s = """set()""" # self.unchanged(s) -# +# # s = """set(a)""" # self.unchanged(s) -# +# # s = """set(a, b, c)""" # self.unchanged(s) -# +# # # Don't transform generators because they might have to be lazy. # s = """set(x for x in y)""" # self.unchanged(s) -# +# # s = """set(x for x in y if z)""" # self.unchanged(s) -# +# # s = """set(a*823-23**2 + f(23))""" # self.unchanged(s) -# -# +# +# # class Test_sys_exc(FixerTestCase): # fixer = "sys_exc" -# +# # def test_0(self): # b = "sys.exc_type" # a = "sys.exc_info()[0]" # self.check(b, a) -# +# # def test_1(self): # b = "sys.exc_value" # a = "sys.exc_info()[1]" # self.check(b, a) -# +# # def test_2(self): # b = "sys.exc_traceback" # a = "sys.exc_info()[2]" # self.check(b, a) -# +# # def test_3(self): # b = "sys.exc_type # Foo" # a = "sys.exc_info()[0] # Foo" # self.check(b, a) -# +# # def test_4(self): # b = "sys. exc_type" # a = "sys. exc_info()[0]" # self.check(b, a) -# +# # def test_5(self): # b = "sys .exc_type" # a = "sys .exc_info()[0]" # self.check(b, a) -# -# +# +# # class Test_paren(FixerTestCase): # fixer = "paren" -# +# # def test_0(self): # b = """[i for i in 1, 2 ]""" # a = """[i for i in (1, 2) ]""" # self.check(b, a) -# +# # def test_1(self): # b = """[i for i in 1, 2, ]""" # a = """[i for i in (1, 2,) ]""" # self.check(b, a) -# +# # def test_2(self): # b = """[i for i in 1, 2 ]""" # a = """[i for i in (1, 2) ]""" # self.check(b, a) -# +# # def test_3(self): # b = """[i for i in 1, 2 if i]""" # a = """[i for i in (1, 2) if i]""" # self.check(b, a) -# +# # def test_4(self): # b = """[i for i in 1, 2 ]""" # a = """[i for i in (1, 2) ]""" # self.check(b, a) -# +# # def test_5(self): # b = """(i for i in 1, 2)""" # a = """(i for i in (1, 2))""" # self.check(b, a) -# +# # def test_6(self): # b = """(i for i in 1 ,2 if i)""" # a = """(i for i in (1 ,2) if i)""" # self.check(b, a) -# +# # def test_unchanged_0(self): # s = """[i for i in (1, 2)]""" # self.unchanged(s) -# +# # def test_unchanged_1(self): # s = """[i for i in foo()]""" # self.unchanged(s) -# +# # def test_unchanged_2(self): # s = """[i for i in (1, 2) if nothing]""" # self.unchanged(s) -# +# # def test_unchanged_3(self): # s = """(i for i in (1, 2))""" # self.unchanged(s) -# +# # def test_unchanged_4(self): # s = """[i for i in m]""" # self.unchanged(s) -# +# # class Test_metaclass(FixerTestCase): -# +# # fixer = 'metaclass' -# +# # def test_unchanged(self): # self.unchanged("class X(): pass") # self.unchanged("class X(object): pass") @@ -4005,19 +4005,19 @@ def test_with_future_print_function(self): # self.unchanged("class X(metaclass=Meta): pass") # self.unchanged("class X(b, arg=23, metclass=Meta): pass") # self.unchanged("class X(b, arg=23, metaclass=Meta, other=42): pass") -# +# # s = """ # class X: # def __metaclass__(self): pass # """ # self.unchanged(s) -# +# # s = """ # class X: # a[23] = 74 # """ # self.unchanged(s) -# +# # def test_comments(self): # b = """ # class X: @@ -4030,7 +4030,7 @@ def test_with_future_print_function(self): # pass # """ # self.check(b, a) -# +# # b = """ # class X: # __metaclass__ = Meta @@ -4042,7 +4042,7 @@ def test_with_future_print_function(self): # # Bedtime! # """ # self.check(b, a) -# +# # def test_meta(self): # # no-parent class, odd body # b = """ @@ -4055,13 +4055,13 @@ def test_with_future_print_function(self): # pass # """ # self.check(b, a) -# +# # # one parent class, no body # b = """class X(object): __metaclass__ = Q""" # a = """class X(object, metaclass=Q): pass""" # self.check(b, a) -# -# +# +# # # one parent, simple body # b = """ # class X(object): @@ -4073,7 +4073,7 @@ def test_with_future_print_function(self): # bar = 7 # """ # self.check(b, a) -# +# # b = """ # class X: # __metaclass__ = Meta; x = 4; g = 23 @@ -4083,7 +4083,7 @@ def test_with_future_print_function(self): # x = 4; g = 23 # """ # self.check(b, a) -# +# # # one parent, simple body, __metaclass__ last # b = """ # class X(object): @@ -4095,7 +4095,7 @@ def test_with_future_print_function(self): # bar = 7 # """ # self.check(b, a) -# +# # # redefining __metaclass__ # b = """ # class X(): @@ -4108,7 +4108,7 @@ def test_with_future_print_function(self): # bar = 7 # """ # self.check(b, a) -# +# # # multiple inheritance, simple body # b = """ # class X(clsA, clsB): @@ -4120,12 +4120,12 @@ def test_with_future_print_function(self): # bar = 7 # """ # self.check(b, a) -# +# # # keywords in the class statement # b = """class m(a, arg=23): __metaclass__ = Meta""" # a = """class m(a, arg=23, metaclass=Meta): pass""" # self.check(b, a) -# +# # b = """ # class X(expression(2 + 4)): # __metaclass__ = Meta @@ -4135,7 +4135,7 @@ def test_with_future_print_function(self): # pass # """ # self.check(b, a) -# +# # b = """ # class X(expression(2 + 4), x**4): # __metaclass__ = Meta @@ -4145,7 +4145,7 @@ def test_with_future_print_function(self): # pass # """ # self.check(b, a) -# +# # b = """ # class X: # __metaclass__ = Meta @@ -4156,44 +4156,44 @@ def test_with_future_print_function(self): # save.py = 23 # """ # self.check(b, a) -# -# +# +# # class Test_getcwdu(FixerTestCase): -# +# # fixer = 'getcwdu' -# +# # def test_basic(self): # b = """os.getcwdu""" # a = """os.getcwd""" # self.check(b, a) -# +# # b = """os.getcwdu()""" # a = """os.getcwd()""" # self.check(b, a) -# +# # b = """meth = os.getcwdu""" # a = """meth = os.getcwd""" # self.check(b, a) -# +# # b = """os.getcwdu(args)""" # a = """os.getcwd(args)""" # self.check(b, a) -# +# # def test_comment(self): # b = """os.getcwdu() # Foo""" # a = """os.getcwd() # Foo""" # self.check(b, a) -# +# # def test_unchanged(self): # s = """os.getcwd()""" # self.unchanged(s) -# +# # s = """getcwdu()""" # self.unchanged(s) -# +# # s = """os.getcwdb()""" # self.unchanged(s) -# +# # def test_indentation(self): # b = """ # if 1: @@ -4204,124 +4204,124 @@ def test_with_future_print_function(self): # os.getcwd() # """ # self.check(b, a) -# +# # def test_multilation(self): # b = """os .getcwdu()""" # a = """os .getcwd()""" # self.check(b, a) -# +# # b = """os. getcwdu""" # a = """os. getcwd""" # self.check(b, a) -# +# # b = """os.getcwdu ( )""" # a = """os.getcwd ( )""" # self.check(b, a) -# -# +# +# # class Test_operator(FixerTestCase): -# +# # fixer = "operator" -# +# # def test_operator_isCallable(self): # b = "operator.isCallable(x)" # a = "hasattr(x, '__call__')" # self.check(b, a) -# +# # def test_operator_sequenceIncludes(self): # b = "operator.sequenceIncludes(x, y)" # a = "operator.contains(x, y)" # self.check(b, a) -# +# # b = "operator .sequenceIncludes(x, y)" # a = "operator .contains(x, y)" # self.check(b, a) -# +# # b = "operator. sequenceIncludes(x, y)" # a = "operator. contains(x, y)" # self.check(b, a) -# +# # def test_operator_isSequenceType(self): # b = "operator.isSequenceType(x)" # a = "import collections\nisinstance(x, collections.Sequence)" # self.check(b, a) -# +# # def test_operator_isMappingType(self): # b = "operator.isMappingType(x)" # a = "import collections\nisinstance(x, collections.Mapping)" # self.check(b, a) -# +# # def test_operator_isNumberType(self): # b = "operator.isNumberType(x)" # a = "import numbers\nisinstance(x, numbers.Number)" # self.check(b, a) -# +# # def test_operator_repeat(self): # b = "operator.repeat(x, n)" # a = "operator.mul(x, n)" # self.check(b, a) -# +# # b = "operator .repeat(x, n)" # a = "operator .mul(x, n)" # self.check(b, a) -# +# # b = "operator. repeat(x, n)" # a = "operator. mul(x, n)" # self.check(b, a) -# +# # def test_operator_irepeat(self): # b = "operator.irepeat(x, n)" # a = "operator.imul(x, n)" # self.check(b, a) -# +# # b = "operator .irepeat(x, n)" # a = "operator .imul(x, n)" # self.check(b, a) -# +# # b = "operator. irepeat(x, n)" # a = "operator. imul(x, n)" # self.check(b, a) -# +# # def test_bare_isCallable(self): # s = "isCallable(x)" # t = "You should use 'hasattr(x, '__call__')' here." # self.warns_unchanged(s, t) -# +# # def test_bare_sequenceIncludes(self): # s = "sequenceIncludes(x, y)" # t = "You should use 'operator.contains(x, y)' here." # self.warns_unchanged(s, t) -# +# # def test_bare_operator_isSequenceType(self): # s = "isSequenceType(z)" # t = "You should use 'isinstance(z, collections.Sequence)' here." # self.warns_unchanged(s, t) -# +# # def test_bare_operator_isMappingType(self): # s = "isMappingType(x)" # t = "You should use 'isinstance(x, collections.Mapping)' here." # self.warns_unchanged(s, t) -# +# # def test_bare_operator_isNumberType(self): # s = "isNumberType(y)" # t = "You should use 'isinstance(y, numbers.Number)' here." # self.warns_unchanged(s, t) -# +# # def test_bare_operator_repeat(self): # s = "repeat(x, n)" # t = "You should use 'operator.mul(x, n)' here." # self.warns_unchanged(s, t) -# +# # def test_bare_operator_irepeat(self): # s = "irepeat(y, 187)" # t = "You should use 'operator.imul(y, 187)' here." # self.warns_unchanged(s, t) -# -# +# +# # class Test_exitfunc(FixerTestCase): -# +# # fixer = "exitfunc" -# +# # def test_simple(self): # b = """ # import sys @@ -4333,7 +4333,7 @@ def test_with_future_print_function(self): # atexit.register(my_atexit) # """ # self.check(b, a) -# +# # def test_names_import(self): # b = """ # import sys, crumbs @@ -4344,7 +4344,7 @@ def test_with_future_print_function(self): # atexit.register(my_func) # """ # self.check(b, a) -# +# # def test_complex_expression(self): # b = """ # import sys @@ -4356,7 +4356,7 @@ def test_with_future_print_function(self): # atexit.register(do(d)/a()+complex(f=23, g=23)*expression) # """ # self.check(b, a) -# +# # def test_comments(self): # b = """ # import sys # Foo @@ -4368,7 +4368,7 @@ def test_with_future_print_function(self): # atexit.register(f) # Blah # """ # self.check(b, a) -# +# # b = """ # import apples, sys, crumbs, larry # Pleasant comments # sys.exitfunc = func @@ -4378,7 +4378,7 @@ def test_with_future_print_function(self): # atexit.register(func) # """ # self.check(b, a) -# +# # def test_in_a_function(self): # b = """ # import sys @@ -4392,15 +4392,15 @@ def test_with_future_print_function(self): # atexit.register(func) # """ # self.check(b, a) -# +# # def test_no_sys_import(self): # b = """sys.exitfunc = f""" # a = """atexit.register(f)""" # msg = ("Can't find sys import; Please add an atexit import at the " # "top of your file.") # self.warns(b, a, msg) -# -# +# +# # def test_unchanged(self): # s = """f(sys.exitfunc)""" # self.unchanged(s) diff --git a/tests/test_future/test_magicsuper.py b/tests/test_future/test_magicsuper.py index 8b725d28..e5bbe093 100644 --- a/tests/test_future/test_magicsuper.py +++ b/tests/test_future/test_magicsuper.py @@ -133,4 +133,3 @@ def getit(cls): if __name__ == '__main__': unittest.main() - diff --git a/tests/test_future/test_object.py b/tests/test_future/test_object.py index 07715029..8352f3a3 100644 --- a/tests/test_future/test_object.py +++ b/tests/test_future/test_object.py @@ -39,7 +39,7 @@ def __str__(self): assert str(a) == str(b) def test_implements_py2_iterator(self): - + class Upper(object): def __init__(self, iterable): self._iter = iter(iterable) @@ -57,7 +57,7 @@ def __next__(self): return 'Next!' def __iter__(self): return self - + itr = MyIter() self.assertEqual(next(itr), 'Next!') @@ -68,7 +68,7 @@ def __iter__(self): self.assertEqual(item, 'Next!') def test_implements_py2_nonzero(self): - + class EvenIsTrue(object): """ An integer that evaluates to True if even. @@ -94,7 +94,7 @@ def test_int_implements_py2_nonzero(self): maps to __bool__ in case the user redefines __bool__ in a subclass of newint. """ - + class EvenIsTrue(int): """ An integer that evaluates to True if even. @@ -137,7 +137,7 @@ class MyClass(object): def test_isinstance_object_subclass(self): """ - This was failing before + This was failing before """ class A(object): pass diff --git a/tests/test_future/test_pasteurize.py b/tests/test_future/test_pasteurize.py index b09e3cba..2b6e2ee6 100644 --- a/tests/test_future/test_pasteurize.py +++ b/tests/test_future/test_pasteurize.py @@ -96,7 +96,7 @@ def test_exception_indentation(self): def test_urllib_request(self): """ Example Python 3 code using the new urllib.request module. - + Does the ``pasteurize`` script handle this? """ before = """ @@ -105,7 +105,7 @@ def test_urllib_request(self): URL = 'http://pypi.python.org/pypi/{}/json' package = 'future' - + r = urllib.request.urlopen(URL.format(package)) pprint.pprint(r.read()) """ @@ -115,7 +115,7 @@ def test_urllib_request(self): URL = 'http://pypi.python.org/pypi/{}/json' package = 'future' - + r = urllib_request.urlopen(URL.format(package)) pprint.pprint(r.read()) """ @@ -150,7 +150,7 @@ def test_correct_exit_status(self): retcode = main([self.textfilename]) self.assertTrue(isinstance(retcode, int)) # i.e. Py2 builtin int - + class TestFuturizeAnnotations(CodeHandler): @unittest.expectedFailure def test_return_annotations_alone(self): @@ -250,7 +250,7 @@ def foo(bar='baz'): pass """ self.unchanged(s, from3=True) - + if __name__ == '__main__': unittest.main() diff --git a/tests/test_future/test_standard_library.py b/tests/test_future/test_standard_library.py index f4dd7942..48fd85d6 100644 --- a/tests/test_future/test_standard_library.py +++ b/tests/test_future/test_standard_library.py @@ -30,7 +30,7 @@ def test_can_import_several(self): """ This test failed in v0.12-pre if e.g. future/standard_library/email/header.py contained: - + from future import standard_library standard_library.remove_hooks() """ @@ -274,7 +274,7 @@ def test_import_failure_from_module(self): # Disabled since v0.16.0: # def test_configparser(self): # import configparser - + def test_copyreg(self): import copyreg @@ -283,7 +283,7 @@ def test_pickle(self): def test_profile(self): import profile - + def test_stringio(self): from io import StringIO s = StringIO(u'test') diff --git a/tests/test_future/test_str.py b/tests/test_future/test_str.py index 7e37a62f..3056d30b 100644 --- a/tests/test_future/test_str.py +++ b/tests/test_future/test_str.py @@ -258,7 +258,7 @@ def test_str_contains_something(self): if utils.PY2: self.assertTrue(b'A' in s) with self.assertRaises(TypeError): - bytes(b'A') in s + bytes(b'A') in s with self.assertRaises(TypeError): 65 in s # unlike bytes diff --git a/tests/test_past/test_builtins.py b/tests/test_past/test_builtins.py index be22b974..d16978ee 100644 --- a/tests/test_past/test_builtins.py +++ b/tests/test_past/test_builtins.py @@ -1768,12 +1768,12 @@ def test_baddecorator(self): # # ("classic int division", DeprecationWarning)): # if True: # run_unittest(*args) -# +# # def test_main(verbose=None): # test_classes = (BuiltinTest, TestSorted) -# +# # _run_unittest(*test_classes) -# +# # # verify reference counting # if verbose and hasattr(sys, "gettotalrefcount"): # import gc diff --git a/tests/test_past/test_olddict.py b/tests/test_past/test_olddict.py index 4ea2471b..9f210608 100644 --- a/tests/test_past/test_olddict.py +++ b/tests/test_past/test_olddict.py @@ -622,7 +622,7 @@ def test_tuple_keyerror(self): # 'd.update({x2: 2})']: # with self.assertRaises(CustomException): # utils.exec_(stmt, locals()) - # + # # def test_resize1(self): # # Dict resizing bug, found by Jack Jansen in 2.2 CVS development. # # This version got an assert failure in debug build, infinite loop in diff --git a/tests/test_past/test_translation.py b/tests/test_past/test_translation.py index bb263695..2b442d96 100644 --- a/tests/test_past/test_translation.py +++ b/tests/test_past/test_translation.py @@ -67,7 +67,7 @@ def write_and_import(self, code, modulename='mymodule'): # print('Hooks removed') sys.path.remove(self.tempdir) return module - + def test_print_statement(self): code = """ print 'Hello from a Python 2-style print statement!' @@ -82,7 +82,7 @@ def test_exec_statement(self): """ module = self.write_and_import(code, 'execer') self.assertEqual(module.x, 7) - + def test_div(self): code = """ x = 3 / 2 @@ -173,14 +173,14 @@ def test_exception_syntax(self): module = self.write_and_import(code, 'py2_exceptions') self.assertEqual(module.value, 'string: success!') - + # class TestFuturizeSimple(CodeHandler): # """ # This class contains snippets of Python 2 code (invalid Python 3) and # tests for whether they can be imported correctly from Python 3 with the # import hooks. # """ -# +# # @unittest.expectedFailure # def test_problematic_string(self): # """ This string generates a SyntaxError on Python 3 unless it has @@ -193,7 +193,7 @@ def test_exception_syntax(self): # s = r'The folder is "C:\Users"'. # """ # self.convert_check(before, after) -# +# # def test_tobytes(self): # """ # The --tobytes option converts all UNADORNED string literals 'abcd' to b'abcd'. @@ -228,7 +228,7 @@ def test_exception_syntax(self): # s8 = b"pqrs" # """ # self.convert_check(before, after, tobytes=True) -# +# # @unittest.expectedFailure # def test_izip(self): # before = """ @@ -243,7 +243,7 @@ def test_exception_syntax(self): # pass # """ # self.convert_check(before, after, stages=(1, 2), ignore_imports=False) -# +# # @unittest.expectedFailure # def test_no_unneeded_list_calls(self): # """ @@ -254,14 +254,14 @@ def test_exception_syntax(self): # pass # """ # self.unchanged(code) -# +# # def test_xrange(self): # code = ''' # for i in xrange(10): # pass # ''' # self.convert(code) -# +# # @unittest.expectedFailure # def test_source_coding_utf8(self): # """ @@ -275,7 +275,7 @@ def test_exception_syntax(self): # icons = [u"◐", u"◓", u"◑", u"◒"] # """ # self.unchanged(code) -# +# # def test_exception_syntax(self): # """ # Test of whether futurize handles the old-style exception syntax @@ -293,7 +293,7 @@ def test_exception_syntax(self): # val = e.errno # """ # self.convert_check(before, after) -# +# # def test_super(self): # """ # This tests whether futurize keeps the old two-argument super() calls the @@ -306,7 +306,7 @@ def test_exception_syntax(self): # super(VerboseList, self).append(item) # ''' # self.unchanged(code) -# +# # @unittest.expectedFailure # def test_file(self): # """ @@ -323,41 +323,41 @@ def test_exception_syntax(self): # f.close() # ''' # self.convert_check(before, after) -# +# # def test_apply(self): # before = ''' # def addup(*x): # return sum(x) -# +# # assert apply(addup, (10,20)) == 30 # ''' # after = """ # def addup(*x): # return sum(x) -# +# # assert addup(*(10,20)) == 30 # """ # self.convert_check(before, after) -# +# # @unittest.skip('not implemented yet') # def test_download_pypi_package_and_test(self, package_name='future'): # URL = 'http://pypi.python.org/pypi/{0}/json' -# +# # import requests # r = requests.get(URL.format(package_name)) # pprint.pprint(r.json()) -# +# # download_url = r.json()['urls'][0]['url'] # filename = r.json()['urls'][0]['filename'] # # r2 = requests.get(download_url) # # with open('/tmp/' + filename, 'w') as tarball: # # tarball.write(r2.content) -# +# # def test_raw_input(self): # """ # Passes in a string to the waiting input() after futurize # conversion. -# +# # The code is the first snippet from these docs: # http://docs.python.org/2/library/2to3.html # """ @@ -376,13 +376,13 @@ def test_exception_syntax(self): # greet(name) # """ # self.convert_check(before, desired, run=False) -# +# # for interpreter in self.interpreters: # p1 = Popen([interpreter, self.tempdir + 'mytestscript.py'], # stdout=PIPE, stdin=PIPE, stderr=PIPE) # (stdout, stderr) = p1.communicate(b'Ed') # self.assertEqual(stdout, b"What's your name?\nHello, Ed!\n") -# +# # def test_literal_prefixes_are_not_stripped(self): # """ # Tests to ensure that the u'' and b'' prefixes on unicode strings and @@ -397,7 +397,7 @@ def test_exception_syntax(self): # b = b'byte string' # ''' # self.unchanged(code) -# +# # @unittest.expectedFailure # def test_division(self): # """ @@ -411,8 +411,8 @@ def test_exception_syntax(self): # x = old_div(1, 2) # """ # self.convert_check(before, after, stages=[1]) -# -# +# +# # class TestFuturizeRenamedStdlib(CodeHandler): # def test_renamed_modules(self): # before = """ @@ -420,7 +420,7 @@ def test_exception_syntax(self): # import copy_reg # import cPickle # import cStringIO -# +# # s = cStringIO.StringIO('blah') # """ # after = """ @@ -428,18 +428,18 @@ def test_exception_syntax(self): # import copyreg # import pickle # import io -# +# # s = io.StringIO('blah') # """ # self.convert_check(before, after) -# +# # @unittest.expectedFailure # def test_urllib_refactor(self): # # Code like this using urllib is refactored by futurize --stage2 to use # # the new Py3 module names, but ``future`` doesn't support urllib yet. # before = """ # import urllib -# +# # URL = 'http://pypi.python.org/pypi/future/json' # package_name = 'future' # r = urllib.urlopen(URL.format(package_name)) @@ -447,14 +447,14 @@ def test_exception_syntax(self): # """ # after = """ # import urllib.request -# +# # URL = 'http://pypi.python.org/pypi/future/json' # package_name = 'future' # r = urllib.request.urlopen(URL.format(package_name)) # data = r.read() # """ # self.convert_check(before, after) -# +# # def test_renamed_copy_reg_and_cPickle_modules(self): # """ # Example from docs.python.org/2/library/copy_reg.html @@ -466,11 +466,11 @@ def test_exception_syntax(self): # class C(object): # def __init__(self, a): # self.a = a -# +# # def pickle_c(c): # print('pickling a C instance...') # return C, (c.a,) -# +# # copy_reg.pickle(C, pickle_c) # c = C(1) # d = copy.copy(c) @@ -483,23 +483,23 @@ def test_exception_syntax(self): # class C(object): # def __init__(self, a): # self.a = a -# +# # def pickle_c(c): # print('pickling a C instance...') # return C, (c.a,) -# +# # copyreg.pickle(C, pickle_c) # c = C(1) # d = copy.copy(c) # p = pickle.dumps(c) # """ # self.convert_check(before, after) -# +# # @unittest.expectedFailure # def test_Py2_StringIO_module(self): # """ # Ideally, there would be a fixer for this. For now: -# +# # TODO: add the Py3 equivalent for this to the docs # """ # before = """ @@ -515,19 +515,19 @@ def test_exception_syntax(self): # # instead? # """ # self.convert_check(before, after) -# -# +# +# # class TestFuturizeStage1(CodeHandler): # # """ # # Tests "stage 1": safe optimizations: modernizing Python 2 code so that it # # uses print functions, new-style exception syntax, etc. -# +# # # The behaviour should not change and this should introduce no dependency on # # the ``future`` package. It produces more modern Python 2-only code. The # # goal is to reduce the size of the real porting patch-set by performing # # the uncontroversial patches first. # # """ -# +# # def test_apply(self): # """ # apply() should be changed by futurize --stage1 @@ -535,7 +535,7 @@ def test_exception_syntax(self): # before = ''' # def f(a, b): # return a + b -# +# # args = (1, 2) # assert apply(f, args) == 3 # assert apply(f, ('a', 'b')) == 'ab' @@ -543,13 +543,13 @@ def test_exception_syntax(self): # after = ''' # def f(a, b): # return a + b -# +# # args = (1, 2) # assert f(*args) == 3 # assert f(*('a', 'b')) == 'ab' # ''' # self.convert_check(before, after, stages=[1]) -# +# # def test_xrange(self): # """ # xrange should not be changed by futurize --stage1 @@ -559,18 +559,18 @@ def test_exception_syntax(self): # pass # ''' # self.unchanged(code, stages=[1]) -# +# # @unittest.expectedFailure # def test_absolute_import_changes(self): # """ # Implicit relative imports should be converted to absolute or explicit # relative imports correctly. -# +# # Issue #16 (with porting bokeh/bbmodel.py) # """ # with open('specialmodels.py', 'w') as f: # f.write('pass') -# +# # before = """ # import specialmodels.pandasmodel # specialmodels.pandasmodel.blah() @@ -581,7 +581,7 @@ def test_exception_syntax(self): # pandasmodel.blah() # """ # self.convert_check(before, after, stages=[1]) -# +# # def test_safe_futurize_imports(self): # """ # The standard library module names should not be changed until stage 2 @@ -590,13 +590,13 @@ def test_exception_syntax(self): # import ConfigParser # import HTMLParser # import collections -# +# # ConfigParser.ConfigParser # HTMLParser.HTMLParser # d = collections.OrderedDict() # """ # self.unchanged(before, stages=[1]) -# +# # def test_print(self): # before = """ # print 'Hello' @@ -605,7 +605,7 @@ def test_exception_syntax(self): # print('Hello') # """ # self.convert_check(before, after, stages=[1]) -# +# # before = """ # import sys # print >> sys.stderr, 'Hello', 'world' @@ -615,16 +615,16 @@ def test_exception_syntax(self): # print('Hello', 'world', file=sys.stderr) # """ # self.convert_check(before, after, stages=[1]) -# +# # def test_print_already_function(self): # """ -# Running futurize --stage1 should not add a second set of parentheses +# Running futurize --stage1 should not add a second set of parentheses # """ # before = """ # print('Hello') # """ # self.unchanged(before, stages=[1]) -# +# # @unittest.expectedFailure # def test_print_already_function_complex(self): # """ @@ -639,7 +639,7 @@ def test_exception_syntax(self): # print('Hello', 'world', file=sys.stderr) # """ # self.unchanged(before, stages=[1]) -# +# # def test_exceptions(self): # before = """ # try: @@ -654,7 +654,7 @@ def test_exception_syntax(self): # pass # """ # self.convert_check(before, after, stages=[1]) -# +# # @unittest.expectedFailure # def test_string_exceptions(self): # """ @@ -674,7 +674,7 @@ def test_exception_syntax(self): # pass # """ # self.convert_check(before, after, stages=[1]) -# +# # @unittest.expectedFailure # def test_oldstyle_classes(self): # """ @@ -689,8 +689,8 @@ def test_exception_syntax(self): # pass # """ # self.convert_check(before, after, stages=[1]) -# -# +# +# # def test_octal_literals(self): # before = """ # mode = 0644 @@ -699,7 +699,7 @@ def test_exception_syntax(self): # mode = 0o644 # """ # self.convert_check(before, after) -# +# # def test_long_int_literals(self): # before = """ # bignumber = 12345678901234567890L @@ -708,7 +708,7 @@ def test_exception_syntax(self): # bignumber = 12345678901234567890 # """ # self.convert_check(before, after) -# +# # def test___future___import_position(self): # """ # Issue #4: __future__ imports inserted too low in file: SyntaxError @@ -722,13 +722,13 @@ def test_exception_syntax(self): # # # # another comment # # -# +# # CONSTANTS = [ 0, 01, 011, 0111, 012, 02, 021, 0211, 02111, 013 ] # _RN_LETTERS = "IVXLCDM" -# +# # def my_func(value): # pass -# +# # ''' Docstring-like comment here ''' # """ # self.convert(code) @@ -736,4 +736,3 @@ def test_exception_syntax(self): if __name__ == '__main__': unittest.main() - From 1b727a4129ebafb7e4bdf43702494cb2db07c6bd Mon Sep 17 00:00:00 2001 From: cclauss Date: Mon, 10 Sep 2018 23:32:45 +0200 Subject: [PATCH 220/425] Travis CI: Run flake8 tests to find syntax errors and undefined names Add [flake8](http://flake8.pycqa.org) tests to find Python syntax errors and undefined names. * Remove versions of Python that are EOL like Python 2.6 and 3.3 -- https://docs.python.org/devguide/index.html#branchstatus __E901,E999,F821,F822,F823__ are the "_showstopper_" flake8 issues that can halt the runtime with a SyntaxError, NameError, etc. Most other flake8 issues are merely "style violations" -- useful for readability but they do not effect runtime safety. * F821: undefined name `name` * F822: undefined name `name` in `__all__` * F823: local variable name referenced before assignment * E901: SyntaxError or IndentationError * E999: SyntaxError -- failed to compile a file into an Abstract Syntax Tree --- .travis.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 526bac0e..22673c86 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,24 @@ +sudo: false language: python cache: pip python: - "3.6" - "3.5" - "3.4" - - "3.3" - "2.7" - - "2.6" - -sudo: false - -# command to install dependencies, e.g. pip install -r requirements.txt -# These packages only exist on Ubuntu 13.04 and newer: -# No dependencies currently unless using Python 2.6. install: - - if [[ $TRAVIS_PYTHON_VERSION == 2.6* ]]; then pip install -r requirements_py26.txt; fi - python setup.py install +before_script: + # 1) stop the build if there are Python syntax errors or undefined names + # 2) exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + - if [[ $TRAVIS_PYTHON_VERSION == 3.6 ]]; then + pip install flake8; + flake8 . --count --exit-zero --select=E901,E999,F821,F822,F823 --show-source --statistics; + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics; + fi + # command to run tests, e.g. python setup.py test script: From 26e50e2601a44f6747944635ab150e19c5d79310 Mon Sep 17 00:00:00 2001 From: cclauss Date: Mon, 10 Sep 2018 23:39:25 +0200 Subject: [PATCH 221/425] Update .travis.yml --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 22673c86..0046cc96 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,8 @@ install: before_script: # 1) stop the build if there are Python syntax errors or undefined names # 2) exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - - if [[ $TRAVIS_PYTHON_VERSION == 3.6 ]]; then + # - if [[ $TRAVIS_PYTHON_VERSION == 3.6 ]]; then + - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then pip install flake8; flake8 . --count --exit-zero --select=E901,E999,F821,F822,F823 --show-source --statistics; flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics; From ae05bd9e9efd229114fd797ebea720dc7b04dff3 Mon Sep 17 00:00:00 2001 From: cclauss Date: Mon, 10 Sep 2018 23:50:30 +0200 Subject: [PATCH 222/425] Run flake8 tests only on Python 2.7 and 3.7 --- .travis.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0046cc96..2bcd0c2c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,20 +1,25 @@ sudo: false language: python cache: pip -python: - - "3.6" - - "3.5" - - "3.4" - - "2.7" + +matrix: + include: + - python: 2.7 + - python: 3.4 + - python: 3.5 + - python: 3.6 + - python: 3.7 + dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069) + sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069) install: - python setup.py install before_script: + # Run flake8 tests only on Python 2.7 and 3.7... # 1) stop the build if there are Python syntax errors or undefined names # 2) exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - # - if [[ $TRAVIS_PYTHON_VERSION == 3.6 ]]; then - - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then + - if [[ $TRAVIS_PYTHON_VERSION == *.7 ]]; then pip install flake8; flake8 . --count --exit-zero --select=E901,E999,F821,F822,F823 --show-source --statistics; flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics; From 4f8b461ad6bd6117b9dd82b302a8301226847400 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Tue, 11 Sep 2018 16:35:01 -0700 Subject: [PATCH 223/425] Support statements with multiple operators in fix_division_safe --- src/libfuturize/fixer_util.py | 9 ++-- src/libfuturize/fixes/fix_division_safe.py | 55 ++++++++++++++-------- tests/test_future/test_futurize.py | 32 +++++++++++++ 3 files changed, 73 insertions(+), 23 deletions(-) diff --git a/src/libfuturize/fixer_util.py b/src/libfuturize/fixer_util.py index 28ec76a0..48e4689d 100644 --- a/src/libfuturize/fixer_util.py +++ b/src/libfuturize/fixer_util.py @@ -507,13 +507,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/fix_division_safe.py b/src/libfuturize/fixes/fix_division_safe.py index a3adfa67..c057c124 100644 --- a/src/libfuturize/fixes/fix_division_safe.py +++ b/src/libfuturize/fixes/fix_division_safe.py @@ -15,7 +15,7 @@ import re import lib2to3.pytree as pytree -from lib2to3.fixer_util import Leaf, Node +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, @@ -33,8 +33,14 @@ def match_division(node): const_re = re.compile('^[0-9]*[.][0-9]*$') +def is_floaty(node, div_idx): + return _is_floaty(node.children[0:div_idx]) or _is_floaty(node.children[div_idx+1:]) + def _is_floaty(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) @@ -44,6 +50,24 @@ def _is_floaty(expr): return expr.children[0].value == u'float' return False +def find_division(node): + for i, child in enumerate(node.children): + if match_division(child): + return i + return False + +def clone_div_operands(node, div_idx): + children = [] + for i, child in enumerate(node.children): + if i == div_idx: + children.append(Comma()) + else: + children.append(child.clone()) + + # Strip any leading space for the first number: + children[0].prefix = u'' + + return children class FixDivisionSafe(fixer_base.BaseFix): # BM_compatible = True @@ -60,33 +84,26 @@ def start_tree(self, tree, name): Skip this fixer if "__future__.division" is already imported. """ super(FixDivisionSafe, self).start_tree(tree, name) - self.skip = "division" in tree.future_features + self.skip = "division" not in tree.future_features def match(self, node): u""" 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: + div_idx = find_division(node) + if div_idx is not False: + # if expr1 or expr2 are obviously floats, we don't need to wrap in + # old_div, as the behavior of division between any number and a float + # should be the same in 2 or 3 + if not is_floaty(node, div_idx): + return clone_div_operands(node, div_idx) + return False def transform(self, node, results): if self.skip: return future_import(u"division", node) - - expr1, expr2 = results[0].clone(), results[1].clone() - # Strip any leading space for the first number: - expr1.prefix = u'' - # if expr1 or expr2 are obviously floats, we don't need to wrap in - # old_div, as the behavior of division between any number and a float - # should be the same in 2 or 3 - if _is_floaty(expr1) or _is_floaty(expr2): - return touch_import_top(u'past.utils', u'old_div', node) - return wrap_in_fn_call("old_div", (expr1, expr2), prefix=node.prefix) + return wrap_in_fn_call("old_div", results, prefix=node.prefix) diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index 49d210f5..4bdb05ba 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -1181,18 +1181,50 @@ def test_safe_division(self): after futurization. """ before = """ + import random x = 3 / 2 y = 3. / 2 + foo = range(100) assert x == 1 and isinstance(x, int) assert y == 1.5 and isinstance(y, float) + a = 1 + foo[len(foo) / 2] + b = 1 + foo[len(foo) * 3 / 4] + assert a == 50 + assert b == 75 + r = random.randint(0, 1000) * 1.0 / 1000 + output = { "SUCCESS": 5, "TOTAL": 10 } + output["SUCCESS"] * 100 / output["TOTAL"] + obj = foo + val = float(obj.numer) / obj.denom * 1e-9 + mount.bytes_free * mount.free_size / bytes_in_gb + obj.total_count() * threshold / 100 + 100 * abs(obj.width - original_width) / float(max(obj.width, original_width)) + 100 * abs(obj.width - original_width) / max(obj.width, original_width) + float(target_width) * float(original_height) / float(original_width) """ after = """ from __future__ import division from past.utils import old_div + import random x = old_div(3, 2) y = 3. / 2 + foo = range(100) assert x == 1 and isinstance(x, int) assert y == 1.5 and isinstance(y, float) + a = 1 + foo[old_div(len(foo), 2)] + b = 1 + foo[old_div(len(foo) * 3, 4)] + assert a == 50 + assert b == 75 + r = old_div(random.randint(0, 1000) * 1.0, 1000) + output = { "SUCCESS": 5, "TOTAL": 10 } + output["SUCCESS"] * 100 / output["TOTAL"] + obj = foo + val = float(obj.numer) / obj.denom * 1e-9 + old_div(mount.bytes_free * mount.free_size, bytes_in_gb) + old_div(obj.total_count() * threshold, 100) + 100 * abs(obj.width - original_width) / float(max(obj.width, original_width)) + 100 * abs(obj.width - original_width), max(obj.width, original_width)) + float(target_width) * float(original_height) / float(original_width) """ self.convert_check(before, after) From db4033c33244f64dde186cf3e1a5cf5e72682780 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 29 Apr 2018 19:02:40 -0700 Subject: [PATCH 224/425] Update pypi.python.org URLs to pypi.org For details on the new PyPI, see the blog post: https://pythoninsider.blogspot.ca/2018/04/new-pypi-launched-legacy-pypi-shutting.html --- docs/_templates/sidebarintro.html | 2 +- docs/other/useful_links.txt | 10 +++++----- docs/pasteurize.rst | 2 +- docs/standard_library_imports.rst | 2 +- docs/whatsnew.rst | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index d2372bf6..e4433221 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -15,7 +15,7 @@

Other Formats

--> diff --git a/docs/other/useful_links.txt b/docs/other/useful_links.txt index e9ff3774..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 @@ -104,7 +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/pasteurize.rst b/docs/pasteurize.rst index 5cdffa3e..070b5d1a 100644 --- a/docs/pasteurize.rst +++ b/docs/pasteurize.rst @@ -30,7 +30,7 @@ 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.python.org/pypi/configparser), so, as +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`` diff --git a/docs/standard_library_imports.rst b/docs/standard_library_imports.rst index 5e9115a0..58aef032 100644 --- a/docs/standard_library_imports.rst +++ b/docs/standard_library_imports.rst @@ -78,7 +78,7 @@ complete list is here:: 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.python.org/pypi/configparser). +``configparser`` module because a full backport exists (see https://pypi.org/project/configparser/). .. _list-standard-library-refactored: diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 582b506b..2450d23e 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -10,7 +10,7 @@ What's new in version 0.16.0 (2016-09-22) This release removes the ``configparser`` package as an alias for ``ConfigParser`` on Py2 to improve compatibility with the backported -`configparser package `. Previously +`configparser package `. Previously ``python-future`` and the PyPI ``configparser`` backport clashed, causing various compatibility issues. (Issues #118, #181) From 39d15cdae44655e0d1fa240f9a49b94c5c494896 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 12 Sep 2018 05:11:40 -0700 Subject: [PATCH 225/425] Fix mistaken assertTrue -> assertEqual --- tests/test_future/test_bytes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_future/test_bytes.py b/tests/test_future/test_bytes.py index 2a623d34..42dd81cf 100644 --- a/tests/test_future/test_bytes.py +++ b/tests/test_future/test_bytes.py @@ -160,7 +160,7 @@ def test_repr(self): def test_str(self): b = bytes(b'ABCD') - self.assertTrue(str(b), "b'ABCD'") + self.assertEqual(str(b), "b'ABCD'") def test_bytes_setitem(self): b = b'ABCD' From 71796592fe14d098481f74a33682020713bed3f3 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 15 Sep 2018 13:58:43 -0700 Subject: [PATCH 226/425] Remove recommendation of using unmaintained nose The nose project has ceased development. The last commit is from Mar 3, 2016. From their docs page: https://nose.readthedocs.io/ > Note to Users > > Nose has been in maintenance mode for the past several years and will > likely cease without a new person/team to take over maintainership. > New projects should consider using Nose2, py.test, or just plain > unittest/unittest2. --- docs/futurize_cheatsheet.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/futurize_cheatsheet.rst b/docs/futurize_cheatsheet.rst index b14fa8c2..fa75ec77 100644 --- a/docs/futurize_cheatsheet.rst +++ b/docs/futurize_cheatsheet.rst @@ -13,7 +13,7 @@ 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``) +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``) 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.:: From 8a7de22aa340a838513d02062a1bee374209f2de Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 16 Sep 2018 18:40:12 -0700 Subject: [PATCH 227/425] Fix typo bythes -> bytes --- docs/3rd-party-py3k-compat-code/pandas_py3k.py | 2 +- src/future/utils/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/src/future/utils/__init__.py b/src/future/utils/__init__.py index e374a81e..38a3026e 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -18,7 +18,7 @@ * 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 * bchr(c): From 37d1d121998d8a1499774b6b1d9bf3f1d5064490 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 12 Sep 2018 05:59:56 -0700 Subject: [PATCH 228/425] Undocument support for EOL Python 2.6 and 3.3 Python 2.6 and 3.3 are end of life. They are no longer receiving bug fixes, including for security issues. Python 2.6 went EOL on 2013-10-29 and on 2017-09-29. For additional details on support Python versions, see: https://devguide.python.org/#status-of-python-branches Removing support for EOL Pythons will reduce testing and maintenance resources. Using pypinfo, here are the PyPI download statistics for the last 30 days, showing low numbers for these EOL Pythons: | python_version | percent | download_count | | -------------- | ------: | -------------: | | 2.7 | 56.49% | 3,841,256 | | 3.6 | 30.41% | 2,067,785 | | 3.5 | 8.38% | 569,774 | | 3.4 | 3.15% | 214,075 | | 3.7 | 1.50% | 102,095 | | 2.6 | 0.04% | 2,960 | | 3.3 | 0.02% | 1,236 | | 3.8 | 0.00% | 84 | | 3.2 | 0.00% | 46 | | None | 0.00% | 33 | https://github.com/ofek/pypinfo This change only removes the documentation of 2.6 and 3.3. Legacy code is expected to be removed in future commits as the changes are quite extensive. --- TESTING.txt | 2 +- docs/compatible_idioms.rst | 36 ++----------------------------- docs/dev_notes.rst | 7 +----- docs/development.rst | 2 +- docs/dict_object.rst | 14 +++++------- docs/faq.rst | 20 ++++++----------- docs/futurize.rst | 4 ++-- docs/futurize_cheatsheet.rst | 2 +- docs/futurize_overview.rst | 2 +- docs/imports.rst | 2 +- docs/open_function.rst | 2 +- docs/quickstart.rst | 14 ++---------- docs/standard_library_imports.rst | 27 ++++++----------------- requirements_py26.txt | 3 --- setup.py | 11 ++-------- 15 files changed, 33 insertions(+), 115 deletions(-) delete mode 100644 requirements_py26.txt diff --git a/TESTING.txt b/TESTING.txt index 13aeca83..68a1c971 100644 --- a/TESTING.txt +++ b/TESTING.txt @@ -1,4 +1,4 @@ -Currently the tests are passing on OS X and Linux on Python 2.6, 2.7, 3.3 and 3.4. +Currently the tests are passing on OS X and Linux on Python 2.7 and 3.4. The test suite can be run either with: diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index 5ed60e2a..0402b915 100644 --- a/docs/compatible_idioms.rst +++ b/docs/compatible_idioms.rst @@ -16,8 +16,8 @@ video is here: http://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 ----- @@ -1188,38 +1188,6 @@ commands / subprocess modules 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 - 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 StringIO module ~~~~~~~~~~~~~~~ diff --git a/docs/dev_notes.rst b/docs/dev_notes.rst index 674d83b3..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,8 +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 06ff7798..a12f2ca5 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -10,7 +10,7 @@ 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 + conda install -n future3 python=3.4 pip git clone https://github.com/PythonCharmers/python-future diff --git a/docs/dict_object.rst b/docs/dict_object.rst index 883beaa8..165cf763 100644 --- a/docs/dict_object.rst +++ b/docs/dict_object.rst @@ -27,7 +27,7 @@ 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:: @@ -43,10 +43,6 @@ dictionaries. For example:: # 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. @@ -90,7 +86,7 @@ The memory-efficient (and CPU-efficient) alternatives are: # 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 6ba443c8..8dc3870f 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -5,10 +5,10 @@ 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. +to Python 3.4+, module by module, without giving up Python 2 compatibility. Why upgrade to Python 3? @@ -217,7 +217,7 @@ 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 @@ -225,7 +225,7 @@ 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. @@ -257,19 +257,13 @@ Platform and version support Which versions of Python does ``python-future`` support? -------------------------------------------------------- -Python 2.6, 2.7, and 3.3+ only. +Python 2.7, and 3.4+ only. -Python 2.6 and 2.7 introduced many important forward-compatibility +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.6 / 2.7. - -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. +close the remaining gap between Python 3 and 2.7. Do you support Pypy? diff --git a/docs/futurize.rst b/docs/futurize.rst index c98848d4..fdbf3026 100644 --- a/docs/futurize.rst +++ b/docs/futurize.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 @@ -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 diff --git a/docs/futurize_cheatsheet.rst b/docs/futurize_cheatsheet.rst index fa75ec77..1fcca365 100644 --- a/docs/futurize_cheatsheet.rst +++ b/docs/futurize_cheatsheet.rst @@ -13,7 +13,7 @@ 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``) +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.:: diff --git a/docs/futurize_overview.rst b/docs/futurize_overview.rst index 732b96b2..2beef1ac 100644 --- a/docs/futurize_overview.rst +++ b/docs/futurize_overview.rst @@ -51,5 +51,5 @@ use the ``-w`` flag. For complex projects, it is probably best to divide the porting into two stages. Stage 1 is for "safe" changes that modernize the code but do not break Python -2.6 compatibility or introduce a depdendency on the ``future`` package. Stage 2 +2.7 compatibility or introduce a depdendency on the ``future`` package. Stage 2 is to complete the process. diff --git a/docs/imports.rst b/docs/imports.rst index cd1acb14..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: diff --git a/docs/open_function.rst b/docs/open_function.rst index 88066050..7915d8a8 100644 --- a/docs/open_function.rst +++ b/docs/open_function.rst @@ -13,7 +13,7 @@ in which case its methods like :func:`read` return Py3 :class:`bytes` objects. 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.6 and Py2.7. +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 diff --git a/docs/quickstart.rst b/docs/quickstart.rst index dbc474da..a5e464f9 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,7 +30,7 @@ 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, 2.7, and 3.3+ mostly +provide support for running your code on Python 2.7, and 3.4+ mostly unchanged. - For explicit import forms, see :ref:`explicit-imports`. @@ -101,7 +91,7 @@ to be accessed under their Python 3 names and locations in Python 2:: import socketserver import queue from collections import UserDict, UserList, UserString - from collections import Counter, OrderedDict, ChainMap # even on Py2.6 + from collections import ChainMap # even on Py2.7 from itertools import filterfalse, zip_longest import html diff --git a/docs/standard_library_imports.rst b/docs/standard_library_imports.rst index 58aef032..60442541 100644 --- a/docs/standard_library_imports.rst +++ b/docs/standard_library_imports.rst @@ -85,7 +85,7 @@ Note that, as of v0.16.0, ``python-future`` no longer includes an alias for the Aliased imports ~~~~~~~~~~~~~~~ -The following 14 modules were refactored or extended from Python 2.6/2.7 to 3.x +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 @@ -129,22 +129,16 @@ HTTPS (as of 2015-09-11) because the SSL support changed considerably in Python Backports also exist of the following features from Python 3.4: - ``math.ceil`` returns an int on Py3 -- ``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 and 2.7) +- ``collections.ChainMap`` (for 2.7) +- ``reprlib.recursive_repr`` (for 2.7) -These can then be imported on Python 2.6+ as follows:: +These can then be imported on Python 2.7+ as follows:: from future.standard_library import install_aliases install_aliases() from math import ceil # now returns an int - from collections import Counter, OrderedDict, ChainMap - from itertools import count - from subprocess import check_output + from collections import ChainMap from reprlib import recursive_repr @@ -158,21 +152,12 @@ 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 full backports ----------------------- diff --git a/requirements_py26.txt b/requirements_py26.txt deleted file mode 100644 index a671277f..00000000 --- a/requirements_py26.txt +++ /dev/null @@ -1,3 +0,0 @@ -unittest2 -argparse # for the http.server module -importlib diff --git a/setup.py b/setup.py index f168e90f..bd8cb2e4 100755 --- a/setup.py +++ b/setup.py @@ -85,11 +85,6 @@ '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,10 +96,9 @@ KEYWORDS = "future past python3 migration futurize backport six 2to3 modernize pasteurize 3to2" CLASSIFIERS = [ "Programming Language :: Python", - "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "License :: OSI Approved", @@ -179,9 +173,8 @@ packages=PACKAGES, package_data=PACKAGE_DATA, include_package_data=True, - install_requires=REQUIRES, + python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", classifiers=CLASSIFIERS, test_suite = "discover_tests", - tests_require=TEST_REQUIRES, **setup_kwds ) From c9bd14afc762d9fdc234fdb47bfdb630c3732649 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 11 Sep 2018 18:47:37 -0700 Subject: [PATCH 229/425] Add Python 3.6/3.7 trove classifiers to document support Python 3.6 support was added in 7a1f48ee1fc357692a8e2fb292b093480fdbd7b1. Python 3.7 testing was added in ae05bd9e9efd229114fd797ebea720dc7b04dff3. Document general Python 2 support as well. --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index bd8cb2e4..62644437 100755 --- a/setup.py +++ b/setup.py @@ -101,6 +101,8 @@ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", "License :: OSI Approved", "License :: OSI Approved :: MIT License", "Development Status :: 4 - Beta", From 1aabdc934ea11e71afa328f3235ffce039dd9db2 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 17 Sep 2018 17:29:41 -0700 Subject: [PATCH 230/425] Add tox.ini to easily test across multiple environments Using tox makes it quick and easy to test all supported environments with a single command, `tox`. It also makes it very simple to recreate the same test environments as used on Travis CI. For details on tox, see: https://tox.readthedocs.io/ --- .travis.yml | 12 +++++++----- tox.ini | 5 +++++ 2 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml index 2bcd0c2c..45c8a1e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,15 +5,20 @@ cache: pip matrix: include: - python: 2.7 + env: TOXENV=py27 - python: 3.4 + env: TOXENV=py34 - python: 3.5 + env: TOXENV=py35 - python: 3.6 + env: TOXENV=py36 - python: 3.7 + env: TOXENV=py37 dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069) sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069) install: - - python setup.py install + - pip install tox before_script: # Run flake8 tests only on Python 2.7 and 3.7... @@ -28,7 +33,4 @@ before_script: # 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 + - tox diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..1f0f1705 --- /dev/null +++ b/tox.ini @@ -0,0 +1,5 @@ +[tox] +envlist = py27,py34,py35,py36,py37 + +[testenv] +commands = python setup.py test From 4e3973569ecfb661ce88a9528fe1f6486c0cc54e Mon Sep 17 00:00:00 2001 From: Miha Purg Date: Mon, 24 Sep 2018 16:26:48 +0200 Subject: [PATCH 231/425] Fix for issue #262 (non-existing tkinter imports in older py27 versions) --- src/future/moves/tkinter/__init__.py | 22 +++++++++++++++++++--- src/tkinter/__init__.py | 22 +++++++++++++++++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/future/moves/tkinter/__init__.py b/src/future/moves/tkinter/__init__.py index bc50b4c6..e4082966 100644 --- a/src/future/moves/tkinter/__init__.py +++ b/src/future/moves/tkinter/__init__.py @@ -4,8 +4,24 @@ if not PY3: from Tkinter import * - from Tkinter import (_cnfmerge, _default_root, _flatten, _join, _setit, - _splitdict, _stringify, _support_default_root, _test, - _tkinter) + 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/tkinter/__init__.py b/src/tkinter/__init__.py index d0e1b0af..bb730c35 100644 --- a/src/tkinter/__init__.py +++ b/src/tkinter/__init__.py @@ -3,9 +3,25 @@ if sys.version_info[0] < 3: from Tkinter import * - from Tkinter import (_cnfmerge, _default_root, _flatten, _join, _setit, - _splitdict, _stringify, _support_default_root, _test, - _tkinter) + 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('This package should not be accessible on Python 3. ' 'Either you are trying to run from the python-future src folder ' From bc0d6b9b1c6b8b6a02ed868fb56fd4158614320e Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Wed, 10 Oct 2018 14:17:17 -0700 Subject: [PATCH 232/425] Remove an unnecessary 'main' import We don't use lib2to3.main.main, and we define our own main() which was shadowing this import. --- src/libfuturize/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libfuturize/main.py b/src/libfuturize/main.py index 13e24314..634c2f25 100644 --- a/src/libfuturize/main.py +++ b/src/libfuturize/main.py @@ -70,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, From c35e6e55eda1a504a1d13f1f7344aa56cf6a618a Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Tue, 16 Jan 2018 09:21:26 -0800 Subject: [PATCH 233/425] app engine compatibility: handle missing httplib._CS_* constants fixes #320 --- src/http/client.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/http/client.py b/src/http/client.py index 7566fe4d..a6a31006 100644 --- a/src/http/client.py +++ b/src/http/client.py @@ -11,10 +11,6 @@ from httplib import (HTTP_PORT, HTTPS_PORT, - _CS_IDLE, - _CS_REQ_STARTED, - _CS_REQ_SENT, - CONTINUE, SWITCHING_PROTOCOLS, PROCESSING, @@ -81,6 +77,9 @@ # These may not be available on all versions of Python 2.6.x or 2.7.x try: from httplib import ( + _CS_IDLE, + _CS_REQ_STARTED, + _CS_REQ_SENT, _MAXLINE, _MAXHEADERS, _is_legal_header_name, From 62b4c35d995aa5503855ffcc5c8fb2262e02c2a2 Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Wed, 21 Mar 2018 07:19:09 -0700 Subject: [PATCH 234/425] allow mixed string type inputs to misc urllib.parse.* functions ...eg urlparse, urlunparse, urlsplit, urlunsplit, urljoin, urldefrag, and parse_qsl. fixes #273. the original bug result in this kind of exception: ``` Traceback (most recent call last): ... File ".../future/backports/urllib/parse.py", line 387, in urlunparse _coerce_args(*components)) File ".../future/backports/urllib/parse.py", line 115, in _coerce_args raise TypeError("Cannot mix str and non-str arguments") TypeError: Cannot mix str and non-str arguments ``` --- src/future/backports/urllib/parse.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/future/backports/urllib/parse.py b/src/future/backports/urllib/parse.py index 04e52d49..2c26846d 100644 --- a/src/future/backports/urllib/parse.py +++ b/src/future/backports/urllib/parse.py @@ -107,11 +107,11 @@ def _coerce_args(*args): # an appropriate result coercion function # - noop for str inputs # - encoding function otherwise - str_input = isinstance(args[0], str) + str_input = isinstance(args[0], basestring) for arg in args[1:]: # We special-case the empty string to support the # "scheme=''" default argument to some functions - if arg and isinstance(arg, str) != str_input: + if arg and isinstance(arg, basestring) != str_input: raise TypeError("Cannot mix str and non-str arguments") if str_input: return args + (_noop,) From 2954adb71eb67e029344f28e4670533f56f2175d Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Sat, 13 Oct 2018 14:50:31 -0700 Subject: [PATCH 235/425] Fix bugs introduced in #368 --- src/libfuturize/fixes/fix_division_safe.py | 2 +- tests/test_future/test_futurize.py | 62 +++++++++++++++------- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/libfuturize/fixes/fix_division_safe.py b/src/libfuturize/fixes/fix_division_safe.py index c057c124..7b0f3cbd 100644 --- a/src/libfuturize/fixes/fix_division_safe.py +++ b/src/libfuturize/fixes/fix_division_safe.py @@ -84,7 +84,7 @@ def start_tree(self, tree, name): Skip this fixer if "__future__.division" is already imported. """ super(FixDivisionSafe, self).start_tree(tree, name) - self.skip = "division" not in tree.future_features + self.skip = "division" in tree.future_features def match(self, node): u""" diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index 4bdb05ba..86913449 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -754,6 +754,8 @@ class TestFuturizeStage1(CodeHandler): the uncontroversial patches first. """ + maxDiff = None + def test_apply(self): """ apply() should be changed by futurize --stage1 @@ -1182,6 +1184,16 @@ def test_safe_division(self): """ before = """ import random + class fraction(object): + numer = 0 + denom = 0 + def __init__(self, numer, denom): + self.numer = numer + self.denom = denom + + def total_count(self): + return self.numer * 50 + x = 3 / 2 y = 3. / 2 foo = range(100) @@ -1189,42 +1201,56 @@ def test_safe_division(self): assert y == 1.5 and isinstance(y, float) a = 1 + foo[len(foo) / 2] b = 1 + foo[len(foo) * 3 / 4] - assert a == 50 - assert b == 75 + assert a == 51 + assert b == 76 r = random.randint(0, 1000) * 1.0 / 1000 output = { "SUCCESS": 5, "TOTAL": 10 } output["SUCCESS"] * 100 / output["TOTAL"] - obj = foo + obj = fraction(1, 50) val = float(obj.numer) / obj.denom * 1e-9 - mount.bytes_free * mount.free_size / bytes_in_gb - obj.total_count() * threshold / 100 - 100 * abs(obj.width - original_width) / float(max(obj.width, original_width)) - 100 * abs(obj.width - original_width) / max(obj.width, original_width) - float(target_width) * float(original_height) / float(original_width) + obj.numer * obj.denom / val + obj.total_count() * val / 100 + original_numer = 1 + original_denom = 50 + 100 * abs(obj.numer - original_numer) / float(max(obj.denom, original_denom)) + 100 * abs(obj.numer - original_numer) / max(obj.denom, original_denom) + float(original_numer) * float(original_denom) / float(obj.numer) """ after = """ from __future__ import division from past.utils import old_div import random + class fraction(object): + numer = 0 + denom = 0 + def __init__(self, numer, denom): + self.numer = numer + self.denom = denom + + def total_count(self): + return self.numer * 50 + x = old_div(3, 2) y = 3. / 2 - foo = range(100) + foo = list(range(100)) assert x == 1 and isinstance(x, int) assert y == 1.5 and isinstance(y, float) a = 1 + foo[old_div(len(foo), 2)] b = 1 + foo[old_div(len(foo) * 3, 4)] - assert a == 50 - assert b == 75 + assert a == 51 + assert b == 76 r = old_div(random.randint(0, 1000) * 1.0, 1000) output = { "SUCCESS": 5, "TOTAL": 10 } - output["SUCCESS"] * 100 / output["TOTAL"] - obj = foo + old_div(output["SUCCESS"] * 100, output["TOTAL"]) + obj = fraction(1, 50) val = float(obj.numer) / obj.denom * 1e-9 - old_div(mount.bytes_free * mount.free_size, bytes_in_gb) - old_div(obj.total_count() * threshold, 100) - 100 * abs(obj.width - original_width) / float(max(obj.width, original_width)) - 100 * abs(obj.width - original_width), max(obj.width, original_width)) - float(target_width) * float(original_height) / float(original_width) + old_div(obj.numer * obj.denom, val) + old_div(obj.total_count() * val, 100) + original_numer = 1 + original_denom = 50 + 100 * abs(obj.numer - original_numer) / float(max(obj.denom, original_denom)) + old_div(100 * abs(obj.numer - original_numer), max(obj.denom, original_denom)) + float(original_numer) * float(original_denom) / float(obj.numer) """ self.convert_check(before, after) From 2231ae1c14fdbfdf3a0ed7b66cdef0e8d998795d Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Sat, 13 Oct 2018 14:52:08 -0700 Subject: [PATCH 236/425] Remove maxDiff --- tests/test_future/test_futurize.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index 86913449..9ba53ee6 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -754,8 +754,6 @@ class TestFuturizeStage1(CodeHandler): the uncontroversial patches first. """ - maxDiff = None - def test_apply(self): """ apply() should be changed by futurize --stage1 From 2e50b219695846f29d9223067afe56a644655f20 Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Fri, 12 Oct 2018 14:07:41 -0700 Subject: [PATCH 237/425] newrange: handle non-numbers passed to index() These kinds of operations failed with a TypeError when a non-numeric value is given: >>> None in newrange(10) >>> newrange(10).index(None) We now catch the initial TypeError in newrange.index() and return the ValueError result immediately. I also considered adding an int(value) conversion, but that isn't consistent with the standard library's range() behavior. For example: >> "1" in range(10) False --- src/future/types/newrange.py | 5 ++++- tests/test_future/test_range.py | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/future/types/newrange.py b/src/future/types/newrange.py index 89cc8bdf..9173b050 100644 --- a/src/future/types/newrange.py +++ b/src/future/types/newrange.py @@ -90,7 +90,10 @@ 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) diff --git a/tests/test_future/test_range.py b/tests/test_future/test_range.py index c14f168f..2e9471ae 100644 --- a/tests/test_future/test_range.py +++ b/tests/test_future/test_range.py @@ -26,6 +26,12 @@ def test_equality_range(self): self.assertEqual(range(0), range(1, 1)) self.assertEqual(range(0, 10, 3), range(0, 11, 3)) + def test_contains(self): + self.assertIn(1, range(2)) + self.assertNotIn(10, range(2)) + self.assertNotIn(None, range(2)) + self.assertNotIn("", range(2)) + # Use strict equality of attributes when slicing to catch subtle differences def assertRangesEqual(self, r1, r2): by_attrs = attrgetter('start', 'stop', 'step') From 69d486107ffd6691568ae698a1e1fe9e989e0094 Mon Sep 17 00:00:00 2001 From: Grant Bakker Date: Tue, 16 Oct 2018 10:25:49 -0400 Subject: [PATCH 238/425] Remove unused tmp files from past translation --- src/past/translation/__init__.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/past/translation/__init__.py b/src/past/translation/__init__.py index c7ae2b7a..bf8998e6 100644 --- a/src/past/translation/__init__.py +++ b/src/past/translation/__init__.py @@ -219,22 +219,9 @@ def detect_python2(source, pathname): if source != str(tree)[:-1]: # remove added newline # The above fixers made changes, so we conclude it's Python 2 code logger.debug('Detected Python 2 code: {0}'.format(pathname)) - with open('/tmp/original_code.py', 'w') as f: - f.write('### Original code (detected as py2): %s\n%s' % - (pathname, source)) - with open('/tmp/py2_detection_code.py', 'w') as f: - f.write('### Code after running py3 detection (from %s)\n%s' % - (pathname, str(tree)[:-1])) return True else: logger.debug('Detected Python 3 code: {0}'.format(pathname)) - with open('/tmp/original_code.py', 'w') as f: - f.write('### Original code (detected as py3): %s\n%s' % - (pathname, source)) - try: - os.remove('/tmp/futurize_code.py') - except OSError: - pass return False @@ -395,9 +382,6 @@ def load_module(self, fullname): if detect_python2(source, self.pathname): source = self.transform(source) - with open('/tmp/futurized_code.py', 'w') as f: - f.write('### Futurized code (from %s)\n%s' % - (self.pathname, source)) code = compile(source, self.pathname, 'exec') From 87bcb830fb6588b7e8d8e6c139e90e9716f19c5c Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Tue, 16 Oct 2018 12:56:07 -0700 Subject: [PATCH 239/425] Port futurize's fix selection logic to pasteurize Previously, pasteurize ignored the `--fix` option and always used the fill set of available fixes. This change ports over futurize's fix selection logic (including set validation), making `--fix` work as intended. --- src/libpasteurize/main.py | 62 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/src/libpasteurize/main.py b/src/libpasteurize/main.py index 8eca78da..4179174b 100644 --- a/src/libpasteurize/main.py +++ b/src/libpasteurize/main.py @@ -114,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: @@ -124,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) From ec7b0f2a86848891b340ade9a6eedac047ad0996 Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Tue, 16 Oct 2018 14:49:56 -0700 Subject: [PATCH 240/425] Remove unused newobject imports These are remnants of the earlier metaclass-based implementation. --- src/future/types/newobject.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/future/types/newobject.py b/src/future/types/newobject.py index 35ec05ec..776d4766 100644 --- a/src/future/types/newobject.py +++ b/src/future/types/newobject.py @@ -37,24 +37,6 @@ def __iter__(self): """ -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): """ From 0676554e68002ce864402d6447b40d9122f4bd61 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 19 Oct 2018 14:05:40 -0700 Subject: [PATCH 241/425] Switch to pytest --- .travis.yml | 4 +--- TESTING.txt | 8 +++---- discover_tests.py | 57 ----------------------------------------------- setup.py | 2 -- tox.ini | 3 ++- 5 files changed, 6 insertions(+), 68 deletions(-) delete mode 100644 discover_tests.py diff --git a/.travis.yml b/.travis.yml index 45c8a1e3..d026a668 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ matrix: sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069) install: - - pip install tox + - pip install tox pytest before_script: # Run flake8 tests only on Python 2.7 and 3.7... @@ -30,7 +30,5 @@ before_script: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics; fi -# command to run tests, e.g. python setup.py test - script: - tox diff --git a/TESTING.txt b/TESTING.txt index 68a1c971..1c29b6a6 100644 --- a/TESTING.txt +++ b/TESTING.txt @@ -1,9 +1,7 @@ Currently the tests are passing on OS X and Linux on Python 2.7 and 3.4. -The test suite can be run either with: +The test suite can be run with: - $ python setup.py test + $ tox -which uses the unittest module's test discovery mechanism, or with: - - $ py.test +which tests the module under a number of different python versions, where available. diff --git a/discover_tests.py b/discover_tests.py deleted file mode 100644 index 1d289418..00000000 --- a/discover_tests.py +++ /dev/null @@ -1,57 +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/setup.py b/setup.py index 62644437..05ee3243 100755 --- a/setup.py +++ b/setup.py @@ -78,7 +78,6 @@ 'LICENSE.txt', 'futurize.py', 'pasteurize.py', - 'discover_tests.py', 'check_rst.sh', 'TESTING.txt', ], @@ -177,6 +176,5 @@ include_package_data=True, python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", classifiers=CLASSIFIERS, - test_suite = "discover_tests", **setup_kwds ) diff --git a/tox.ini b/tox.ini index 1f0f1705..96f949a3 100644 --- a/tox.ini +++ b/tox.ini @@ -2,4 +2,5 @@ envlist = py27,py34,py35,py36,py37 [testenv] -commands = python setup.py test +deps = pytest +commands = pytest {posargs} From 9803efc7f5f5e2090d19d5b3346419bb9e815bd3 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 19 Oct 2018 14:10:51 -0700 Subject: [PATCH 242/425] Remove unnecessary pytest dep for travis as tox will take care of it --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d026a668..684ac64a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ matrix: sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069) install: - - pip install tox pytest + - pip install tox before_script: # Run flake8 tests only on Python 2.7 and 3.7... From 579a197e8a7736a5ed1148d9cb11c46576c60f6e Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 19 Oct 2018 15:09:47 -0700 Subject: [PATCH 243/425] Fix tests on Py3 --- src/future/utils/__init__.py | 2 +- tests/test_future/test_bytes.py | 10 +++++----- tests/test_future/test_urllibnet.py | 4 +++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index 38a3026e..a6bbab06 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -56,7 +56,7 @@ PY3 = sys.version_info[0] == 3 -PY35 = sys.version_info[0:2] >= (3, 5) +PY35_PLUS = sys.version_info[0:2] >= (3, 5) PY2 = sys.version_info[0] == 2 PY26 = sys.version_info[0:2] == (2, 6) PY27 = sys.version_info[0:2] == (2, 7) diff --git a/tests/test_future/test_bytes.py b/tests/test_future/test_bytes.py index 42dd81cf..bb90a71c 100644 --- a/tests/test_future/test_bytes.py +++ b/tests/test_future/test_bytes.py @@ -566,7 +566,7 @@ class MyDict(UserDict.UserDict): self.assertEqual(bytes(b'%(foo)s') % d, b'bar') - @unittest.skipUnless(utils.PY35 or utils.PY2, + @unittest.skipUnless(utils.PY35_PLUS or utils.PY2, 'test requires Python 2 or 3.5+') def test_mod_more(self): self.assertEqual(b'%s' % b'aaa', b'aaa') @@ -577,10 +577,10 @@ def test_mod_more(self): self.assertEqual(bytes(b'%s') % (b'aaa',), b'aaa') self.assertEqual(bytes(b'%s') % (bytes(b'aaa'),), b'aaa') - self.assertEqual(bytes(b'%(x)s') % {'x': b'aaa'}, b'aaa') - self.assertEqual(bytes(b'%(x)s') % {'x': bytes(b'aaa')}, b'aaa') + self.assertEqual(bytes(b'%(x)s') % {b'x': b'aaa'}, b'aaa') + self.assertEqual(bytes(b'%(x)s') % {b'x': bytes(b'aaa')}, b'aaa') - @unittest.skipUnless(utils.PY35 or utils.PY2, + @unittest.skipUnless(utils.PY35_PLUS or utils.PY2, 'test requires Python 2 or 3.5+') def test_mod(self): """ @@ -606,7 +606,7 @@ def test_mod(self): a = b % (bytes(b'seventy-nine'), 79) self.assertEqual(a, b'seventy-nine / 100 = 79%') - @unittest.skipUnless(utils.PY35 or utils.PY2, + @unittest.skipUnless(utils.PY35_PLUS or utils.PY2, 'test requires Python 2 or 3.5+') def test_imod(self): """ diff --git a/tests/test_future/test_urllibnet.py b/tests/test_future/test_urllibnet.py index f5f59875..bc087258 100644 --- a/tests/test_future/test_urllibnet.py +++ b/tests/test_future/test_urllibnet.py @@ -109,7 +109,9 @@ def test_getcode(self): # On Windows, socket handles are not file descriptors; this # test can't pass on Windows. - @unittest.skipIf(sys.platform in ('win32',), 'not appropriate for Windows') + # + # On macOS, this behavior is undocumented and this test fails. + @unittest.skipIf(sys.platform in ('darwin', 'win32',), 'not appropriate for macOS or Windows') @skip26 def test_fileno(self): # Make sure fd returned by fileno is valid. From b9212afa331174bc1389abd747323fd799232f61 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 19 Oct 2018 15:21:15 -0700 Subject: [PATCH 244/425] Skip the test not if it is OSX, but if it is Py3.6+, which appears to be the origination of the behavior breakage --- src/future/utils/__init__.py | 1 + tests/test_future/test_urllibnet.py | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index a6bbab06..906f1e46 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -57,6 +57,7 @@ PY3 = sys.version_info[0] == 3 PY35_PLUS = sys.version_info[0:2] >= (3, 5) +PY36_PLUS = sys.version_info[0:2] >= (3, 6) PY2 = sys.version_info[0] == 2 PY26 = sys.version_info[0:2] == (2, 6) PY27 = sys.version_info[0:2] == (2, 7) diff --git a/tests/test_future/test_urllibnet.py b/tests/test_future/test_urllibnet.py index bc087258..f9639bfc 100644 --- a/tests/test_future/test_urllibnet.py +++ b/tests/test_future/test_urllibnet.py @@ -109,9 +109,8 @@ def test_getcode(self): # On Windows, socket handles are not file descriptors; this # test can't pass on Windows. - # - # On macOS, this behavior is undocumented and this test fails. - @unittest.skipIf(sys.platform in ('darwin', 'win32',), 'not appropriate for macOS or Windows') + @unittest.skipIf(sys.platform in ('darwin', 'win32',), 'not appropriate for Windows') + @unittest.skipIf(utils.PY36_PLUS, 'test not applicable on Python 3.5 and higher') @skip26 def test_fileno(self): # Make sure fd returned by fileno is valid. From 0d12a9ffac4fc929c940e3a3d10e980b1314ce8e Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 19 Oct 2018 15:42:48 -0700 Subject: [PATCH 245/425] drop -dev suffix --- src/future/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/__init__.py b/src/future/__init__.py index e1b7fe08..aee268a9 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -88,6 +88,6 @@ __ver_major__ = 0 __ver_minor__ = 17 __ver_patch__ = 0 -__ver_sub__ = '-dev' +__ver_sub__ = '' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From 99948c387702496274147a71f8bf0e600569f6b5 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 19 Oct 2018 16:29:23 -0700 Subject: [PATCH 246/425] Update docs for v0.17.x release --- docs/credits.rst | 40 +++++++++++++++++++++++++++++----------- docs/whatsnew.rst | 27 +++++++++++++++++++++------ 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/docs/credits.rst b/docs/credits.rst index c713d36d..5288a91c 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -30,33 +30,40 @@ The software is distributed under an MIT licence. The text is as follows .. _sponsor: -Sponsor -------- +Sponsors +-------- Python Charmers Pty Ltd, Australia, and Python Charmers Pte Ltd, Singapore. http://pythoncharmers.com +Pinterest https://opensource.pinterest.com/ .. _authors: Authors ------- -Python-Future is written and maintained by Ed Schofield with the help of -various contributors: +Python-Future is written and maintained by Ed Schofield and Jordan Adler +with the help of various contributors: -Development Lead -~~~~~~~~~~~~~~~~ +Development Leads +~~~~~~~~~~~~~~~~~ - Ed Schofield +- Jordan Adler Patches ~~~~~~~ +- Grant Bakker - Jacob Beck +- Nate Bogdanowicz - Denis Cornehl - Nicolas Delaby +- Jon Dufresne - Corey Farwell - Eric Firing +- Maximilian Hils +- Martijn Jacobs - Michael Joseph - Waldemar Kornewald - Alexey Kotlyarov @@ -65,26 +72,37 @@ Patches - Joshua Landau - German Larrain - Chris Lasher +- Calum Lind +- Jon Parise +- Matthew Parnell +- Miga Purg +- Éloi Rivard - Elliott Sales de Andrade +- Yury Selivanov - Tim Shaffer +- Louis Sautier - Daniel Szoska +- Flaviu Tamas - Jeff Tratner - Tim Tröndle - Brad Walker -- Mystic-Mirage (GitHub) -- str4d (GitHub) -- 9seconds (GitHub) -- Varriount (GitHub) +- cclaus (GiHub user) +- lsm (GiHub user) +- Mystic-Mirage (GitHub user) +- str4d (GitHub user) +- 9seconds (GitHub user) +- Varriount (GitHub user) Suggestions and Feedback ~~~~~~~~~~~~~~~~~~~~~~~~ - Chris Adams - Martijn Faassen +- Joe Gordon - Lion Krischer - Danielle Madeley - Val Markovic -- wluebbe (GitHub) +- wluebbe (GitHub user) Other Credits diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 46b49bbf..b1ee5826 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -3,14 +3,29 @@ What's New ********** -.. _whats-new-0.16.x: - -What's new in version 0.16.1 (unreleased) +What's new in version 0.17 (2018-10-19) ========================================= -This is a minor bug-fix release: - -- Fix `from collections import ChainMap` after install_aliases() (issue #226) +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 betwen 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. + +In this release, we also drop official support for Py2.6 and Py3.3. They should +still work, but support is no longer guaranteed, and breaking changes will be +introduced in subsequent versions. What's new in version 0.16.0 (2016-10-27) From 76255c41868f2e7726c637851bb75499fa279883 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 19 Oct 2018 16:41:20 -0700 Subject: [PATCH 247/425] update --- README.rst | 8 +++++--- docs/credits.rst | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index e6801d90..d5038efc 100644 --- a/README.rst +++ b/README.rst @@ -264,12 +264,14 @@ For more info, see :ref:`translation`. Licensing --------- -:Author: Ed Schofield +:Author: Ed Schofield, Jordan M. Adler, et al :Copyright: 2013-2016 Python Charmers Pty Ltd, Australia. -:Sponsor: Python Charmers Pty Ltd, Australia, and Python Charmers Pte - Ltd, Singapore. http://pythoncharmers.com +:Sponsors: Python Charmers Pty Ltd, Australia, and Python Charmers Pte + Ltd, Singapore. http://pythoncharmers.com + + Pinterest https://opensource.pinterest.com/ :Licence: MIT. See ``LICENSE.txt`` or `here `_. diff --git a/docs/credits.rst b/docs/credits.rst index 5288a91c..28b220ff 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -42,14 +42,14 @@ Pinterest https://opensource.pinterest.com/ Authors ------- -Python-Future is written and maintained by Ed Schofield and Jordan Adler +Python-Future is written and maintained by Ed Schofield and Jordan M. Adler with the help of various contributors: Development Leads ~~~~~~~~~~~~~~~~~ - Ed Schofield -- Jordan Adler +- Jordan M. Adler Patches ~~~~~~~ From bce91cef408fca83f7078cdbdf0eb9810b7657c3 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Tue, 23 Oct 2018 16:20:32 -0700 Subject: [PATCH 248/425] Update copyright notices to 2018; Re-add Py2.6 and Py3.3 support; Update credits --- .travis.yml | 4 ++++ LICENSE.txt | 2 +- README.rst | 2 +- docs/compatible_idioms.rst | 2 +- docs/conf.py | 2 +- docs/credits.rst | 19 ++++++------------- .../Writing Python 2-3 compatible code.ipynb | 2 +- docs/whatsnew.rst | 5 ----- futurize.py | 2 +- pasteurize.py | 2 +- setup.py | 4 +++- src/future/__init__.py | 4 ++-- src/past/__init__.py | 2 +- tox.ini | 2 +- 14 files changed, 24 insertions(+), 30 deletions(-) diff --git a/.travis.yml b/.travis.yml index 684ac64a..13dd540d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,12 @@ cache: pip matrix: include: + - python: 2.6 + env: TOXENV=py26 - python: 2.7 env: TOXENV=py27 + - python: 3.3 + env: TOXENV=py33 - python: 3.4 env: TOXENV=py34 - python: 3.5 diff --git a/LICENSE.txt b/LICENSE.txt index c4dfd4b0..d41c85d1 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2013-2016 Python Charmers Pty Ltd, Australia +Copyright (c) 2013-2018 Python Charmers Pty Ltd, Australia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.rst b/README.rst index d5038efc..807ea2a0 100644 --- a/README.rst +++ b/README.rst @@ -266,7 +266,7 @@ Licensing :Author: Ed Schofield, Jordan M. Adler, et al -:Copyright: 2013-2016 Python Charmers Pty Ltd, Australia. +:Copyright: 2013-2018 Python Charmers Pty Ltd, Australia. :Sponsors: Python Charmers Pty Ltd, Australia, and Python Charmers Pte Ltd, Singapore. http://pythoncharmers.com diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index 9fa0a19e..52a6a8b0 100644 --- a/docs/compatible_idioms.rst +++ b/docs/compatible_idioms.rst @@ -3,7 +3,7 @@ Cheat Sheet: Writing Python 2-3 compatible code =============================================== -- **Copyright (c):** 2013-2016 Python Charmers Pty Ltd, Australia. +- **Copyright (c):** 2013-2018 Python Charmers Pty Ltd, Australia. - **Author:** Ed Schofield. - **Licence:** Creative Commons Attribution. diff --git a/docs/conf.py b/docs/conf.py index cce52ff6..72911405 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,7 +51,7 @@ # General information about the project. project = u'Python-Future' -copyright = u'2013-2016, Python Charmers Pty Ltd, Australia' +copyright = u'2013-2018, Python Charmers Pty Ltd, Australia' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/credits.rst b/docs/credits.rst index 28b220ff..3cf1c9c8 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -8,7 +8,7 @@ Licence The software is distributed under an MIT licence. The text is as follows (from ``LICENSE.txt``):: - Copyright (c) 2013-2016 Python Charmers Pty Ltd, Australia + Copyright (c) 2013-2018 Python Charmers Pty Ltd, Australia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -39,20 +39,13 @@ Pinterest https://opensource.pinterest.com/ .. _authors: +Maintainer +---------- +Python-Future is currently maintained by Jordan M. Adler . + Authors ------- - -Python-Future is written and maintained by Ed Schofield and Jordan M. Adler -with the help of various contributors: - -Development Leads -~~~~~~~~~~~~~~~~~ - -- Ed Schofield -- Jordan M. Adler - -Patches -~~~~~~~ +Python-Future is largely written by Ed Schofield with the help of various contributors: - Grant Bakker - Jacob Beck diff --git a/docs/notebooks/Writing Python 2-3 compatible code.ipynb b/docs/notebooks/Writing Python 2-3 compatible code.ipynb index 8ac0bd80..a3e5e156 100644 --- a/docs/notebooks/Writing Python 2-3 compatible code.ipynb +++ b/docs/notebooks/Writing Python 2-3 compatible code.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "- **Copyright (c):** 2013-2016 Python Charmers Pty Ltd, Australia.\n", + "- **Copyright (c):** 2013-2018 Python Charmers Pty Ltd, Australia.\n", "- **Author:** Ed Schofield.\n", "- **Licence:** Creative Commons Attribution.\n", "\n", diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index b1ee5826..1cde2a44 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -23,11 +23,6 @@ This is a major bug-fix release, including: As well as a number of corrections to a variety of documentation, and updates to test infrastructure. -In this release, we also drop official support for Py2.6 and Py3.3. They should -still work, but support is no longer guaranteed, and breaking changes will be -introduced in subsequent versions. - - What's new in version 0.16.0 (2016-10-27) ========================================== diff --git a/futurize.py b/futurize.py index 5f1a7732..41080cf0 100755 --- a/futurize.py +++ b/futurize.py @@ -13,7 +13,7 @@ Licensing --------- -Copyright 2013-2016 Python Charmers Pty Ltd, Australia. +Copyright 2013-2018 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ diff --git a/pasteurize.py b/pasteurize.py index db7931ab..c0bd7e09 100755 --- a/pasteurize.py +++ b/pasteurize.py @@ -12,7 +12,7 @@ Licensing --------- -Copyright 2013-2016 Python Charmers Pty Ltd, Australia. +Copyright 2013-2018 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ diff --git a/setup.py b/setup.py index 05ee3243..2f7b36a3 100755 --- a/setup.py +++ b/setup.py @@ -96,8 +96,10 @@ 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", @@ -174,7 +176,7 @@ packages=PACKAGES, package_data=PACKAGE_DATA, include_package_data=True, - python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", + python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*", classifiers=CLASSIFIERS, **setup_kwds ) diff --git a/src/future/__init__.py b/src/future/__init__.py index aee268a9..211fb43b 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -76,7 +76,7 @@ Licensing --------- -Copyright 2013-2016 Python Charmers Pty Ltd, Australia. +Copyright 2013-2018 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ @@ -84,7 +84,7 @@ __title__ = 'future' __author__ = 'Ed Schofield' __license__ = 'MIT' -__copyright__ = 'Copyright 2013-2016 Python Charmers Pty Ltd' +__copyright__ = 'Copyright 2013-2018 Python Charmers Pty Ltd' __ver_major__ = 0 __ver_minor__ = 17 __ver_patch__ = 0 diff --git a/src/past/__init__.py b/src/past/__init__.py index 06293a89..3b5d9db1 100644 --- a/src/past/__init__.py +++ b/src/past/__init__.py @@ -80,7 +80,7 @@ Licensing --------- -Copyright 2013-2016 Python Charmers Pty Ltd, Australia. +Copyright 2013-2018 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ diff --git a/tox.ini b/tox.ini index 96f949a3..2f0b3499 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,py34,py35,py36,py37 +envlist = py26,py27,py33,py34,py35,py36,py37 [testenv] deps = pytest From 3739f92feed3d05663eda367530d1e71c41a6b5d Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Tue, 23 Oct 2018 16:48:15 -0700 Subject: [PATCH 249/425] Peg versions of test tools --- .travis.yml | 5 ++++- tox.ini | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 13dd540d..eb28e459 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,10 @@ matrix: sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069) install: - - pip install tox + - pip install tox==2.9.1 + - pip install virtualenv==15.2.0 + - pip install py==1.4.30 + - pip install pluggy==0.5.2 before_script: # Run flake8 tests only on Python 2.7 and 3.7... diff --git a/tox.ini b/tox.ini index 2f0b3499..b18dfdd1 100644 --- a/tox.ini +++ b/tox.ini @@ -2,5 +2,7 @@ envlist = py26,py27,py33,py34,py35,py36,py37 [testenv] -deps = pytest +deps = + pytest + unittest2 commands = pytest {posargs} From d1c72419ed17d84b8126df300051f4ef642e7ef7 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Tue, 23 Oct 2018 16:55:41 -0700 Subject: [PATCH 250/425] Add importlib as dep for py26 --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index b18dfdd1..94e95948 100644 --- a/tox.ini +++ b/tox.ini @@ -5,4 +5,5 @@ envlist = py26,py27,py33,py34,py35,py36,py37 deps = pytest unittest2 + importlib commands = pytest {posargs} From 0bb7b077b347c3be3b9adf58dd5ea76a416bc1b2 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Tue, 23 Oct 2018 16:58:34 -0700 Subject: [PATCH 251/425] iterate --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 94e95948..f5c013f8 100644 --- a/tox.ini +++ b/tox.ini @@ -5,5 +5,5 @@ envlist = py26,py27,py33,py34,py35,py36,py37 deps = pytest unittest2 - importlib + py26: importlib commands = pytest {posargs} From dadf17bd43e9b723108c61d02490022b59aecbe6 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Thu, 25 Oct 2018 15:21:01 -0700 Subject: [PATCH 252/425] Fix py2.6 compat issues --- src/future/backports/misc.py | 4 ++-- tests/test_future/test_futurize.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/future/backports/misc.py b/src/future/backports/misc.py index e883d335..ef752078 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -817,7 +817,7 @@ def __delitem__(self, key): try: del self.maps[0][key] except KeyError: - raise KeyError('Key not found in the first mapping: {!r}'.format(key)) + 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.' @@ -831,7 +831,7 @@ def pop(self, key, *args): try: return self.maps[0].pop(key, *args) except KeyError: - raise KeyError('Key not found in the first mapping: {!r}'.format(key)) + raise KeyError('Key not found in the first mapping: {0!r}'.format(key)) def clear(self): 'Clear maps[0], leaving maps[1:] intact.' diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index 9ba53ee6..5549a010 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -1194,7 +1194,7 @@ def total_count(self): x = 3 / 2 y = 3. / 2 - foo = range(100) + foo = list(range(100)) assert x == 1 and isinstance(x, int) assert y == 1.5 and isinstance(y, float) a = 1 + foo[len(foo) / 2] From 2523ecf32bd795f858a0fc4245974040640693f6 Mon Sep 17 00:00:00 2001 From: Alexander Duryagin Date: Fri, 26 Oct 2018 22:14:53 +0300 Subject: [PATCH 253/425] "future" wheel is not universal --- setup.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 2e9053c0..498ec14a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,2 @@ -[bdist_wheel] -universal = 1 - [metadata] license_file = LICENSE.txt From e2e679269a64cda4f814c58a7a10053dd046fea0 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 26 Oct 2018 18:17:01 -0700 Subject: [PATCH 254/425] Bail if building with Py3 --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index 2f7b36a3..3cad4492 100755 --- a/setup.py +++ b/setup.py @@ -12,6 +12,9 @@ except ImportError: from distutils.core import setup +if sys.argv[-1] == 'sdist' and sys.version_info[0] >= 3: + print('ERROR: You must build python-future with Python 2', file=sys.stderr) + sys.exit(1) if sys.argv[-1] == 'publish': os.system('python setup.py sdist upload') From 5337ae1a4c048e332292c8fdedd61fbd561fac9a Mon Sep 17 00:00:00 2001 From: Vincent Philippon Date: Mon, 29 Oct 2018 10:37:39 -0400 Subject: [PATCH 255/425] Revert "Bail if building with Py3" --- setup.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.py b/setup.py index 3cad4492..2f7b36a3 100755 --- a/setup.py +++ b/setup.py @@ -12,9 +12,6 @@ except ImportError: from distutils.core import setup -if sys.argv[-1] == 'sdist' and sys.version_info[0] >= 3: - print('ERROR: You must build python-future with Python 2', file=sys.stderr) - sys.exit(1) if sys.argv[-1] == 'publish': os.system('python setup.py sdist upload') From 988bf89bf652d3df39de63e97992a5ee64b4a0ca Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Tue, 30 Oct 2018 17:15:21 -0700 Subject: [PATCH 256/425] Bump for release --- docs/whatsnew.rst | 7 ++++++- setup.py | 4 ---- src/future/__init__.py | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 1cde2a44..68790527 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -3,7 +3,12 @@ What's New ********** -What's new in version 0.17 (2018-10-19) +What's new in version 0.17.1 (2019-10-30) +========================================= +This release address a packaging error because of an erroneous declaration that +any built wheels are universal. + +What's new in version 0.17.0 (2018-10-19) ========================================= This is a major bug-fix release, including: diff --git a/setup.py b/setup.py index 3cad4492..d3db5371 100755 --- a/setup.py +++ b/setup.py @@ -12,10 +12,6 @@ except ImportError: from distutils.core import setup -if sys.argv[-1] == 'sdist' and sys.version_info[0] >= 3: - print('ERROR: You must build python-future with Python 2', file=sys.stderr) - sys.exit(1) - if sys.argv[-1] == 'publish': os.system('python setup.py sdist upload') sys.exit() diff --git a/src/future/__init__.py b/src/future/__init__.py index 211fb43b..f7a6fbeb 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -87,7 +87,7 @@ __copyright__ = 'Copyright 2013-2018 Python Charmers Pty Ltd' __ver_major__ = 0 __ver_minor__ = 17 -__ver_patch__ = 0 +__ver_patch__ = 1 __ver_sub__ = '' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From 7ff6a5ce803ef34a8b56d0c08ee3b5e687eac647 Mon Sep 17 00:00:00 2001 From: cclauss Date: Wed, 31 Oct 2018 12:34:16 +0100 Subject: [PATCH 257/425] credits.rst: Remove my misspelled GitHub ID and add my name --- docs/credits.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/credits.rst b/docs/credits.rst index 3cf1c9c8..23e00f3b 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -50,6 +50,7 @@ Python-Future is largely written by Ed Schofield with th - Grant Bakker - Jacob Beck - Nate Bogdanowicz +- Christian Clauss - Denis Cornehl - Nicolas Delaby - Jon Dufresne @@ -79,7 +80,6 @@ Python-Future is largely written by Ed Schofield with th - Jeff Tratner - Tim Tröndle - Brad Walker -- cclaus (GiHub user) - lsm (GiHub user) - Mystic-Mirage (GitHub user) - str4d (GitHub user) From 1233756c8fa2f5a76c410badb54a5162a6b825a6 Mon Sep 17 00:00:00 2001 From: Timothy Hopper Date: Thu, 1 Nov 2018 08:57:19 -0400 Subject: [PATCH 258/425] typo --- docs/futurize_overview.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/futurize_overview.rst b/docs/futurize_overview.rst index 2beef1ac..769b65c7 100644 --- a/docs/futurize_overview.rst +++ b/docs/futurize_overview.rst @@ -51,5 +51,5 @@ use the ``-w`` flag. For complex projects, it is probably best to divide the porting into two stages. Stage 1 is for "safe" changes that modernize the code but do not break Python -2.7 compatibility or introduce a depdendency on the ``future`` package. Stage 2 +2.7 compatibility or introduce a dependency on the ``future`` package. Stage 2 is to complete the process. From e9415516d51e922a6444426e3ef1198f228ba4d8 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 2 Nov 2018 18:46:02 -0700 Subject: [PATCH 259/425] Remove empty and unused past.tests module --- setup.py | 1 - src/past/builtins/noniterators.py | 2 +- src/past/tests/__init__.py | 0 3 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 src/past/tests/__init__.py diff --git a/setup.py b/setup.py index 2f7b36a3..11d694c2 100755 --- a/setup.py +++ b/setup.py @@ -46,7 +46,6 @@ "past.builtins", "past.types", "past.utils", - # "past.tests", "past.translation", "libfuturize", "libfuturize.fixes", diff --git a/src/past/builtins/noniterators.py b/src/past/builtins/noniterators.py index 5826b97c..183ffffd 100644 --- a/src/past/builtins/noniterators.py +++ b/src/past/builtins/noniterators.py @@ -72,7 +72,7 @@ def oldmap(func, *iterables): >>> oldmap(None, range(4)) [0, 1, 2, 3] - More test cases are in past.tests.test_builtins. + More test cases are in test_past.test_builtins. """ zipped = itertools.zip_longest(*iterables) l = list(zipped) diff --git a/src/past/tests/__init__.py b/src/past/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 From cfc494e86d7f942fcaf71452be8f2d491591a487 Mon Sep 17 00:00:00 2001 From: Joe Gordon Date: Wed, 7 Nov 2018 16:34:50 -0800 Subject: [PATCH 260/425] Update doc/futurize to reflect what gets run Compare rc/libfuturize/fixes/__init__.py and doc/futurize.rst to make sure they align. --- docs/futurize.rst | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/docs/futurize.rst b/docs/futurize.rst index fdbf3026..e7e593c0 100644 --- a/docs/futurize.rst +++ b/docs/futurize.rst @@ -81,6 +81,7 @@ The complete set of fixers applied by ``futurize --stage1`` is: lib2to3.fixes.fix_apply lib2to3.fixes.fix_except + lib2to3.fixes.fix_exec lib2to3.fixes.fix_exitfunc lib2to3.fixes.fix_funcattrs lib2to3.fixes.fix_has_key @@ -224,9 +225,7 @@ becomes:: The complete list of fixers applied in Stage 2 is:: - lib2to3.fixes.fix_basestring lib2to3.fixes.fix_dict - lib2to3.fixes.fix_exec lib2to3.fixes.fix_getcwdu lib2to3.fixes.fix_input lib2to3.fixes.fix_itertools @@ -239,8 +238,9 @@ The complete list of fixers applied in Stage 2 is:: 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. From 02fdb329d782a1bbf6c08700d63e95a5de01fd86 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Mon, 19 Nov 2018 17:44:27 -0500 Subject: [PATCH 261/425] Fix several warnings --- src/future/backports/misc.py | 8 ++++++-- src/future/backports/urllib/request.py | 10 ++++++++-- src/past/builtins/misc.py | 10 +++++++--- src/past/types/oldstr.py | 6 +++++- tests/test_future/test_backports.py | 8 ++++++-- tests/test_future/test_bytes.py | 7 +++++-- tests/test_future/test_dict.py | 7 +++++-- tests/test_future/test_int.py | 6 +++--- tests/test_future/test_list.py | 7 +++++-- tests/test_future/test_object.py | 7 +++++-- tests/test_future/test_range.py | 10 ++++++++-- tests/test_future/test_str.py | 7 +++++-- tests/test_future/test_urllib_response.py | 4 ++-- 13 files changed, 70 insertions(+), 27 deletions(-) diff --git a/src/future/backports/misc.py b/src/future/backports/misc.py index ef752078..098a0667 100644 --- a/src/future/backports/misc.py +++ b/src/future/backports/misc.py @@ -16,7 +16,6 @@ import subprocess from math import ceil as oldceil -from collections import Mapping, MutableMapping from operator import itemgetter as _itemgetter, eq as _eq import sys @@ -25,7 +24,12 @@ from itertools import repeat as _repeat, chain as _chain, starmap as _starmap from socket import getaddrinfo, SOCK_STREAM, error, socket -from future.utils import iteritems, itervalues, PY26, PY3 +from future.utils import iteritems, itervalues, PY2, PY26, PY3 + +if PY2: + from collections import Mapping, MutableMapping +else: + from collections.abc import Mapping, MutableMapping def ceil(x): 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/past/builtins/misc.py b/src/past/builtins/misc.py index 06fbb92d..ed5800a7 100644 --- a/src/past/builtins/misc.py +++ b/src/past/builtins/misc.py @@ -1,9 +1,13 @@ from __future__ import unicode_literals import sys import inspect -from collections import Mapping -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: @@ -76,7 +80,7 @@ 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: + with open(filename, "rb") as fin: source = fin.read() code = compile(source, filename, "exec") exec_(code, myglobals, mylocals) diff --git a/src/past/types/oldstr.py b/src/past/types/oldstr.py index 7768d328..0237d239 100644 --- a/src/past/types/oldstr.py +++ b/src/past/types/oldstr.py @@ -2,11 +2,15 @@ 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 diff --git a/tests/test_future/test_backports.py b/tests/test_future/test_backports.py index 21ebb202..9eeb741b 100644 --- a/tests/test_future/test_backports.py +++ b/tests/test_future/test_backports.py @@ -10,7 +10,6 @@ import inspect import pickle from random import randrange, shuffle -from collections import Mapping, MutableMapping from future.backports.misc import (count, _count, @@ -18,9 +17,14 @@ Counter, ChainMap, _count_elements) -from future.utils import PY26 +from future.utils import PY2, PY26 from future.tests.base import unittest, skip26, expectedFailurePY27 +if PY2: + from collections import Mapping, MutableMapping +else: + from collections.abc import Mapping, MutableMapping + class CountTest(unittest.TestCase): """Test the count function.""" diff --git a/tests/test_future/test_bytes.py b/tests/test_future/test_bytes.py index bb90a71c..b9b157d8 100644 --- a/tests/test_future/test_bytes.py +++ b/tests/test_future/test_bytes.py @@ -701,12 +701,15 @@ def test_multiple_inheritance(self): """ Issue #96 (for newbytes instead of newobject) """ - import collections + if utils.PY2: + from collections import Container + else: + from collections.abc import Container class Base(bytes): pass - class Foo(Base, collections.Container): + class Foo(Base, Container): def __contains__(self, item): return False diff --git a/tests/test_future/test_dict.py b/tests/test_future/test_dict.py index 68856828..ff9dd4ab 100644 --- a/tests/test_future/test_dict.py +++ b/tests/test_future/test_dict.py @@ -111,12 +111,15 @@ def test_multiple_inheritance(self): """ Issue #96 (for newdict instead of newobject) """ - import collections + if utils.PY2: + from collections import Container + else: + from collections.abc import Container class Base(dict): pass - class Foo(Base, collections.Container): + class Foo(Base, Container): def __contains__(self, item): return False diff --git a/tests/test_future/test_int.py b/tests/test_future/test_int.py index 9acbd23a..573a0d53 100644 --- a/tests/test_future/test_int.py +++ b/tests/test_future/test_int.py @@ -344,7 +344,7 @@ def __int__(self): class Foo3(int): def __int__(self): - return self + return self.real class Foo4(int): def __int__(self): @@ -1069,12 +1069,12 @@ def test_multiple_inheritance(self): """ Issue #96 (for newint instead of newobject) """ - import collections + import collections.abc class Base(int): pass - class Foo(Base, collections.Container): + class Foo(Base, collections.abc.Container): def __add__(self, other): return 0 diff --git a/tests/test_future/test_list.py b/tests/test_future/test_list.py index 6134c74f..16fb84c5 100644 --- a/tests/test_future/test_list.py +++ b/tests/test_future/test_list.py @@ -162,12 +162,15 @@ def test_multiple_inheritance(self): """ Issue #96 (for newdict instead of newobject) """ - import collections + if utils.PY2: + from collections import Container + else: + from collections.abc import Container class Base(list): pass - class Foo(Base, collections.Container): + class Foo(Base, Container): def __contains__(self, item): return False diff --git a/tests/test_future/test_object.py b/tests/test_future/test_object.py index 8352f3a3..eb876471 100644 --- a/tests/test_future/test_object.py +++ b/tests/test_future/test_object.py @@ -209,12 +209,15 @@ def test_multiple_inheritance(self): """ Issue #96 """ - import collections + if utils.PY2: + from collections import Container + else: + from collections.abc import Container class Base(object): pass - class Foo(Base, collections.Container): + class Foo(Base, Container): def __contains__(self, item): return False diff --git a/tests/test_future/test_range.py b/tests/test_future/test_range.py index 2e9471ae..dba15228 100644 --- a/tests/test_future/test_range.py +++ b/tests/test_future/test_range.py @@ -6,9 +6,15 @@ from future.builtins import range from future.tests.base import unittest -from collections import Iterator, Sequence from operator import attrgetter +from future.utils import PY2 + +if PY2: + from collections import Iterator, Sequence +else: + from collections.abc import Iterator, Sequence + class RangeTests(unittest.TestCase): def test_range(self): @@ -192,7 +198,7 @@ def test_rev_stepped_slice_rev_stepped_range(self): def test_slice_zero_step(self): msg = '^slice step cannot be zero$' - with self.assertRaisesRegexp(ValueError, msg): + with self.assertRaisesRegex(ValueError, msg): range(8)[::0] def test_properties(self): diff --git a/tests/test_future/test_str.py b/tests/test_future/test_str.py index dcc15628..98e71c41 100644 --- a/tests/test_future/test_str.py +++ b/tests/test_future/test_str.py @@ -525,12 +525,15 @@ def test_multiple_inheritance(self): """ Issue #96 (for newstr instead of newobject) """ - import collections + if utils.PY2: + from collections import Container + else: + from collections.abc import Container class Base(str): pass - class Foo(Base, collections.Container): + class Foo(Base, Container): def __contains__(self, item): return False diff --git a/tests/test_future/test_urllib_response.py b/tests/test_future/test_urllib_response.py index 27da4a31..e8f4b4f1 100644 --- a/tests/test_future/test_urllib_response.py +++ b/tests/test_future/test_urllib_response.py @@ -8,7 +8,7 @@ from future.tests.base import unittest -class TestFile(object): +class File(object): def __init__(self): self.closed = False @@ -28,7 +28,7 @@ class Testaddbase(unittest.TestCase): # TODO(jhylton): Write tests for other functionality of addbase() def setUp(self): - self.fp = TestFile() + self.fp = File() self.addbase = urllib_response.addbase(self.fp) def test_with(self): From 89c8030dffa8a22aca7db44fffdb807ea2a67642 Mon Sep 17 00:00:00 2001 From: Tomer Chachamu Date: Fri, 30 Nov 2018 10:10:11 +0000 Subject: [PATCH 262/425] Corrected lists of fixers --- docs/futurize.rst | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/docs/futurize.rst b/docs/futurize.rst index fdbf3026..2d2f9c28 100644 --- a/docs/futurize.rst +++ b/docs/futurize.rst @@ -81,6 +81,7 @@ The complete set of fixers applied by ``futurize --stage1`` is: lib2to3.fixes.fix_apply lib2to3.fixes.fix_except + lib2to3.fixes.fix_exec lib2to3.fixes.fix_exitfunc lib2to3.fixes.fix_funcattrs lib2to3.fixes.fix_has_key @@ -106,7 +107,6 @@ The complete set of fixers applied by ``futurize --stage1`` is: libfuturize.fixes.fix_print_with_import libfuturize.fixes.fix_raise - The following fixers from ``lib2to3`` are not applied: .. code-block:: python @@ -224,23 +224,23 @@ becomes:: The complete list of fixers applied in Stage 2 is:: - lib2to3.fixes.fix_basestring lib2to3.fixes.fix_dict - lib2to3.fixes.fix_exec + lib2to3.fixes.fix_filter lib2to3.fixes.fix_getcwdu lib2to3.fixes.fix_input lib2to3.fixes.fix_itertools lib2to3.fixes.fix_itertools_imports - lib2to3.fixes.fix_filter lib2to3.fixes.fix_long lib2to3.fixes.fix_map + lib2to3.fixes.fix_next lib2to3.fixes.fix_nonzero lib2to3.fixes.fix_operator lib2to3.fixes.fix_raw_input lib2to3.fixes.fix_zip - + + libfuturize.fixes.fix_basestring libfuturize.fixes.fix_cmp - libfuturize.fixes.fix_division + libfuturize.fixes.fix_division_safe libfuturize.fixes.fix_execfile libfuturize.fixes.fix_future_builtins libfuturize.fixes.fix_future_standard_library @@ -269,12 +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. From 13611f355c86bf2ed97b6922429102c6acc0fc6a Mon Sep 17 00:00:00 2001 From: Dan Yeaw Date: Fri, 7 Dec 2018 21:34:29 -0500 Subject: [PATCH 263/425] Fix deprecation warnings for abc module in Python 3.7 Signed-off-by: Dan Yeaw --- src/past/builtins/misc.py | 6 +++--- src/past/types/oldstr.py | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/past/builtins/misc.py b/src/past/builtins/misc.py index 06fbb92d..a5393104 100644 --- a/src/past/builtins/misc.py +++ b/src/past/builtins/misc.py @@ -1,13 +1,12 @@ from __future__ import unicode_literals -import sys + import inspect -from collections import Mapping from future.utils import PY3, exec_ - if PY3: import builtins + from collections.abc import Mapping def apply(f, *args, **kw): return f(*args, **kw) @@ -44,6 +43,7 @@ def oct(number): xrange = range else: import __builtin__ + from collections import Mapping apply = __builtin__.apply chr = __builtin__.chr cmp = __builtin__.cmp diff --git a/src/past/types/oldstr.py b/src/past/types/oldstr.py index 7768d328..163276d6 100644 --- a/src/past/types/oldstr.py +++ b/src/past/types/oldstr.py @@ -2,11 +2,9 @@ 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 - +from past.utils import with_metaclass _builtin_bytes = bytes From d977505cab6d213bf6d492622d1980c365e3bd6c Mon Sep 17 00:00:00 2001 From: Kjell Wooding Date: Fri, 4 Jan 2019 13:16:26 -0500 Subject: [PATCH 264/425] on python 2, socket.fp doesn't have a readinto() method. Work around this like limitation like is done elsewhere in this module --- src/future/backports/http/client.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/future/backports/http/client.py b/src/future/backports/http/client.py index 5dd983d8..1a43f013 100644 --- a/src/future/backports/http/client.py +++ b/src/future/backports/http/client.py @@ -696,9 +696,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:] From 961aa0f6baf7d6cb88c9fa51c6d6312e3b4635a9 Mon Sep 17 00:00:00 2001 From: Sesh Sadasivam Date: Fri, 18 Jan 2019 22:48:24 -0500 Subject: [PATCH 265/425] Fixed bug in email Message.set_boundary() Also added tests that would expose the bug. --- src/future/backports/email/message.py | 6 ++--- tests/test_future/test_email_multipart.py | 31 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 tests/test_future/test_email_multipart.py 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/tests/test_future/test_email_multipart.py b/tests/test_future/test_email_multipart.py new file mode 100644 index 00000000..cbd93b89 --- /dev/null +++ b/tests/test_future/test_email_multipart.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +"""Tests for multipart emails.""" + +from future.tests.base import unittest +import future.backports.email as email +import future.backports.email.mime.multipart +from future.builtins import list + +class EmailMultiPartTests(unittest.TestCase): + """Tests for handling multipart email Messages.""" + + def test_multipart_serialize_without_boundary(self): + """Tests that serializing an empty multipart email does not fail.""" + multipart_message = email.mime.multipart.MIMEMultipart() + self.assertIsNot(multipart_message.as_string(), None) + + def test_multipart_set_boundary_does_not_change_header_type(self): + """ + Tests that Message.set_boundary() does not cause Python2 errors. + + In particular, tests that set_boundary does not cause the type of the + message headers list to be changed from the future built-in list. + """ + multipart_message = email.mime.multipart.MIMEMultipart() + headers_type = type(multipart_message._headers) + self.assertEqual(headers_type, type(list())) + + boundary = '===============6387699881409002085==' + multipart_message.set_boundary(boundary) + headers_type = type(multipart_message._headers) + self.assertEqual(headers_type, type(list())) From 2c410ded2a424b9b57f770e5d6084524ac37d440 Mon Sep 17 00:00:00 2001 From: Andrew Wason Date: Wed, 20 Feb 2019 11:33:08 -0500 Subject: [PATCH 266/425] Do not globally destroy re.ASCII in PY3 --- src/future/backports/http/cookiejar.py | 5 +++-- src/future/backports/http/cookies.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/future/backports/http/cookiejar.py b/src/future/backports/http/cookiejar.py index cad72f9b..af3ef415 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 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"] From 49495ee850573259e4d18fecc57ec71d16d87ddc Mon Sep 17 00:00:00 2001 From: "Fumihiro Bessho (Ben)" Date: Sun, 17 Mar 2019 08:05:23 +0900 Subject: [PATCH 267/425] Fixed bug in FixDivisionSafe() --- src/libfuturize/fixes/fix_division_safe.py | 55 ++++++++++------------ tests/test_future/test_futurize.py | 10 +++- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/libfuturize/fixes/fix_division_safe.py b/src/libfuturize/fixes/fix_division_safe.py index 7b0f3cbd..3d5909cc 100644 --- a/src/libfuturize/fixes/fix_division_safe.py +++ b/src/libfuturize/fixes/fix_division_safe.py @@ -14,10 +14,8 @@ """ import re -import lib2to3.pytree as pytree from lib2to3.fixer_util import Leaf, Node, Comma from lib2to3 import fixer_base -from lib2to3.fixer_util import syms, does_tree_import from libfuturize.fixer_util import (token, future_import, touch_import_top, wrap_in_fn_call) @@ -33,8 +31,8 @@ def match_division(node): const_re = re.compile('^[0-9]*[.][0-9]*$') -def is_floaty(node, div_idx): - return _is_floaty(node.children[0:div_idx]) or _is_floaty(node.children[div_idx+1:]) +def is_floaty(node): + return _is_floaty(node.prev_sibling) or _is_floaty(node.next_sibling) def _is_floaty(expr): @@ -50,24 +48,6 @@ def _is_floaty(expr): return expr.children[0].value == u'float' return False -def find_division(node): - for i, child in enumerate(node.children): - if match_division(child): - return i - return False - -def clone_div_operands(node, div_idx): - children = [] - for i, child in enumerate(node.children): - if i == div_idx: - children.append(Comma()) - else: - children.append(child.clone()) - - # Strip any leading space for the first number: - children[0].prefix = u'' - - return children class FixDivisionSafe(fixer_base.BaseFix): # BM_compatible = True @@ -92,13 +72,28 @@ def match(self, node): matches, we can start discarding matches after the first. """ if node.type == self.syms.term: - div_idx = find_division(node) - if div_idx is not False: - # if expr1 or expr2 are obviously floats, we don't need to wrap in - # old_div, as the behavior of division between any number and a float - # should be the same in 2 or 3 - if not is_floaty(node, div_idx): - return clone_div_operands(node, div_idx) + matched = False + skip = False + children = [] + for child in node.children: + if skip: + skip = False + continue + if match_division(child) and not is_floaty(child): + matched = True + + # Strip any leading space for the first number: + children[0].prefix = u'' + + children = [wrap_in_fn_call("old_div", + children + [Comma(), child.next_sibling.clone()], + prefix=node.prefix)] + skip = True + else: + children.append(child.clone()) + if matched: + return Node(node.type, children, fixers_applied=node.fixers_applied) + return False def transform(self, node, results): @@ -106,4 +101,4 @@ def transform(self, node, results): return future_import(u"division", node) touch_import_top(u'past.utils', u'old_div', node) - return wrap_in_fn_call("old_div", results, prefix=node.prefix) + return results diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index 5549a010..82ba6563 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -1208,6 +1208,10 @@ def total_count(self): val = float(obj.numer) / obj.denom * 1e-9 obj.numer * obj.denom / val obj.total_count() * val / 100 + obj.numer / obj.denom * 1e-9 + obj.numer / (obj.denom * 1e-9) + obj.numer / obj.denom / 1e-9 + obj.numer / (obj.denom / 1e-9) original_numer = 1 original_denom = 50 100 * abs(obj.numer - original_numer) / float(max(obj.denom, original_denom)) @@ -1237,13 +1241,17 @@ def total_count(self): b = 1 + foo[old_div(len(foo) * 3, 4)] assert a == 51 assert b == 76 - r = old_div(random.randint(0, 1000) * 1.0, 1000) + r = random.randint(0, 1000) * 1.0 / 1000 output = { "SUCCESS": 5, "TOTAL": 10 } old_div(output["SUCCESS"] * 100, output["TOTAL"]) obj = fraction(1, 50) val = float(obj.numer) / obj.denom * 1e-9 old_div(obj.numer * obj.denom, val) old_div(obj.total_count() * val, 100) + old_div(obj.numer, obj.denom) * 1e-9 + old_div(obj.numer, (obj.denom * 1e-9)) + old_div(old_div(obj.numer, obj.denom), 1e-9) + old_div(obj.numer, (old_div(obj.denom, 1e-9))) original_numer = 1 original_denom = 50 100 * abs(obj.numer - original_numer) / float(max(obj.denom, original_denom)) From a39f2dabd6108867ba7f3a4d820b55546b095208 Mon Sep 17 00:00:00 2001 From: Aiden Scandella Date: Wed, 3 Apr 2019 10:36:19 -0700 Subject: [PATCH 268/425] Fix future import ordering Fixes #445 --- src/future/tests/base.py | 6 +++++- ...ix_add__future__imports_except_unicode_literals.py | 4 ++-- .../fixes/fix_add_all__future__imports.py | 6 +++--- tests/test_future/test_futurize.py | 11 ++++++----- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/future/tests/base.py b/src/future/tests/base.py index 9f4607b6..a7cc8ec1 100644 --- a/src/future/tests/base.py +++ b/src/future/tests/base.py @@ -272,7 +272,11 @@ def convert_check(self, before, expected, stages=(1, 2), all_imports=False, else: headers = '' - self.compare(output, headers + reformat_code(expected), + reformatted = reformat_code(expected) + if headers in reformatted: + headers = '' + + self.compare(output, headers + reformatted, ignore_imports=ignore_imports) def unchanged(self, code, **kwargs): diff --git a/src/libfuturize/fixes/fix_add__future__imports_except_unicode_literals.py b/src/libfuturize/fixes/fix_add__future__imports_except_unicode_literals.py index 1d419a1c..37d7feec 100644 --- a/src/libfuturize/fixes/fix_add__future__imports_except_unicode_literals.py +++ b/src/libfuturize/fixes/fix_add__future__imports_except_unicode_literals.py @@ -21,6 +21,6 @@ class FixAddFutureImportsExceptUnicodeLiterals(fixer_base.BaseFix): def transform(self, node, results): # Reverse order: - future_import(u"print_function", node) - future_import(u"division", node) future_import(u"absolute_import", node) + future_import(u"division", node) + future_import(u"print_function", node) diff --git a/src/libpasteurize/fixes/fix_add_all__future__imports.py b/src/libpasteurize/fixes/fix_add_all__future__imports.py index 37897946..a151f9f1 100644 --- a/src/libpasteurize/fixes/fix_add_all__future__imports.py +++ b/src/libpasteurize/fixes/fix_add_all__future__imports.py @@ -18,7 +18,7 @@ class FixAddAllFutureImports(fixer_base.BaseFix): run_order = 1 def transform(self, node, results): - future_import(u"unicode_literals", node) - future_import(u"print_function", node) - future_import(u"division", node) future_import(u"absolute_import", node) + future_import(u"division", node) + future_import(u"print_function", node) + future_import(u"unicode_literals", node) diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index 5549a010..633eb648 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -1361,6 +1361,7 @@ def test_open(self): """ self.convert_check(before, after, conservative=True) + class TestFuturizeAllImports(CodeHandler): """ Tests "futurize --all-imports". @@ -1378,14 +1379,14 @@ def test_all_imports(self): print('Hello') """ after = """ - from __future__ import unicode_literals - from __future__ import print_function - from __future__ import division from __future__ import absolute_import + from __future__ import division + from __future__ import print_function + from __future__ import unicode_literals from future import standard_library standard_library.install_aliases() - from builtins import range from builtins import * + from builtins import range import math import os l = list(range(10)) @@ -1395,7 +1396,7 @@ def test_all_imports(self): pass print('Hello') """ - self.convert_check(before, after, all_imports=True) + self.convert_check(before, after, all_imports=True, ignore_imports=False) if __name__ == '__main__': From 4b1ddb49e8e7d4c812c50ac38d2c11db64ac9c1e Mon Sep 17 00:00:00 2001 From: Chih-Hsuan Yen Date: Fri, 28 Sep 2018 14:39:32 +0800 Subject: [PATCH 269/425] Fix urllib.request imports for Python 3.8 compatibility Fixes https://github.com/PythonCharmers/python-future/issues/447 --- src/future/moves/urllib/request.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/future/moves/urllib/request.py b/src/future/moves/urllib/request.py index 60e440a7..972aa4ab 100644 --- a/src/future/moves/urllib/request.py +++ b/src/future/moves/urllib/request.py @@ -11,19 +11,8 @@ proxy_bypass, quote, request_host, - splitattr, - splithost, - splitpasswd, - splitport, - splitquery, - splittag, - splittype, - splituser, - splitvalue, thishost, - to_bytes, unquote, - unwrap, url2pathname, urlcleanup, urljoin, @@ -32,6 +21,18 @@ urlretrieve, urlsplit, urlunparse) + + from urllib.parse import (splitattr, + splithost, + splitpasswd, + splitport, + splitquery, + splittag, + splittype, + splituser, + splitvalue, + to_bytes, + unwrap) else: __future_module__ = True with suspend_hooks(): From 202ca193955eb1bfc4bc46cc830006eb169f4d2e Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Tue, 23 Apr 2019 17:13:11 -0700 Subject: [PATCH 270/425] Fix awkward wording > you may like to use X instead reads a bit awkward / non-native English to my ears. Afraid I can't point to a specific rule why though. Anyway, just thought I'd help fix it up. Thank you for the super helpful package! --- docs/compatible_idioms.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index 52a6a8b0..9ba94280 100644 --- a/docs/compatible_idioms.rst +++ b/docs/compatible_idioms.rst @@ -1270,7 +1270,7 @@ urllib module ~~~~~~~~~~~~~ ``urllib`` is the hardest module to use from Python 2/3 compatible code. -You may like to use Requests (http://python-requests.org) instead. +You might want to switch to Requests (http://python-requests.org) instead. .. code:: python From 23989c4d61a5e3b2308b107efc1402bc727e8fe6 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Mon, 6 May 2019 10:56:37 -0400 Subject: [PATCH 271/425] Future-proof for Py4.0 --- src/future/backports/__init__.py | 2 +- src/future/moves/__init__.py | 2 +- src/future/utils/__init__.py | 2 +- src/html/parser.py | 2 +- src/past/translation/__init__.py | 2 +- src/past/utils/__init__.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/future/backports/__init__.py b/src/future/backports/__init__.py index 68291141..c71e0653 100644 --- a/src/future/backports/__init__.py +++ b/src/future/backports/__init__.py @@ -10,7 +10,7 @@ from future.standard_library import import_top_level_modules -if sys.version_info[0] == 3: +if sys.version_info[0] >= 3: import_top_level_modules() diff --git a/src/future/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/utils/__init__.py b/src/future/utils/__init__.py index 906f1e46..090a2bbe 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -55,7 +55,7 @@ import inspect -PY3 = sys.version_info[0] == 3 +PY3 = sys.version_info[0] >= 3 PY35_PLUS = sys.version_info[0:2] >= (3, 5) PY36_PLUS = sys.version_info[0:2] >= (3, 6) PY2 = sys.version_info[0] == 2 diff --git a/src/html/parser.py b/src/html/parser.py index 541def39..e3948879 100644 --- a/src/html/parser.py +++ b/src/html/parser.py @@ -2,7 +2,7 @@ import sys __future_module__ = True -if sys.version_info[0] == 3: +if sys.version_info[0] >= 3: raise ImportError('Cannot import module from python-future source folder') else: from future.moves.html.parser import * diff --git a/src/past/translation/__init__.py b/src/past/translation/__init__.py index c7ae2b7a..5ac57068 100644 --- a/src/past/translation/__init__.py +++ b/src/past/translation/__init__.py @@ -432,7 +432,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 diff --git a/src/past/utils/__init__.py b/src/past/utils/__init__.py index c6606d0b..f6b2642d 100644 --- a/src/past/utils/__init__.py +++ b/src/past/utils/__init__.py @@ -16,7 +16,7 @@ import sys import numbers -PY3 = sys.version_info[0] == 3 +PY3 = sys.version_info[0] >= 3 PY2 = sys.version_info[0] == 2 PYPY = hasattr(sys, 'pypy_translation_info') From 671a63e7f7b693b26df6285f3cdaf4a2fb598194 Mon Sep 17 00:00:00 2001 From: Ghanshyam Lele Date: Mon, 6 May 2019 15:15:51 -0400 Subject: [PATCH 272/425] Bug fix #454: Implement __hash__() in newstr --- src/future/types/newstr.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/future/types/newstr.py b/src/future/types/newstr.py index e6272fb9..76374656 100644 --- a/src/future/types/newstr.py +++ b/src/future/types/newstr.py @@ -105,6 +105,7 @@ def __repr__(self): """ Without the u prefix """ + value = super(newstr, self).__repr__() # assert value[0] == u'u' return value[1:] @@ -292,6 +293,13 @@ def __eq__(self, other): else: return False + 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 isinstance(other, bytes) and not isnewbytes(other)): From 064a12ae5b1e70e9be1faaee74259a3d6cf4b2f2 Mon Sep 17 00:00:00 2001 From: Ghanshyam Lele Date: Mon, 6 May 2019 15:42:11 -0400 Subject: [PATCH 273/425] add test for newstr.__hash__() --- tests/test_future/test_str.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_future/test_str.py b/tests/test_future/test_str.py index dcc15628..debbd849 100644 --- a/tests/test_future/test_str.py +++ b/tests/test_future/test_str.py @@ -363,6 +363,10 @@ def test_eq(self): self.assertFalse(b'ABCD' == s) self.assertFalse(bytes(b'ABCD') == s) + def test_hash(self): + s = str('ABCD') + self.assertIsInstance(hash(s),int) + def test_ne(self): s = str('ABCD') self.assertNotEqual('A', s) From f141395ce73acc3d1461ff21a29813c1e9e15baf Mon Sep 17 00:00:00 2001 From: Sameera Date: Tue, 7 May 2019 11:50:40 -0400 Subject: [PATCH 274/425] for issue 334 --- src/future/builtins/__init__.py | 4 +-- src/future/builtins/misc.py | 9 ++++++- src/future/builtins/new_min_max.py | 43 ++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 src/future/builtins/new_min_max.py diff --git a/src/future/builtins/__init__.py b/src/future/builtins/__init__.py index 216465a1..8bc1649d 100644 --- a/src/future/builtins/__init__.py +++ b/src/future/builtins/__init__.py @@ -11,7 +11,7 @@ # The isinstance import is no longer needed. We provide it only for # backward-compatibility with future v0.8.2. It will be removed in future v1.0. from future.builtins.misc import (ascii, chr, hex, input, isinstance, next, - oct, open, pow, round, super) + oct, open, pow, round, super, max, min) from future.utils import PY3 if PY3: @@ -43,7 +43,7 @@ __all__ = ['filter', 'map', 'zip', 'ascii', 'chr', 'hex', 'input', 'next', 'oct', 'open', 'pow', 'round', 'super', - 'bytes', 'dict', 'int', 'list', 'object', 'range', 'str', + 'bytes', 'dict', 'int', 'list', 'object', 'range', 'str', 'max', 'min' ] else: diff --git a/src/future/builtins/misc.py b/src/future/builtins/misc.py index 90dc384a..b0b67066 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 max behaviour from Py3) +- ``min`` (new min behaviour from Py3) ``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,6 +114,8 @@ def pow(x, y, z=_SENTINEL): pow = builtins.pow round = builtins.round super = builtins.super + max = builtins.max + min = builtins.min __all__ = [] diff --git a/src/future/builtins/new_min_max.py b/src/future/builtins/new_min_max.py new file mode 100644 index 00000000..128a7f16 --- /dev/null +++ b/src/future/builtins/new_min_max.py @@ -0,0 +1,43 @@ +from __builtin__ import max as _builtin_max, min as _builtin_min + + +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) != 1 and kwargs.get('default') is not None: + raise TypeError + + if len(args) == 1: + if len(args[0]) == 0: + if kwargs.get('default') is not None: + return kwargs.get('default') + else: + raise ValueError('iterable is an empty sequence') + if kwargs.get('key') is not None: + return _builtin_func(args[0], key=kwargs.get('key')) + else: + return _builtin_func(args[0]) + + if len(args) > 1: + if kwargs.get('key') is not None: + return _builtin_func(args, key=kwargs.get('key')) + else: + return _builtin_func(args) From 98e8ae154e90434be0a32205c5f78b22c201afb3 Mon Sep 17 00:00:00 2001 From: Sameera Date: Tue, 7 May 2019 13:56:35 -0400 Subject: [PATCH 275/425] adding tests and fixing functions --- src/future/builtins/new_min_max.py | 9 +++++++-- tests/test_future/test_builtins.py | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/future/builtins/new_min_max.py b/src/future/builtins/new_min_max.py index 128a7f16..1adbd491 100644 --- a/src/future/builtins/new_min_max.py +++ b/src/future/builtins/new_min_max.py @@ -19,14 +19,19 @@ def new_min_max(_builtin_func, *args, **kwargs): """ for key, _ in kwargs.items(): - if key not in set(['key', 'default']): + if key not in {'key', 'default'}: raise TypeError('Illegal argument %s', key) + if len(args) == 0: + raise TypeError + if len(args) != 1 and kwargs.get('default') is not None: raise TypeError if len(args) == 1: - if len(args[0]) == 0: + try: + next(iter(args[0])) + except StopIteration: if kwargs.get('default') is not None: return kwargs.get('default') else: diff --git a/tests/test_future/test_builtins.py b/tests/test_future/test_builtins.py index 05d597a5..a514d697 100644 --- a/tests/test_future/test_builtins.py +++ b/tests/test_future/test_builtins.py @@ -6,7 +6,7 @@ from __future__ import absolute_import, division, print_function, unicode_literals from future.builtins import (bytes, dict, int, range, round, str, super, ascii, chr, hex, input, next, oct, open, pow, - filter, map, zip) + filter, map, zip, min, max) from future.utils import PY3, exec_, native_str, implements_iterator from future.tests.base import (unittest, skip26, expectedFailurePY2, @@ -138,6 +138,7 @@ def test_round(self): self.assertEqual(round(0.125000001, 2), 0.13) self.assertEqual(round(123.5, 0), 124.0) self.assertEqual(round(123.5), 124) + self.assertEqual(round(123.5), 124) self.assertEqual(round(12.35, 2), 12.35) self.assertEqual(round(12.35, 1), 12.3) self.assertEqual(round(12.35, 0), 12.0) @@ -1099,6 +1100,13 @@ def test_max(self): self.assertEqual(max(data, key=f), sorted(reversed(data), key=f)[-1]) + self.assertEqual(max([], default=5), 5) + with self.assertRaises(TypeError): + max(None, default=5) + with self.assertRaises(TypeError): + max(1, 2, default=0) + self.assertEqual(max([], default=0), 0) + def test_min(self): self.assertEqual(min('123123'), '1') self.assertEqual(min(1, 2, 3), 1) @@ -1140,6 +1148,12 @@ def __getitem__(self, index): f = keys.__getitem__ self.assertEqual(min(data, key=f), sorted(data, key=f)[0]) + self.assertEqual(min([], default=5), 5) + self.assertEqual(min([], default=0), 0) + with self.assertRaises(TypeError): + max(None, default=5) + with self.assertRaises(TypeError): + max(1, 2, default=0) def test_next(self): it = iter(range(2)) From 5c3f9949614b92bf75132c0f122d099363b957d7 Mon Sep 17 00:00:00 2001 From: Sameera Date: Tue, 7 May 2019 14:17:03 -0400 Subject: [PATCH 276/425] minor changes --- src/future/builtins/misc.py | 4 ++-- tests/test_future/test_builtins.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/future/builtins/misc.py b/src/future/builtins/misc.py index b0b67066..52d19926 100644 --- a/src/future/builtins/misc.py +++ b/src/future/builtins/misc.py @@ -13,8 +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 max behaviour from Py3) -- ``min`` (new min behaviour from Py3) +- ``max`` (new default option from Py3) +- ``min`` (new default option from Py3) ``isinstance`` is also currently exported for backwards compatibility with v0.8.2, although this has been deprecated since v0.9. diff --git a/tests/test_future/test_builtins.py b/tests/test_future/test_builtins.py index a514d697..d983f9d6 100644 --- a/tests/test_future/test_builtins.py +++ b/tests/test_future/test_builtins.py @@ -138,7 +138,6 @@ def test_round(self): self.assertEqual(round(0.125000001, 2), 0.13) self.assertEqual(round(123.5, 0), 124.0) self.assertEqual(round(123.5), 124) - self.assertEqual(round(123.5), 124) self.assertEqual(round(12.35, 2), 12.35) self.assertEqual(round(12.35, 1), 12.3) self.assertEqual(round(12.35, 0), 12.0) From fbea2790e6ab207681cd7049cea2b79e52548af9 Mon Sep 17 00:00:00 2001 From: Sameera Date: Tue, 7 May 2019 15:04:04 -0400 Subject: [PATCH 277/425] set represenation for python 2.6 --- src/future/builtins/new_min_max.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/builtins/new_min_max.py b/src/future/builtins/new_min_max.py index 1adbd491..2858e541 100644 --- a/src/future/builtins/new_min_max.py +++ b/src/future/builtins/new_min_max.py @@ -19,7 +19,7 @@ def new_min_max(_builtin_func, *args, **kwargs): """ for key, _ in kwargs.items(): - if key not in {'key', 'default'}: + if key not in set(['key', 'default']): raise TypeError('Illegal argument %s', key) if len(args) == 0: From ee0d6dc217be6ee051c1fe058d426a21d73100c3 Mon Sep 17 00:00:00 2001 From: Sameera Date: Tue, 7 May 2019 15:31:17 -0400 Subject: [PATCH 278/425] fix for python 3.3 and below --- src/future/builtins/misc.py | 9 +++++++-- src/future/utils/__init__.py | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/future/builtins/misc.py b/src/future/builtins/misc.py index 52d19926..0691dc3a 100644 --- a/src/future/builtins/misc.py +++ b/src/future/builtins/misc.py @@ -13,8 +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) -- ``min`` (new default option 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. @@ -100,6 +100,11 @@ def pow(x, y, z=_SENTINEL): __all__ = ['ascii', 'chr', 'hex', 'input', 'isinstance', 'next', 'oct', 'open', 'pow', 'round', 'super', 'max', 'min'] +elif not utils.PY34_PLUS: + from future.builtins.new_min_max import newmax as max + from future.builtins.new_min_max import newmin as min + __all__ = ['min', 'max'] + else: import builtins ascii = builtins.ascii diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index 090a2bbe..9ce2ec08 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -56,6 +56,7 @@ 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) PY2 = sys.version_info[0] == 2 From 4102c9a14d5208914902858da2912806c1ae5e1e Mon Sep 17 00:00:00 2001 From: Sameera Date: Tue, 7 May 2019 16:00:27 -0400 Subject: [PATCH 279/425] correction for python 3.3 and below --- src/future/builtins/misc.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/future/builtins/misc.py b/src/future/builtins/misc.py index 0691dc3a..f86ce5f3 100644 --- a/src/future/builtins/misc.py +++ b/src/future/builtins/misc.py @@ -100,11 +100,6 @@ def pow(x, y, z=_SENTINEL): __all__ = ['ascii', 'chr', 'hex', 'input', 'isinstance', 'next', 'oct', 'open', 'pow', 'round', 'super', 'max', 'min'] -elif not utils.PY34_PLUS: - from future.builtins.new_min_max import newmax as max - from future.builtins.new_min_max import newmin as min - __all__ = ['min', 'max'] - else: import builtins ascii = builtins.ascii @@ -119,10 +114,14 @@ def pow(x, y, z=_SENTINEL): pow = builtins.pow round = builtins.round super = builtins.super - max = builtins.max - min = builtins.min - - __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 From 99f2a8a6d3883d3fc72e1fc17605f50b83bc1679 Mon Sep 17 00:00:00 2001 From: shivapbhusal Date: Tue, 7 May 2019 16:08:38 -0400 Subject: [PATCH 280/425] add unit test to prove the behaviour for string equality --- tests/test_future/test_str.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_future/test_str.py b/tests/test_future/test_str.py index 7135ed1d..5563abf3 100644 --- a/tests/test_future/test_str.py +++ b/tests/test_future/test_str.py @@ -363,6 +363,19 @@ def test_eq(self): self.assertFalse(b'ABCD' == s) self.assertFalse(bytes(b'ABCD') == s) + class OurCustomString(object): + def __init__(self, string): + self.string = string + + def __str__(self): + return self.string + + our_str = OurCustomString("foobar") + new_str = str("foobar") + + self.assertFalse(our_str == new_str) + self.assertFalse(new_str == our_str) + def test_hash(self): s = str('ABCD') self.assertIsInstance(hash(s),int) From 61c9aec4aa4113aee3e37edbca97658a00632819 Mon Sep 17 00:00:00 2001 From: Sameera Date: Tue, 7 May 2019 16:13:01 -0400 Subject: [PATCH 281/425] fixing builtin library for python 3.3 and below in min, max --- src/future/builtins/new_min_max.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/future/builtins/new_min_max.py b/src/future/builtins/new_min_max.py index 2858e541..8fd63fdf 100644 --- a/src/future/builtins/new_min_max.py +++ b/src/future/builtins/new_min_max.py @@ -1,4 +1,8 @@ -from __builtin__ import max as _builtin_max, min as _builtin_min +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 def newmin(*args, **kwargs): From df3a9dcc5328d15efdedcd96e25674b3db4ceaf3 Mon Sep 17 00:00:00 2001 From: Mark Huang Date: Tue, 7 May 2019 21:51:20 -0700 Subject: [PATCH 282/425] Translate "raise E, V" to "future.utils.raise_(E, V)" 1. Handle the 3-argument version of "raise E, V, T". 2. "raise E, V, T" cannot be statically translated safely in general. If V is not a tuple or a (number, string, None) literal, then translate "raise E, V, T" to "from future.utils import raise_; raise_(E, V, T)" and let raise_() handle determining whether V is an instance of E at runtime. Fixes #387 and #455. --- src/future/utils/__init__.py | 32 ++++++++++-- src/libfuturize/fixes/fix_raise.py | 82 +++++++++++++++++++++--------- 2 files changed, 85 insertions(+), 29 deletions(-) diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index 628b8f97..59920077 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -406,12 +406,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, Exception): + # 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 not issubclass(tp, Exception): + # If the first object is a class, it becomes the type of the + # exception. + raise TypeError("class must derive from Exception") + 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 diff --git a/src/libfuturize/fixes/fix_raise.py b/src/libfuturize/fixes/fix_raise.py index 3e8323de..f7518416 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) + + 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) From b3628048352a1a878139c354d0db50a88c3f1c84 Mon Sep 17 00:00:00 2001 From: Mark Huang Date: Tue, 7 May 2019 21:51:20 -0700 Subject: [PATCH 283/425] Add tests for raise translation and raise_() functionality. 1. Uncomment the Test_raise tests now that support for tracebacks has been added. Add test_unknown_value() to test for #455. 2. Run the test_raise_() test now that #455 is fixed. Add a test for #387 (raise_(E, V, T) now detects if V is an instance of E and no longer blindly calls E(V)). --- tests/test_future/test_libfuturize_fixers.py | 261 ++++++++++--------- tests/test_future/test_utils.py | 21 +- 2 files changed, 151 insertions(+), 131 deletions(-) diff --git a/tests/test_future/test_libfuturize_fixers.py b/tests/test_future/test_libfuturize_fixers.py index 8c4a9a3e..4ac0b7e1 100644 --- a/tests/test_future/test_libfuturize_fixers.py +++ b/tests/test_future/test_libfuturize_fixers.py @@ -702,133 +702,140 @@ def test_with_future_print_function(self): # except (Exception, SystemExit): # pass""" # self.unchanged(s) -# -# class Test_raise(FixerTestCase): -# fixer = "raise" -# -# def test_basic(self): -# b = """raise Exception, 5""" -# a = """raise Exception(5)""" -# self.check(b, a) -# -# def test_prefix_preservation(self): -# b = """raise Exception,5""" -# a = """raise Exception(5)""" -# self.check(b, a) -# -# b = """raise Exception, 5""" -# a = """raise Exception(5)""" -# self.check(b, a) -# -# def test_with_comments(self): -# b = """raise Exception, 5 # foo""" -# a = """raise Exception(5) # foo""" -# self.check(b, a) -# -# b = """raise E, (5, 6) % (a, b) # foo""" -# a = """raise E((5, 6) % (a, b)) # foo""" -# self.check(b, a) -# -# b = """def foo(): -# raise Exception, 5, 6 # foo""" -# a = """def foo(): -# raise Exception(5).with_traceback(6) # foo""" -# self.check(b, a) -# -# def test_None_value(self): -# b = """raise Exception(5), None, tb""" -# a = """raise Exception(5).with_traceback(tb)""" -# self.check(b, a) -# -# def test_tuple_value(self): -# b = """raise Exception, (5, 6, 7)""" -# a = """raise Exception(5, 6, 7)""" -# self.check(b, a) -# -# def test_tuple_detection(self): -# b = """raise E, (5, 6) % (a, b)""" -# a = """raise E((5, 6) % (a, b))""" -# self.check(b, a) -# -# def test_tuple_exc_1(self): -# b = """raise (((E1, E2), E3), E4), V""" -# a = """raise E1(V)""" -# self.check(b, a) -# -# def test_tuple_exc_2(self): -# b = """raise (E1, (E2, E3), E4), V""" -# a = """raise E1(V)""" -# self.check(b, a) -# -# # These should produce a warning -# -# def test_string_exc(self): -# s = """raise 'foo'""" -# self.warns_unchanged(s, "Python 3 does not support string exceptions") -# -# def test_string_exc_val(self): -# s = """raise "foo", 5""" -# self.warns_unchanged(s, "Python 3 does not support string exceptions") -# -# def test_string_exc_val_tb(self): -# s = """raise "foo", 5, 6""" -# self.warns_unchanged(s, "Python 3 does not support string exceptions") -# -# # These should result in traceback-assignment -# -# def test_tb_1(self): -# b = """def foo(): -# raise Exception, 5, 6""" -# a = """def foo(): -# raise Exception(5).with_traceback(6)""" -# self.check(b, a) -# -# def test_tb_2(self): -# b = """def foo(): -# a = 5 -# raise Exception, 5, 6 -# b = 6""" -# a = """def foo(): -# a = 5 -# raise Exception(5).with_traceback(6) -# b = 6""" -# self.check(b, a) -# -# def test_tb_3(self): -# b = """def foo(): -# raise Exception,5,6""" -# a = """def foo(): -# raise Exception(5).with_traceback(6)""" -# self.check(b, a) -# -# def test_tb_4(self): -# b = """def foo(): -# a = 5 -# raise Exception,5,6 -# b = 6""" -# a = """def foo(): -# a = 5 -# raise Exception(5).with_traceback(6) -# b = 6""" -# self.check(b, a) -# -# def test_tb_5(self): -# b = """def foo(): -# raise Exception, (5, 6, 7), 6""" -# a = """def foo(): -# raise Exception(5, 6, 7).with_traceback(6)""" -# self.check(b, a) -# -# def test_tb_6(self): -# b = """def foo(): -# a = 5 -# raise Exception, (5, 6, 7), 6 -# b = 6""" -# a = """def foo(): -# a = 5 -# raise Exception(5, 6, 7).with_traceback(6) -# b = 6""" -# self.check(b, a) + +class Test_raise(FixerTestCase): + fixer = "raise" + + def test_basic(self): + b = """raise Exception, 5""" + a = """raise Exception(5)""" + self.check(b, a) + + def test_prefix_preservation(self): + b = """raise Exception,5""" + a = """raise Exception(5)""" + self.check(b, a) + + b = """raise Exception, 5""" + a = """raise Exception(5)""" + self.check(b, a) + + def test_with_comments(self): + b = """raise Exception, 5 # foo""" + a = """raise Exception(5) # foo""" + self.check(b, a) + + b = """def foo(): + raise Exception, 5, 6 # foo""" + a = """def foo(): + raise Exception(5).with_traceback(6) # foo""" + self.check(b, a) + + def test_None_value(self): + b = """raise Exception(5), None, tb""" + a = """raise Exception(5).with_traceback(tb)""" + self.check(b, a) + + def test_tuple_value(self): + b = """raise Exception, (5, 6, 7)""" + a = """raise Exception(5, 6, 7)""" + self.check(b, a) + + def test_tuple_exc_1(self): + b = """raise (((E1, E2), E3), E4), 5""" + a = """raise E1(5)""" + self.check(b, a) + + def test_tuple_exc_2(self): + b = """raise (E1, (E2, E3), E4), 5""" + a = """raise E1(5)""" + self.check(b, a) + + def test_unknown_value(self): + b = """ + raise E, V""" + a = """ + from future.utils import raise_ + raise_(E, V)""" + self.check(b, a) + + def test_unknown_value_with_traceback_with_comments(self): + b = """ + raise E, Func(arg1, arg2, arg3), tb # foo""" + a = """ + from future.utils import raise_ + raise_(E, Func(arg1, arg2, arg3), tb) # foo""" + self.check(b, a) + + # These should produce a warning + + def test_string_exc(self): + s = """raise 'foo'""" + self.warns_unchanged(s, "Python 3 does not support string exceptions") + + def test_string_exc_val(self): + s = """raise "foo", 5""" + self.warns_unchanged(s, "Python 3 does not support string exceptions") + + def test_string_exc_val_tb(self): + s = """raise "foo", 5, 6""" + self.warns_unchanged(s, "Python 3 does not support string exceptions") + + # These should result in traceback-assignment + + def test_tb_1(self): + b = """def foo(): + raise Exception, 5, 6""" + a = """def foo(): + raise Exception(5).with_traceback(6)""" + self.check(b, a) + + def test_tb_2(self): + b = """def foo(): + a = 5 + raise Exception, 5, 6 + b = 6""" + a = """def foo(): + a = 5 + raise Exception(5).with_traceback(6) + b = 6""" + self.check(b, a) + + def test_tb_3(self): + b = """def foo(): + raise Exception,5,6""" + a = """def foo(): + raise Exception(5).with_traceback(6)""" + self.check(b, a) + + def test_tb_4(self): + b = """def foo(): + a = 5 + raise Exception,5,6 + b = 6""" + a = """def foo(): + a = 5 + raise Exception(5).with_traceback(6) + b = 6""" + self.check(b, a) + + def test_tb_5(self): + b = """def foo(): + raise Exception, (5, 6, 7), 6""" + a = """def foo(): + raise Exception(5, 6, 7).with_traceback(6)""" + self.check(b, a) + + def test_tb_6(self): + b = """def foo(): + a = 5 + raise Exception, (5, 6, 7), 6 + b = 6""" + a = """def foo(): + a = 5 + raise Exception(5, 6, 7).with_traceback(6) + b = 6""" + self.check(b, a) # # class Test_throw(FixerTestCase): # fixer = "throw" diff --git a/tests/test_future/test_utils.py b/tests/test_future/test_utils.py index b80e8c17..488c0064 100644 --- a/tests/test_future/test_utils.py +++ b/tests/test_future/test_utils.py @@ -110,11 +110,7 @@ def test_isbytes(self): self.assertFalse(isbytes(self.s)) self.assertFalse(isbytes(self.s2)) - @unittest.skipIf(PY3, 'test_raise_ currently fails on Py3') def test_raise_(self): - """ - The with_value() test currently fails on Py3 - """ def valerror(): try: raise ValueError("Apples!") @@ -173,6 +169,23 @@ def bar(): pass # incorrectly raises a TypeError on Py3 as of v0.15.2. + def test_raise_custom_exception(self): + """ + Test issue #387. + """ + class CustomException(Exception): + def __init__(self, severity, message): + super().__init__("custom message of severity %d: %s" % ( + severity, message)) + + def raise_custom_exception(): + try: + raise CustomException(1, "hello") + except CustomException: + raise_(*sys.exc_info()) + + self.assertRaises(CustomException, raise_custom_exception) + @skip26 def test_as_native_str(self): """ From d87c56e7bd264a701395a9929c4f28af82d9ebf3 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Wed, 8 May 2019 12:17:50 -0400 Subject: [PATCH 284/425] Update behavior of newstr.__eq__() to match str.__eq__() as per reference docs --- src/future/types/newstr.py | 2 +- tests/test_future/test_str.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/future/types/newstr.py b/src/future/types/newstr.py index 76374656..d41ea969 100644 --- a/src/future/types/newstr.py +++ b/src/future/types/newstr.py @@ -291,7 +291,7 @@ 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 diff --git a/tests/test_future/test_str.py b/tests/test_future/test_str.py index 5563abf3..51085481 100644 --- a/tests/test_future/test_str.py +++ b/tests/test_future/test_str.py @@ -363,18 +363,24 @@ def test_eq(self): self.assertFalse(b'ABCD' == s) self.assertFalse(bytes(b'ABCD') == s) + # We want to ensure comparison against unknown types return + # NotImplemented so that the interpreter can rerun the test with the + # other class. We expect the operator to return False if both return + # NotImplemented. class OurCustomString(object): def __init__(self, string): self.string = string - def __str__(self): - return self.string + def __eq__(self, other): + return NotImplemented our_str = OurCustomString("foobar") new_str = str("foobar") self.assertFalse(our_str == new_str) self.assertFalse(new_str == our_str) + self.assertIs(new_str.__eq__(our_str), NotImplemented) + self.assertIs(our_str.__eq__(new_str), NotImplemented) def test_hash(self): s = str('ABCD') From a13917aff638ea9be99b33a55bda78268b475d76 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Wed, 8 May 2019 13:06:41 -0400 Subject: [PATCH 285/425] Revert "allow mixed string type inputs to misc urllib.parse.* functions" This reverts commit 62b4c35d995aa5503855ffcc5c8fb2262e02c2a2. --- src/future/backports/urllib/parse.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/future/backports/urllib/parse.py b/src/future/backports/urllib/parse.py index 2c26846d..04e52d49 100644 --- a/src/future/backports/urllib/parse.py +++ b/src/future/backports/urllib/parse.py @@ -107,11 +107,11 @@ def _coerce_args(*args): # an appropriate result coercion function # - noop for str inputs # - encoding function otherwise - str_input = isinstance(args[0], basestring) + str_input = isinstance(args[0], str) for arg in args[1:]: # We special-case the empty string to support the # "scheme=''" default argument to some functions - if arg and isinstance(arg, basestring) != str_input: + if arg and isinstance(arg, str) != str_input: raise TypeError("Cannot mix str and non-str arguments") if str_input: return args + (_noop,) From 80ccca6024ca96a39e02b357af2147798a448a6e Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Wed, 8 May 2019 14:07:03 -0400 Subject: [PATCH 286/425] Remove guidance on recursively selecting only python files from a directory as shells are not portable --- docs/futurize_cheatsheet.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/futurize_cheatsheet.rst b/docs/futurize_cheatsheet.rst index 1fcca365..82f211c6 100644 --- a/docs/futurize_cheatsheet.rst +++ b/docs/futurize_cheatsheet.rst @@ -31,10 +31,10 @@ The goal for this step is to modernize the Python 2 code without introducing any pip install future **1b**. Run ``futurize --stage1 -w *.py subdir1/*.py subdir2/*.py``. Note that with -recursive globbing in ``bash`` or ``zsh``, you can apply stage 1 to all Python -source files recursively with:: +recursive globbing in ``bash`` or ``zsh``, you can apply stage 1 to all source files +recursively with:: - futurize --stage1 -w **/*.py + futurize --stage1 -w . **1c**. Commit all changes @@ -79,10 +79,9 @@ again with the help of the ``future`` package. futurize --stage2 myfolder1/*.py myfolder2/*.py -Or, using recursive globbing with ``bash`` or ``zsh``, you can view the stage 2 -changes to all Python source files recursively with:: +You can view the stage 2 changes to all Python source files recursively with:: - futurize --stage2 **/*.py + futurize --stage2 . To apply the changes, add the ``-w`` argument. From 41de8ee03f86460f0c3fa236a4c8106b7cca0cb0 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Wed, 8 May 2019 14:14:46 -0400 Subject: [PATCH 287/425] Fix changelog --- docs/whatsnew.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 68790527..a2b6fc05 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -48,7 +48,7 @@ effect on your system. This releases also fixes these bugs: -- Fix ``newbytes`` constructor bug. (Issue #163) +- Fix ``newbytes`` constructor bug. (Issue #171) - Fix semantics of ``bool()`` with ``newobject``. (Issue #211) - Fix ``standard_library.install_aliases()`` on PyPy. (Issue #205) - Fix assertRaises for ``pow`` and ``compile``` on Python 3.5. (Issue #183) From a949bf4cc64d1d10ead87c4fe32e5d4f492f2ffc Mon Sep 17 00:00:00 2001 From: Jeremy Paige Date: Wed, 8 May 2019 14:25:52 -0400 Subject: [PATCH 288/425] Add fixer for itertools Use of ifilter, imap, and izip as generator forms of PY2 builtins are replaced with future.builtins versions that are always generators --- docs/futurize.rst | 1 + src/future/builtins/__init__.py | 6 +++ src/libfuturize/fixes/__init__.py | 1 + src/libfuturize/fixes/fix_itertools.py | 54 +++++++++++++++++++ tests/test_future/test_futurize.py | 75 ++++++++++++++++++++++++++ 5 files changed, 137 insertions(+) create mode 100644 src/libfuturize/fixes/fix_itertools.py diff --git a/docs/futurize.rst b/docs/futurize.rst index 11520a6c..f5e9fddc 100644 --- a/docs/futurize.rst +++ b/docs/futurize.rst @@ -103,6 +103,7 @@ The complete set of fixers applied by ``futurize --stage1`` is: lib2to3.fixes.fix_ws_comma lib2to3.fixes.fix_xreadlines libfuturize.fixes.fix_absolute_import + libfuturize.fixes.fix_itertools libfuturize.fixes.fix_next_call libfuturize.fixes.fix_print_with_import libfuturize.fixes.fix_raise diff --git a/src/future/builtins/__init__.py b/src/future/builtins/__init__.py index 8bc1649d..6244959b 100644 --- a/src/future/builtins/__init__.py +++ b/src/future/builtins/__init__.py @@ -18,11 +18,14 @@ import builtins bytes = builtins.bytes dict = builtins.dict + filter = builtins.filter int = builtins.int list = builtins.list + map = builtins.map object = builtins.object range = builtins.range str = builtins.str + zip = builtins.zip __all__ = [] else: from future.types import (newbytes as bytes, @@ -32,6 +35,9 @@ newobject as object, newrange as range, newstr as str) + from itertools import (ifilter as filter, + imap as map, + izip as zip) from future import utils diff --git a/src/libfuturize/fixes/__init__.py b/src/libfuturize/fixes/__init__.py index 7de304da..24d37bcf 100644 --- a/src/libfuturize/fixes/__init__.py +++ b/src/libfuturize/fixes/__init__.py @@ -69,6 +69,7 @@ libfuturize_fix_names_stage1 = set([ 'libfuturize.fixes.fix_absolute_import', + 'libfuturize.fixes.fix_itertools', 'libfuturize.fixes.fix_next_call', # obj.next() -> next(obj). Unlike # lib2to3.fixes.fix_next, doesn't change # the ``next`` method to ``__next__``. diff --git a/src/libfuturize/fixes/fix_itertools.py b/src/libfuturize/fixes/fix_itertools.py new file mode 100644 index 00000000..f8234839 --- /dev/null +++ b/src/libfuturize/fixes/fix_itertools.py @@ -0,0 +1,54 @@ +""" +For the ``future`` package. + +Fixer for itertools methods that no longer deviate from builtins. + +This applies to imap, izip, and ifilter + +Adds this import line: + + from builtins import filter, map, zip + +at the top. +""" + +from lib2to3 import fixer_base + +from libfuturize.fixer_util import touch_import_top + +filter_expression = "name='ifilter'" +map_expression = "name='imap'" +zip_expression = "name='izip'" + +class FixFilter(fixer_base.BaseFix): + + PATTERN = """ + power< + ({0}) trailer< '(' args=[any] ')' > + rest=any* > + """.format(filter_expression) + + def transform(self, node, results): + touch_import_top(u'builtins', 'filter', node) + +class FixMap(fixer_base.BaseFix): + + PATTERN = """ + power< + ({0}) trailer< '(' args=[any] ')' > + rest=any* > + """.format(map_rexpression) + + def transform(self, node, results): + touch_import_top(u'builtins', 'map', node) + +class FixZip(fixer_base.BaseFix): + + PATTERN = """ + power< + ({0}) trailer< '(' args=[any] ')' > + rest=any* > + """.format(zip_expression) + + def transform(self, node, results): + touch_import_top(u'builtins', 'zip', node) diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index f2201141..7170103b 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -453,6 +453,81 @@ def test_xrange(self): """ self.convert_check(before, after, ignore_imports=False) + def test_filter(self): + """ + Tests correct handling of itertools.ifilter + """ + before = """ + import itertools + itertools.ifilter(lambda x: x%2, [1, 2, 3, 4]) + """ + after = """ + from builtins import filter + import itertools + filter(lambda x: x%2, [1, 2, 3, 4]) + """ + self.convert_check(before, after, ignore_imports=False) + before = """ + from itertools import ifilter + ifilter(lambda x: x%2, [1, 2, 3, 4]) + """ + after = """ + from builtins import filter + + filter(lambda x: x%2, [1, 2, 3, 4]) + """ + self.convert_check(before, after, ignore_imports=False) + + def test_map(self): + """ + Tests correct handling of itertools.imap + """ + before = """ + import itertools + itertools.imap(pow, (2,3,10), (5,2,3)) + """ + after = """ + from builtins import map + import itertools + map(pow, (2,3,10), (5,2,3)) + """ + self.convert_check(before, after, ignore_imports=False) + before = """ + from itertools import imap + imap(pow, (2,3,10), (5,2,3)) + """ + after = """ + from builtins import map + + map(pow, (2,3,10), (5,2,3)) + """ + self.convert_check(before, after, ignore_imports=False) + + def test_zip(self): + """ + Tests correct handling of itertools.izip + """ + before = """ + import itertools + itertools.izip('ABCD', 'xy') + """ + after = """ + from builtins import zip + import itertools + zip('ABCD', 'xy') + """ + self.convert_check(before, after, ignore_imports=False) + before = """ + from itertools import izip + izip('ABCD', 'xy') + """ + after = """ + from builtins import zip + + zip('ABCD', 'xy') + """ + self.convert_check(before, after, ignore_imports=False) + def test_source_coding_utf8(self): """ Tests to ensure that the source coding line is not corrupted or From 6a4064b9a106800cbfcfb2cf47fbcce282a53a5d Mon Sep 17 00:00:00 2001 From: Jeremy Paige Date: Wed, 8 May 2019 14:39:01 -0400 Subject: [PATCH 289/425] Fix typo in a949bf4 --- src/libfuturize/fixes/fix_itertools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libfuturize/fixes/fix_itertools.py b/src/libfuturize/fixes/fix_itertools.py index f8234839..de746307 100644 --- a/src/libfuturize/fixes/fix_itertools.py +++ b/src/libfuturize/fixes/fix_itertools.py @@ -37,7 +37,7 @@ class FixMap(fixer_base.BaseFix): power< ({0}) trailer< '(' args=[any] ')' > rest=any* > - """.format(map_rexpression) + """.format(map_expression) def transform(self, node, results): touch_import_top(u'builtins', 'map', node) From ede6f096dae610781e673f1fd91dd8c19c1060b4 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Thu, 16 May 2019 13:13:26 -0700 Subject: [PATCH 290/425] Make future.moves.copyreg just _be_ copyreg on PY3 The existing `from copyreg import *` is insufficient on Python 3 as `copyreg.__all__` does not include all of the public API names. --- src/future/moves/copyreg.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 * From e4b7fa1d3346ef2561fd424fd1cc51558c44f192 Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Wed, 3 Jul 2019 16:48:59 +0100 Subject: [PATCH 291/425] Avoid DeprecationWarning caused by invalid escape Fixes: #479 --- src/past/types/oldstr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/past/types/oldstr.py b/src/past/types/oldstr.py index a477d884..5a0e3789 100644 --- a/src/past/types/oldstr.py +++ b/src/past/types/oldstr.py @@ -20,7 +20,7 @@ def __instancecheck__(cls, instance): def unescape(s): - """ + r""" Interprets strings with escape sequences Example: From d42f0784860e2ac2acdde712799673032645ef7f Mon Sep 17 00:00:00 2001 From: "Jordan M. Adler" Date: Mon, 8 Jul 2019 15:53:39 -0700 Subject: [PATCH 292/425] Revert "Add fixer for itertools" --- docs/futurize.rst | 1 - src/future/builtins/__init__.py | 6 --- src/libfuturize/fixes/__init__.py | 1 - src/libfuturize/fixes/fix_itertools.py | 54 ------------------- tests/test_future/test_futurize.py | 75 -------------------------- 5 files changed, 137 deletions(-) delete mode 100644 src/libfuturize/fixes/fix_itertools.py diff --git a/docs/futurize.rst b/docs/futurize.rst index f5e9fddc..11520a6c 100644 --- a/docs/futurize.rst +++ b/docs/futurize.rst @@ -103,7 +103,6 @@ The complete set of fixers applied by ``futurize --stage1`` is: lib2to3.fixes.fix_ws_comma lib2to3.fixes.fix_xreadlines libfuturize.fixes.fix_absolute_import - libfuturize.fixes.fix_itertools libfuturize.fixes.fix_next_call libfuturize.fixes.fix_print_with_import libfuturize.fixes.fix_raise diff --git a/src/future/builtins/__init__.py b/src/future/builtins/__init__.py index 6244959b..8bc1649d 100644 --- a/src/future/builtins/__init__.py +++ b/src/future/builtins/__init__.py @@ -18,14 +18,11 @@ import builtins bytes = builtins.bytes dict = builtins.dict - filter = builtins.filter int = builtins.int list = builtins.list - map = builtins.map object = builtins.object range = builtins.range str = builtins.str - zip = builtins.zip __all__ = [] else: from future.types import (newbytes as bytes, @@ -35,9 +32,6 @@ newobject as object, newrange as range, newstr as str) - from itertools import (ifilter as filter, - imap as map, - izip as zip) from future import utils diff --git a/src/libfuturize/fixes/__init__.py b/src/libfuturize/fixes/__init__.py index 24d37bcf..7de304da 100644 --- a/src/libfuturize/fixes/__init__.py +++ b/src/libfuturize/fixes/__init__.py @@ -69,7 +69,6 @@ libfuturize_fix_names_stage1 = set([ 'libfuturize.fixes.fix_absolute_import', - 'libfuturize.fixes.fix_itertools', 'libfuturize.fixes.fix_next_call', # obj.next() -> next(obj). Unlike # lib2to3.fixes.fix_next, doesn't change # the ``next`` method to ``__next__``. diff --git a/src/libfuturize/fixes/fix_itertools.py b/src/libfuturize/fixes/fix_itertools.py deleted file mode 100644 index de746307..00000000 --- a/src/libfuturize/fixes/fix_itertools.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -For the ``future`` package. - -Fixer for itertools methods that no longer deviate from builtins. - -This applies to imap, izip, and ifilter - -Adds this import line: - - from builtins import filter, map, zip - -at the top. -""" - -from lib2to3 import fixer_base - -from libfuturize.fixer_util import touch_import_top - -filter_expression = "name='ifilter'" -map_expression = "name='imap'" -zip_expression = "name='izip'" - -class FixFilter(fixer_base.BaseFix): - - PATTERN = """ - power< - ({0}) trailer< '(' args=[any] ')' > - rest=any* > - """.format(filter_expression) - - def transform(self, node, results): - touch_import_top(u'builtins', 'filter', node) - -class FixMap(fixer_base.BaseFix): - - PATTERN = """ - power< - ({0}) trailer< '(' args=[any] ')' > - rest=any* > - """.format(map_expression) - - def transform(self, node, results): - touch_import_top(u'builtins', 'map', node) - -class FixZip(fixer_base.BaseFix): - - PATTERN = """ - power< - ({0}) trailer< '(' args=[any] ')' > - rest=any* > - """.format(zip_expression) - - def transform(self, node, results): - touch_import_top(u'builtins', 'zip', node) diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index 7170103b..f2201141 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -453,81 +453,6 @@ def test_xrange(self): """ self.convert_check(before, after, ignore_imports=False) - def test_filter(self): - """ - Tests correct handling of itertools.ifilter - """ - before = """ - import itertools - itertools.ifilter(lambda x: x%2, [1, 2, 3, 4]) - """ - after = """ - from builtins import filter - import itertools - filter(lambda x: x%2, [1, 2, 3, 4]) - """ - self.convert_check(before, after, ignore_imports=False) - before = """ - from itertools import ifilter - ifilter(lambda x: x%2, [1, 2, 3, 4]) - """ - after = """ - from builtins import filter - - filter(lambda x: x%2, [1, 2, 3, 4]) - """ - self.convert_check(before, after, ignore_imports=False) - - def test_map(self): - """ - Tests correct handling of itertools.imap - """ - before = """ - import itertools - itertools.imap(pow, (2,3,10), (5,2,3)) - """ - after = """ - from builtins import map - import itertools - map(pow, (2,3,10), (5,2,3)) - """ - self.convert_check(before, after, ignore_imports=False) - before = """ - from itertools import imap - imap(pow, (2,3,10), (5,2,3)) - """ - after = """ - from builtins import map - - map(pow, (2,3,10), (5,2,3)) - """ - self.convert_check(before, after, ignore_imports=False) - - def test_zip(self): - """ - Tests correct handling of itertools.izip - """ - before = """ - import itertools - itertools.izip('ABCD', 'xy') - """ - after = """ - from builtins import zip - import itertools - zip('ABCD', 'xy') - """ - self.convert_check(before, after, ignore_imports=False) - before = """ - from itertools import izip - izip('ABCD', 'xy') - """ - after = """ - from builtins import zip - - zip('ABCD', 'xy') - """ - self.convert_check(before, after, ignore_imports=False) - def test_source_coding_utf8(self): """ Tests to ensure that the source coding line is not corrupted or From d711695da6e54fb9351d0f989ea5cd6dd5d3e4c4 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Mon, 8 Jul 2019 16:32:06 -0700 Subject: [PATCH 293/425] Remove flakiness in test_single_exception --- tests/test_future/test_utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_future/test_utils.py b/tests/test_future/test_utils.py index 488c0064..30eac7a6 100644 --- a/tests/test_future/test_utils.py +++ b/tests/test_future/test_utils.py @@ -4,7 +4,7 @@ """ from __future__ import absolute_import, unicode_literals, print_function -import sys, traceback +import re, sys, traceback from future.builtins import * from future.utils import (old_div, istext, isbytes, native, PY2, PY3, native_str, raise_, as_native_str, ensure_new_type, @@ -335,12 +335,14 @@ def test_single_exception_stacktrace(self): if PY2: expected += 'CustomException: ERROR\n' else: - expected += 'tests.test_future.test_utils.CustomException: ERROR\n' + expected += 'test_future.test_utils.CustomException: ERROR\n' try: raise CustomException('ERROR') except: - self.assertEqual(expected, traceback.format_exc()) + ret = re.sub(r'"[^"]*tests/test_future', '"/opt/python-future/tests/test_future', traceback.format_exc()) + ret = re.sub(r', line \d+,', ', line 328,', ret) + self.assertEqual(expected, ret) else: self.fail('No exception raised') From 2b82a8818005755920e6978c5ac3055731e43997 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Mon, 8 Jul 2019 16:50:37 -0700 Subject: [PATCH 294/425] Remove flakiness in test_chained_exception --- tests/test_future/test_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_future/test_utils.py b/tests/test_future/test_utils.py index 30eac7a6..314d7698 100644 --- a/tests/test_future/test_utils.py +++ b/tests/test_future/test_utils.py @@ -368,7 +368,8 @@ def test_chained_exceptions_stacktrace(self): except ValueError as val_err: raise_from(CustomException('ERROR'), val_err) except Exception as err: - self.assertEqual(expected.splitlines(), traceback.format_exc().splitlines()) + ret = re.sub(r'"[^"]*tests/test_future', '"/opt/python-future/tests/test_future', traceback.format_exc()) + self.assertEqual(expected.splitlines(), ret.splitlines()) else: self.fail('No exception raised') From f7c7ebd8dc7db9580927de6644271c784d204b0e Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Mon, 8 Jul 2019 16:54:09 -0700 Subject: [PATCH 295/425] iter --- tests/test_future/test_utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_future/test_utils.py b/tests/test_future/test_utils.py index 314d7698..afe14041 100644 --- a/tests/test_future/test_utils.py +++ b/tests/test_future/test_utils.py @@ -349,15 +349,15 @@ def test_single_exception_stacktrace(self): if PY2: def test_chained_exceptions_stacktrace(self): expected = '''Traceback (most recent call last): - File "/opt/python-future/tests/test_future/test_utils.py", line 354, in test_chained_exceptions_stacktrace + File "/opt/python-future/tests/test_future/test_utils.py", line 1, in test_chained_exceptions_stacktrace raise_from(CustomException('ERROR'), val_err) - File "/opt/python-future/src/future/utils/__init__.py", line 456, in raise_from + File "/opt/python-future/src/future/utils/__init__.py", line 1, in raise_from raise e CustomException: ERROR The above exception was the direct cause of the following exception: - File "/opt/python-future/tests/test_future/test_utils.py", line 352, in test_chained_exceptions_stacktrace + File "/opt/python-future/tests/test_future/test_utils.py", line 1, in test_chained_exceptions_stacktrace raise ValueError('Wooops') ValueError: Wooops ''' @@ -369,6 +369,7 @@ def test_chained_exceptions_stacktrace(self): raise_from(CustomException('ERROR'), val_err) except Exception as err: ret = re.sub(r'"[^"]*tests/test_future', '"/opt/python-future/tests/test_future', traceback.format_exc()) + ret = re.sub(r', line \d+,', ', line 1,', ret) self.assertEqual(expected.splitlines(), ret.splitlines()) else: self.fail('No exception raised') From a0e400767763c7fca39546514f14621e625caa4d Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Mon, 8 Jul 2019 17:00:57 -0700 Subject: [PATCH 296/425] iter --- tests/test_future/test_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_future/test_utils.py b/tests/test_future/test_utils.py index afe14041..95ef4d67 100644 --- a/tests/test_future/test_utils.py +++ b/tests/test_future/test_utils.py @@ -369,6 +369,7 @@ def test_chained_exceptions_stacktrace(self): raise_from(CustomException('ERROR'), val_err) except Exception as err: ret = re.sub(r'"[^"]*tests/test_future', '"/opt/python-future/tests/test_future', traceback.format_exc()) + ret = re.sub(r'"[^"]*src/future', '"/opt/python-future/src/future', traceback.format_exc()) ret = re.sub(r', line \d+,', ', line 1,', ret) self.assertEqual(expected.splitlines(), ret.splitlines()) else: From 95550463a8fcb699096d9864e97a5e4d3cc43549 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Mon, 8 Jul 2019 17:04:24 -0700 Subject: [PATCH 297/425] bugfix --- tests/test_future/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_future/test_utils.py b/tests/test_future/test_utils.py index 95ef4d67..73de3076 100644 --- a/tests/test_future/test_utils.py +++ b/tests/test_future/test_utils.py @@ -369,7 +369,7 @@ def test_chained_exceptions_stacktrace(self): raise_from(CustomException('ERROR'), val_err) except Exception as err: ret = re.sub(r'"[^"]*tests/test_future', '"/opt/python-future/tests/test_future', traceback.format_exc()) - ret = re.sub(r'"[^"]*src/future', '"/opt/python-future/src/future', traceback.format_exc()) + ret = re.sub(r'"[^"]*src/future', '"/opt/python-future/src/future', ret) ret = re.sub(r', line \d+,', ', line 1,', ret) self.assertEqual(expected.splitlines(), ret.splitlines()) else: From 015e837df95580e164fb8595236aea382b279c37 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Mon, 8 Jul 2019 17:10:21 -0700 Subject: [PATCH 298/425] bugfix --- tests/test_future/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_future/test_utils.py b/tests/test_future/test_utils.py index 73de3076..9034400f 100644 --- a/tests/test_future/test_utils.py +++ b/tests/test_future/test_utils.py @@ -369,7 +369,7 @@ def test_chained_exceptions_stacktrace(self): raise_from(CustomException('ERROR'), val_err) except Exception as err: ret = re.sub(r'"[^"]*tests/test_future', '"/opt/python-future/tests/test_future', traceback.format_exc()) - ret = re.sub(r'"[^"]*src/future', '"/opt/python-future/src/future', ret) + ret = re.sub(r'"[^"]*future/utils/__init__.py', '"/opt/python-future/src/future/utils/__init__.py', ret) ret = re.sub(r', line \d+,', ', line 1,', ret) self.assertEqual(expected.splitlines(), ret.splitlines()) else: From d2a61c10550423abad7e88701b1e9ef210f59400 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Mon, 22 Jul 2019 12:47:42 -0700 Subject: [PATCH 299/425] Don't import past.translation by default when importing past --- README.rst | 4 ++-- docs/changelog.rst | 10 +++++----- docs/quickstart.rst | 2 +- docs/translation.rst | 4 ++-- src/past/__init__.py | 4 +--- src/past/translation/__init__.py | 6 +++++- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/README.rst b/README.rst index 807ea2a0..6266618c 100644 --- a/README.rst +++ b/README.rst @@ -243,7 +243,7 @@ Example: $ python3 - >>> from past import autotranslate + >>> from past.translation import autotranslate >>> autotranslate(['plotrique']) >>> import plotrique @@ -270,7 +270,7 @@ Licensing :Sponsors: Python Charmers Pty Ltd, Australia, and Python Charmers Pte Ltd, Singapore. http://pythoncharmers.com - + Pinterest https://opensource.pinterest.com/ :Licence: MIT. See ``LICENSE.txt`` or `here `_. diff --git a/docs/changelog.rst b/docs/changelog.rst index aa317b96..059ad4f5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -616,10 +616,10 @@ it like this:: $ pip3 install plotrique==0.2.5-7 --no-compile # to ignore SyntaxErrors $ python3 -Then pass in a whitelist of module name prefixes to the ``past.autotranslate()`` -function. Example:: +Then pass in a whitelist of module name prefixes to the +``past.translation.autotranslate()`` function. Example:: - >>> from past import autotranslate + >>> from past.translation import autotranslate >>> autotranslate(['plotrique']) >>> import plotrique @@ -949,8 +949,8 @@ v0.11.3: objects as on Py3. v0.11.2: - * The ``past.autotranslate`` feature now finds modules to import more - robustly and works with Python eggs. + * The ``past.translation.autotranslate`` feature now finds modules to import + more robustly and works with Python eggs. v0.11.1: * Update to ``requirements_py26.txt`` for Python 2.6. Small updates to diff --git a/docs/quickstart.rst b/docs/quickstart.rst index a5e464f9..6042e059 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -132,7 +132,7 @@ environment:: Then add the following code at the top of your (Py3 or Py2/3-compatible) code:: - from past import autotranslate + from past.translation import autotranslate autotranslate(['mypackagename']) import mypackagename diff --git a/docs/translation.rst b/docs/translation.rst index 49f558b0..632c46b1 100644 --- a/docs/translation.rst +++ b/docs/translation.rst @@ -21,9 +21,9 @@ Here is how to use it:: $ python3 Then pass in a whitelist of module name prefixes to the -``past.autotranslate()`` function. Example:: +``past.translation.autotranslate()`` function. Example:: - >>> from past import autotranslate + >>> from past.translation import autotranslate >>> autotranslate(['plotrique']) >>> import plotrique diff --git a/src/past/__init__.py b/src/past/__init__.py index 3b5d9db1..07422a03 100644 --- a/src/past/__init__.py +++ b/src/past/__init__.py @@ -61,7 +61,7 @@ $ python3 - >>> from past import autotranslate + >>> from past.translation import autotranslate >>> authotranslate('mypy2module') >>> import mypy2module @@ -84,8 +84,6 @@ The software is distributed under an MIT licence. See LICENSE.txt. """ - -from past.translation import install_hooks as autotranslate from future import __version__, __copyright__, __license__ __title__ = 'past' diff --git a/src/past/translation/__init__.py b/src/past/translation/__init__.py index 1b941662..7c678866 100644 --- a/src/past/translation/__init__.py +++ b/src/past/translation/__init__.py @@ -16,7 +16,7 @@ Once your Py2 package is installed in the usual module search path, the import hook is invoked as follows: - >>> from past import autotranslate + >>> from past.translation import autotranslate >>> autotranslate('mypackagename') Or: @@ -479,3 +479,7 @@ def __enter__(self): def __exit__(self, *args): if self.hooks_were_installed: install_hooks() + + +# alias +autotranslate = install_hooks From 6ff061e57f772c19f7afd04dce5f15212609352f Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 3 Aug 2019 14:15:25 +0200 Subject: [PATCH 300/425] Use print() function in both Python 2 and Python 3 __futurize --stage1 -w docs/other/find_pattern.py__ --- docs/other/find_pattern.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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): From e2c320bf627c501656a8c2dc3acf51be1f716faf Mon Sep 17 00:00:00 2001 From: Tobias Megies Date: Fri, 13 Sep 2019 15:14:20 +0200 Subject: [PATCH 301/425] fix collections abc import for py38 --- src/future/backports/http/client.py | 8 ++++++-- src/future/types/newbytes.py | 8 ++++++-- src/future/types/newint.py | 6 ++++-- src/future/types/newmemoryview.py | 8 +++++--- src/future/types/newrange.py | 7 ++++++- src/future/types/newstr.py | 4 +++- 6 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/future/backports/http/client.py b/src/future/backports/http/client.py index 1a43f013..e663d125 100644 --- a/src/future/backports/http/client.py +++ b/src/future/backports/http/client.py @@ -79,11 +79,15 @@ import io import os import socket -import collections from future.backports.urllib.parse import urlsplit import warnings from array import array +if PY2: + from collections import Iterable +else: + from collections.abc import Iterable + __all__ = ["HTTPResponse", "HTTPConnection", "HTTPException", "NotConnected", "UnknownProtocol", "UnknownTransferEncoding", "UnimplementedFileMode", @@ -902,7 +906,7 @@ def send(self, data): try: self.sock.sendall(data) except TypeError: - if isinstance(data, collections.Iterable): + if isinstance(data, Iterable): for d in data: self.sock.sendall(d) else: diff --git a/src/future/types/newbytes.py b/src/future/types/newbytes.py index 2a337c86..c9d584a7 100644 --- a/src/future/types/newbytes.py +++ b/src/future/types/newbytes.py @@ -5,15 +5,19 @@ different beast to the Python 3 bytes object. """ -from collections import Iterable from numbers import Integral import string import copy -from future.utils import istext, isbytes, PY3, with_metaclass +from future.utils import istext, isbytes, PY2, PY3, with_metaclass from future.types import no, issubset from future.types.newobject import newobject +if PY2: + from collections import Iterable +else: + from collections.abc import Iterable + _builtin_bytes = bytes diff --git a/src/future/types/newint.py b/src/future/types/newint.py index 705b8fa9..748dba9d 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): @@ -356,7 +358,7 @@ def from_bytes(cls, mybytes, byteorder='big', signed=False): raise TypeError("cannot convert unicode objects to bytes") # mybytes can also be passed as a sequence of integers on Py3. # Test for this: - elif isinstance(mybytes, collections.Iterable): + elif isinstance(mybytes, Iterable): mybytes = newbytes(mybytes) b = mybytes if byteorder == 'big' else mybytes[::-1] if len(b) == 0: diff --git a/src/future/types/newmemoryview.py b/src/future/types/newmemoryview.py index 72c6990a..09f804dc 100644 --- a/src/future/types/newmemoryview.py +++ b/src/future/types/newmemoryview.py @@ -1,14 +1,16 @@ """ A pretty lame implementation of a memoryview object for Python 2.6. """ - -from collections import Iterable from numbers import Integral import string -from future.utils import istext, isbytes, PY3, with_metaclass +from future.utils import istext, isbytes, PY2, with_metaclass from future.types import no, issubset +if PY2: + from collections import Iterable +else: + from collections.abc import Iterable # class BaseNewBytes(type): # def __instancecheck__(cls, instance): diff --git a/src/future/types/newrange.py b/src/future/types/newrange.py index 9173b050..eda01a5a 100644 --- a/src/future/types/newrange.py +++ b/src/future/types/newrange.py @@ -19,7 +19,12 @@ """ from __future__ import absolute_import -from collections import Sequence, Iterator +from future.utils import PY2 + +if PY2: + from collections import Sequence, Iterator +else: + from collections.abc import Sequence, Iterator from itertools import islice from future.backports.misc import count # with step parameter on Py2.6 diff --git a/src/future/types/newstr.py b/src/future/types/newstr.py index d41ea969..8ca191f9 100644 --- a/src/future/types/newstr.py +++ b/src/future/types/newstr.py @@ -40,7 +40,6 @@ """ -from collections import Iterable from numbers import Number from future.utils import PY3, istext, with_metaclass, isnewbytes @@ -51,6 +50,9 @@ if PY3: # We'll probably never use newstr on Py3 anyway... unicode = str + from collections.abc import Iterable +else: + from collections import Iterable class BaseNewStr(type): From 52b434d2cc0bfa01185524a82198558ee4272e5b Mon Sep 17 00:00:00 2001 From: Anika Mukherji Date: Thu, 26 Sep 2019 18:35:59 -0700 Subject: [PATCH 302/425] remove import for isnewbytes --- src/future/utils/__init__.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index 59920077..34e08fcc 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -577,15 +577,13 @@ 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): From 934cebb76e861d4ebab1b97957f5f9522a2ea4c3 Mon Sep 17 00:00:00 2001 From: Anika Mukherji Date: Fri, 27 Sep 2019 08:44:05 -0700 Subject: [PATCH 303/425] edit yml file per https://travis-ci.community/t/python-builds-started-failing-to-download-2-7-12/3516 --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index eb28e459..fc2e14ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,9 @@ sudo: false language: python cache: pip +dist: + - trusty + matrix: include: - python: 2.6 From 08dec19511b7fdae5d24e7ff8bfbeba0fccdbd18 Mon Sep 17 00:00:00 2001 From: Anika Mukherji Date: Fri, 27 Sep 2019 09:03:50 -0700 Subject: [PATCH 304/425] minor change to run travis again --- src/future/utils/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index 34e08fcc..4b2f1f66 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -580,6 +580,7 @@ def isnewbytes(obj): 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) """ From 95ed95e067795d0b236b04cc94cd95458c5d4a2a Mon Sep 17 00:00:00 2001 From: Anika Mukherji Date: Fri, 27 Sep 2019 09:15:47 -0700 Subject: [PATCH 305/425] add check for output attr --- src/future/tests/base.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/future/tests/base.py b/src/future/tests/base.py index a7cc8ec1..958067ac 100644 --- a/src/future/tests/base.py +++ b/src/future/tests/base.py @@ -342,6 +342,9 @@ def _futurize_test_script(self, filename='mytestscript.py', stages=(1, 2), '----\n%s\n----' % f.read(), ) ErrorClass = (FuturizeError if 'futurize' in script else PasteurizeError) + if not hasattr(e, 'output'): + # The attribute CalledProcessError.output doesn't exist on Py2.6 + e.output = None raise ErrorClass(msg, e.returncode, e.cmd, output=e.output) return output From 2c84ded5494ce22f4de15966f9a840d94945047e Mon Sep 17 00:00:00 2001 From: Anika Mukherji Date: Fri, 27 Sep 2019 11:51:15 -0700 Subject: [PATCH 306/425] drop py 2.6 --- .travis.yml | 6 ++---- setup.py | 3 +-- tox.ini | 3 +-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index fc2e14ea..a6d7113f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,17 +2,15 @@ sudo: false language: python cache: pip -dist: - - trusty matrix: include: - - python: 2.6 - env: TOXENV=py26 - python: 2.7 env: TOXENV=py27 - python: 3.3 env: TOXENV=py33 + dist: + - trusty - python: 3.4 env: TOXENV=py34 - python: 3.5 diff --git a/setup.py b/setup.py index 11d694c2..ad137db6 100755 --- a/setup.py +++ b/setup.py @@ -95,7 +95,6 @@ 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", @@ -175,7 +174,7 @@ packages=PACKAGES, package_data=PACKAGE_DATA, include_package_data=True, - python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*", + python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*", classifiers=CLASSIFIERS, **setup_kwds ) diff --git a/tox.ini b/tox.ini index f5c013f8..1e4cb150 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,8 @@ [tox] -envlist = py26,py27,py33,py34,py35,py36,py37 +envlist = py27,py33,py34,py35,py36,py37 [testenv] deps = pytest unittest2 - py26: importlib commands = pytest {posargs} From 94a86f9ec543087ec96c11ac4f3c60eafd9e85cc Mon Sep 17 00:00:00 2001 From: Anika Mukherji Date: Fri, 27 Sep 2019 12:14:18 -0700 Subject: [PATCH 307/425] inline --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a6d7113f..ed4618e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,8 @@ matrix: env: TOXENV=py27 - python: 3.3 env: TOXENV=py33 - dist: - - trusty + dist: trusty + sudo: false - python: 3.4 env: TOXENV=py34 - python: 3.5 From 730e6b7caf73773609a3913f5fc4a26a5456a405 Mon Sep 17 00:00:00 2001 From: Anika Mukherji Date: Fri, 27 Sep 2019 12:19:40 -0700 Subject: [PATCH 308/425] removing hasattr checks since removing py2.6 --- src/future/tests/base.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/future/tests/base.py b/src/future/tests/base.py index 958067ac..f0fae871 100644 --- a/src/future/tests/base.py +++ b/src/future/tests/base.py @@ -342,9 +342,6 @@ def _futurize_test_script(self, filename='mytestscript.py', stages=(1, 2), '----\n%s\n----' % f.read(), ) ErrorClass = (FuturizeError if 'futurize' in script else PasteurizeError) - if not hasattr(e, 'output'): - # The attribute CalledProcessError.output doesn't exist on Py2.6 - e.output = None raise ErrorClass(msg, e.returncode, e.cmd, output=e.output) return output @@ -368,9 +365,6 @@ def _run_test_script(self, filename='mytestscript.py', 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 From 7ba867f670840898f1027f7af38d1a89331e6db7 Mon Sep 17 00:00:00 2001 From: Anika Mukherji Date: Mon, 30 Sep 2019 11:48:51 -0700 Subject: [PATCH 309/425] add back in py2.6 --- .travis.yml | 3 +++ setup.py | 2 +- src/future/tests/base.py | 7 +++++++ tox.ini | 3 ++- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ed4618e3..4b74e8d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,9 @@ cache: pip matrix: include: + - python: 2.6 + env: TOXENV=py26 + dist: trusty - python: 2.7 env: TOXENV=py27 - python: 3.3 diff --git a/setup.py b/setup.py index ad137db6..7a91c57a 100755 --- a/setup.py +++ b/setup.py @@ -174,7 +174,7 @@ packages=PACKAGES, package_data=PACKAGE_DATA, include_package_data=True, - python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*", + python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*", classifiers=CLASSIFIERS, **setup_kwds ) diff --git a/src/future/tests/base.py b/src/future/tests/base.py index f0fae871..4ef437ba 100644 --- a/src/future/tests/base.py +++ b/src/future/tests/base.py @@ -342,6 +342,10 @@ def _futurize_test_script(self, filename='mytestscript.py', stages=(1, 2), '----\n%s\n----' % f.read(), ) ErrorClass = (FuturizeError if 'futurize' in script else PasteurizeError) + + if not hasattr(e, 'output'): + # The attribute CalledProcessError.output doesn't exist on Py2.6 + e.output = None raise ErrorClass(msg, e.returncode, e.cmd, output=e.output) return output @@ -365,6 +369,9 @@ def _run_test_script(self, filename='mytestscript.py', 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 diff --git a/tox.ini b/tox.ini index 1e4cb150..f5c013f8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,9 @@ [tox] -envlist = py27,py33,py34,py35,py36,py37 +envlist = py26,py27,py33,py34,py35,py36,py37 [testenv] deps = pytest unittest2 + py26: importlib commands = pytest {posargs} From a27243bda70583e1cbc9e06c6d3bf07a26da0684 Mon Sep 17 00:00:00 2001 From: Anika Mukherji Date: Wed, 2 Oct 2019 18:25:19 -0700 Subject: [PATCH 310/425] add py2.6 classifier --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 7a91c57a..11d694c2 100755 --- a/setup.py +++ b/setup.py @@ -95,6 +95,7 @@ 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", From 600344df9288810bf5092336e17abfc80d2e6665 Mon Sep 17 00:00:00 2001 From: Anika Mukherji Date: Wed, 2 Oct 2019 23:27:07 -0700 Subject: [PATCH 311/425] attempt to fix another py2.6 test --- tests/test_future/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_future/test_utils.py b/tests/test_future/test_utils.py index 9034400f..ed2f6da8 100644 --- a/tests/test_future/test_utils.py +++ b/tests/test_future/test_utils.py @@ -382,7 +382,7 @@ def __str__(self): out = Exception.__str__(self) if hasattr(self, '__cause__') and self.__cause__ and hasattr(self.__cause__, '__traceback__') and self.__cause__.__traceback__: out += '\n\nThe above exception was the direct cause of the following exception:\n\n' - out += ''.join(traceback.format_tb(self.__cause__.__traceback__) + ['{}: {}'.format(self.__cause__.__class__.__name__, self.__cause__)]) + out += ''.join(traceback.format_tb(self.__cause__.__traceback__) + ['{}: {}'.format(self.__cause__.__class__.__name__, self.__cause__.message)]) return out else: pass From 8cb18050eae94ba5a4abeace32ed0f7533cc1c84 Mon Sep 17 00:00:00 2001 From: Anika Mukherji Date: Thu, 3 Oct 2019 09:16:46 -0700 Subject: [PATCH 312/425] wrap str method in try/except to see why its fialing --- tests/test_future/test_utils.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/test_future/test_utils.py b/tests/test_future/test_utils.py index ed2f6da8..a115593b 100644 --- a/tests/test_future/test_utils.py +++ b/tests/test_future/test_utils.py @@ -379,11 +379,14 @@ def test_chained_exceptions_stacktrace(self): class CustomException(Exception): if PY2: def __str__(self): - out = Exception.__str__(self) - if hasattr(self, '__cause__') and self.__cause__ and hasattr(self.__cause__, '__traceback__') and self.__cause__.__traceback__: - out += '\n\nThe above exception was the direct cause of the following exception:\n\n' - out += ''.join(traceback.format_tb(self.__cause__.__traceback__) + ['{}: {}'.format(self.__cause__.__class__.__name__, self.__cause__.message)]) - return out + try: + out = Exception.__str__(self) + if hasattr(self, '__cause__') and self.__cause__ and hasattr(self.__cause__, '__traceback__') and self.__cause__.__traceback__: + out += '\n\nThe above exception was the direct cause of the following exception:\n\n' + out += ''.join(traceback.format_tb(self.__cause__.__traceback__) + ['{}: {}'.format(self.__cause__.__class__.__name__, self.__cause__)]) + return out + except Exception as e: + print(e) else: pass From 5cfebd1c0e1a326c96c94d1b7e19454ef8769db6 Mon Sep 17 00:00:00 2001 From: Anika Mukherji Date: Thu, 3 Oct 2019 09:30:17 -0700 Subject: [PATCH 313/425] add indices --- tests/test_future/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_future/test_utils.py b/tests/test_future/test_utils.py index a115593b..b6a27d98 100644 --- a/tests/test_future/test_utils.py +++ b/tests/test_future/test_utils.py @@ -383,7 +383,7 @@ def __str__(self): out = Exception.__str__(self) if hasattr(self, '__cause__') and self.__cause__ and hasattr(self.__cause__, '__traceback__') and self.__cause__.__traceback__: out += '\n\nThe above exception was the direct cause of the following exception:\n\n' - out += ''.join(traceback.format_tb(self.__cause__.__traceback__) + ['{}: {}'.format(self.__cause__.__class__.__name__, self.__cause__)]) + out += ''.join(traceback.format_tb(self.__cause__.__traceback__) + ['{0}: {1}'.format(self.__cause__.__class__.__name__, self.__cause__)]) return out except Exception as e: print(e) From 4aaf34d92e4ac9e3b1828a84a78aaaa3f607ba6b Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Wed, 9 Oct 2019 17:04:52 -0700 Subject: [PATCH 314/425] Prepare 0.18.0 release --- docs/compatible_idioms.rst | 2 +- docs/conf.py | 2 +- docs/credits.rst | 24 ++++++++++++++++- .../Writing Python 2-3 compatible code.ipynb | 2 +- docs/whatsnew.rst | 27 ++++++++++++++++++- src/future/__init__.py | 6 ++--- 6 files changed, 55 insertions(+), 8 deletions(-) diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index 9ba94280..b0cb05a3 100644 --- a/docs/compatible_idioms.rst +++ b/docs/compatible_idioms.rst @@ -3,7 +3,7 @@ Cheat Sheet: Writing Python 2-3 compatible code =============================================== -- **Copyright (c):** 2013-2018 Python Charmers Pty Ltd, Australia. +- **Copyright (c):** 2013-2019 Python Charmers Pty Ltd, Australia. - **Author:** Ed Schofield. - **Licence:** Creative Commons Attribution. diff --git a/docs/conf.py b/docs/conf.py index 72911405..fd106fa0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,7 +51,7 @@ # General information about the project. project = u'Python-Future' -copyright = u'2013-2018, Python Charmers Pty Ltd, Australia' +copyright = u'2013-2019, Python Charmers Pty Ltd, Australia' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/credits.rst b/docs/credits.rst index 23e00f3b..e66136d5 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -8,7 +8,7 @@ Licence The software is distributed under an MIT licence. The text is as follows (from ``LICENSE.txt``):: - Copyright (c) 2013-2018 Python Charmers Pty Ltd, Australia + Copyright (c) 2013-2019 Python Charmers Pty Ltd, Australia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -47,16 +47,25 @@ Authors ------- Python-Future is largely written by Ed Schofield with the help of various contributors: +- Jordan Adler +- Kyle Altendorf - Grant Bakker - Jacob Beck +- Fumihiro (Ben) Bessho +- Shiva Bhusal - Nate Bogdanowicz +- Tomer Chachamu - Christian Clauss - Denis Cornehl - Nicolas Delaby +- Chad Dombrova - Jon Dufresne - Corey Farwell - Eric Firing +- Joe Gordon - Maximilian Hils +- Miro Hrončok +- Mark Huang - Martijn Jacobs - Michael Joseph - Waldemar Kornewald @@ -66,23 +75,36 @@ Python-Future is largely written by Ed Schofield with th - Joshua Landau - German Larrain - Chris Lasher +- ghanshyam lele - Calum Lind +- Tobias Megies +- Anika Mukherji - Jon Parise - Matthew Parnell - Miga Purg - Éloi Rivard +- Sesh Sadasivam - Elliott Sales de Andrade +- Aiden Scandella - Yury Selivanov - Tim Shaffer +- Sameera Somisetty - Louis Sautier +- Gregory P. Smith - Daniel Szoska - Flaviu Tamas - Jeff Tratner - Tim Tröndle - Brad Walker +- Andrew Wason +- Jeff Widman +- Dan Yeaw +- Hackalog (GitHub user) - lsm (GiHub user) - Mystic-Mirage (GitHub user) - str4d (GitHub user) +- ucodery (GitHub user) +- urain39 (GitHub user) - 9seconds (GitHub user) - Varriount (GitHub user) diff --git a/docs/notebooks/Writing Python 2-3 compatible code.ipynb b/docs/notebooks/Writing Python 2-3 compatible code.ipynb index 40859c4e..0f585d29 100644 --- a/docs/notebooks/Writing Python 2-3 compatible code.ipynb +++ b/docs/notebooks/Writing Python 2-3 compatible code.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "- **Copyright (c):** 2013-2018 Python Charmers Pty Ltd, Australia.\n", + "- **Copyright (c):** 2013-2019 Python Charmers Pty Ltd, Australia.\n", "- **Author:** Ed Schofield.\n", "- **Licence:** Creative Commons Attribution.\n", "\n", diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index a2b6fc05..50b2a99c 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -3,7 +3,32 @@ What's New ********** -What's new in version 0.17.1 (2019-10-30) +What's new in version 0.18.0 (2019-10-09) +========================================= +This is a major bug-fix and feature release, including: + +- Fix collections.abc import for py38+ +- Remove import for isnewbytes() function, reducing CPU cost significantly +- Fix bug with importing past.translation when importing past which breaks zipped python installations +- Fix an issue with copyreg import under Py3 that results in unexposed stdlib functionality +- Export and document types in future.utils +- Update behavior of newstr.__eq__() to match str.__eq__() as per reference docs +- Fix raising and the raising fixer to handle cases where the syntax is ambigious +- Allow "default" parameter in min() and max() (Issue #334) +- Implement __hash__() in newstr (Issue #454) +- Future proof some version checks to handle the fact that Py4 won't be a major breaking release +- Fix urllib.request imports for Python 3.8 compatibility (Issue #447) +- Fix future import ordering (Issue #445) +- Fixed bug in fix_division_safe fixture (Issue #434) +- Do not globally destroy re.ASCII in PY3 +- Fix a bug in email.Message.set_boundary() (Issue #429) +- Implement format_map() in str +- Implement readinto() for socket.fp + +As well as a number of corrections to a variety of documentation, and updates to +test infrastructure. + +What's new in version 0.17.1 (2018-10-30) ========================================= This release address a packaging error because of an erroneous declaration that any built wheels are universal. diff --git a/src/future/__init__.py b/src/future/__init__.py index f7a6fbeb..bbb77594 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -68,7 +68,7 @@ Credits ------- -:Author: Ed Schofield +:Author: Ed Schofield, Jordan M. Adler, et al :Sponsor: Python Charmers Pty Ltd, Australia, and Python Charmers Pte Ltd, Singapore. http://pythoncharmers.com :Others: See docs/credits.rst or http://python-future.org/credits.html @@ -86,8 +86,8 @@ __license__ = 'MIT' __copyright__ = 'Copyright 2013-2018 Python Charmers Pty Ltd' __ver_major__ = 0 -__ver_minor__ = 17 -__ver_patch__ = 1 +__ver_minor__ = 18 +__ver_patch__ = 0 __ver_sub__ = '' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From c2e26d4ef17508b9270f4e0e49242fd3b0423913 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Wed, 9 Oct 2019 17:10:28 -0700 Subject: [PATCH 315/425] Update dates --- LICENSE.txt | 2 +- README.rst | 2 +- futurize.py | 2 +- pasteurize.py | 2 +- src/future/__init__.py | 4 ++-- src/past/__init__.py | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index d41c85d1..4c904dba 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2013-2018 Python Charmers Pty Ltd, Australia +Copyright (c) 2013-2019 Python Charmers Pty Ltd, Australia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.rst b/README.rst index 6266618c..ea806538 100644 --- a/README.rst +++ b/README.rst @@ -266,7 +266,7 @@ Licensing :Author: Ed Schofield, Jordan M. Adler, et al -:Copyright: 2013-2018 Python Charmers Pty Ltd, Australia. +:Copyright: 2013-2019 Python Charmers Pty Ltd, Australia. :Sponsors: Python Charmers Pty Ltd, Australia, and Python Charmers Pte Ltd, Singapore. http://pythoncharmers.com diff --git a/futurize.py b/futurize.py index 41080cf0..cb446ab2 100755 --- a/futurize.py +++ b/futurize.py @@ -13,7 +13,7 @@ Licensing --------- -Copyright 2013-2018 Python Charmers Pty Ltd, Australia. +Copyright 2013-2019 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ diff --git a/pasteurize.py b/pasteurize.py index c0bd7e09..2b98327c 100755 --- a/pasteurize.py +++ b/pasteurize.py @@ -12,7 +12,7 @@ Licensing --------- -Copyright 2013-2018 Python Charmers Pty Ltd, Australia. +Copyright 2013-2019 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ diff --git a/src/future/__init__.py b/src/future/__init__.py index bbb77594..d44e16c8 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -76,7 +76,7 @@ Licensing --------- -Copyright 2013-2018 Python Charmers Pty Ltd, Australia. +Copyright 2013-2019 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ @@ -84,7 +84,7 @@ __title__ = 'future' __author__ = 'Ed Schofield' __license__ = 'MIT' -__copyright__ = 'Copyright 2013-2018 Python Charmers Pty Ltd' +__copyright__ = 'Copyright 2013-2019 Python Charmers Pty Ltd' __ver_major__ = 0 __ver_minor__ = 18 __ver_patch__ = 0 diff --git a/src/past/__init__.py b/src/past/__init__.py index 07422a03..14713039 100644 --- a/src/past/__init__.py +++ b/src/past/__init__.py @@ -74,13 +74,13 @@ Credits ------- -:Author: Ed Schofield +:Author: Ed Schofield, Jordan M. Adler, et al :Sponsor: Python Charmers Pty Ltd, Australia: http://pythoncharmers.com Licensing --------- -Copyright 2013-2018 Python Charmers Pty Ltd, Australia. +Copyright 2013-2019 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ From 25d1141d1c04365d80e7b1858b446e2689878345 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Wed, 9 Oct 2019 17:10:28 -0700 Subject: [PATCH 316/425] Update dates --- LICENSE.txt | 2 +- README.rst | 2 +- futurize.py | 2 +- pasteurize.py | 2 +- src/future/__init__.py | 4 ++-- src/past/__init__.py | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index d41c85d1..4c904dba 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2013-2018 Python Charmers Pty Ltd, Australia +Copyright (c) 2013-2019 Python Charmers Pty Ltd, Australia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.rst b/README.rst index 6266618c..ea806538 100644 --- a/README.rst +++ b/README.rst @@ -266,7 +266,7 @@ Licensing :Author: Ed Schofield, Jordan M. Adler, et al -:Copyright: 2013-2018 Python Charmers Pty Ltd, Australia. +:Copyright: 2013-2019 Python Charmers Pty Ltd, Australia. :Sponsors: Python Charmers Pty Ltd, Australia, and Python Charmers Pte Ltd, Singapore. http://pythoncharmers.com diff --git a/futurize.py b/futurize.py index 41080cf0..cb446ab2 100755 --- a/futurize.py +++ b/futurize.py @@ -13,7 +13,7 @@ Licensing --------- -Copyright 2013-2018 Python Charmers Pty Ltd, Australia. +Copyright 2013-2019 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ diff --git a/pasteurize.py b/pasteurize.py index c0bd7e09..2b98327c 100755 --- a/pasteurize.py +++ b/pasteurize.py @@ -12,7 +12,7 @@ Licensing --------- -Copyright 2013-2018 Python Charmers Pty Ltd, Australia. +Copyright 2013-2019 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ diff --git a/src/future/__init__.py b/src/future/__init__.py index bbb77594..d44e16c8 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -76,7 +76,7 @@ Licensing --------- -Copyright 2013-2018 Python Charmers Pty Ltd, Australia. +Copyright 2013-2019 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ @@ -84,7 +84,7 @@ __title__ = 'future' __author__ = 'Ed Schofield' __license__ = 'MIT' -__copyright__ = 'Copyright 2013-2018 Python Charmers Pty Ltd' +__copyright__ = 'Copyright 2013-2019 Python Charmers Pty Ltd' __ver_major__ = 0 __ver_minor__ = 18 __ver_patch__ = 0 diff --git a/src/past/__init__.py b/src/past/__init__.py index 07422a03..14713039 100644 --- a/src/past/__init__.py +++ b/src/past/__init__.py @@ -74,13 +74,13 @@ Credits ------- -:Author: Ed Schofield +:Author: Ed Schofield, Jordan M. Adler, et al :Sponsor: Python Charmers Pty Ltd, Australia: http://pythoncharmers.com Licensing --------- -Copyright 2013-2018 Python Charmers Pty Ltd, Australia. +Copyright 2013-2019 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ From c24dfc48ea045f40c348e444bc2add983dd149e3 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Thu, 10 Oct 2019 15:07:22 -0700 Subject: [PATCH 317/425] bugfix for issubclass check --- src/future/utils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index a6b10210..443dae65 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -415,7 +415,7 @@ def raise_(tp, value=None, tb=None): if value is not None: raise TypeError("instance exception may not have a separate value") exc = tp - elif not issubclass(tp, Exception): + elif isinstance(tp, type) and not issubclass(tp, Exception): # If the first object is a class, it becomes the type of the # exception. raise TypeError("class must derive from Exception") From a4f0145b105a6bb1d8d669991ed71d55eecd3187 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Thu, 10 Oct 2019 15:07:22 -0700 Subject: [PATCH 318/425] bugfix for issubclass check --- src/future/utils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index a6b10210..443dae65 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -415,7 +415,7 @@ def raise_(tp, value=None, tb=None): if value is not None: raise TypeError("instance exception may not have a separate value") exc = tp - elif not issubclass(tp, Exception): + elif isinstance(tp, type) and not issubclass(tp, Exception): # If the first object is a class, it becomes the type of the # exception. raise TypeError("class must derive from Exception") From c9e4e962478f16932d592584a0351d727ee71b6d Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Thu, 17 Oct 2019 18:07:07 +0000 Subject: [PATCH 319/425] Prepare for 0.18.1 release --- docs/whatsnew.rst | 6 ++++++ src/future/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 50b2a99c..7d6a1071 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -3,6 +3,12 @@ What's New ********** +What's new in version 0.18.1 (2019-10-09) +========================================= +This is a minor bug-fix release containing a fix for raise_() +when passed an exception that's not an Exception (e.g. BaseException +subclasses) + What's new in version 0.18.0 (2019-10-09) ========================================= This is a major bug-fix and feature release, including: diff --git a/src/future/__init__.py b/src/future/__init__.py index d44e16c8..24f10fa1 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -87,7 +87,7 @@ __copyright__ = 'Copyright 2013-2019 Python Charmers Pty Ltd' __ver_major__ = 0 __ver_minor__ = 18 -__ver_patch__ = 0 +__ver_patch__ = 1 __ver_sub__ = '' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From 51509ad3aaefe5437dafa93954b39662133e6008 Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Thu, 17 Oct 2019 17:04:54 -0700 Subject: [PATCH 320/425] Use BaseException in raise_() In both Python 2 and 3, the Exception type is derived from BaseException: The base class for all built-in exceptions. It is not meant to be directly inherited by user-defined classes (for that, use Exception). In practice, some libraries provide exception types that do derive directly from BaseException (such as `gevent.Timeout`), and this code should recognize them as valid exception types. As further evidence, Python 2 explicitly states that raised types must be derived from BaseException: exceptions must be old-style classes or derived from BaseException Python 3 is more flexible here, which is why we provide a TypeError case for non-BaseException-derived types. While I'm here, I made that message a little more helpful by including the name of the incompatible type. --- src/future/utils/__init__.py | 6 +++--- tests/test_future/test_utils.py | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index 443dae65..7a0954d1 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -408,17 +408,17 @@ def raise_(tp, value=None, tb=None): allows re-raising exceptions with the cls value and traceback on Python 2 and 3. """ - if isinstance(tp, Exception): + 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, Exception): + 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 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 diff --git a/tests/test_future/test_utils.py b/tests/test_future/test_utils.py index b6a27d98..46f5196c 100644 --- a/tests/test_future/test_utils.py +++ b/tests/test_future/test_utils.py @@ -111,13 +111,13 @@ def test_isbytes(self): self.assertFalse(isbytes(self.s2)) def test_raise_(self): - def valerror(): + def valuerror(): try: raise ValueError("Apples!") except Exception as e: raise_(e) - self.assertRaises(ValueError, valerror) + self.assertRaises(ValueError, valuerror) def with_value(): raise_(IOError, "This is an error") @@ -143,6 +143,17 @@ def with_traceback(): except IOError as e: self.assertEqual(str(e), "An error") + class Timeout(BaseException): + pass + + self.assertRaises(Timeout, raise_, Timeout) + self.assertRaises(Timeout, raise_, Timeout()) + + if PY3: + self.assertRaisesRegexp( + TypeError, "class must derive from BaseException", + raise_, int) + def test_raise_from_None(self): try: try: From f60f67269a861c3ff19dbee17342789a49a02cde Mon Sep 17 00:00:00 2001 From: Chase Sterling Date: Thu, 17 Oct 2019 21:16:56 -0400 Subject: [PATCH 321/425] Add test for min/max with generators. --- tests/test_future/test_builtins.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_future/test_builtins.py b/tests/test_future/test_builtins.py index d983f9d6..29f4c84a 100644 --- a/tests/test_future/test_builtins.py +++ b/tests/test_future/test_builtins.py @@ -1154,6 +1154,10 @@ def __getitem__(self, index): with self.assertRaises(TypeError): max(1, 2, default=0) + # Test iterables that can only be looped once #510 + self.assertEqual(min(x for x in [5]), 5) + self.assertEqual(max(x for x in [5, 4, 3]), 5) + def test_next(self): it = iter(range(2)) self.assertEqual(next(it), 0) From 03f94a325251f4ef19d4b4caabc72a5a88c60172 Mon Sep 17 00:00:00 2001 From: Chase Sterling Date: Thu, 17 Oct 2019 21:19:25 -0400 Subject: [PATCH 322/425] Fix behavior of new min/max with generators. fix #510 --- src/future/builtins/new_min_max.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/future/builtins/new_min_max.py b/src/future/builtins/new_min_max.py index 8fd63fdf..cc7acbee 100644 --- a/src/future/builtins/new_min_max.py +++ b/src/future/builtins/new_min_max.py @@ -1,3 +1,5 @@ +import itertools + from future import utils if utils.PY2: from __builtin__ import max as _builtin_max, min as _builtin_min @@ -33,17 +35,20 @@ def new_min_max(_builtin_func, *args, **kwargs): raise TypeError if len(args) == 1: + iterator = iter(args[0]) try: - next(iter(args[0])) + first = next(iterator) except StopIteration: if kwargs.get('default') is not None: return kwargs.get('default') else: raise ValueError('iterable is an empty sequence') + else: + iterator = itertools.chain([first], iterator) if kwargs.get('key') is not None: - return _builtin_func(args[0], key=kwargs.get('key')) + return _builtin_func(iterator, key=kwargs.get('key')) else: - return _builtin_func(args[0]) + return _builtin_func(iterator) if len(args) > 1: if kwargs.get('key') is not None: From 9774207b622966c8a5172156439c0d41c0335f33 Mon Sep 17 00:00:00 2001 From: Steve Kowalik Date: Fri, 18 Oct 2019 16:03:22 +1100 Subject: [PATCH 323/425] MIME guessing works in Python 3.8 in test_urllib2 To continue to support other versions of Python, use sys.hexversion to check if we're using 3.8 and set the MIME type appropriately. Fixes #508 --- tests/test_future/test_urllib2.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_future/test_urllib2.py b/tests/test_future/test_urllib2.py index e7fb4dd7..2d69dad1 100644 --- a/tests/test_future/test_urllib2.py +++ b/tests/test_future/test_urllib2.py @@ -691,6 +691,10 @@ def connect_ftp(self, user, passwd, host, port, dirs, h = NullFTPHandler(data) h.parent = MockOpener() + # MIME guessing works in Python 3.8! + guessed_mime = None + if sys.hexversion >= 0x03080000: + guessed_mime = "image/gif" for url, host, port, user, passwd, type_, dirs, filename, mimetype in [ ("ftp://localhost/foo/bar/baz.html", "localhost", ftplib.FTP_PORT, "", "", "I", @@ -709,7 +713,7 @@ def connect_ftp(self, user, passwd, host, port, dirs, ["foo", "bar"], "", None), ("ftp://localhost/baz.gif;type=a", "localhost", ftplib.FTP_PORT, "", "", "A", - [], "baz.gif", None), # XXX really this should guess image/gif + [], "baz.gif", guessed_mime), ]: req = Request(url) req.timeout = None From b64245c8444d93aee6862bb0cd2361ddfe431eea Mon Sep 17 00:00:00 2001 From: Chase Sterling Date: Fri, 18 Oct 2019 09:24:57 -0400 Subject: [PATCH 324/425] Move max test to appropriate test method --- tests/test_future/test_builtins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_future/test_builtins.py b/tests/test_future/test_builtins.py index 29f4c84a..d5ea8972 100644 --- a/tests/test_future/test_builtins.py +++ b/tests/test_future/test_builtins.py @@ -1123,6 +1123,7 @@ class BadSeq: def __getitem__(self, index): raise ValueError self.assertRaises(ValueError, min, BadSeq()) + self.assertEqual(max(x for x in [5, 4, 3]), 5) for stmt in ( "min(key=int)", # no args @@ -1156,7 +1157,6 @@ def __getitem__(self, index): # Test iterables that can only be looped once #510 self.assertEqual(min(x for x in [5]), 5) - self.assertEqual(max(x for x in [5, 4, 3]), 5) def test_next(self): it = iter(range(2)) From dbc74ad81b384897970edd65fef1bc53ddee5acb Mon Sep 17 00:00:00 2001 From: Chase Sterling Date: Fri, 18 Oct 2019 10:31:21 -0400 Subject: [PATCH 325/425] Fix min/max functions not allowing 'None' as default --- src/future/builtins/new_min_max.py | 6 ++++-- tests/test_future/test_builtins.py | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/future/builtins/new_min_max.py b/src/future/builtins/new_min_max.py index cc7acbee..1bfd8a90 100644 --- a/src/future/builtins/new_min_max.py +++ b/src/future/builtins/new_min_max.py @@ -6,6 +6,8 @@ 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) @@ -31,7 +33,7 @@ def new_min_max(_builtin_func, *args, **kwargs): if len(args) == 0: raise TypeError - if len(args) != 1 and kwargs.get('default') is not None: + if len(args) != 1 and kwargs.get('default', _SENTINEL) is not _SENTINEL: raise TypeError if len(args) == 1: @@ -39,7 +41,7 @@ def new_min_max(_builtin_func, *args, **kwargs): try: first = next(iterator) except StopIteration: - if kwargs.get('default') is not None: + if kwargs.get('default', _SENTINEL) is not _SENTINEL: return kwargs.get('default') else: raise ValueError('iterable is an empty sequence') diff --git a/tests/test_future/test_builtins.py b/tests/test_future/test_builtins.py index d5ea8972..ca07b9ef 100644 --- a/tests/test_future/test_builtins.py +++ b/tests/test_future/test_builtins.py @@ -1105,6 +1105,7 @@ def test_max(self): with self.assertRaises(TypeError): max(1, 2, default=0) self.assertEqual(max([], default=0), 0) + self.assertIs(max([], default=None), None) def test_min(self): self.assertEqual(min('123123'), '1') @@ -1150,6 +1151,7 @@ def __getitem__(self, index): sorted(data, key=f)[0]) self.assertEqual(min([], default=5), 5) self.assertEqual(min([], default=0), 0) + self.assertIs(min([], default=None), None) with self.assertRaises(TypeError): max(None, default=5) with self.assertRaises(TypeError): From f4926e51c940ef9afb4c94038c48b8a5382d04a2 Mon Sep 17 00:00:00 2001 From: Chase Sterling Date: Fri, 18 Oct 2019 10:32:22 -0400 Subject: [PATCH 326/425] Make new min/max error message consistent builtin error message --- src/future/builtins/new_min_max.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/builtins/new_min_max.py b/src/future/builtins/new_min_max.py index 1bfd8a90..6f0c2a86 100644 --- a/src/future/builtins/new_min_max.py +++ b/src/future/builtins/new_min_max.py @@ -44,7 +44,7 @@ def new_min_max(_builtin_func, *args, **kwargs): if kwargs.get('default', _SENTINEL) is not _SENTINEL: return kwargs.get('default') else: - raise ValueError('iterable is an empty sequence') + raise ValueError('{}() arg is an empty sequence'.format(_builtin_func.__name__)) else: iterator = itertools.chain([first], iterator) if kwargs.get('key') is not None: From 53f4685e67142e234a4ccf4f01fd96228de855b7 Mon Sep 17 00:00:00 2001 From: Tom Picton Date: Tue, 22 Oct 2019 09:47:59 -0700 Subject: [PATCH 327/425] Fix round function failing on Decimal objects --- src/future/builtins/newround.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/future/builtins/newround.py b/src/future/builtins/newround.py index 3943ebb6..b0e6a001 100644 --- a/src/future/builtins/newround.py +++ b/src/future/builtins/newround.py @@ -38,11 +38,14 @@ def newround(number, ndigits=None): 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(d, Decimal): + d = number else: - d = from_float_26(number).quantize(exponent, rounding=ROUND_HALF_EVEN) + if not PY26: + d = Decimal.from_float(number).quantize(exponent, + rounding=ROUND_HALF_EVEN) + else: + d = from_float_26(number).quantize(exponent, rounding=ROUND_HALF_EVEN) if return_int: return int(d) From 8ba82a8ea5456f2d9b489aa1d23bba053999f18d Mon Sep 17 00:00:00 2001 From: Anika Mukherji Date: Tue, 22 Oct 2019 15:06:32 -0700 Subject: [PATCH 328/425] use object.setattr to bypass check for immutable objects --- src/future/utils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index 59920077..7282a9cf 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -473,7 +473,7 @@ def raise_from(exc, cause): e.__suppress_context__ = True elif isinstance(cause, BaseException): e.__cause__ = cause - e.__cause__.__traceback__ = sys.exc_info()[2] + object.__setattr__(e.__cause__, '__traceback__', sys.exc_info()[2]) e.__suppress_context__ = True else: raise TypeError("exception causes must derive from BaseException") From d7ff41c2d528f5af551e0fd065aa0c1364067ced Mon Sep 17 00:00:00 2001 From: Jeroen Akkerman Date: Wed, 23 Oct 2019 15:37:46 +0200 Subject: [PATCH 329/425] Add check for input import before running FixInput Fixes #427 --- src/libfuturize/fixes/__init__.py | 3 ++- src/libfuturize/fixes/fix_input.py | 32 ++++++++++++++++++++++++++++++ tests/test_future/test_futurize.py | 21 ++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/libfuturize/fixes/fix_input.py diff --git a/src/libfuturize/fixes/__init__.py b/src/libfuturize/fixes/__init__.py index 7de304da..0b562501 100644 --- a/src/libfuturize/fixes/__init__.py +++ b/src/libfuturize/fixes/__init__.py @@ -50,7 +50,7 @@ 'lib2to3.fixes.fix_getcwdu', # 'lib2to3.fixes.fix_imports', # called by libfuturize.fixes.fix_future_standard_library # 'lib2to3.fixes.fix_imports2', # we don't handle this yet (dbm) - 'lib2to3.fixes.fix_input', + # 'lib2to3.fixes.fix_input', # Called conditionally by libfuturize.fixes.fix_input 'lib2to3.fixes.fix_itertools', 'lib2to3.fixes.fix_itertools_imports', 'lib2to3.fixes.fix_filter', @@ -86,6 +86,7 @@ 'libfuturize.fixes.fix_future_builtins', 'libfuturize.fixes.fix_future_standard_library', 'libfuturize.fixes.fix_future_standard_library_urllib', + 'libfuturize.fixes.fix_input', 'libfuturize.fixes.fix_metaclass', 'libpasteurize.fixes.fix_newstyle', 'libfuturize.fixes.fix_object', diff --git a/src/libfuturize/fixes/fix_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/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index f2201141..0d7c42de 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -436,6 +436,27 @@ def test_import_builtins(self): """ self.convert_check(before, after, ignore_imports=False, run=False) + def test_input_without_import(self): + before = """ + a = input() + """ + after = """ + from builtins import input + a = eval(input()) + """ + self.convert_check(before, after, ignore_imports=False, run=False) + + def test_input_with_import(self): + before = """ + from builtins import input + a = input() + """ + after = """ + from builtins import input + a = input() + """ + self.convert_check(before, after, ignore_imports=False, run=False) + def test_xrange(self): """ The ``from builtins import range`` line was being added to the From a1d6cd3980901b1904c7ed02c8abe76e5f3e1f7e Mon Sep 17 00:00:00 2001 From: Tom Picton Date: Thu, 24 Oct 2019 16:40:05 -0700 Subject: [PATCH 330/425] Fix typo in newround --- src/future/builtins/newround.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/builtins/newround.py b/src/future/builtins/newround.py index b0e6a001..394a2c63 100644 --- a/src/future/builtins/newround.py +++ b/src/future/builtins/newround.py @@ -38,7 +38,7 @@ def newround(number, ndigits=None): if 'numpy' in repr(type(number)): number = float(number) - if isinstance(d, Decimal): + if isinstance(number, Decimal): d = number else: if not PY26: From ad5738312fab39c9829d3e4617703b684d8fa4a2 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Thu, 31 Oct 2019 00:49:27 +0000 Subject: [PATCH 331/425] Prepare for 0.18.2 release --- docs/credits.rst | 4 ++++ docs/whatsnew.rst | 13 +++++++++++++ src/future/__init__.py | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/credits.rst b/docs/credits.rst index e66136d5..275e148e 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -48,6 +48,7 @@ Authors Python-Future is largely written by Ed Schofield with the help of various contributors: - Jordan Adler +- Jeroen Akkerman - Kyle Altendorf - Grant Bakker - Jacob Beck @@ -70,6 +71,7 @@ Python-Future is largely written by Ed Schofield with th - Michael Joseph - Waldemar Kornewald - Alexey Kotlyarov +- Steve Kowalik - Lion Krischer - Marcin Kuzminski - Joshua Landau @@ -81,6 +83,7 @@ Python-Future is largely written by Ed Schofield with th - Anika Mukherji - Jon Parise - Matthew Parnell +- Tom Picton - Miga Purg - Éloi Rivard - Sesh Sadasivam @@ -91,6 +94,7 @@ Python-Future is largely written by Ed Schofield with th - Sameera Somisetty - Louis Sautier - Gregory P. Smith +- Chase Sterling - Daniel Szoska - Flaviu Tamas - Jeff Tratner diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 7d6a1071..e0b4603d 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -3,6 +3,19 @@ What's New ********** +What's new in version 0.18.2 (2019-10-30) +========================================= +This is a minor bug-fix release containing a number of fixes: +- Fix min/max functions with generators, and 'None' default (PR #514) +- Use BaseException in raise_() (PR #515) +- Fix builtins.round() for Decimals (Issue #501) +- Fix raise_from() to prevent failures with immutable classes (PR #518) +- Make FixInput idempotent (Issue #427) +- Fix type in newround (PR #521) +- Support mimetype guessing in urllib2 for Py3.8+ (Issue #508) + +Python 3.8 is not yet officially supported. + What's new in version 0.18.1 (2019-10-09) ========================================= This is a minor bug-fix release containing a fix for raise_() diff --git a/src/future/__init__.py b/src/future/__init__.py index 24f10fa1..ad419d67 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -87,7 +87,7 @@ __copyright__ = 'Copyright 2013-2019 Python Charmers Pty Ltd' __ver_major__ = 0 __ver_minor__ = 18 -__ver_patch__ = 1 +__ver_patch__ = 2 __ver_sub__ = '' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From 4236061ae33897a1e6731297babb543417c06d6e Mon Sep 17 00:00:00 2001 From: Kaustubh Maske Patil <37668193+nikochiko@users.noreply.github.com> Date: Thu, 31 Oct 2019 18:14:50 +0530 Subject: [PATCH 332/425] Fix typo Fix typo extra ` . `newstr`` --> `newstr` --- docs/str_object.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/str_object.rst b/docs/str_object.rst index 4c5257a0..568b897a 100644 --- a/docs/str_object.rst +++ b/docs/str_object.rst @@ -14,7 +14,7 @@ There are also other differences, such as the ``repr`` of unicode strings in Py2 having a ``u'...'`` prefix, versus simply ``'...'``, and the removal of the :func:`str.decode` method in Py3. -:mod:`future` contains a :class:`newstr`` type that is a backport of the +:mod:`future` contains a :class:`newstr` type that is a backport of the :mod:`str` object from Python 3. This inherits from the Python 2 :class:`unicode` class but has customizations to improve compatibility with Python 3's :class:`str` object. You can use it as follows:: From 1663dfa4be398c00ad5209f301854e905645e222 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Tue, 5 Nov 2019 17:15:23 -0500 Subject: [PATCH 333/425] Fix formatting in "What's new" --- docs/whatsnew.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index e0b4603d..c6fa8f86 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -6,6 +6,7 @@ What's New What's new in version 0.18.2 (2019-10-30) ========================================= This is a minor bug-fix release containing a number of fixes: + - Fix min/max functions with generators, and 'None' default (PR #514) - Use BaseException in raise_() (PR #515) - Fix builtins.round() for Decimals (Issue #501) From 4dfa099c49bc6c5bd0162ed2bab9b24a91de69ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Conti?= Date: Wed, 6 Nov 2019 12:05:35 -0300 Subject: [PATCH 334/425] Fix typo --- docs/str_object.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/str_object.rst b/docs/str_object.rst index 4c5257a0..568b897a 100644 --- a/docs/str_object.rst +++ b/docs/str_object.rst @@ -14,7 +14,7 @@ There are also other differences, such as the ``repr`` of unicode strings in Py2 having a ``u'...'`` prefix, versus simply ``'...'``, and the removal of the :func:`str.decode` method in Py3. -:mod:`future` contains a :class:`newstr`` type that is a backport of the +:mod:`future` contains a :class:`newstr` type that is a backport of the :mod:`str` object from Python 3. This inherits from the Python 2 :class:`unicode` class but has customizations to improve compatibility with Python 3's :class:`str` object. You can use it as follows:: From 6126997eb8953c379f7686d0df692db030afd6ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Fri, 15 Nov 2019 21:23:42 +0100 Subject: [PATCH 335/425] Sort before comparing dicts in TestChainMap Sort the results of items() of a dict before comparing them. PyPy apparently does not produce consistent ordering on .items(). Fixes one of the failures from bug #530 --- tests/test_future/test_backports.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_future/test_backports.py b/tests/test_future/test_backports.py index 9eeb741b..63b1afea 100644 --- a/tests/test_future/test_backports.py +++ b/tests/test_future/test_backports.py @@ -87,7 +87,8 @@ def test_basics(self): d['b'] = 20 d['c'] = 30 self.assertEqual(d.maps, [{'b':20, 'c':30}, {'a':1, 'b':2}]) # check internal state - self.assertEqual(d.items(), dict(a=1, b=20, c=30).items()) # check items/iter/getitem + self.assertEqual(sorted(d.items()), + sorted(dict(a=1, b=20, c=30).items())) # check items/iter/getitem self.assertEqual(len(d), 3) # check len for key in 'abc': # check contains self.assertIn(key, d) @@ -96,7 +97,8 @@ def test_basics(self): del d['b'] # unmask a value self.assertEqual(d.maps, [{'c':30}, {'a':1, 'b':2}]) # check internal state - self.assertEqual(d.items(), dict(a=1, b=2, c=30).items()) # check items/iter/getitem + self.assertEqual(sorted(d.items()), + sorted(dict(a=1, b=2, c=30).items())) # check items/iter/getitem self.assertEqual(len(d), 3) # check len for key in 'abc': # check contains self.assertIn(key, d) From f6a654993968a27d63d99ec1cf40e4ebf00f094d Mon Sep 17 00:00:00 2001 From: Kyle King Date: Fri, 6 Dec 2019 14:26:33 -0500 Subject: [PATCH 336/425] Add tkFileDialog to future.movers.tkinter Related to issue #233 / Commit a6ed514 --- src/future/moves/tkinter/filedialog.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/future/moves/tkinter/filedialog.py b/src/future/moves/tkinter/filedialog.py index 973923e2..6a6f03ca 100644 --- a/src/future/moves/tkinter/filedialog.py +++ b/src/future/moves/tkinter/filedialog.py @@ -10,3 +10,9 @@ except ImportError: raise ImportError('The FileDialog module is missing. Does your Py2 ' 'installation include tkinter?') + + try: + from tkFileDialog import * + except ImportError: + raise ImportError('The tkFileDialog module is missing. Does your Py2 ' + 'installation include tkinter?') From a4911b9dd748aac4a419455a2bc94f270f050528 Mon Sep 17 00:00:00 2001 From: Kayne Barclay Date: Thu, 19 Dec 2019 13:18:35 -0800 Subject: [PATCH 337/425] #322 Add support for negative ndigits in round; additionally, fixing a bug so that it handles passing in Decimal properly --- src/future/builtins/newround.py | 17 ++++++++++------- tests/test_future/test_builtins.py | 1 - 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/future/builtins/newround.py b/src/future/builtins/newround.py index 394a2c63..e2976a76 100644 --- a/src/future/builtins/newround.py +++ b/src/future/builtins/newround.py @@ -2,6 +2,7 @@ ``python-future``: pure Python implementation of Python 3 round(). """ +from __future__ import division from future.utils import PYPY, PY26, bind_method # Use the decimal module for simplicity of implementation (and @@ -29,8 +30,6 @@ def newround(number, ndigits=None): if hasattr(number, '__round__'): return number.__round__(ndigits) - if ndigits < 0: - raise NotImplementedError('negative ndigits not supported yet') exponent = Decimal('10') ** (-ndigits) if PYPY: @@ -42,15 +41,19 @@ def newround(number, ndigits=None): d = number else: if not PY26: - d = Decimal.from_float(number).quantize(exponent, - rounding=ROUND_HALF_EVEN) + d = Decimal.from_float(number) else: - d = from_float_26(number).quantize(exponent, rounding=ROUND_HALF_EVEN) + 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/tests/test_future/test_builtins.py b/tests/test_future/test_builtins.py index ca07b9ef..3921a608 100644 --- a/tests/test_future/test_builtins.py +++ b/tests/test_future/test_builtins.py @@ -146,7 +146,6 @@ def test_round(self): self.assertTrue(isinstance(round(123.5, 0), float)) self.assertTrue(isinstance(round(123.5), Integral)) - @unittest.skip('negative ndigits not implemented yet') def test_round_negative_ndigits(self): self.assertEqual(round(10.1350, 0), 10.0) self.assertEqual(round(10.1350, -1), 10.0) From 3eaa8fd85cbc75e8b4b1fd797a650cce33d42d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Sampaio?= Date: Fri, 10 Jan 2020 11:38:50 -0300 Subject: [PATCH 338/425] Compare headers to correct definition of str --- src/future/backports/email/base64mime.py | 1 + 1 file changed, 1 insertion(+) 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', From 461d77e2c4335b4f3cc3c4ff2d2af8c5a9cdf9bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Sampaio?= Date: Fri, 10 Jan 2020 14:45:04 -0300 Subject: [PATCH 339/425] Add a test for our fix --- tests/test_future/test_email_generation.py | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests/test_future/test_email_generation.py diff --git a/tests/test_future/test_email_generation.py b/tests/test_future/test_email_generation.py new file mode 100644 index 00000000..10e61138 --- /dev/null +++ b/tests/test_future/test_email_generation.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +"""Tests for email generation.""" + +from __future__ import unicode_literals + +from future.backports.email.mime.multipart import MIMEMultipart +from future.backports.email.mime.text import MIMEText +from future.backports.email.utils import formatdate +from future.tests.base import unittest + + +class EmailGenerationTests(unittest.TestCase): + def test_email_custom_header_can_contain_unicode(self): + msg = MIMEMultipart() + alternative = MIMEMultipart('alternative') + alternative.attach(MIMEText('Plain content with Únicødê', _subtype='plain', _charset='utf-8')) + alternative.attach(MIMEText('HTML content with Únicødê', _subtype='html', _charset='utf-8')) + msg.attach(alternative) + + msg['Subject'] = 'Subject with Únicødê' + msg['From'] = 'sender@test.com' + msg['To'] = 'recipient@test.com' + msg['Date'] = formatdate(None, localtime=True) + msg['Message-ID'] = 'anIdWithÚnicødêForThisEmail' + + msg_lines = msg.as_string().split('\n') + self.assertEqual(msg_lines[2], 'Subject: =?utf-8?b?U3ViamVjdCB3aXRoIMOabmljw7hkw6o=?=') + self.assertEqual(msg_lines[6], 'Message-ID: =?utf-8?b?YW5JZFdpdGjDmm5pY8O4ZMOqRm9yVGhpc0VtYWls?=') + self.assertEqual(msg_lines[17], 'UGxhaW4gY29udGVudCB3aXRoIMOabmljw7hkw6o=') + self.assertEqual(msg_lines[24], 'SFRNTCBjb250ZW50IHdpdGggw5puaWPDuGTDqg==') From 52b0ff92c8ad2c4dac274749a85c5f7a629f185a Mon Sep 17 00:00:00 2001 From: Glen Walker Date: Fri, 17 Jan 2020 13:29:33 +1300 Subject: [PATCH 340/425] Handling of __next__ and next by future.utils.get_next was reversed I assume nobody is using future.utils.get_next, because the builtin next() function exists from Python 2.6 on and meets most needs, and because nobody has noticed this before. Fixing in case it saves anyone confusion in future. --- src/future/utils/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index 46bd96de..846d5da6 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -527,9 +527,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): From a9c6a3762b5391986833737e7fea49a8b324b69c Mon Sep 17 00:00:00 2001 From: Dominik Kozaczko Date: Fri, 7 Feb 2020 17:30:49 +0100 Subject: [PATCH 341/425] Add pre-commit hooks --- .gitignore | 3 +++ .pre-commit-hooks.yaml | 14 ++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 .pre-commit-hooks.yaml diff --git a/.gitignore b/.gitignore index 8c52a551..1b142a7a 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ nosetests.xml .mr.developer.cfg .project .pydevproject + +# PyCharm +.idea diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 00000000..ea5abd63 --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -0,0 +1,14 @@ +- 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 + types: [python] + entry: pasteurize -w -n --no-diffs From 6c6e3aea6f9a26b2827e5987a4e1c47602a73b4d Mon Sep 17 00:00:00 2001 From: Dominik Kozaczko Date: Fri, 7 Feb 2020 22:38:04 +0100 Subject: [PATCH 342/425] Add simple documentation --- .pre-commit-hooks.yaml | 1 + README.rst | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index ea5abd63..dd8d0d65 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -10,5 +10,6 @@ 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/README.rst b/README.rst index ea806538..c0c69905 100644 --- a/README.rst +++ b/README.rst @@ -57,6 +57,8 @@ Features decoding the backported ``str`` and ``bytes`` objects. [This feature is currently in alpha.] +- support for pre-commit hooks + .. _code-examples: Code examples @@ -261,6 +263,39 @@ 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: feature/pre-commit + hooks: + - id: futurize + args: [--both-stages] + +The ``args`` part is optional, by default only stage1 is applied. + Licensing --------- From 681e78ce52d80012f115941665550aa0e96e9531 Mon Sep 17 00:00:00 2001 From: Dominik Kozaczko Date: Sat, 8 Feb 2020 01:01:39 +0100 Subject: [PATCH 343/425] Correct example in README --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index c0c69905..5c090804 100644 --- a/README.rst +++ b/README.rst @@ -289,7 +289,7 @@ Next, you need to add this entry to your ``.pre-commit-config.yaml`` .. code-block:: yaml - repo: https://github.com/PythonCharmers/python-future - rev: feature/pre-commit + rev: master hooks: - id: futurize args: [--both-stages] From c9bc0ff1807cb663be9bcaadd69e57f6ba60fe13 Mon Sep 17 00:00:00 2001 From: Yaseen Mowzer Date: Thu, 5 Mar 2020 08:54:27 +0200 Subject: [PATCH 344/425] Add __subclasscheck__ for past.types.basestring Previously `__subclasscheck__` was not implemented which lead to inconsistencies between `isinstance` and `issubclass` (i.e. `isinstance("a string", basestring)` is true, but `issubclass(str, basestring)` was false. This commit fixes this incorrect behavior. --- src/past/types/basestring.py | 5 ++--- tests/test_past/test_basestring.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/past/types/basestring.py b/src/past/types/basestring.py index 1cab22f6..9c21715a 100644 --- a/src/past/types/basestring.py +++ b/src/past/types/basestring.py @@ -25,9 +25,8 @@ class BaseBaseString(type): def __instancecheck__(cls, instance): return isinstance(instance, (bytes, str)) - def __subclasshook__(cls, thing): - # TODO: What should go here? - raise NotImplemented + def __subclasscheck__(cls, subclass): + return super(BaseBaseString, cls).__subclasscheck__(subclass) or issubclass(subclass, (bytes, str)) class basestring(with_metaclass(BaseBaseString)): diff --git a/tests/test_past/test_basestring.py b/tests/test_past/test_basestring.py index d002095e..6c224b3e 100644 --- a/tests/test_past/test_basestring.py +++ b/tests/test_past/test_basestring.py @@ -19,6 +19,25 @@ def test_isinstance(self): s2 = oldstr(b'abc') self.assertTrue(isinstance(s2, basestring)) + def test_issubclass(self): + self.assertTrue(issubclass(str, basestring)) + self.assertTrue(issubclass(bytes, basestring)) + self.assertTrue(issubclass(basestring, basestring)) + self.assertFalse(issubclass(int, basestring)) + self.assertFalse(issubclass(list, basestring)) + self.assertTrue(issubclass(basestring, object)) + + class CustomString(basestring): + pass + class NotString(object): + pass + class OldStyleClass: + pass + self.assertTrue(issubclass(CustomString, basestring)) + self.assertFalse(issubclass(NotString, basestring)) + self.assertFalse(issubclass(OldStyleClass, basestring)) + + if __name__ == '__main__': unittest.main() From 5f945723dc6218584dac0608117176759853738d Mon Sep 17 00:00:00 2001 From: Vitaly Goldshteyn Date: Mon, 6 Apr 2020 04:33:51 +0200 Subject: [PATCH 345/425] Added r""" to the docstring to avoid warnings in python3 Added r""" to the docstring to avoid warnings in python3 Warning example: DeprecationWarning: invalid escape sequence \d --- src/past/types/oldstr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/past/types/oldstr.py b/src/past/types/oldstr.py index a477d884..5a0e3789 100644 --- a/src/past/types/oldstr.py +++ b/src/past/types/oldstr.py @@ -20,7 +20,7 @@ def __instancecheck__(cls, instance): def unescape(s): - """ + r""" Interprets strings with escape sequences Example: From b4b54f000efe3b6d9f0a9eed57865fdfc18fcd4f Mon Sep 17 00:00:00 2001 From: "Michael P. Nitowski" Date: Mon, 6 Apr 2020 08:39:45 -0400 Subject: [PATCH 346/425] Support NumPy's specialized int types in builtins.round --- src/future/builtins/newround.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/future/builtins/newround.py b/src/future/builtins/newround.py index e2976a76..b06c1169 100644 --- a/src/future/builtins/newround.py +++ b/src/future/builtins/newround.py @@ -32,10 +32,10 @@ def newround(number, ndigits=None): 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 isinstance(number, Decimal): d = number From 3ee9e7ffeec7fabed672e2240eac9eca318e4438 Mon Sep 17 00:00:00 2001 From: Liuyang Wan Date: Sat, 13 Jun 2020 10:03:05 +0800 Subject: [PATCH 347/425] Add docs building to tox.ini --- docs/conf.py | 1 - tox.ini | 10 +++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index fd106fa0..6f00536b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,7 +13,6 @@ from __future__ import absolute_import, print_function import sys, os -from future import __version__ import sphinx_bootstrap_theme # If extensions (or modules to document with autodoc) are in another directory, diff --git a/tox.ini b/tox.ini index f5c013f8..1ca3286b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,7 @@ [tox] -envlist = py26,py27,py33,py34,py35,py36,py37 +envlist = + py{26,27,33,34,35,36,37}, + docs [testenv] deps = @@ -7,3 +9,9 @@ deps = unittest2 py26: importlib commands = pytest {posargs} + +[testenv:docs] +deps = + sphinx + sphinx_bootstrap_theme +commands = sphinx-build docs build From e55f91595c88a528e2b305cf67e7d892e58fafdb Mon Sep 17 00:00:00 2001 From: Liuyang Wan Date: Sat, 13 Jun 2020 10:11:31 +0800 Subject: [PATCH 348/425] Add initial contributing guide with docs build instruction --- .github/CONTRIBUTING.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/CONTRIBUTING.md 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 +``` From 9ca5a14504dae9bf317c671f30d9f56fecd8a844 Mon Sep 17 00:00:00 2001 From: Liuyang Wan Date: Sun, 14 Jun 2020 14:31:14 +0800 Subject: [PATCH 349/425] Fix various py26 unit test failures --- .gitignore | 3 +++ src/libfuturize/fixes/fix_division_safe.py | 7 ++++++- tests/test_future/test_futurize.py | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1b142a7a..3b7bce98 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ nosetests.xml # PyCharm .idea + +# Generated test file +mytempfile.py diff --git a/src/libfuturize/fixes/fix_division_safe.py b/src/libfuturize/fixes/fix_division_safe.py index 3d5909cc..65c8c1da 100644 --- a/src/libfuturize/fixes/fix_division_safe.py +++ b/src/libfuturize/fixes/fix_division_safe.py @@ -92,7 +92,12 @@ def match(self, node): else: children.append(child.clone()) if matched: - return Node(node.type, children, fixers_applied=node.fixers_applied) + # 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 diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index 0d7c42de..c3696a54 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -436,6 +436,7 @@ def test_import_builtins(self): """ self.convert_check(before, after, ignore_imports=False, run=False) + @expectedFailurePY26 def test_input_without_import(self): before = """ a = input() From fa73942dfbfa3ca6d56632b70231c1d64daa6291 Mon Sep 17 00:00:00 2001 From: azjps Date: Tue, 16 Jun 2020 02:13:59 -0400 Subject: [PATCH 350/425] Bugfix with undefined children_hooks when package is None touch_import_top() is always called with a package within the futurize codebase, but it does have a code path where package can be None (e.g. import six instead of from six import string_types). Fixed a minor bug in this code path, so that a custom fixer can use it. Change-Id: Iec2891586fe852e35a91c69d2fb146645d7c53dd --- src/libfuturize/fixer_util.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libfuturize/fixer_util.py b/src/libfuturize/fixer_util.py index 48e4689d..b59ad3c2 100644 --- a/src/libfuturize/fixer_util.py +++ b/src/libfuturize/fixer_util.py @@ -390,6 +390,7 @@ def touch_import_top(package, name_to_import, node): break insert_pos = idx + children_hooks = [] if package is None: import_ = Node(syms.import_name, [ Leaf(token.NAME, u"import"), @@ -413,8 +414,6 @@ 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" ")]) From 1a48f1b70d0b479c535f362ecc1e58b4a0c1c913 Mon Sep 17 00:00:00 2001 From: Liuyang Wan Date: Fri, 10 Jul 2020 10:14:08 +0800 Subject: [PATCH 351/425] Pin typing==3.7.4.1 for Python 3.3 compatiblity --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 4b74e8d2..1be7fac8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,7 @@ matrix: sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069) install: + - pip install typing==3.7.4.1 # required for Python 3.3 - pip install tox==2.9.1 - pip install virtualenv==15.2.0 - pip install py==1.4.30 From 0d95a40fa7ac86d9e0bd4bd54693b42d25f5c0a5 Mon Sep 17 00:00:00 2001 From: "Roman A. Taycher" Date: Sun, 30 Aug 2020 19:24:54 -0700 Subject: [PATCH 352/425] improve cmp function, add unittest --- src/past/builtins/misc.py | 63 ++++++++++- tests/test_past/test_misc.py | 39 +++++++ tests/test_past/test_values.py | 201 +++++++++++++++++++++++++++++++++ 3 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 tests/test_past/test_misc.py create mode 100644 tests/test_past/test_values.py diff --git a/src/past/builtins/misc.py b/src/past/builtins/misc.py index ba50aa9e..889e0ff8 100644 --- a/src/past/builtins/misc.py +++ b/src/past/builtins/misc.py @@ -1,6 +1,8 @@ from __future__ import unicode_literals import inspect +import math +import numbers from future.utils import PY2, PY3, exec_ @@ -29,8 +31,67 @@ def cmp(x, y): cmp(x, y) -> integer Return negative if xy. + Python2 had looser comparison allowing cmp None and non Numerical types and collections. + Try to match the old behavior """ - return (x > y) - (x < y) + if isinstance(x, set) and isinstance(y, set): + raise TypeError('cannot compare sets using cmp()',) + try: + if isinstance(x, numbers.Number) and math.isnan(x): + if not isinstance(y, numbers.Number): + raise TypeError(f'cannot compare float("nan"), {type(y)} with cmp') + 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(f'cannot compare {type(x)}, float("nan") with cmp') + 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 diff --git a/tests/test_past/test_misc.py b/tests/test_past/test_misc.py new file mode 100644 index 00000000..3d1b7a09 --- /dev/null +++ b/tests/test_past/test_misc.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +""" +Tests for the resurrected Py2-like cmp funtion +""" + +from __future__ import absolute_import, unicode_literals, print_function + +import os.path +import sys +import traceback + +from future.tests.base import unittest +from past.builtins import cmp + +_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(_dir) +import test_values + + +class TestCmp(unittest.TestCase): + def test_cmp(self): + for x, y, cmp_python2_value in test_values.cmp_python2_value: + with self.subTest(x=x, y=y): + try: + past_cmp_value = cmp(x, y) + except Exception as ex: + past_cmp_value = traceback.format_exc().strip().split('\n')[-1] + + self.assertEqual(cmp_python2_value, past_cmp_value, + "expected result matching python2 __builtins__.cmp({x!r},{y!r}) " + "== {cmp_python2_value} " + "got past.builtins.cmp({x!r},{y!r}) " + "== {past_cmp_value} " + "".format(x=x, y=y, past_cmp_value=past_cmp_value, + cmp_python2_value=cmp_python2_value)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_past/test_values.py b/tests/test_past/test_values.py new file mode 100644 index 00000000..393ad761 --- /dev/null +++ b/tests/test_past/test_values.py @@ -0,0 +1,201 @@ +from math import pi + +inf, nan = float('inf'), float('nan') +test_values = [ + 0, 1, 2, -1, -9999999999, 9999999, + 0.0, inf, nan, pi, + # [], [[]], [1,2,3], + set(), set([1, 2, 3]), + " ", "", "1", "dsada saA.", "2", "dsa", b"", b"dsa", b" ", + {5: 3}, dict(), dict(a=99), dict(a=1, b=2, c=3), None +] + +# cmp_python2_values are pre-calculated from running cmp under python2 first values are x and y, last is results of cmp +cmp_python2_value = [[0, 1, -1], [0, 2, -1], [0, -1, 1], [0, -9999999999999999, 1], [0, 9999999999999999, -1], + [0, 0.0, 0], [0, inf, -1], [0, nan, -1], [0, 3.141592653589793, -1], [0, '', -1], [0, ' ', -1], + [0, '1', -1], [0, 'a bee cd.', -1], [0, '', -1], [0, ' ', -1], [0, '1', -1], [0, 'a bee cd.', -1], + [0, set([]), -1], [0, set([1, 2, 3]), -1], [0, {5: 3}, -1], [0, {}, -1], [0, {'a': 99}, -1], + [0, {'a': 1, 'c': 3, 'b': 2}, -1], [0, {'a': 99, 'c': 3, 'b': 5}, -1], [0, None, 1], [1, 0, 1], + [1, 2, -1], [1, -1, 1], [1, -9999999999999999, 1], [1, 9999999999999999, -1], [1, 0.0, 1], + [1, inf, -1], [1, nan, -1], [1, 3.141592653589793, -1], [1, '', -1], [1, ' ', -1], [1, '1', -1], + [1, 'a bee cd.', -1], [1, '', -1], [1, ' ', -1], [1, '1', -1], [1, 'a bee cd.', -1], + [1, set([]), -1], [1, set([1, 2, 3]), -1], [1, {5: 3}, -1], [1, {}, -1], [1, {'a': 99}, -1], + [1, {'a': 1, 'c': 3, 'b': 2}, -1], [1, {'a': 99, 'c': 3, 'b': 5}, -1], [1, None, 1], [2, 0, 1], + [2, 1, 1], [2, -1, 1], [2, -9999999999999999, 1], [2, 9999999999999999, -1], [2, 0.0, 1], + [2, inf, -1], [2, nan, -1], [2, 3.141592653589793, -1], [2, '', -1], [2, ' ', -1], [2, '1', -1], + [2, 'a bee cd.', -1], [2, '', -1], [2, ' ', -1], [2, '1', -1], [2, 'a bee cd.', -1], + [2, set([]), -1], [2, set([1, 2, 3]), -1], [2, {5: 3}, -1], [2, {}, -1], [2, {'a': 99}, -1], + [2, {'a': 1, 'c': 3, 'b': 2}, -1], [2, {'a': 99, 'c': 3, 'b': 5}, -1], [2, None, 1], [-1, 0, -1], + [-1, 1, -1], [-1, 2, -1], [-1, -9999999999999999, 1], [-1, 9999999999999999, -1], [-1, 0.0, -1], + [-1, inf, -1], [-1, nan, -1], [-1, 3.141592653589793, -1], [-1, '', -1], [-1, ' ', -1], + [-1, '1', -1], [-1, 'a bee cd.', -1], [-1, '', -1], [-1, ' ', -1], [-1, '1', -1], + [-1, 'a bee cd.', -1], [-1, set([]), -1], [-1, set([1, 2, 3]), -1], [-1, {5: 3}, -1], [-1, {}, -1], + [-1, {'a': 99}, -1], [-1, {'a': 1, 'c': 3, 'b': 2}, -1], [-1, {'a': 99, 'c': 3, 'b': 5}, -1], + [-1, None, 1], [-9999999999999999, 0, -1], [-9999999999999999, 1, -1], + [-9999999999999999, 2, -1], [-9999999999999999, -1, -1], + [-9999999999999999, 9999999999999999, -1], [-9999999999999999, 0.0, -1], + [-9999999999999999, inf, -1], [-9999999999999999, nan, -1], + [-9999999999999999, 3.141592653589793, -1], [-9999999999999999, '', -1], + [-9999999999999999, ' ', -1], [-9999999999999999, '1', -1], + [-9999999999999999, 'a bee cd.', -1], [-9999999999999999, '', -1], [-9999999999999999, ' ', -1], + [-9999999999999999, '1', -1], [-9999999999999999, 'a bee cd.', -1], + [-9999999999999999, set([]), -1], [-9999999999999999, set([1, 2, 3]), -1], + [-9999999999999999, {5: 3}, -1], [-9999999999999999, {}, -1], + [-9999999999999999, {'a': 99}, -1], [-9999999999999999, {'a': 1, 'c': 3, 'b': 2}, -1], + [-9999999999999999, {'a': 99, 'c': 3, 'b': 5}, -1], [-9999999999999999, None, 1], + [9999999999999999, 0, 1], [9999999999999999, 1, 1], [9999999999999999, 2, 1], + [9999999999999999, -1, 1], [9999999999999999, -9999999999999999, 1], + [9999999999999999, 0.0, 1], [9999999999999999, inf, -1], [9999999999999999, nan, -1], + [9999999999999999, 3.141592653589793, 1], [9999999999999999, '', -1], + [9999999999999999, ' ', -1], [9999999999999999, '1', -1], [9999999999999999, 'a bee cd.', -1], + [9999999999999999, '', -1], [9999999999999999, ' ', -1], [9999999999999999, '1', -1], + [9999999999999999, 'a bee cd.', -1], [9999999999999999, set([]), -1], + [9999999999999999, set([1, 2, 3]), -1], [9999999999999999, {5: 3}, -1], + [9999999999999999, {}, -1], [9999999999999999, {'a': 99}, -1], + [9999999999999999, {'a': 1, 'c': 3, 'b': 2}, -1], + [9999999999999999, {'a': 99, 'c': 3, 'b': 5}, -1], [9999999999999999, None, 1], [0.0, 0, 0], + [0.0, 1, -1], [0.0, 2, -1], [0.0, -1, 1], [0.0, -9999999999999999, 1], + [0.0, 9999999999999999, -1], [0.0, inf, -1], [0.0, nan, 1], [0.0, 3.141592653589793, -1], + [0.0, '', -1], [0.0, ' ', -1], [0.0, '1', -1], [0.0, 'a bee cd.', -1], [0.0, '', -1], + [0.0, ' ', -1], [0.0, '1', -1], [0.0, 'a bee cd.', -1], [0.0, set([]), -1], + [0.0, set([1, 2, 3]), -1], [0.0, {5: 3}, -1], [0.0, {}, -1], [0.0, {'a': 99}, -1], + [0.0, {'a': 1, 'c': 3, 'b': 2}, -1], [0.0, {'a': 99, 'c': 3, 'b': 5}, -1], [0.0, None, 1], + [inf, 0, 1], [inf, 1, 1], [inf, 2, 1], [inf, -1, 1], [inf, -9999999999999999, 1], + [inf, 9999999999999999, 1], [inf, 0.0, 1], [inf, nan, 1], [inf, 3.141592653589793, 1], + [inf, '', -1], [inf, ' ', -1], [inf, '1', -1], [inf, 'a bee cd.', -1], [inf, '', -1], + [inf, ' ', -1], [inf, '1', -1], [inf, 'a bee cd.', -1], [inf, set([]), -1], + [inf, set([1, 2, 3]), -1], [inf, {5: 3}, -1], [inf, {}, -1], [inf, {'a': 99}, -1], + [inf, {'a': 1, 'c': 3, 'b': 2}, -1], [inf, {'a': 99, 'c': 3, 'b': 5}, -1], [inf, None, 1], + [nan, 0, 1], [nan, 1, 1], [nan, 2, 1], [nan, -1, 1], [nan, -9999999999999999, 1], + [nan, 9999999999999999, 1], [nan, 0.0, -1], [nan, inf, -1], [nan, 3.141592653589793, -1], + [nan, '', -1], [nan, ' ', -1], [nan, '1', -1], [nan, 'a bee cd.', -1], [nan, '', -1], + [nan, ' ', -1], [nan, '1', -1], [nan, 'a bee cd.', -1], [nan, set([]), -1], + [nan, set([1, 2, 3]), -1], [nan, {5: 3}, -1], [nan, {}, -1], [nan, {'a': 99}, -1], + [nan, {'a': 1, 'c': 3, 'b': 2}, -1], [nan, {'a': 99, 'c': 3, 'b': 5}, -1], [nan, None, 1], + [3.141592653589793, 0, 1], [3.141592653589793, 1, 1], [3.141592653589793, 2, 1], + [3.141592653589793, -1, 1], [3.141592653589793, -9999999999999999, 1], + [3.141592653589793, 9999999999999999, -1], [3.141592653589793, 0.0, 1], + [3.141592653589793, inf, -1], [3.141592653589793, nan, 1], [3.141592653589793, '', -1], + [3.141592653589793, ' ', -1], [3.141592653589793, '1', -1], [3.141592653589793, 'a bee cd.', -1], + [3.141592653589793, '', -1], [3.141592653589793, ' ', -1], [3.141592653589793, '1', -1], + [3.141592653589793, 'a bee cd.', -1], [3.141592653589793, set([]), -1], + [3.141592653589793, set([1, 2, 3]), -1], [3.141592653589793, {5: 3}, -1], + [3.141592653589793, {}, -1], [3.141592653589793, {'a': 99}, -1], + [3.141592653589793, {'a': 1, 'c': 3, 'b': 2}, -1], + [3.141592653589793, {'a': 99, 'c': 3, 'b': 5}, -1], [3.141592653589793, None, 1], ['', 0, 1], + ['', 1, 1], ['', 2, 1], ['', -1, 1], ['', -9999999999999999, 1], ['', 9999999999999999, 1], + ['', 0.0, 1], ['', inf, 1], ['', nan, 1], ['', 3.141592653589793, 1], ['', ' ', -1], ['', '1', -1], + ['', 'a bee cd.', -1], ['', '', 0], ['', ' ', -1], ['', '1', -1], ['', 'a bee cd.', -1], + ['', set([]), 1], ['', set([1, 2, 3]), 1], ['', {5: 3}, 1], ['', {}, 1], ['', {'a': 99}, 1], + ['', {'a': 1, 'c': 3, 'b': 2}, 1], ['', {'a': 99, 'c': 3, 'b': 5}, 1], ['', None, 1], [' ', 0, 1], + [' ', 1, 1], [' ', 2, 1], [' ', -1, 1], [' ', -9999999999999999, 1], [' ', 9999999999999999, 1], + [' ', 0.0, 1], [' ', inf, 1], [' ', nan, 1], [' ', 3.141592653589793, 1], [' ', '', 1], + [' ', '1', -1], [' ', 'a bee cd.', -1], [' ', '', 1], [' ', ' ', 0], [' ', '1', -1], + [' ', 'a bee cd.', -1], [' ', set([]), 1], [' ', set([1, 2, 3]), 1], [' ', {5: 3}, 1], + [' ', {}, 1], [' ', {'a': 99}, 1], [' ', {'a': 1, 'c': 3, 'b': 2}, 1], + [' ', {'a': 99, 'c': 3, 'b': 5}, 1], [' ', None, 1], ['1', 0, 1], ['1', 1, 1], ['1', 2, 1], + ['1', -1, 1], ['1', -9999999999999999, 1], ['1', 9999999999999999, 1], ['1', 0.0, 1], + ['1', inf, 1], ['1', nan, 1], ['1', 3.141592653589793, 1], ['1', '', 1], ['1', ' ', 1], + ['1', 'a bee cd.', -1], ['1', '', 1], ['1', ' ', 1], ['1', '1', 0], ['1', 'a bee cd.', -1], + ['1', set([]), 1], ['1', set([1, 2, 3]), 1], ['1', {5: 3}, 1], ['1', {}, 1], ['1', {'a': 99}, 1], + ['1', {'a': 1, 'c': 3, 'b': 2}, 1], ['1', {'a': 99, 'c': 3, 'b': 5}, 1], ['1', None, 1], + ['a bee cd.', 0, 1], ['a bee cd.', 1, 1], ['a bee cd.', 2, 1], ['a bee cd.', -1, 1], + ['a bee cd.', -9999999999999999, 1], ['a bee cd.', 9999999999999999, 1], ['a bee cd.', 0.0, 1], + ['a bee cd.', inf, 1], ['a bee cd.', nan, 1], ['a bee cd.', 3.141592653589793, 1], + ['a bee cd.', '', 1], ['a bee cd.', ' ', 1], ['a bee cd.', '1', 1], ['a bee cd.', '', 1], + ['a bee cd.', ' ', 1], ['a bee cd.', '1', 1], ['a bee cd.', 'a bee cd.', 0], + ['a bee cd.', set([]), 1], ['a bee cd.', set([1, 2, 3]), 1], ['a bee cd.', {5: 3}, 1], + ['a bee cd.', {}, 1], ['a bee cd.', {'a': 99}, 1], ['a bee cd.', {'a': 1, 'c': 3, 'b': 2}, 1], + ['a bee cd.', {'a': 99, 'c': 3, 'b': 5}, 1], ['a bee cd.', None, 1], ['', 0, 1], ['', 1, 1], + ['', 2, 1], ['', -1, 1], ['', -9999999999999999, 1], ['', 9999999999999999, 1], ['', 0.0, 1], + ['', inf, 1], ['', nan, 1], ['', 3.141592653589793, 1], ['', '', 0], ['', ' ', -1], ['', '1', -1], + ['', 'a bee cd.', -1], ['', ' ', -1], ['', '1', -1], ['', 'a bee cd.', -1], ['', set([]), 1], + ['', set([1, 2, 3]), 1], ['', {5: 3}, 1], ['', {}, 1], ['', {'a': 99}, 1], + ['', {'a': 1, 'c': 3, 'b': 2}, 1], ['', {'a': 99, 'c': 3, 'b': 5}, 1], ['', None, 1], [' ', 0, 1], + [' ', 1, 1], [' ', 2, 1], [' ', -1, 1], [' ', -9999999999999999, 1], [' ', 9999999999999999, 1], + [' ', 0.0, 1], [' ', inf, 1], [' ', nan, 1], [' ', 3.141592653589793, 1], [' ', '', 1], + [' ', ' ', 0], [' ', '1', -1], [' ', 'a bee cd.', -1], [' ', '', 1], [' ', '1', -1], + [' ', 'a bee cd.', -1], [' ', set([]), 1], [' ', set([1, 2, 3]), 1], [' ', {5: 3}, 1], + [' ', {}, 1], [' ', {'a': 99}, 1], [' ', {'a': 1, 'c': 3, 'b': 2}, 1], + [' ', {'a': 99, 'c': 3, 'b': 5}, 1], [' ', None, 1], ['1', 0, 1], ['1', 1, 1], ['1', 2, 1], + ['1', -1, 1], ['1', -9999999999999999, 1], ['1', 9999999999999999, 1], ['1', 0.0, 1], + ['1', inf, 1], ['1', nan, 1], ['1', 3.141592653589793, 1], ['1', '', 1], ['1', ' ', 1], + ['1', '1', 0], ['1', 'a bee cd.', -1], ['1', '', 1], ['1', ' ', 1], ['1', 'a bee cd.', -1], + ['1', set([]), 1], ['1', set([1, 2, 3]), 1], ['1', {5: 3}, 1], ['1', {}, 1], ['1', {'a': 99}, 1], + ['1', {'a': 1, 'c': 3, 'b': 2}, 1], ['1', {'a': 99, 'c': 3, 'b': 5}, 1], ['1', None, 1], + ['a bee cd.', 0, 1], ['a bee cd.', 1, 1], ['a bee cd.', 2, 1], ['a bee cd.', -1, 1], + ['a bee cd.', -9999999999999999, 1], ['a bee cd.', 9999999999999999, 1], ['a bee cd.', 0.0, 1], + ['a bee cd.', inf, 1], ['a bee cd.', nan, 1], ['a bee cd.', 3.141592653589793, 1], + ['a bee cd.', '', 1], ['a bee cd.', ' ', 1], ['a bee cd.', '1', 1], ['a bee cd.', 'a bee cd.', 0], + ['a bee cd.', '', 1], ['a bee cd.', ' ', 1], ['a bee cd.', '1', 1], ['a bee cd.', set([]), 1], + ['a bee cd.', set([1, 2, 3]), 1], ['a bee cd.', {5: 3}, 1], ['a bee cd.', {}, 1], + ['a bee cd.', {'a': 99}, 1], ['a bee cd.', {'a': 1, 'c': 3, 'b': 2}, 1], + ['a bee cd.', {'a': 99, 'c': 3, 'b': 5}, 1], ['a bee cd.', None, 1], [set([]), 0, 1], + [set([]), 1, 1], [set([]), 2, 1], [set([]), -1, 1], [set([]), -9999999999999999, 1], + [set([]), 9999999999999999, 1], [set([]), 0.0, 1], [set([]), inf, 1], [set([]), nan, 1], + [set([]), 3.141592653589793, 1], [set([]), '', -1], [set([]), ' ', -1], [set([]), '1', -1], + [set([]), 'a bee cd.', -1], [set([]), '', -1], [set([]), ' ', -1], [set([]), '1', -1], + [set([]), 'a bee cd.', -1], + [set([]), set([1, 2, 3]), 'TypeError: cannot compare sets using cmp()'], [set([]), {5: 3}, 1], + [set([]), {}, 1], [set([]), {'a': 99}, 1], [set([]), {'a': 1, 'c': 3, 'b': 2}, 1], + [set([]), {'a': 99, 'c': 3, 'b': 5}, 1], [set([]), None, 1], [set([1, 2, 3]), 0, 1], + [set([1, 2, 3]), 1, 1], [set([1, 2, 3]), 2, 1], [set([1, 2, 3]), -1, 1], + [set([1, 2, 3]), -9999999999999999, 1], [set([1, 2, 3]), 9999999999999999, 1], + [set([1, 2, 3]), 0.0, 1], [set([1, 2, 3]), inf, 1], [set([1, 2, 3]), nan, 1], + [set([1, 2, 3]), 3.141592653589793, 1], [set([1, 2, 3]), '', -1], [set([1, 2, 3]), ' ', -1], + [set([1, 2, 3]), '1', -1], [set([1, 2, 3]), 'a bee cd.', -1], [set([1, 2, 3]), '', -1], + [set([1, 2, 3]), ' ', -1], [set([1, 2, 3]), '1', -1], [set([1, 2, 3]), 'a bee cd.', -1], + [set([1, 2, 3]), set([]), 'TypeError: cannot compare sets using cmp()'], + [set([1, 2, 3]), {5: 3}, 1], [set([1, 2, 3]), {}, 1], [set([1, 2, 3]), {'a': 99}, 1], + [set([1, 2, 3]), {'a': 1, 'c': 3, 'b': 2}, 1], [set([1, 2, 3]), {'a': 99, 'c': 3, 'b': 5}, 1], + [set([1, 2, 3]), None, 1], [{5: 3}, 0, 1], [{5: 3}, 1, 1], [{5: 3}, 2, 1], [{5: 3}, -1, 1], + [{5: 3}, -9999999999999999, 1], [{5: 3}, 9999999999999999, 1], [{5: 3}, 0.0, 1], + [{5: 3}, inf, 1], [{5: 3}, nan, 1], [{5: 3}, 3.141592653589793, 1], [{5: 3}, '', -1], + [{5: 3}, ' ', -1], [{5: 3}, '1', -1], [{5: 3}, 'a bee cd.', -1], [{5: 3}, '', -1], + [{5: 3}, ' ', -1], [{5: 3}, '1', -1], [{5: 3}, 'a bee cd.', -1], [{5: 3}, set([]), -1], + [{5: 3}, set([1, 2, 3]), -1], [{5: 3}, {}, 1], [{5: 3}, {'a': 99}, -1], + [{5: 3}, {'a': 1, 'c': 3, 'b': 2}, -1], [{5: 3}, {'a': 99, 'c': 3, 'b': 5}, -1], [{5: 3}, None, 1], + [{}, 0, 1], [{}, 1, 1], [{}, 2, 1], [{}, -1, 1], [{}, -9999999999999999, 1], + [{}, 9999999999999999, 1], [{}, 0.0, 1], [{}, inf, 1], [{}, nan, 1], [{}, 3.141592653589793, 1], + [{}, '', -1], [{}, ' ', -1], [{}, '1', -1], [{}, 'a bee cd.', -1], [{}, '', -1], [{}, ' ', -1], + [{}, '1', -1], [{}, 'a bee cd.', -1], [{}, set([]), -1], [{}, set([1, 2, 3]), -1], + [{}, {5: 3}, -1], [{}, {'a': 99}, -1], [{}, {'a': 1, 'c': 3, 'b': 2}, -1], + [{}, {'a': 99, 'c': 3, 'b': 5}, -1], [{}, None, 1], [{'a': 99}, 0, 1], [{'a': 99}, 1, 1], + [{'a': 99}, 2, 1], [{'a': 99}, -1, 1], [{'a': 99}, -9999999999999999, 1], + [{'a': 99}, 9999999999999999, 1], [{'a': 99}, 0.0, 1], [{'a': 99}, inf, 1], [{'a': 99}, nan, 1], + [{'a': 99}, 3.141592653589793, 1], [{'a': 99}, '', -1], [{'a': 99}, ' ', -1], [{'a': 99}, '1', -1], + [{'a': 99}, 'a bee cd.', -1], [{'a': 99}, '', -1], [{'a': 99}, ' ', -1], [{'a': 99}, '1', -1], + [{'a': 99}, 'a bee cd.', -1], [{'a': 99}, set([]), -1], [{'a': 99}, set([1, 2, 3]), -1], + [{'a': 99}, {5: 3}, 1], [{'a': 99}, {}, 1], [{'a': 99}, {'a': 1, 'c': 3, 'b': 2}, -1], + [{'a': 99}, {'a': 99, 'c': 3, 'b': 5}, -1], [{'a': 99}, None, 1], [{'a': 1, 'c': 3, 'b': 2}, 0, 1], + [{'a': 1, 'c': 3, 'b': 2}, 1, 1], [{'a': 1, 'c': 3, 'b': 2}, 2, 1], + [{'a': 1, 'c': 3, 'b': 2}, -1, 1], [{'a': 1, 'c': 3, 'b': 2}, -9999999999999999, 1], + [{'a': 1, 'c': 3, 'b': 2}, 9999999999999999, 1], [{'a': 1, 'c': 3, 'b': 2}, 0.0, 1], + [{'a': 1, 'c': 3, 'b': 2}, inf, 1], [{'a': 1, 'c': 3, 'b': 2}, nan, 1], + [{'a': 1, 'c': 3, 'b': 2}, 3.141592653589793, 1], [{'a': 1, 'c': 3, 'b': 2}, '', -1], + [{'a': 1, 'c': 3, 'b': 2}, ' ', -1], [{'a': 1, 'c': 3, 'b': 2}, '1', -1], + [{'a': 1, 'c': 3, 'b': 2}, 'a bee cd.', -1], [{'a': 1, 'c': 3, 'b': 2}, '', -1], + [{'a': 1, 'c': 3, 'b': 2}, ' ', -1], [{'a': 1, 'c': 3, 'b': 2}, '1', -1], + [{'a': 1, 'c': 3, 'b': 2}, 'a bee cd.', -1], [{'a': 1, 'c': 3, 'b': 2}, set([]), -1], + [{'a': 1, 'c': 3, 'b': 2}, set([1, 2, 3]), -1], [{'a': 1, 'c': 3, 'b': 2}, {5: 3}, 1], + [{'a': 1, 'c': 3, 'b': 2}, {}, 1], [{'a': 1, 'c': 3, 'b': 2}, {'a': 99}, 1], + [{'a': 1, 'c': 3, 'b': 2}, {'a': 99, 'c': 3, 'b': 5}, -1], [{'a': 1, 'c': 3, 'b': 2}, None, 1], + [{'a': 99, 'c': 3, 'b': 5}, 0, 1], [{'a': 99, 'c': 3, 'b': 5}, 1, 1], + [{'a': 99, 'c': 3, 'b': 5}, 2, 1], [{'a': 99, 'c': 3, 'b': 5}, -1, 1], + [{'a': 99, 'c': 3, 'b': 5}, -9999999999999999, 1], + [{'a': 99, 'c': 3, 'b': 5}, 9999999999999999, 1], [{'a': 99, 'c': 3, 'b': 5}, 0.0, 1], + [{'a': 99, 'c': 3, 'b': 5}, inf, 1], [{'a': 99, 'c': 3, 'b': 5}, nan, 1], + [{'a': 99, 'c': 3, 'b': 5}, 3.141592653589793, 1], [{'a': 99, 'c': 3, 'b': 5}, '', -1], + [{'a': 99, 'c': 3, 'b': 5}, ' ', -1], [{'a': 99, 'c': 3, 'b': 5}, '1', -1], + [{'a': 99, 'c': 3, 'b': 5}, 'a bee cd.', -1], [{'a': 99, 'c': 3, 'b': 5}, '', -1], + [{'a': 99, 'c': 3, 'b': 5}, ' ', -1], [{'a': 99, 'c': 3, 'b': 5}, '1', -1], + [{'a': 99, 'c': 3, 'b': 5}, 'a bee cd.', -1], [{'a': 99, 'c': 3, 'b': 5}, set([]), -1], + [{'a': 99, 'c': 3, 'b': 5}, set([1, 2, 3]), -1], [{'a': 99, 'c': 3, 'b': 5}, {5: 3}, 1], + [{'a': 99, 'c': 3, 'b': 5}, {}, 1], [{'a': 99, 'c': 3, 'b': 5}, {'a': 99}, 1], + [{'a': 99, 'c': 3, 'b': 5}, {'a': 1, 'c': 3, 'b': 2}, 1], [{'a': 99, 'c': 3, 'b': 5}, None, 1], + [None, 0, -1], [None, 1, -1], [None, 2, -1], [None, -1, -1], [None, -9999999999999999, -1], + [None, 9999999999999999, -1], [None, 0.0, -1], [None, inf, -1], [None, nan, -1], + [None, 3.141592653589793, -1], [None, '', -1], [None, ' ', -1], [None, '1', -1], + [None, 'a bee cd.', -1], [None, '', -1], [None, ' ', -1], [None, '1', -1], [None, 'a bee cd.', -1], + [None, set([]), -1], [None, set([1, 2, 3]), -1], [None, {5: 3}, -1], [None, {}, -1], + [None, {'a': 99}, -1], [None, {'a': 1, 'c': 3, 'b': 2}, -1], [None, {'a': 99, 'c': 3, 'b': 5}, -1]] From 8302d8c8f1f61c9b1cb978337eddcdcc48701310 Mon Sep 17 00:00:00 2001 From: "Roman A. Taycher" Date: Sun, 30 Aug 2020 19:37:37 -0700 Subject: [PATCH 353/425] minor style/spelling fixes --- src/past/builtins/misc.py | 4 ++-- tests/test_past/test_misc.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/past/builtins/misc.py b/src/past/builtins/misc.py index 889e0ff8..6342a8ec 100644 --- a/src/past/builtins/misc.py +++ b/src/past/builtins/misc.py @@ -103,7 +103,7 @@ def oct(number): return '0' + builtins.oct(number)[2:] raw_input = input - from imp import reload + from importlib import reload unicode = str unichr = chr xrange = range @@ -143,7 +143,7 @@ def execfile(filename, myglobals=None, mylocals=None): if not isinstance(mylocals, Mapping): raise TypeError('locals must be a mapping') with open(filename, "rb") as fin: - source = fin.read() + source = fin.read() code = compile(source, filename, "exec") exec_(code, myglobals, mylocals) diff --git a/tests/test_past/test_misc.py b/tests/test_past/test_misc.py index 3d1b7a09..3952ab19 100644 --- a/tests/test_past/test_misc.py +++ b/tests/test_past/test_misc.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Tests for the resurrected Py2-like cmp funtion +Tests for the resurrected Py2-like cmp function """ from __future__ import absolute_import, unicode_literals, print_function @@ -23,7 +23,7 @@ def test_cmp(self): with self.subTest(x=x, y=y): try: past_cmp_value = cmp(x, y) - except Exception as ex: + except Exception: past_cmp_value = traceback.format_exc().strip().split('\n')[-1] self.assertEqual(cmp_python2_value, past_cmp_value, From 4a687ea190fb3a18e808373fd571adb65218cf9e Mon Sep 17 00:00:00 2001 From: "Roman A. Taycher" Date: Sun, 30 Aug 2020 20:06:38 -0700 Subject: [PATCH 354/425] replace fstrings with format for python 3.4,3.5 --- src/past/builtins/misc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/past/builtins/misc.py b/src/past/builtins/misc.py index 6342a8ec..dda6ff33 100644 --- a/src/past/builtins/misc.py +++ b/src/past/builtins/misc.py @@ -39,14 +39,14 @@ def cmp(x, y): try: if isinstance(x, numbers.Number) and math.isnan(x): if not isinstance(y, numbers.Number): - raise TypeError(f'cannot compare float("nan"), {type(y)} with cmp') + 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(f'cannot compare {type(x)}, float("nan") with cmp') + raise TypeError('cannot compare {type_x}, float("nan") with cmp'.format(type_x=type(x))) if isinstance(x, int): return -1 else: From fc84fa8dd46d5ccff8d80b5ac1cac68af5c83055 Mon Sep 17 00:00:00 2001 From: "Roman A. Taycher" Date: Sun, 30 Aug 2020 20:16:59 -0700 Subject: [PATCH 355/425] import from old imp library on older python versions --- src/past/builtins/misc.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/past/builtins/misc.py b/src/past/builtins/misc.py index dda6ff33..3600695c 100644 --- a/src/past/builtins/misc.py +++ b/src/past/builtins/misc.py @@ -103,7 +103,13 @@ def oct(number): return '0' + builtins.oct(number)[2:] raw_input = input - from importlib import reload + + try: + from importlib import reload + except ImportError: + # for python2, python3 <= 3.4 + from imp import reload + unicode = str unichr = chr xrange = range From f006cad1cf66d691086df058698404cd4bdf2216 Mon Sep 17 00:00:00 2001 From: "Roman A. Taycher" Date: Sun, 30 Aug 2020 20:32:00 -0700 Subject: [PATCH 356/425] fix missing subTest --- tests/test_past/test_misc.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/test_past/test_misc.py b/tests/test_past/test_misc.py index 3952ab19..2bbdea2d 100644 --- a/tests/test_past/test_misc.py +++ b/tests/test_past/test_misc.py @@ -8,6 +8,7 @@ import os.path import sys import traceback +from contextlib import contextmanager from future.tests.base import unittest from past.builtins import cmp @@ -17,10 +18,16 @@ import test_values +@contextmanager +def empty_context_manager(*args, **kwargs): + return dict(args=args, kwargs=kwargs) + + class TestCmp(unittest.TestCase): def test_cmp(self): for x, y, cmp_python2_value in test_values.cmp_python2_value: - with self.subTest(x=x, y=y): + # to get this to run on python <3.4 which lacks subTest + with getattr(self, 'subTest', empty_context_manager)(x=x, y=y): try: past_cmp_value = cmp(x, y) except Exception: From c0510266fd62297ea54629bac89cbb4644c8786f Mon Sep 17 00:00:00 2001 From: "Roman A. Taycher" Date: Sun, 30 Aug 2020 20:42:10 -0700 Subject: [PATCH 357/425] fix other python2 test issues --- tests/test_past/test_misc.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_past/test_misc.py b/tests/test_past/test_misc.py index 2bbdea2d..db07896f 100644 --- a/tests/test_past/test_misc.py +++ b/tests/test_past/test_misc.py @@ -11,7 +11,10 @@ from contextlib import contextmanager from future.tests.base import unittest -from past.builtins import cmp +from future.utils import PY3 + +if PY3: + from past.builtins import cmp _dir = os.path.dirname(os.path.abspath(__file__)) sys.path.append(_dir) @@ -20,7 +23,7 @@ @contextmanager def empty_context_manager(*args, **kwargs): - return dict(args=args, kwargs=kwargs) + yield dict(args=args, kwargs=kwargs) class TestCmp(unittest.TestCase): From e3f1a128a978e23b2993222cc3379a2e64f54e9a Mon Sep 17 00:00:00 2001 From: "Roman A. Taycher" Date: Sun, 30 Aug 2020 21:10:26 -0700 Subject: [PATCH 358/425] include list test values --- tests/test_past/test_values.py | 281 +++++++++++++++++++-------------- 1 file changed, 160 insertions(+), 121 deletions(-) diff --git a/tests/test_past/test_values.py b/tests/test_past/test_values.py index 393ad761..7c85a8f2 100644 --- a/tests/test_past/test_values.py +++ b/tests/test_past/test_values.py @@ -4,7 +4,7 @@ test_values = [ 0, 1, 2, -1, -9999999999, 9999999, 0.0, inf, nan, pi, - # [], [[]], [1,2,3], + [], [[]], [1, 2, 3], set(), set([1, 2, 3]), " ", "", "1", "dsada saA.", "2", "dsa", b"", b"dsa", b" ", {5: 3}, dict(), dict(a=99), dict(a=1, b=2, c=3), None @@ -12,62 +12,65 @@ # cmp_python2_values are pre-calculated from running cmp under python2 first values are x and y, last is results of cmp cmp_python2_value = [[0, 1, -1], [0, 2, -1], [0, -1, 1], [0, -9999999999999999, 1], [0, 9999999999999999, -1], - [0, 0.0, 0], [0, inf, -1], [0, nan, -1], [0, 3.141592653589793, -1], [0, '', -1], [0, ' ', -1], - [0, '1', -1], [0, 'a bee cd.', -1], [0, '', -1], [0, ' ', -1], [0, '1', -1], [0, 'a bee cd.', -1], - [0, set([]), -1], [0, set([1, 2, 3]), -1], [0, {5: 3}, -1], [0, {}, -1], [0, {'a': 99}, -1], - [0, {'a': 1, 'c': 3, 'b': 2}, -1], [0, {'a': 99, 'c': 3, 'b': 5}, -1], [0, None, 1], [1, 0, 1], - [1, 2, -1], [1, -1, 1], [1, -9999999999999999, 1], [1, 9999999999999999, -1], [1, 0.0, 1], - [1, inf, -1], [1, nan, -1], [1, 3.141592653589793, -1], [1, '', -1], [1, ' ', -1], [1, '1', -1], - [1, 'a bee cd.', -1], [1, '', -1], [1, ' ', -1], [1, '1', -1], [1, 'a bee cd.', -1], - [1, set([]), -1], [1, set([1, 2, 3]), -1], [1, {5: 3}, -1], [1, {}, -1], [1, {'a': 99}, -1], - [1, {'a': 1, 'c': 3, 'b': 2}, -1], [1, {'a': 99, 'c': 3, 'b': 5}, -1], [1, None, 1], [2, 0, 1], - [2, 1, 1], [2, -1, 1], [2, -9999999999999999, 1], [2, 9999999999999999, -1], [2, 0.0, 1], - [2, inf, -1], [2, nan, -1], [2, 3.141592653589793, -1], [2, '', -1], [2, ' ', -1], [2, '1', -1], + [0, 0.0, 0], [0, inf, -1], [0, nan, -1], [0, 3.141592653589793, -1], [0, [], -1], [0, [[]], -1], + [0, [1, 2, 3], -1], [0, '', -1], [0, ' ', -1], [0, '1', -1], [0, 'a bee cd.', -1], [0, '', -1], + [0, ' ', -1], [0, '1', -1], [0, 'a bee cd.', -1], [0, set([]), -1], [0, set([1, 2, 3]), -1], + [0, {5: 3}, -1], [0, {}, -1], [0, {'a': 99}, -1], [0, {'a': 1, 'c': 3, 'b': 2}, -1], + [0, {'a': 99, 'c': 3, 'b': 5}, -1], [0, None, 1], [1, 0, 1], [1, 2, -1], [1, -1, 1], + [1, -9999999999999999, 1], [1, 9999999999999999, -1], [1, 0.0, 1], [1, inf, -1], [1, nan, -1], + [1, 3.141592653589793, -1], [1, [], -1], [1, [[]], -1], [1, [1, 2, 3], -1], [1, '', -1], + [1, ' ', -1], [1, '1', -1], [1, 'a bee cd.', -1], [1, '', -1], [1, ' ', -1], [1, '1', -1], + [1, 'a bee cd.', -1], [1, set([]), -1], [1, set([1, 2, 3]), -1], [1, {5: 3}, -1], [1, {}, -1], + [1, {'a': 99}, -1], [1, {'a': 1, 'c': 3, 'b': 2}, -1], [1, {'a': 99, 'c': 3, 'b': 5}, -1], + [1, None, 1], [2, 0, 1], [2, 1, 1], [2, -1, 1], [2, -9999999999999999, 1], + [2, 9999999999999999, -1], [2, 0.0, 1], [2, inf, -1], [2, nan, -1], [2, 3.141592653589793, -1], + [2, [], -1], [2, [[]], -1], [2, [1, 2, 3], -1], [2, '', -1], [2, ' ', -1], [2, '1', -1], [2, 'a bee cd.', -1], [2, '', -1], [2, ' ', -1], [2, '1', -1], [2, 'a bee cd.', -1], [2, set([]), -1], [2, set([1, 2, 3]), -1], [2, {5: 3}, -1], [2, {}, -1], [2, {'a': 99}, -1], [2, {'a': 1, 'c': 3, 'b': 2}, -1], [2, {'a': 99, 'c': 3, 'b': 5}, -1], [2, None, 1], [-1, 0, -1], [-1, 1, -1], [-1, 2, -1], [-1, -9999999999999999, 1], [-1, 9999999999999999, -1], [-1, 0.0, -1], - [-1, inf, -1], [-1, nan, -1], [-1, 3.141592653589793, -1], [-1, '', -1], [-1, ' ', -1], - [-1, '1', -1], [-1, 'a bee cd.', -1], [-1, '', -1], [-1, ' ', -1], [-1, '1', -1], - [-1, 'a bee cd.', -1], [-1, set([]), -1], [-1, set([1, 2, 3]), -1], [-1, {5: 3}, -1], [-1, {}, -1], - [-1, {'a': 99}, -1], [-1, {'a': 1, 'c': 3, 'b': 2}, -1], [-1, {'a': 99, 'c': 3, 'b': 5}, -1], - [-1, None, 1], [-9999999999999999, 0, -1], [-9999999999999999, 1, -1], - [-9999999999999999, 2, -1], [-9999999999999999, -1, -1], - [-9999999999999999, 9999999999999999, -1], [-9999999999999999, 0.0, -1], - [-9999999999999999, inf, -1], [-9999999999999999, nan, -1], - [-9999999999999999, 3.141592653589793, -1], [-9999999999999999, '', -1], - [-9999999999999999, ' ', -1], [-9999999999999999, '1', -1], - [-9999999999999999, 'a bee cd.', -1], [-9999999999999999, '', -1], [-9999999999999999, ' ', -1], - [-9999999999999999, '1', -1], [-9999999999999999, 'a bee cd.', -1], - [-9999999999999999, set([]), -1], [-9999999999999999, set([1, 2, 3]), -1], - [-9999999999999999, {5: 3}, -1], [-9999999999999999, {}, -1], - [-9999999999999999, {'a': 99}, -1], [-9999999999999999, {'a': 1, 'c': 3, 'b': 2}, -1], + [-1, inf, -1], [-1, nan, -1], [-1, 3.141592653589793, -1], [-1, [], -1], [-1, [[]], -1], + [-1, [1, 2, 3], -1], [-1, '', -1], [-1, ' ', -1], [-1, '1', -1], [-1, 'a bee cd.', -1], + [-1, '', -1], [-1, ' ', -1], [-1, '1', -1], [-1, 'a bee cd.', -1], [-1, set([]), -1], + [-1, set([1, 2, 3]), -1], [-1, {5: 3}, -1], [-1, {}, -1], [-1, {'a': 99}, -1], + [-1, {'a': 1, 'c': 3, 'b': 2}, -1], [-1, {'a': 99, 'c': 3, 'b': 5}, -1], [-1, None, 1], + [-9999999999999999, 0, -1], [-9999999999999999, 1, -1], [-9999999999999999, 2, -1], + [-9999999999999999, -1, -1], [-9999999999999999, 9999999999999999, -1], + [-9999999999999999, 0.0, -1], [-9999999999999999, inf, -1], [-9999999999999999, nan, -1], + [-9999999999999999, 3.141592653589793, -1], [-9999999999999999, [], -1], + [-9999999999999999, [[]], -1], [-9999999999999999, [1, 2, 3], -1], [-9999999999999999, '', -1], + [-9999999999999999, ' ', -1], [-9999999999999999, '1', -1], [-9999999999999999, 'a bee cd.', -1], + [-9999999999999999, '', -1], [-9999999999999999, ' ', -1], [-9999999999999999, '1', -1], + [-9999999999999999, 'a bee cd.', -1], [-9999999999999999, set([]), -1], + [-9999999999999999, set([1, 2, 3]), -1], [-9999999999999999, {5: 3}, -1], + [-9999999999999999, {}, -1], [-9999999999999999, {'a': 99}, -1], + [-9999999999999999, {'a': 1, 'c': 3, 'b': 2}, -1], [-9999999999999999, {'a': 99, 'c': 3, 'b': 5}, -1], [-9999999999999999, None, 1], [9999999999999999, 0, 1], [9999999999999999, 1, 1], [9999999999999999, 2, 1], - [9999999999999999, -1, 1], [9999999999999999, -9999999999999999, 1], - [9999999999999999, 0.0, 1], [9999999999999999, inf, -1], [9999999999999999, nan, -1], - [9999999999999999, 3.141592653589793, 1], [9999999999999999, '', -1], - [9999999999999999, ' ', -1], [9999999999999999, '1', -1], [9999999999999999, 'a bee cd.', -1], + [9999999999999999, -1, 1], [9999999999999999, -9999999999999999, 1], [9999999999999999, 0.0, 1], + [9999999999999999, inf, -1], [9999999999999999, nan, -1], [9999999999999999, 3.141592653589793, 1], + [9999999999999999, [], -1], [9999999999999999, [[]], -1], [9999999999999999, [1, 2, 3], -1], [9999999999999999, '', -1], [9999999999999999, ' ', -1], [9999999999999999, '1', -1], - [9999999999999999, 'a bee cd.', -1], [9999999999999999, set([]), -1], - [9999999999999999, set([1, 2, 3]), -1], [9999999999999999, {5: 3}, -1], - [9999999999999999, {}, -1], [9999999999999999, {'a': 99}, -1], - [9999999999999999, {'a': 1, 'c': 3, 'b': 2}, -1], + [9999999999999999, 'a bee cd.', -1], [9999999999999999, '', -1], [9999999999999999, ' ', -1], + [9999999999999999, '1', -1], [9999999999999999, 'a bee cd.', -1], [9999999999999999, set([]), -1], + [9999999999999999, set([1, 2, 3]), -1], [9999999999999999, {5: 3}, -1], [9999999999999999, {}, -1], + [9999999999999999, {'a': 99}, -1], [9999999999999999, {'a': 1, 'c': 3, 'b': 2}, -1], [9999999999999999, {'a': 99, 'c': 3, 'b': 5}, -1], [9999999999999999, None, 1], [0.0, 0, 0], - [0.0, 1, -1], [0.0, 2, -1], [0.0, -1, 1], [0.0, -9999999999999999, 1], - [0.0, 9999999999999999, -1], [0.0, inf, -1], [0.0, nan, 1], [0.0, 3.141592653589793, -1], - [0.0, '', -1], [0.0, ' ', -1], [0.0, '1', -1], [0.0, 'a bee cd.', -1], [0.0, '', -1], - [0.0, ' ', -1], [0.0, '1', -1], [0.0, 'a bee cd.', -1], [0.0, set([]), -1], + [0.0, 1, -1], [0.0, 2, -1], [0.0, -1, 1], [0.0, -9999999999999999, 1], [0.0, 9999999999999999, -1], + [0.0, inf, -1], [0.0, nan, 1], [0.0, 3.141592653589793, -1], [0.0, [], -1], [0.0, [[]], -1], + [0.0, [1, 2, 3], -1], [0.0, '', -1], [0.0, ' ', -1], [0.0, '1', -1], [0.0, 'a bee cd.', -1], + [0.0, '', -1], [0.0, ' ', -1], [0.0, '1', -1], [0.0, 'a bee cd.', -1], [0.0, set([]), -1], [0.0, set([1, 2, 3]), -1], [0.0, {5: 3}, -1], [0.0, {}, -1], [0.0, {'a': 99}, -1], [0.0, {'a': 1, 'c': 3, 'b': 2}, -1], [0.0, {'a': 99, 'c': 3, 'b': 5}, -1], [0.0, None, 1], [inf, 0, 1], [inf, 1, 1], [inf, 2, 1], [inf, -1, 1], [inf, -9999999999999999, 1], [inf, 9999999999999999, 1], [inf, 0.0, 1], [inf, nan, 1], [inf, 3.141592653589793, 1], - [inf, '', -1], [inf, ' ', -1], [inf, '1', -1], [inf, 'a bee cd.', -1], [inf, '', -1], - [inf, ' ', -1], [inf, '1', -1], [inf, 'a bee cd.', -1], [inf, set([]), -1], - [inf, set([1, 2, 3]), -1], [inf, {5: 3}, -1], [inf, {}, -1], [inf, {'a': 99}, -1], - [inf, {'a': 1, 'c': 3, 'b': 2}, -1], [inf, {'a': 99, 'c': 3, 'b': 5}, -1], [inf, None, 1], - [nan, 0, 1], [nan, 1, 1], [nan, 2, 1], [nan, -1, 1], [nan, -9999999999999999, 1], - [nan, 9999999999999999, 1], [nan, 0.0, -1], [nan, inf, -1], [nan, 3.141592653589793, -1], + [inf, [], -1], [inf, [[]], -1], [inf, [1, 2, 3], -1], [inf, '', -1], [inf, ' ', -1], + [inf, '1', -1], [inf, 'a bee cd.', -1], [inf, '', -1], [inf, ' ', -1], [inf, '1', -1], + [inf, 'a bee cd.', -1], [inf, set([]), -1], [inf, set([1, 2, 3]), -1], [inf, {5: 3}, -1], + [inf, {}, -1], [inf, {'a': 99}, -1], [inf, {'a': 1, 'c': 3, 'b': 2}, -1], + [inf, {'a': 99, 'c': 3, 'b': 5}, -1], [inf, None, 1], [nan, 0, 1], [nan, 1, 1], [nan, 2, 1], + [nan, -1, 1], [nan, -9999999999999999, 1], [nan, 9999999999999999, 1], [nan, 0.0, -1], + [nan, inf, -1], [nan, 3.141592653589793, -1], [nan, [], -1], [nan, [[]], -1], [nan, [1, 2, 3], -1], [nan, '', -1], [nan, ' ', -1], [nan, '1', -1], [nan, 'a bee cd.', -1], [nan, '', -1], [nan, ' ', -1], [nan, '1', -1], [nan, 'a bee cd.', -1], [nan, set([]), -1], [nan, set([1, 2, 3]), -1], [nan, {5: 3}, -1], [nan, {}, -1], [nan, {'a': 99}, -1], @@ -75,95 +78,126 @@ [3.141592653589793, 0, 1], [3.141592653589793, 1, 1], [3.141592653589793, 2, 1], [3.141592653589793, -1, 1], [3.141592653589793, -9999999999999999, 1], [3.141592653589793, 9999999999999999, -1], [3.141592653589793, 0.0, 1], - [3.141592653589793, inf, -1], [3.141592653589793, nan, 1], [3.141592653589793, '', -1], + [3.141592653589793, inf, -1], [3.141592653589793, nan, 1], [3.141592653589793, [], -1], + [3.141592653589793, [[]], -1], [3.141592653589793, [1, 2, 3], -1], [3.141592653589793, '', -1], [3.141592653589793, ' ', -1], [3.141592653589793, '1', -1], [3.141592653589793, 'a bee cd.', -1], [3.141592653589793, '', -1], [3.141592653589793, ' ', -1], [3.141592653589793, '1', -1], [3.141592653589793, 'a bee cd.', -1], [3.141592653589793, set([]), -1], [3.141592653589793, set([1, 2, 3]), -1], [3.141592653589793, {5: 3}, -1], [3.141592653589793, {}, -1], [3.141592653589793, {'a': 99}, -1], [3.141592653589793, {'a': 1, 'c': 3, 'b': 2}, -1], - [3.141592653589793, {'a': 99, 'c': 3, 'b': 5}, -1], [3.141592653589793, None, 1], ['', 0, 1], - ['', 1, 1], ['', 2, 1], ['', -1, 1], ['', -9999999999999999, 1], ['', 9999999999999999, 1], - ['', 0.0, 1], ['', inf, 1], ['', nan, 1], ['', 3.141592653589793, 1], ['', ' ', -1], ['', '1', -1], - ['', 'a bee cd.', -1], ['', '', 0], ['', ' ', -1], ['', '1', -1], ['', 'a bee cd.', -1], - ['', set([]), 1], ['', set([1, 2, 3]), 1], ['', {5: 3}, 1], ['', {}, 1], ['', {'a': 99}, 1], - ['', {'a': 1, 'c': 3, 'b': 2}, 1], ['', {'a': 99, 'c': 3, 'b': 5}, 1], ['', None, 1], [' ', 0, 1], - [' ', 1, 1], [' ', 2, 1], [' ', -1, 1], [' ', -9999999999999999, 1], [' ', 9999999999999999, 1], - [' ', 0.0, 1], [' ', inf, 1], [' ', nan, 1], [' ', 3.141592653589793, 1], [' ', '', 1], - [' ', '1', -1], [' ', 'a bee cd.', -1], [' ', '', 1], [' ', ' ', 0], [' ', '1', -1], - [' ', 'a bee cd.', -1], [' ', set([]), 1], [' ', set([1, 2, 3]), 1], [' ', {5: 3}, 1], - [' ', {}, 1], [' ', {'a': 99}, 1], [' ', {'a': 1, 'c': 3, 'b': 2}, 1], + [3.141592653589793, {'a': 99, 'c': 3, 'b': 5}, -1], [3.141592653589793, None, 1], [[], 0, 1], + [[], 1, 1], [[], 2, 1], [[], -1, 1], [[], -9999999999999999, 1], [[], 9999999999999999, 1], + [[], 0.0, 1], [[], inf, 1], [[], nan, 1], [[], 3.141592653589793, 1], [[], [[]], -1], + [[], [1, 2, 3], -1], [[], '', -1], [[], ' ', -1], [[], '1', -1], [[], 'a bee cd.', -1], + [[], '', -1], [[], ' ', -1], [[], '1', -1], [[], 'a bee cd.', -1], [[], set([]), -1], + [[], set([1, 2, 3]), -1], [[], {5: 3}, 1], [[], {}, 1], [[], {'a': 99}, 1], + [[], {'a': 1, 'c': 3, 'b': 2}, 1], [[], {'a': 99, 'c': 3, 'b': 5}, 1], [[], None, 1], [[[]], 0, 1], + [[[]], 1, 1], [[[]], 2, 1], [[[]], -1, 1], [[[]], -9999999999999999, 1], + [[[]], 9999999999999999, 1], [[[]], 0.0, 1], [[[]], inf, 1], [[[]], nan, 1], + [[[]], 3.141592653589793, 1], [[[]], [], 1], [[[]], [1, 2, 3], 1], [[[]], '', -1], [[[]], ' ', -1], + [[[]], '1', -1], [[[]], 'a bee cd.', -1], [[[]], '', -1], [[[]], ' ', -1], [[[]], '1', -1], + [[[]], 'a bee cd.', -1], [[[]], set([]), -1], [[[]], set([1, 2, 3]), -1], [[[]], {5: 3}, 1], + [[[]], {}, 1], [[[]], {'a': 99}, 1], [[[]], {'a': 1, 'c': 3, 'b': 2}, 1], + [[[]], {'a': 99, 'c': 3, 'b': 5}, 1], [[[]], None, 1], [[1, 2, 3], 0, 1], [[1, 2, 3], 1, 1], + [[1, 2, 3], 2, 1], [[1, 2, 3], -1, 1], [[1, 2, 3], -9999999999999999, 1], + [[1, 2, 3], 9999999999999999, 1], [[1, 2, 3], 0.0, 1], [[1, 2, 3], inf, 1], [[1, 2, 3], nan, 1], + [[1, 2, 3], 3.141592653589793, 1], [[1, 2, 3], [], 1], [[1, 2, 3], [[]], -1], [[1, 2, 3], '', -1], + [[1, 2, 3], ' ', -1], [[1, 2, 3], '1', -1], [[1, 2, 3], 'a bee cd.', -1], [[1, 2, 3], '', -1], + [[1, 2, 3], ' ', -1], [[1, 2, 3], '1', -1], [[1, 2, 3], 'a bee cd.', -1], [[1, 2, 3], set([]), -1], + [[1, 2, 3], set([1, 2, 3]), -1], [[1, 2, 3], {5: 3}, 1], [[1, 2, 3], {}, 1], + [[1, 2, 3], {'a': 99}, 1], [[1, 2, 3], {'a': 1, 'c': 3, 'b': 2}, 1], + [[1, 2, 3], {'a': 99, 'c': 3, 'b': 5}, 1], [[1, 2, 3], None, 1], ['', 0, 1], ['', 1, 1], + ['', 2, 1], ['', -1, 1], ['', -9999999999999999, 1], ['', 9999999999999999, 1], ['', 0.0, 1], + ['', inf, 1], ['', nan, 1], ['', 3.141592653589793, 1], ['', [], 1], ['', [[]], 1], + ['', [1, 2, 3], 1], ['', ' ', -1], ['', '1', -1], ['', 'a bee cd.', -1], ['', '', 0], + ['', ' ', -1], ['', '1', -1], ['', 'a bee cd.', -1], ['', set([]), 1], ['', set([1, 2, 3]), 1], + ['', {5: 3}, 1], ['', {}, 1], ['', {'a': 99}, 1], ['', {'a': 1, 'c': 3, 'b': 2}, 1], + ['', {'a': 99, 'c': 3, 'b': 5}, 1], ['', None, 1], [' ', 0, 1], [' ', 1, 1], [' ', 2, 1], + [' ', -1, 1], [' ', -9999999999999999, 1], [' ', 9999999999999999, 1], [' ', 0.0, 1], + [' ', inf, 1], [' ', nan, 1], [' ', 3.141592653589793, 1], [' ', [], 1], [' ', [[]], 1], + [' ', [1, 2, 3], 1], [' ', '', 1], [' ', '1', -1], [' ', 'a bee cd.', -1], [' ', '', 1], + [' ', ' ', 0], [' ', '1', -1], [' ', 'a bee cd.', -1], [' ', set([]), 1], [' ', set([1, 2, 3]), 1], + [' ', {5: 3}, 1], [' ', {}, 1], [' ', {'a': 99}, 1], [' ', {'a': 1, 'c': 3, 'b': 2}, 1], [' ', {'a': 99, 'c': 3, 'b': 5}, 1], [' ', None, 1], ['1', 0, 1], ['1', 1, 1], ['1', 2, 1], ['1', -1, 1], ['1', -9999999999999999, 1], ['1', 9999999999999999, 1], ['1', 0.0, 1], - ['1', inf, 1], ['1', nan, 1], ['1', 3.141592653589793, 1], ['1', '', 1], ['1', ' ', 1], - ['1', 'a bee cd.', -1], ['1', '', 1], ['1', ' ', 1], ['1', '1', 0], ['1', 'a bee cd.', -1], - ['1', set([]), 1], ['1', set([1, 2, 3]), 1], ['1', {5: 3}, 1], ['1', {}, 1], ['1', {'a': 99}, 1], - ['1', {'a': 1, 'c': 3, 'b': 2}, 1], ['1', {'a': 99, 'c': 3, 'b': 5}, 1], ['1', None, 1], - ['a bee cd.', 0, 1], ['a bee cd.', 1, 1], ['a bee cd.', 2, 1], ['a bee cd.', -1, 1], - ['a bee cd.', -9999999999999999, 1], ['a bee cd.', 9999999999999999, 1], ['a bee cd.', 0.0, 1], - ['a bee cd.', inf, 1], ['a bee cd.', nan, 1], ['a bee cd.', 3.141592653589793, 1], - ['a bee cd.', '', 1], ['a bee cd.', ' ', 1], ['a bee cd.', '1', 1], ['a bee cd.', '', 1], - ['a bee cd.', ' ', 1], ['a bee cd.', '1', 1], ['a bee cd.', 'a bee cd.', 0], - ['a bee cd.', set([]), 1], ['a bee cd.', set([1, 2, 3]), 1], ['a bee cd.', {5: 3}, 1], - ['a bee cd.', {}, 1], ['a bee cd.', {'a': 99}, 1], ['a bee cd.', {'a': 1, 'c': 3, 'b': 2}, 1], - ['a bee cd.', {'a': 99, 'c': 3, 'b': 5}, 1], ['a bee cd.', None, 1], ['', 0, 1], ['', 1, 1], - ['', 2, 1], ['', -1, 1], ['', -9999999999999999, 1], ['', 9999999999999999, 1], ['', 0.0, 1], - ['', inf, 1], ['', nan, 1], ['', 3.141592653589793, 1], ['', '', 0], ['', ' ', -1], ['', '1', -1], - ['', 'a bee cd.', -1], ['', ' ', -1], ['', '1', -1], ['', 'a bee cd.', -1], ['', set([]), 1], - ['', set([1, 2, 3]), 1], ['', {5: 3}, 1], ['', {}, 1], ['', {'a': 99}, 1], - ['', {'a': 1, 'c': 3, 'b': 2}, 1], ['', {'a': 99, 'c': 3, 'b': 5}, 1], ['', None, 1], [' ', 0, 1], - [' ', 1, 1], [' ', 2, 1], [' ', -1, 1], [' ', -9999999999999999, 1], [' ', 9999999999999999, 1], - [' ', 0.0, 1], [' ', inf, 1], [' ', nan, 1], [' ', 3.141592653589793, 1], [' ', '', 1], + ['1', inf, 1], ['1', nan, 1], ['1', 3.141592653589793, 1], ['1', [], 1], ['1', [[]], 1], + ['1', [1, 2, 3], 1], ['1', '', 1], ['1', ' ', 1], ['1', 'a bee cd.', -1], ['1', '', 1], + ['1', ' ', 1], ['1', '1', 0], ['1', 'a bee cd.', -1], ['1', set([]), 1], ['1', set([1, 2, 3]), 1], + ['1', {5: 3}, 1], ['1', {}, 1], ['1', {'a': 99}, 1], ['1', {'a': 1, 'c': 3, 'b': 2}, 1], + ['1', {'a': 99, 'c': 3, 'b': 5}, 1], ['1', None, 1], ['a bee cd.', 0, 1], ['a bee cd.', 1, 1], + ['a bee cd.', 2, 1], ['a bee cd.', -1, 1], ['a bee cd.', -9999999999999999, 1], + ['a bee cd.', 9999999999999999, 1], ['a bee cd.', 0.0, 1], ['a bee cd.', inf, 1], + ['a bee cd.', nan, 1], ['a bee cd.', 3.141592653589793, 1], ['a bee cd.', [], 1], + ['a bee cd.', [[]], 1], ['a bee cd.', [1, 2, 3], 1], ['a bee cd.', '', 1], ['a bee cd.', ' ', 1], + ['a bee cd.', '1', 1], ['a bee cd.', '', 1], ['a bee cd.', ' ', 1], ['a bee cd.', '1', 1], + ['a bee cd.', 'a bee cd.', 0], ['a bee cd.', set([]), 1], ['a bee cd.', set([1, 2, 3]), 1], + ['a bee cd.', {5: 3}, 1], ['a bee cd.', {}, 1], ['a bee cd.', {'a': 99}, 1], + ['a bee cd.', {'a': 1, 'c': 3, 'b': 2}, 1], ['a bee cd.', {'a': 99, 'c': 3, 'b': 5}, 1], + ['a bee cd.', None, 1], ['', 0, 1], ['', 1, 1], ['', 2, 1], ['', -1, 1], + ['', -9999999999999999, 1], ['', 9999999999999999, 1], ['', 0.0, 1], ['', inf, 1], ['', nan, 1], + ['', 3.141592653589793, 1], ['', [], 1], ['', [[]], 1], ['', [1, 2, 3], 1], ['', '', 0], + ['', ' ', -1], ['', '1', -1], ['', 'a bee cd.', -1], ['', ' ', -1], ['', '1', -1], + ['', 'a bee cd.', -1], ['', set([]), 1], ['', set([1, 2, 3]), 1], ['', {5: 3}, 1], ['', {}, 1], + ['', {'a': 99}, 1], ['', {'a': 1, 'c': 3, 'b': 2}, 1], ['', {'a': 99, 'c': 3, 'b': 5}, 1], + ['', None, 1], [' ', 0, 1], [' ', 1, 1], [' ', 2, 1], [' ', -1, 1], [' ', -9999999999999999, 1], + [' ', 9999999999999999, 1], [' ', 0.0, 1], [' ', inf, 1], [' ', nan, 1], + [' ', 3.141592653589793, 1], [' ', [], 1], [' ', [[]], 1], [' ', [1, 2, 3], 1], [' ', '', 1], [' ', ' ', 0], [' ', '1', -1], [' ', 'a bee cd.', -1], [' ', '', 1], [' ', '1', -1], [' ', 'a bee cd.', -1], [' ', set([]), 1], [' ', set([1, 2, 3]), 1], [' ', {5: 3}, 1], [' ', {}, 1], [' ', {'a': 99}, 1], [' ', {'a': 1, 'c': 3, 'b': 2}, 1], [' ', {'a': 99, 'c': 3, 'b': 5}, 1], [' ', None, 1], ['1', 0, 1], ['1', 1, 1], ['1', 2, 1], ['1', -1, 1], ['1', -9999999999999999, 1], ['1', 9999999999999999, 1], ['1', 0.0, 1], - ['1', inf, 1], ['1', nan, 1], ['1', 3.141592653589793, 1], ['1', '', 1], ['1', ' ', 1], - ['1', '1', 0], ['1', 'a bee cd.', -1], ['1', '', 1], ['1', ' ', 1], ['1', 'a bee cd.', -1], - ['1', set([]), 1], ['1', set([1, 2, 3]), 1], ['1', {5: 3}, 1], ['1', {}, 1], ['1', {'a': 99}, 1], - ['1', {'a': 1, 'c': 3, 'b': 2}, 1], ['1', {'a': 99, 'c': 3, 'b': 5}, 1], ['1', None, 1], - ['a bee cd.', 0, 1], ['a bee cd.', 1, 1], ['a bee cd.', 2, 1], ['a bee cd.', -1, 1], - ['a bee cd.', -9999999999999999, 1], ['a bee cd.', 9999999999999999, 1], ['a bee cd.', 0.0, 1], - ['a bee cd.', inf, 1], ['a bee cd.', nan, 1], ['a bee cd.', 3.141592653589793, 1], - ['a bee cd.', '', 1], ['a bee cd.', ' ', 1], ['a bee cd.', '1', 1], ['a bee cd.', 'a bee cd.', 0], - ['a bee cd.', '', 1], ['a bee cd.', ' ', 1], ['a bee cd.', '1', 1], ['a bee cd.', set([]), 1], - ['a bee cd.', set([1, 2, 3]), 1], ['a bee cd.', {5: 3}, 1], ['a bee cd.', {}, 1], - ['a bee cd.', {'a': 99}, 1], ['a bee cd.', {'a': 1, 'c': 3, 'b': 2}, 1], - ['a bee cd.', {'a': 99, 'c': 3, 'b': 5}, 1], ['a bee cd.', None, 1], [set([]), 0, 1], - [set([]), 1, 1], [set([]), 2, 1], [set([]), -1, 1], [set([]), -9999999999999999, 1], - [set([]), 9999999999999999, 1], [set([]), 0.0, 1], [set([]), inf, 1], [set([]), nan, 1], - [set([]), 3.141592653589793, 1], [set([]), '', -1], [set([]), ' ', -1], [set([]), '1', -1], - [set([]), 'a bee cd.', -1], [set([]), '', -1], [set([]), ' ', -1], [set([]), '1', -1], - [set([]), 'a bee cd.', -1], + ['1', inf, 1], ['1', nan, 1], ['1', 3.141592653589793, 1], ['1', [], 1], ['1', [[]], 1], + ['1', [1, 2, 3], 1], ['1', '', 1], ['1', ' ', 1], ['1', '1', 0], ['1', 'a bee cd.', -1], + ['1', '', 1], ['1', ' ', 1], ['1', 'a bee cd.', -1], ['1', set([]), 1], ['1', set([1, 2, 3]), 1], + ['1', {5: 3}, 1], ['1', {}, 1], ['1', {'a': 99}, 1], ['1', {'a': 1, 'c': 3, 'b': 2}, 1], + ['1', {'a': 99, 'c': 3, 'b': 5}, 1], ['1', None, 1], ['a bee cd.', 0, 1], ['a bee cd.', 1, 1], + ['a bee cd.', 2, 1], ['a bee cd.', -1, 1], ['a bee cd.', -9999999999999999, 1], + ['a bee cd.', 9999999999999999, 1], ['a bee cd.', 0.0, 1], ['a bee cd.', inf, 1], + ['a bee cd.', nan, 1], ['a bee cd.', 3.141592653589793, 1], ['a bee cd.', [], 1], + ['a bee cd.', [[]], 1], ['a bee cd.', [1, 2, 3], 1], ['a bee cd.', '', 1], ['a bee cd.', ' ', 1], + ['a bee cd.', '1', 1], ['a bee cd.', 'a bee cd.', 0], ['a bee cd.', '', 1], ['a bee cd.', ' ', 1], + ['a bee cd.', '1', 1], ['a bee cd.', set([]), 1], ['a bee cd.', set([1, 2, 3]), 1], + ['a bee cd.', {5: 3}, 1], ['a bee cd.', {}, 1], ['a bee cd.', {'a': 99}, 1], + ['a bee cd.', {'a': 1, 'c': 3, 'b': 2}, 1], ['a bee cd.', {'a': 99, 'c': 3, 'b': 5}, 1], + ['a bee cd.', None, 1], [set([]), 0, 1], [set([]), 1, 1], [set([]), 2, 1], [set([]), -1, 1], + [set([]), -9999999999999999, 1], [set([]), 9999999999999999, 1], [set([]), 0.0, 1], + [set([]), inf, 1], [set([]), nan, 1], [set([]), 3.141592653589793, 1], [set([]), [], 1], + [set([]), [[]], 1], [set([]), [1, 2, 3], 1], [set([]), '', -1], [set([]), ' ', -1], + [set([]), '1', -1], [set([]), 'a bee cd.', -1], [set([]), '', -1], [set([]), ' ', -1], + [set([]), '1', -1], [set([]), 'a bee cd.', -1], [set([]), set([1, 2, 3]), 'TypeError: cannot compare sets using cmp()'], [set([]), {5: 3}, 1], [set([]), {}, 1], [set([]), {'a': 99}, 1], [set([]), {'a': 1, 'c': 3, 'b': 2}, 1], [set([]), {'a': 99, 'c': 3, 'b': 5}, 1], [set([]), None, 1], [set([1, 2, 3]), 0, 1], [set([1, 2, 3]), 1, 1], [set([1, 2, 3]), 2, 1], [set([1, 2, 3]), -1, 1], [set([1, 2, 3]), -9999999999999999, 1], [set([1, 2, 3]), 9999999999999999, 1], [set([1, 2, 3]), 0.0, 1], [set([1, 2, 3]), inf, 1], [set([1, 2, 3]), nan, 1], - [set([1, 2, 3]), 3.141592653589793, 1], [set([1, 2, 3]), '', -1], [set([1, 2, 3]), ' ', -1], + [set([1, 2, 3]), 3.141592653589793, 1], [set([1, 2, 3]), [], 1], [set([1, 2, 3]), [[]], 1], + [set([1, 2, 3]), [1, 2, 3], 1], [set([1, 2, 3]), '', -1], [set([1, 2, 3]), ' ', -1], [set([1, 2, 3]), '1', -1], [set([1, 2, 3]), 'a bee cd.', -1], [set([1, 2, 3]), '', -1], [set([1, 2, 3]), ' ', -1], [set([1, 2, 3]), '1', -1], [set([1, 2, 3]), 'a bee cd.', -1], [set([1, 2, 3]), set([]), 'TypeError: cannot compare sets using cmp()'], [set([1, 2, 3]), {5: 3}, 1], [set([1, 2, 3]), {}, 1], [set([1, 2, 3]), {'a': 99}, 1], [set([1, 2, 3]), {'a': 1, 'c': 3, 'b': 2}, 1], [set([1, 2, 3]), {'a': 99, 'c': 3, 'b': 5}, 1], [set([1, 2, 3]), None, 1], [{5: 3}, 0, 1], [{5: 3}, 1, 1], [{5: 3}, 2, 1], [{5: 3}, -1, 1], - [{5: 3}, -9999999999999999, 1], [{5: 3}, 9999999999999999, 1], [{5: 3}, 0.0, 1], - [{5: 3}, inf, 1], [{5: 3}, nan, 1], [{5: 3}, 3.141592653589793, 1], [{5: 3}, '', -1], - [{5: 3}, ' ', -1], [{5: 3}, '1', -1], [{5: 3}, 'a bee cd.', -1], [{5: 3}, '', -1], - [{5: 3}, ' ', -1], [{5: 3}, '1', -1], [{5: 3}, 'a bee cd.', -1], [{5: 3}, set([]), -1], - [{5: 3}, set([1, 2, 3]), -1], [{5: 3}, {}, 1], [{5: 3}, {'a': 99}, -1], - [{5: 3}, {'a': 1, 'c': 3, 'b': 2}, -1], [{5: 3}, {'a': 99, 'c': 3, 'b': 5}, -1], [{5: 3}, None, 1], - [{}, 0, 1], [{}, 1, 1], [{}, 2, 1], [{}, -1, 1], [{}, -9999999999999999, 1], - [{}, 9999999999999999, 1], [{}, 0.0, 1], [{}, inf, 1], [{}, nan, 1], [{}, 3.141592653589793, 1], + [{5: 3}, -9999999999999999, 1], [{5: 3}, 9999999999999999, 1], [{5: 3}, 0.0, 1], [{5: 3}, inf, 1], + [{5: 3}, nan, 1], [{5: 3}, 3.141592653589793, 1], [{5: 3}, [], -1], [{5: 3}, [[]], -1], + [{5: 3}, [1, 2, 3], -1], [{5: 3}, '', -1], [{5: 3}, ' ', -1], [{5: 3}, '1', -1], + [{5: 3}, 'a bee cd.', -1], [{5: 3}, '', -1], [{5: 3}, ' ', -1], [{5: 3}, '1', -1], + [{5: 3}, 'a bee cd.', -1], [{5: 3}, set([]), -1], [{5: 3}, set([1, 2, 3]), -1], [{5: 3}, {}, 1], + [{5: 3}, {'a': 99}, -1], [{5: 3}, {'a': 1, 'c': 3, 'b': 2}, -1], + [{5: 3}, {'a': 99, 'c': 3, 'b': 5}, -1], [{5: 3}, None, 1], [{}, 0, 1], [{}, 1, 1], [{}, 2, 1], + [{}, -1, 1], [{}, -9999999999999999, 1], [{}, 9999999999999999, 1], [{}, 0.0, 1], [{}, inf, 1], + [{}, nan, 1], [{}, 3.141592653589793, 1], [{}, [], -1], [{}, [[]], -1], [{}, [1, 2, 3], -1], [{}, '', -1], [{}, ' ', -1], [{}, '1', -1], [{}, 'a bee cd.', -1], [{}, '', -1], [{}, ' ', -1], [{}, '1', -1], [{}, 'a bee cd.', -1], [{}, set([]), -1], [{}, set([1, 2, 3]), -1], [{}, {5: 3}, -1], [{}, {'a': 99}, -1], [{}, {'a': 1, 'c': 3, 'b': 2}, -1], [{}, {'a': 99, 'c': 3, 'b': 5}, -1], [{}, None, 1], [{'a': 99}, 0, 1], [{'a': 99}, 1, 1], [{'a': 99}, 2, 1], [{'a': 99}, -1, 1], [{'a': 99}, -9999999999999999, 1], [{'a': 99}, 9999999999999999, 1], [{'a': 99}, 0.0, 1], [{'a': 99}, inf, 1], [{'a': 99}, nan, 1], - [{'a': 99}, 3.141592653589793, 1], [{'a': 99}, '', -1], [{'a': 99}, ' ', -1], [{'a': 99}, '1', -1], + [{'a': 99}, 3.141592653589793, 1], [{'a': 99}, [], -1], [{'a': 99}, [[]], -1], + [{'a': 99}, [1, 2, 3], -1], [{'a': 99}, '', -1], [{'a': 99}, ' ', -1], [{'a': 99}, '1', -1], [{'a': 99}, 'a bee cd.', -1], [{'a': 99}, '', -1], [{'a': 99}, ' ', -1], [{'a': 99}, '1', -1], [{'a': 99}, 'a bee cd.', -1], [{'a': 99}, set([]), -1], [{'a': 99}, set([1, 2, 3]), -1], [{'a': 99}, {5: 3}, 1], [{'a': 99}, {}, 1], [{'a': 99}, {'a': 1, 'c': 3, 'b': 2}, -1], @@ -172,30 +206,35 @@ [{'a': 1, 'c': 3, 'b': 2}, -1, 1], [{'a': 1, 'c': 3, 'b': 2}, -9999999999999999, 1], [{'a': 1, 'c': 3, 'b': 2}, 9999999999999999, 1], [{'a': 1, 'c': 3, 'b': 2}, 0.0, 1], [{'a': 1, 'c': 3, 'b': 2}, inf, 1], [{'a': 1, 'c': 3, 'b': 2}, nan, 1], - [{'a': 1, 'c': 3, 'b': 2}, 3.141592653589793, 1], [{'a': 1, 'c': 3, 'b': 2}, '', -1], - [{'a': 1, 'c': 3, 'b': 2}, ' ', -1], [{'a': 1, 'c': 3, 'b': 2}, '1', -1], - [{'a': 1, 'c': 3, 'b': 2}, 'a bee cd.', -1], [{'a': 1, 'c': 3, 'b': 2}, '', -1], - [{'a': 1, 'c': 3, 'b': 2}, ' ', -1], [{'a': 1, 'c': 3, 'b': 2}, '1', -1], - [{'a': 1, 'c': 3, 'b': 2}, 'a bee cd.', -1], [{'a': 1, 'c': 3, 'b': 2}, set([]), -1], - [{'a': 1, 'c': 3, 'b': 2}, set([1, 2, 3]), -1], [{'a': 1, 'c': 3, 'b': 2}, {5: 3}, 1], - [{'a': 1, 'c': 3, 'b': 2}, {}, 1], [{'a': 1, 'c': 3, 'b': 2}, {'a': 99}, 1], + [{'a': 1, 'c': 3, 'b': 2}, 3.141592653589793, 1], [{'a': 1, 'c': 3, 'b': 2}, [], -1], + [{'a': 1, 'c': 3, 'b': 2}, [[]], -1], [{'a': 1, 'c': 3, 'b': 2}, [1, 2, 3], -1], + [{'a': 1, 'c': 3, 'b': 2}, '', -1], [{'a': 1, 'c': 3, 'b': 2}, ' ', -1], + [{'a': 1, 'c': 3, 'b': 2}, '1', -1], [{'a': 1, 'c': 3, 'b': 2}, 'a bee cd.', -1], + [{'a': 1, 'c': 3, 'b': 2}, '', -1], [{'a': 1, 'c': 3, 'b': 2}, ' ', -1], + [{'a': 1, 'c': 3, 'b': 2}, '1', -1], [{'a': 1, 'c': 3, 'b': 2}, 'a bee cd.', -1], + [{'a': 1, 'c': 3, 'b': 2}, set([]), -1], [{'a': 1, 'c': 3, 'b': 2}, set([1, 2, 3]), -1], + [{'a': 1, 'c': 3, 'b': 2}, {5: 3}, 1], [{'a': 1, 'c': 3, 'b': 2}, {}, 1], + [{'a': 1, 'c': 3, 'b': 2}, {'a': 99}, 1], [{'a': 1, 'c': 3, 'b': 2}, {'a': 99, 'c': 3, 'b': 5}, -1], [{'a': 1, 'c': 3, 'b': 2}, None, 1], [{'a': 99, 'c': 3, 'b': 5}, 0, 1], [{'a': 99, 'c': 3, 'b': 5}, 1, 1], [{'a': 99, 'c': 3, 'b': 5}, 2, 1], [{'a': 99, 'c': 3, 'b': 5}, -1, 1], [{'a': 99, 'c': 3, 'b': 5}, -9999999999999999, 1], [{'a': 99, 'c': 3, 'b': 5}, 9999999999999999, 1], [{'a': 99, 'c': 3, 'b': 5}, 0.0, 1], [{'a': 99, 'c': 3, 'b': 5}, inf, 1], [{'a': 99, 'c': 3, 'b': 5}, nan, 1], - [{'a': 99, 'c': 3, 'b': 5}, 3.141592653589793, 1], [{'a': 99, 'c': 3, 'b': 5}, '', -1], - [{'a': 99, 'c': 3, 'b': 5}, ' ', -1], [{'a': 99, 'c': 3, 'b': 5}, '1', -1], - [{'a': 99, 'c': 3, 'b': 5}, 'a bee cd.', -1], [{'a': 99, 'c': 3, 'b': 5}, '', -1], - [{'a': 99, 'c': 3, 'b': 5}, ' ', -1], [{'a': 99, 'c': 3, 'b': 5}, '1', -1], - [{'a': 99, 'c': 3, 'b': 5}, 'a bee cd.', -1], [{'a': 99, 'c': 3, 'b': 5}, set([]), -1], - [{'a': 99, 'c': 3, 'b': 5}, set([1, 2, 3]), -1], [{'a': 99, 'c': 3, 'b': 5}, {5: 3}, 1], - [{'a': 99, 'c': 3, 'b': 5}, {}, 1], [{'a': 99, 'c': 3, 'b': 5}, {'a': 99}, 1], + [{'a': 99, 'c': 3, 'b': 5}, 3.141592653589793, 1], [{'a': 99, 'c': 3, 'b': 5}, [], -1], + [{'a': 99, 'c': 3, 'b': 5}, [[]], -1], [{'a': 99, 'c': 3, 'b': 5}, [1, 2, 3], -1], + [{'a': 99, 'c': 3, 'b': 5}, '', -1], [{'a': 99, 'c': 3, 'b': 5}, ' ', -1], + [{'a': 99, 'c': 3, 'b': 5}, '1', -1], [{'a': 99, 'c': 3, 'b': 5}, 'a bee cd.', -1], + [{'a': 99, 'c': 3, 'b': 5}, '', -1], [{'a': 99, 'c': 3, 'b': 5}, ' ', -1], + [{'a': 99, 'c': 3, 'b': 5}, '1', -1], [{'a': 99, 'c': 3, 'b': 5}, 'a bee cd.', -1], + [{'a': 99, 'c': 3, 'b': 5}, set([]), -1], [{'a': 99, 'c': 3, 'b': 5}, set([1, 2, 3]), -1], + [{'a': 99, 'c': 3, 'b': 5}, {5: 3}, 1], [{'a': 99, 'c': 3, 'b': 5}, {}, 1], + [{'a': 99, 'c': 3, 'b': 5}, {'a': 99}, 1], [{'a': 99, 'c': 3, 'b': 5}, {'a': 1, 'c': 3, 'b': 2}, 1], [{'a': 99, 'c': 3, 'b': 5}, None, 1], [None, 0, -1], [None, 1, -1], [None, 2, -1], [None, -1, -1], [None, -9999999999999999, -1], [None, 9999999999999999, -1], [None, 0.0, -1], [None, inf, -1], [None, nan, -1], - [None, 3.141592653589793, -1], [None, '', -1], [None, ' ', -1], [None, '1', -1], - [None, 'a bee cd.', -1], [None, '', -1], [None, ' ', -1], [None, '1', -1], [None, 'a bee cd.', -1], - [None, set([]), -1], [None, set([1, 2, 3]), -1], [None, {5: 3}, -1], [None, {}, -1], - [None, {'a': 99}, -1], [None, {'a': 1, 'c': 3, 'b': 2}, -1], [None, {'a': 99, 'c': 3, 'b': 5}, -1]] + [None, 3.141592653589793, -1], [None, [], -1], [None, [[]], -1], [None, [1, 2, 3], -1], + [None, '', -1], [None, ' ', -1], [None, '1', -1], [None, 'a bee cd.', -1], [None, '', -1], + [None, ' ', -1], [None, '1', -1], [None, 'a bee cd.', -1], [None, set([]), -1], + [None, set([1, 2, 3]), -1], [None, {5: 3}, -1], [None, {}, -1], [None, {'a': 99}, -1], + [None, {'a': 1, 'c': 3, 'b': 2}, -1], [None, {'a': 99, 'c': 3, 'b': 5}, -1]] From 4dbded1fa5a25087acf1dc073268f2d427fba402 Mon Sep 17 00:00:00 2001 From: "Roman A. Taycher" Date: Sun, 30 Aug 2020 21:23:37 -0700 Subject: [PATCH 359/425] remove nan test --- tests/test_past/test_values.py | 275 ++++++++++++++++----------------- 1 file changed, 130 insertions(+), 145 deletions(-) diff --git a/tests/test_past/test_values.py b/tests/test_past/test_values.py index 7c85a8f2..11872084 100644 --- a/tests/test_past/test_values.py +++ b/tests/test_past/test_values.py @@ -3,7 +3,7 @@ inf, nan = float('inf'), float('nan') test_values = [ 0, 1, 2, -1, -9999999999, 9999999, - 0.0, inf, nan, pi, + 0.0, inf, pi, [], [[]], [1, 2, 3], set(), set([1, 2, 3]), " ", "", "1", "dsada saA.", "2", "dsa", b"", b"dsa", b" ", @@ -12,96 +12,86 @@ # cmp_python2_values are pre-calculated from running cmp under python2 first values are x and y, last is results of cmp cmp_python2_value = [[0, 1, -1], [0, 2, -1], [0, -1, 1], [0, -9999999999999999, 1], [0, 9999999999999999, -1], - [0, 0.0, 0], [0, inf, -1], [0, nan, -1], [0, 3.141592653589793, -1], [0, [], -1], [0, [[]], -1], + [0, 0.0, 0], [0, inf, -1], [0, 3.141592653589793, -1], [0, [], -1], [0, [[]], -1], [0, [1, 2, 3], -1], [0, '', -1], [0, ' ', -1], [0, '1', -1], [0, 'a bee cd.', -1], [0, '', -1], [0, ' ', -1], [0, '1', -1], [0, 'a bee cd.', -1], [0, set([]), -1], [0, set([1, 2, 3]), -1], [0, {5: 3}, -1], [0, {}, -1], [0, {'a': 99}, -1], [0, {'a': 1, 'c': 3, 'b': 2}, -1], [0, {'a': 99, 'c': 3, 'b': 5}, -1], [0, None, 1], [1, 0, 1], [1, 2, -1], [1, -1, 1], - [1, -9999999999999999, 1], [1, 9999999999999999, -1], [1, 0.0, 1], [1, inf, -1], [1, nan, -1], + [1, -9999999999999999, 1], [1, 9999999999999999, -1], [1, 0.0, 1], [1, inf, -1], [1, 3.141592653589793, -1], [1, [], -1], [1, [[]], -1], [1, [1, 2, 3], -1], [1, '', -1], [1, ' ', -1], [1, '1', -1], [1, 'a bee cd.', -1], [1, '', -1], [1, ' ', -1], [1, '1', -1], [1, 'a bee cd.', -1], [1, set([]), -1], [1, set([1, 2, 3]), -1], [1, {5: 3}, -1], [1, {}, -1], [1, {'a': 99}, -1], [1, {'a': 1, 'c': 3, 'b': 2}, -1], [1, {'a': 99, 'c': 3, 'b': 5}, -1], [1, None, 1], [2, 0, 1], [2, 1, 1], [2, -1, 1], [2, -9999999999999999, 1], - [2, 9999999999999999, -1], [2, 0.0, 1], [2, inf, -1], [2, nan, -1], [2, 3.141592653589793, -1], - [2, [], -1], [2, [[]], -1], [2, [1, 2, 3], -1], [2, '', -1], [2, ' ', -1], [2, '1', -1], - [2, 'a bee cd.', -1], [2, '', -1], [2, ' ', -1], [2, '1', -1], [2, 'a bee cd.', -1], - [2, set([]), -1], [2, set([1, 2, 3]), -1], [2, {5: 3}, -1], [2, {}, -1], [2, {'a': 99}, -1], + [2, 9999999999999999, -1], [2, 0.0, 1], [2, inf, -1], [2, 3.141592653589793, -1], [2, [], -1], + [2, [[]], -1], [2, [1, 2, 3], -1], [2, '', -1], [2, ' ', -1], [2, '1', -1], [2, 'a bee cd.', -1], + [2, '', -1], [2, ' ', -1], [2, '1', -1], [2, 'a bee cd.', -1], [2, set([]), -1], + [2, set([1, 2, 3]), -1], [2, {5: 3}, -1], [2, {}, -1], [2, {'a': 99}, -1], [2, {'a': 1, 'c': 3, 'b': 2}, -1], [2, {'a': 99, 'c': 3, 'b': 5}, -1], [2, None, 1], [-1, 0, -1], [-1, 1, -1], [-1, 2, -1], [-1, -9999999999999999, 1], [-1, 9999999999999999, -1], [-1, 0.0, -1], - [-1, inf, -1], [-1, nan, -1], [-1, 3.141592653589793, -1], [-1, [], -1], [-1, [[]], -1], - [-1, [1, 2, 3], -1], [-1, '', -1], [-1, ' ', -1], [-1, '1', -1], [-1, 'a bee cd.', -1], - [-1, '', -1], [-1, ' ', -1], [-1, '1', -1], [-1, 'a bee cd.', -1], [-1, set([]), -1], - [-1, set([1, 2, 3]), -1], [-1, {5: 3}, -1], [-1, {}, -1], [-1, {'a': 99}, -1], - [-1, {'a': 1, 'c': 3, 'b': 2}, -1], [-1, {'a': 99, 'c': 3, 'b': 5}, -1], [-1, None, 1], - [-9999999999999999, 0, -1], [-9999999999999999, 1, -1], [-9999999999999999, 2, -1], - [-9999999999999999, -1, -1], [-9999999999999999, 9999999999999999, -1], - [-9999999999999999, 0.0, -1], [-9999999999999999, inf, -1], [-9999999999999999, nan, -1], - [-9999999999999999, 3.141592653589793, -1], [-9999999999999999, [], -1], - [-9999999999999999, [[]], -1], [-9999999999999999, [1, 2, 3], -1], [-9999999999999999, '', -1], - [-9999999999999999, ' ', -1], [-9999999999999999, '1', -1], [-9999999999999999, 'a bee cd.', -1], + [-1, inf, -1], [-1, 3.141592653589793, -1], [-1, [], -1], [-1, [[]], -1], [-1, [1, 2, 3], -1], + [-1, '', -1], [-1, ' ', -1], [-1, '1', -1], [-1, 'a bee cd.', -1], [-1, '', -1], [-1, ' ', -1], + [-1, '1', -1], [-1, 'a bee cd.', -1], [-1, set([]), -1], [-1, set([1, 2, 3]), -1], + [-1, {5: 3}, -1], [-1, {}, -1], [-1, {'a': 99}, -1], [-1, {'a': 1, 'c': 3, 'b': 2}, -1], + [-1, {'a': 99, 'c': 3, 'b': 5}, -1], [-1, None, 1], [-9999999999999999, 0, -1], + [-9999999999999999, 1, -1], [-9999999999999999, 2, -1], [-9999999999999999, -1, -1], + [-9999999999999999, 9999999999999999, -1], [-9999999999999999, 0.0, -1], + [-9999999999999999, inf, -1], [-9999999999999999, 3.141592653589793, -1], + [-9999999999999999, [], -1], [-9999999999999999, [[]], -1], [-9999999999999999, [1, 2, 3], -1], [-9999999999999999, '', -1], [-9999999999999999, ' ', -1], [-9999999999999999, '1', -1], - [-9999999999999999, 'a bee cd.', -1], [-9999999999999999, set([]), -1], - [-9999999999999999, set([1, 2, 3]), -1], [-9999999999999999, {5: 3}, -1], - [-9999999999999999, {}, -1], [-9999999999999999, {'a': 99}, -1], + [-9999999999999999, 'a bee cd.', -1], [-9999999999999999, '', -1], [-9999999999999999, ' ', -1], + [-9999999999999999, '1', -1], [-9999999999999999, 'a bee cd.', -1], + [-9999999999999999, set([]), -1], [-9999999999999999, set([1, 2, 3]), -1], + [-9999999999999999, {5: 3}, -1], [-9999999999999999, {}, -1], [-9999999999999999, {'a': 99}, -1], [-9999999999999999, {'a': 1, 'c': 3, 'b': 2}, -1], [-9999999999999999, {'a': 99, 'c': 3, 'b': 5}, -1], [-9999999999999999, None, 1], [9999999999999999, 0, 1], [9999999999999999, 1, 1], [9999999999999999, 2, 1], [9999999999999999, -1, 1], [9999999999999999, -9999999999999999, 1], [9999999999999999, 0.0, 1], - [9999999999999999, inf, -1], [9999999999999999, nan, -1], [9999999999999999, 3.141592653589793, 1], - [9999999999999999, [], -1], [9999999999999999, [[]], -1], [9999999999999999, [1, 2, 3], -1], + [9999999999999999, inf, -1], [9999999999999999, 3.141592653589793, 1], [9999999999999999, [], -1], + [9999999999999999, [[]], -1], [9999999999999999, [1, 2, 3], -1], [9999999999999999, '', -1], + [9999999999999999, ' ', -1], [9999999999999999, '1', -1], [9999999999999999, 'a bee cd.', -1], [9999999999999999, '', -1], [9999999999999999, ' ', -1], [9999999999999999, '1', -1], - [9999999999999999, 'a bee cd.', -1], [9999999999999999, '', -1], [9999999999999999, ' ', -1], - [9999999999999999, '1', -1], [9999999999999999, 'a bee cd.', -1], [9999999999999999, set([]), -1], + [9999999999999999, 'a bee cd.', -1], [9999999999999999, set([]), -1], [9999999999999999, set([1, 2, 3]), -1], [9999999999999999, {5: 3}, -1], [9999999999999999, {}, -1], [9999999999999999, {'a': 99}, -1], [9999999999999999, {'a': 1, 'c': 3, 'b': 2}, -1], [9999999999999999, {'a': 99, 'c': 3, 'b': 5}, -1], [9999999999999999, None, 1], [0.0, 0, 0], [0.0, 1, -1], [0.0, 2, -1], [0.0, -1, 1], [0.0, -9999999999999999, 1], [0.0, 9999999999999999, -1], - [0.0, inf, -1], [0.0, nan, 1], [0.0, 3.141592653589793, -1], [0.0, [], -1], [0.0, [[]], -1], - [0.0, [1, 2, 3], -1], [0.0, '', -1], [0.0, ' ', -1], [0.0, '1', -1], [0.0, 'a bee cd.', -1], - [0.0, '', -1], [0.0, ' ', -1], [0.0, '1', -1], [0.0, 'a bee cd.', -1], [0.0, set([]), -1], + [0.0, inf, -1], [0.0, 3.141592653589793, -1], [0.0, [], -1], [0.0, [[]], -1], [0.0, [1, 2, 3], -1], + [0.0, '', -1], [0.0, ' ', -1], [0.0, '1', -1], [0.0, 'a bee cd.', -1], [0.0, '', -1], + [0.0, ' ', -1], [0.0, '1', -1], [0.0, 'a bee cd.', -1], [0.0, set([]), -1], [0.0, set([1, 2, 3]), -1], [0.0, {5: 3}, -1], [0.0, {}, -1], [0.0, {'a': 99}, -1], [0.0, {'a': 1, 'c': 3, 'b': 2}, -1], [0.0, {'a': 99, 'c': 3, 'b': 5}, -1], [0.0, None, 1], [inf, 0, 1], [inf, 1, 1], [inf, 2, 1], [inf, -1, 1], [inf, -9999999999999999, 1], - [inf, 9999999999999999, 1], [inf, 0.0, 1], [inf, nan, 1], [inf, 3.141592653589793, 1], - [inf, [], -1], [inf, [[]], -1], [inf, [1, 2, 3], -1], [inf, '', -1], [inf, ' ', -1], - [inf, '1', -1], [inf, 'a bee cd.', -1], [inf, '', -1], [inf, ' ', -1], [inf, '1', -1], - [inf, 'a bee cd.', -1], [inf, set([]), -1], [inf, set([1, 2, 3]), -1], [inf, {5: 3}, -1], - [inf, {}, -1], [inf, {'a': 99}, -1], [inf, {'a': 1, 'c': 3, 'b': 2}, -1], - [inf, {'a': 99, 'c': 3, 'b': 5}, -1], [inf, None, 1], [nan, 0, 1], [nan, 1, 1], [nan, 2, 1], - [nan, -1, 1], [nan, -9999999999999999, 1], [nan, 9999999999999999, 1], [nan, 0.0, -1], - [nan, inf, -1], [nan, 3.141592653589793, -1], [nan, [], -1], [nan, [[]], -1], [nan, [1, 2, 3], -1], - [nan, '', -1], [nan, ' ', -1], [nan, '1', -1], [nan, 'a bee cd.', -1], [nan, '', -1], - [nan, ' ', -1], [nan, '1', -1], [nan, 'a bee cd.', -1], [nan, set([]), -1], - [nan, set([1, 2, 3]), -1], [nan, {5: 3}, -1], [nan, {}, -1], [nan, {'a': 99}, -1], - [nan, {'a': 1, 'c': 3, 'b': 2}, -1], [nan, {'a': 99, 'c': 3, 'b': 5}, -1], [nan, None, 1], - [3.141592653589793, 0, 1], [3.141592653589793, 1, 1], [3.141592653589793, 2, 1], + [inf, 9999999999999999, 1], [inf, 0.0, 1], [inf, 3.141592653589793, 1], [inf, [], -1], + [inf, [[]], -1], [inf, [1, 2, 3], -1], [inf, '', -1], [inf, ' ', -1], [inf, '1', -1], + [inf, 'a bee cd.', -1], [inf, '', -1], [inf, ' ', -1], [inf, '1', -1], [inf, 'a bee cd.', -1], + [inf, set([]), -1], [inf, set([1, 2, 3]), -1], [inf, {5: 3}, -1], [inf, {}, -1], + [inf, {'a': 99}, -1], [inf, {'a': 1, 'c': 3, 'b': 2}, -1], [inf, {'a': 99, 'c': 3, 'b': 5}, -1], + [inf, None, 1], [3.141592653589793, 0, 1], [3.141592653589793, 1, 1], [3.141592653589793, 2, 1], [3.141592653589793, -1, 1], [3.141592653589793, -9999999999999999, 1], [3.141592653589793, 9999999999999999, -1], [3.141592653589793, 0.0, 1], - [3.141592653589793, inf, -1], [3.141592653589793, nan, 1], [3.141592653589793, [], -1], - [3.141592653589793, [[]], -1], [3.141592653589793, [1, 2, 3], -1], [3.141592653589793, '', -1], + [3.141592653589793, inf, -1], [3.141592653589793, [], -1], [3.141592653589793, [[]], -1], + [3.141592653589793, [1, 2, 3], -1], [3.141592653589793, '', -1], [3.141592653589793, ' ', -1], + [3.141592653589793, '1', -1], [3.141592653589793, 'a bee cd.', -1], [3.141592653589793, '', -1], [3.141592653589793, ' ', -1], [3.141592653589793, '1', -1], [3.141592653589793, 'a bee cd.', -1], - [3.141592653589793, '', -1], [3.141592653589793, ' ', -1], [3.141592653589793, '1', -1], - [3.141592653589793, 'a bee cd.', -1], [3.141592653589793, set([]), -1], - [3.141592653589793, set([1, 2, 3]), -1], [3.141592653589793, {5: 3}, -1], - [3.141592653589793, {}, -1], [3.141592653589793, {'a': 99}, -1], + [3.141592653589793, set([]), -1], [3.141592653589793, set([1, 2, 3]), -1], + [3.141592653589793, {5: 3}, -1], [3.141592653589793, {}, -1], [3.141592653589793, {'a': 99}, -1], [3.141592653589793, {'a': 1, 'c': 3, 'b': 2}, -1], [3.141592653589793, {'a': 99, 'c': 3, 'b': 5}, -1], [3.141592653589793, None, 1], [[], 0, 1], [[], 1, 1], [[], 2, 1], [[], -1, 1], [[], -9999999999999999, 1], [[], 9999999999999999, 1], - [[], 0.0, 1], [[], inf, 1], [[], nan, 1], [[], 3.141592653589793, 1], [[], [[]], -1], - [[], [1, 2, 3], -1], [[], '', -1], [[], ' ', -1], [[], '1', -1], [[], 'a bee cd.', -1], - [[], '', -1], [[], ' ', -1], [[], '1', -1], [[], 'a bee cd.', -1], [[], set([]), -1], - [[], set([1, 2, 3]), -1], [[], {5: 3}, 1], [[], {}, 1], [[], {'a': 99}, 1], - [[], {'a': 1, 'c': 3, 'b': 2}, 1], [[], {'a': 99, 'c': 3, 'b': 5}, 1], [[], None, 1], [[[]], 0, 1], - [[[]], 1, 1], [[[]], 2, 1], [[[]], -1, 1], [[[]], -9999999999999999, 1], - [[[]], 9999999999999999, 1], [[[]], 0.0, 1], [[[]], inf, 1], [[[]], nan, 1], - [[[]], 3.141592653589793, 1], [[[]], [], 1], [[[]], [1, 2, 3], 1], [[[]], '', -1], [[[]], ' ', -1], - [[[]], '1', -1], [[[]], 'a bee cd.', -1], [[[]], '', -1], [[[]], ' ', -1], [[[]], '1', -1], - [[[]], 'a bee cd.', -1], [[[]], set([]), -1], [[[]], set([1, 2, 3]), -1], [[[]], {5: 3}, 1], - [[[]], {}, 1], [[[]], {'a': 99}, 1], [[[]], {'a': 1, 'c': 3, 'b': 2}, 1], + [[], 0.0, 1], [[], inf, 1], [[], 3.141592653589793, 1], [[], [[]], -1], [[], [1, 2, 3], -1], + [[], '', -1], [[], ' ', -1], [[], '1', -1], [[], 'a bee cd.', -1], [[], '', -1], [[], ' ', -1], + [[], '1', -1], [[], 'a bee cd.', -1], [[], set([]), -1], [[], set([1, 2, 3]), -1], [[], {5: 3}, 1], + [[], {}, 1], [[], {'a': 99}, 1], [[], {'a': 1, 'c': 3, 'b': 2}, 1], + [[], {'a': 99, 'c': 3, 'b': 5}, 1], [[], None, 1], [[[]], 0, 1], [[[]], 1, 1], [[[]], 2, 1], + [[[]], -1, 1], [[[]], -9999999999999999, 1], [[[]], 9999999999999999, 1], [[[]], 0.0, 1], + [[[]], inf, 1], [[[]], 3.141592653589793, 1], [[[]], [], 1], [[[]], [1, 2, 3], 1], [[[]], '', -1], + [[[]], ' ', -1], [[[]], '1', -1], [[[]], 'a bee cd.', -1], [[[]], '', -1], [[[]], ' ', -1], + [[[]], '1', -1], [[[]], 'a bee cd.', -1], [[[]], set([]), -1], [[[]], set([1, 2, 3]), -1], + [[[]], {5: 3}, 1], [[[]], {}, 1], [[[]], {'a': 99}, 1], [[[]], {'a': 1, 'c': 3, 'b': 2}, 1], [[[]], {'a': 99, 'c': 3, 'b': 5}, 1], [[[]], None, 1], [[1, 2, 3], 0, 1], [[1, 2, 3], 1, 1], [[1, 2, 3], 2, 1], [[1, 2, 3], -1, 1], [[1, 2, 3], -9999999999999999, 1], - [[1, 2, 3], 9999999999999999, 1], [[1, 2, 3], 0.0, 1], [[1, 2, 3], inf, 1], [[1, 2, 3], nan, 1], + [[1, 2, 3], 9999999999999999, 1], [[1, 2, 3], 0.0, 1], [[1, 2, 3], inf, 1], [[1, 2, 3], 3.141592653589793, 1], [[1, 2, 3], [], 1], [[1, 2, 3], [[]], -1], [[1, 2, 3], '', -1], [[1, 2, 3], ' ', -1], [[1, 2, 3], '1', -1], [[1, 2, 3], 'a bee cd.', -1], [[1, 2, 3], '', -1], [[1, 2, 3], ' ', -1], [[1, 2, 3], '1', -1], [[1, 2, 3], 'a bee cd.', -1], [[1, 2, 3], set([]), -1], @@ -109,53 +99,51 @@ [[1, 2, 3], {'a': 99}, 1], [[1, 2, 3], {'a': 1, 'c': 3, 'b': 2}, 1], [[1, 2, 3], {'a': 99, 'c': 3, 'b': 5}, 1], [[1, 2, 3], None, 1], ['', 0, 1], ['', 1, 1], ['', 2, 1], ['', -1, 1], ['', -9999999999999999, 1], ['', 9999999999999999, 1], ['', 0.0, 1], - ['', inf, 1], ['', nan, 1], ['', 3.141592653589793, 1], ['', [], 1], ['', [[]], 1], - ['', [1, 2, 3], 1], ['', ' ', -1], ['', '1', -1], ['', 'a bee cd.', -1], ['', '', 0], - ['', ' ', -1], ['', '1', -1], ['', 'a bee cd.', -1], ['', set([]), 1], ['', set([1, 2, 3]), 1], - ['', {5: 3}, 1], ['', {}, 1], ['', {'a': 99}, 1], ['', {'a': 1, 'c': 3, 'b': 2}, 1], - ['', {'a': 99, 'c': 3, 'b': 5}, 1], ['', None, 1], [' ', 0, 1], [' ', 1, 1], [' ', 2, 1], - [' ', -1, 1], [' ', -9999999999999999, 1], [' ', 9999999999999999, 1], [' ', 0.0, 1], - [' ', inf, 1], [' ', nan, 1], [' ', 3.141592653589793, 1], [' ', [], 1], [' ', [[]], 1], - [' ', [1, 2, 3], 1], [' ', '', 1], [' ', '1', -1], [' ', 'a bee cd.', -1], [' ', '', 1], - [' ', ' ', 0], [' ', '1', -1], [' ', 'a bee cd.', -1], [' ', set([]), 1], [' ', set([1, 2, 3]), 1], - [' ', {5: 3}, 1], [' ', {}, 1], [' ', {'a': 99}, 1], [' ', {'a': 1, 'c': 3, 'b': 2}, 1], - [' ', {'a': 99, 'c': 3, 'b': 5}, 1], [' ', None, 1], ['1', 0, 1], ['1', 1, 1], ['1', 2, 1], - ['1', -1, 1], ['1', -9999999999999999, 1], ['1', 9999999999999999, 1], ['1', 0.0, 1], - ['1', inf, 1], ['1', nan, 1], ['1', 3.141592653589793, 1], ['1', [], 1], ['1', [[]], 1], - ['1', [1, 2, 3], 1], ['1', '', 1], ['1', ' ', 1], ['1', 'a bee cd.', -1], ['1', '', 1], - ['1', ' ', 1], ['1', '1', 0], ['1', 'a bee cd.', -1], ['1', set([]), 1], ['1', set([1, 2, 3]), 1], - ['1', {5: 3}, 1], ['1', {}, 1], ['1', {'a': 99}, 1], ['1', {'a': 1, 'c': 3, 'b': 2}, 1], - ['1', {'a': 99, 'c': 3, 'b': 5}, 1], ['1', None, 1], ['a bee cd.', 0, 1], ['a bee cd.', 1, 1], - ['a bee cd.', 2, 1], ['a bee cd.', -1, 1], ['a bee cd.', -9999999999999999, 1], - ['a bee cd.', 9999999999999999, 1], ['a bee cd.', 0.0, 1], ['a bee cd.', inf, 1], - ['a bee cd.', nan, 1], ['a bee cd.', 3.141592653589793, 1], ['a bee cd.', [], 1], + ['', inf, 1], ['', 3.141592653589793, 1], ['', [], 1], ['', [[]], 1], ['', [1, 2, 3], 1], + ['', ' ', -1], ['', '1', -1], ['', 'a bee cd.', -1], ['', '', 0], ['', ' ', -1], ['', '1', -1], + ['', 'a bee cd.', -1], ['', set([]), 1], ['', set([1, 2, 3]), 1], ['', {5: 3}, 1], ['', {}, 1], + ['', {'a': 99}, 1], ['', {'a': 1, 'c': 3, 'b': 2}, 1], ['', {'a': 99, 'c': 3, 'b': 5}, 1], + ['', None, 1], [' ', 0, 1], [' ', 1, 1], [' ', 2, 1], [' ', -1, 1], [' ', -9999999999999999, 1], + [' ', 9999999999999999, 1], [' ', 0.0, 1], [' ', inf, 1], [' ', 3.141592653589793, 1], + [' ', [], 1], [' ', [[]], 1], [' ', [1, 2, 3], 1], [' ', '', 1], [' ', '1', -1], + [' ', 'a bee cd.', -1], [' ', '', 1], [' ', ' ', 0], [' ', '1', -1], [' ', 'a bee cd.', -1], + [' ', set([]), 1], [' ', set([1, 2, 3]), 1], [' ', {5: 3}, 1], [' ', {}, 1], [' ', {'a': 99}, 1], + [' ', {'a': 1, 'c': 3, 'b': 2}, 1], [' ', {'a': 99, 'c': 3, 'b': 5}, 1], [' ', None, 1], + ['1', 0, 1], ['1', 1, 1], ['1', 2, 1], ['1', -1, 1], ['1', -9999999999999999, 1], + ['1', 9999999999999999, 1], ['1', 0.0, 1], ['1', inf, 1], ['1', 3.141592653589793, 1], + ['1', [], 1], ['1', [[]], 1], ['1', [1, 2, 3], 1], ['1', '', 1], ['1', ' ', 1], + ['1', 'a bee cd.', -1], ['1', '', 1], ['1', ' ', 1], ['1', '1', 0], ['1', 'a bee cd.', -1], + ['1', set([]), 1], ['1', set([1, 2, 3]), 1], ['1', {5: 3}, 1], ['1', {}, 1], ['1', {'a': 99}, 1], + ['1', {'a': 1, 'c': 3, 'b': 2}, 1], ['1', {'a': 99, 'c': 3, 'b': 5}, 1], ['1', None, 1], + ['a bee cd.', 0, 1], ['a bee cd.', 1, 1], ['a bee cd.', 2, 1], ['a bee cd.', -1, 1], + ['a bee cd.', -9999999999999999, 1], ['a bee cd.', 9999999999999999, 1], ['a bee cd.', 0.0, 1], + ['a bee cd.', inf, 1], ['a bee cd.', 3.141592653589793, 1], ['a bee cd.', [], 1], ['a bee cd.', [[]], 1], ['a bee cd.', [1, 2, 3], 1], ['a bee cd.', '', 1], ['a bee cd.', ' ', 1], ['a bee cd.', '1', 1], ['a bee cd.', '', 1], ['a bee cd.', ' ', 1], ['a bee cd.', '1', 1], ['a bee cd.', 'a bee cd.', 0], ['a bee cd.', set([]), 1], ['a bee cd.', set([1, 2, 3]), 1], ['a bee cd.', {5: 3}, 1], ['a bee cd.', {}, 1], ['a bee cd.', {'a': 99}, 1], ['a bee cd.', {'a': 1, 'c': 3, 'b': 2}, 1], ['a bee cd.', {'a': 99, 'c': 3, 'b': 5}, 1], ['a bee cd.', None, 1], ['', 0, 1], ['', 1, 1], ['', 2, 1], ['', -1, 1], - ['', -9999999999999999, 1], ['', 9999999999999999, 1], ['', 0.0, 1], ['', inf, 1], ['', nan, 1], + ['', -9999999999999999, 1], ['', 9999999999999999, 1], ['', 0.0, 1], ['', inf, 1], ['', 3.141592653589793, 1], ['', [], 1], ['', [[]], 1], ['', [1, 2, 3], 1], ['', '', 0], ['', ' ', -1], ['', '1', -1], ['', 'a bee cd.', -1], ['', ' ', -1], ['', '1', -1], ['', 'a bee cd.', -1], ['', set([]), 1], ['', set([1, 2, 3]), 1], ['', {5: 3}, 1], ['', {}, 1], ['', {'a': 99}, 1], ['', {'a': 1, 'c': 3, 'b': 2}, 1], ['', {'a': 99, 'c': 3, 'b': 5}, 1], ['', None, 1], [' ', 0, 1], [' ', 1, 1], [' ', 2, 1], [' ', -1, 1], [' ', -9999999999999999, 1], - [' ', 9999999999999999, 1], [' ', 0.0, 1], [' ', inf, 1], [' ', nan, 1], - [' ', 3.141592653589793, 1], [' ', [], 1], [' ', [[]], 1], [' ', [1, 2, 3], 1], [' ', '', 1], - [' ', ' ', 0], [' ', '1', -1], [' ', 'a bee cd.', -1], [' ', '', 1], [' ', '1', -1], - [' ', 'a bee cd.', -1], [' ', set([]), 1], [' ', set([1, 2, 3]), 1], [' ', {5: 3}, 1], - [' ', {}, 1], [' ', {'a': 99}, 1], [' ', {'a': 1, 'c': 3, 'b': 2}, 1], - [' ', {'a': 99, 'c': 3, 'b': 5}, 1], [' ', None, 1], ['1', 0, 1], ['1', 1, 1], ['1', 2, 1], - ['1', -1, 1], ['1', -9999999999999999, 1], ['1', 9999999999999999, 1], ['1', 0.0, 1], - ['1', inf, 1], ['1', nan, 1], ['1', 3.141592653589793, 1], ['1', [], 1], ['1', [[]], 1], - ['1', [1, 2, 3], 1], ['1', '', 1], ['1', ' ', 1], ['1', '1', 0], ['1', 'a bee cd.', -1], - ['1', '', 1], ['1', ' ', 1], ['1', 'a bee cd.', -1], ['1', set([]), 1], ['1', set([1, 2, 3]), 1], - ['1', {5: 3}, 1], ['1', {}, 1], ['1', {'a': 99}, 1], ['1', {'a': 1, 'c': 3, 'b': 2}, 1], - ['1', {'a': 99, 'c': 3, 'b': 5}, 1], ['1', None, 1], ['a bee cd.', 0, 1], ['a bee cd.', 1, 1], - ['a bee cd.', 2, 1], ['a bee cd.', -1, 1], ['a bee cd.', -9999999999999999, 1], - ['a bee cd.', 9999999999999999, 1], ['a bee cd.', 0.0, 1], ['a bee cd.', inf, 1], - ['a bee cd.', nan, 1], ['a bee cd.', 3.141592653589793, 1], ['a bee cd.', [], 1], + [' ', 9999999999999999, 1], [' ', 0.0, 1], [' ', inf, 1], [' ', 3.141592653589793, 1], + [' ', [], 1], [' ', [[]], 1], [' ', [1, 2, 3], 1], [' ', '', 1], [' ', ' ', 0], [' ', '1', -1], + [' ', 'a bee cd.', -1], [' ', '', 1], [' ', '1', -1], [' ', 'a bee cd.', -1], [' ', set([]), 1], + [' ', set([1, 2, 3]), 1], [' ', {5: 3}, 1], [' ', {}, 1], [' ', {'a': 99}, 1], + [' ', {'a': 1, 'c': 3, 'b': 2}, 1], [' ', {'a': 99, 'c': 3, 'b': 5}, 1], [' ', None, 1], + ['1', 0, 1], ['1', 1, 1], ['1', 2, 1], ['1', -1, 1], ['1', -9999999999999999, 1], + ['1', 9999999999999999, 1], ['1', 0.0, 1], ['1', inf, 1], ['1', 3.141592653589793, 1], + ['1', [], 1], ['1', [[]], 1], ['1', [1, 2, 3], 1], ['1', '', 1], ['1', ' ', 1], ['1', '1', 0], + ['1', 'a bee cd.', -1], ['1', '', 1], ['1', ' ', 1], ['1', 'a bee cd.', -1], ['1', set([]), 1], + ['1', set([1, 2, 3]), 1], ['1', {5: 3}, 1], ['1', {}, 1], ['1', {'a': 99}, 1], + ['1', {'a': 1, 'c': 3, 'b': 2}, 1], ['1', {'a': 99, 'c': 3, 'b': 5}, 1], ['1', None, 1], + ['a bee cd.', 0, 1], ['a bee cd.', 1, 1], ['a bee cd.', 2, 1], ['a bee cd.', -1, 1], + ['a bee cd.', -9999999999999999, 1], ['a bee cd.', 9999999999999999, 1], ['a bee cd.', 0.0, 1], + ['a bee cd.', inf, 1], ['a bee cd.', 3.141592653589793, 1], ['a bee cd.', [], 1], ['a bee cd.', [[]], 1], ['a bee cd.', [1, 2, 3], 1], ['a bee cd.', '', 1], ['a bee cd.', ' ', 1], ['a bee cd.', '1', 1], ['a bee cd.', 'a bee cd.', 0], ['a bee cd.', '', 1], ['a bee cd.', ' ', 1], ['a bee cd.', '1', 1], ['a bee cd.', set([]), 1], ['a bee cd.', set([1, 2, 3]), 1], @@ -163,40 +151,39 @@ ['a bee cd.', {'a': 1, 'c': 3, 'b': 2}, 1], ['a bee cd.', {'a': 99, 'c': 3, 'b': 5}, 1], ['a bee cd.', None, 1], [set([]), 0, 1], [set([]), 1, 1], [set([]), 2, 1], [set([]), -1, 1], [set([]), -9999999999999999, 1], [set([]), 9999999999999999, 1], [set([]), 0.0, 1], - [set([]), inf, 1], [set([]), nan, 1], [set([]), 3.141592653589793, 1], [set([]), [], 1], - [set([]), [[]], 1], [set([]), [1, 2, 3], 1], [set([]), '', -1], [set([]), ' ', -1], - [set([]), '1', -1], [set([]), 'a bee cd.', -1], [set([]), '', -1], [set([]), ' ', -1], - [set([]), '1', -1], [set([]), 'a bee cd.', -1], + [set([]), inf, 1], [set([]), 3.141592653589793, 1], [set([]), [], 1], [set([]), [[]], 1], + [set([]), [1, 2, 3], 1], [set([]), '', -1], [set([]), ' ', -1], [set([]), '1', -1], + [set([]), 'a bee cd.', -1], [set([]), '', -1], [set([]), ' ', -1], [set([]), '1', -1], + [set([]), 'a bee cd.', -1], [set([]), set([1, 2, 3]), 'TypeError: cannot compare sets using cmp()'], [set([]), {5: 3}, 1], [set([]), {}, 1], [set([]), {'a': 99}, 1], [set([]), {'a': 1, 'c': 3, 'b': 2}, 1], [set([]), {'a': 99, 'c': 3, 'b': 5}, 1], [set([]), None, 1], [set([1, 2, 3]), 0, 1], [set([1, 2, 3]), 1, 1], [set([1, 2, 3]), 2, 1], [set([1, 2, 3]), -1, 1], [set([1, 2, 3]), -9999999999999999, 1], [set([1, 2, 3]), 9999999999999999, 1], - [set([1, 2, 3]), 0.0, 1], [set([1, 2, 3]), inf, 1], [set([1, 2, 3]), nan, 1], - [set([1, 2, 3]), 3.141592653589793, 1], [set([1, 2, 3]), [], 1], [set([1, 2, 3]), [[]], 1], - [set([1, 2, 3]), [1, 2, 3], 1], [set([1, 2, 3]), '', -1], [set([1, 2, 3]), ' ', -1], - [set([1, 2, 3]), '1', -1], [set([1, 2, 3]), 'a bee cd.', -1], [set([1, 2, 3]), '', -1], - [set([1, 2, 3]), ' ', -1], [set([1, 2, 3]), '1', -1], [set([1, 2, 3]), 'a bee cd.', -1], + [set([1, 2, 3]), 0.0, 1], [set([1, 2, 3]), inf, 1], [set([1, 2, 3]), 3.141592653589793, 1], + [set([1, 2, 3]), [], 1], [set([1, 2, 3]), [[]], 1], [set([1, 2, 3]), [1, 2, 3], 1], + [set([1, 2, 3]), '', -1], [set([1, 2, 3]), ' ', -1], [set([1, 2, 3]), '1', -1], + [set([1, 2, 3]), 'a bee cd.', -1], [set([1, 2, 3]), '', -1], [set([1, 2, 3]), ' ', -1], + [set([1, 2, 3]), '1', -1], [set([1, 2, 3]), 'a bee cd.', -1], [set([1, 2, 3]), set([]), 'TypeError: cannot compare sets using cmp()'], [set([1, 2, 3]), {5: 3}, 1], [set([1, 2, 3]), {}, 1], [set([1, 2, 3]), {'a': 99}, 1], [set([1, 2, 3]), {'a': 1, 'c': 3, 'b': 2}, 1], [set([1, 2, 3]), {'a': 99, 'c': 3, 'b': 5}, 1], [set([1, 2, 3]), None, 1], [{5: 3}, 0, 1], [{5: 3}, 1, 1], [{5: 3}, 2, 1], [{5: 3}, -1, 1], [{5: 3}, -9999999999999999, 1], [{5: 3}, 9999999999999999, 1], [{5: 3}, 0.0, 1], [{5: 3}, inf, 1], - [{5: 3}, nan, 1], [{5: 3}, 3.141592653589793, 1], [{5: 3}, [], -1], [{5: 3}, [[]], -1], - [{5: 3}, [1, 2, 3], -1], [{5: 3}, '', -1], [{5: 3}, ' ', -1], [{5: 3}, '1', -1], - [{5: 3}, 'a bee cd.', -1], [{5: 3}, '', -1], [{5: 3}, ' ', -1], [{5: 3}, '1', -1], - [{5: 3}, 'a bee cd.', -1], [{5: 3}, set([]), -1], [{5: 3}, set([1, 2, 3]), -1], [{5: 3}, {}, 1], - [{5: 3}, {'a': 99}, -1], [{5: 3}, {'a': 1, 'c': 3, 'b': 2}, -1], - [{5: 3}, {'a': 99, 'c': 3, 'b': 5}, -1], [{5: 3}, None, 1], [{}, 0, 1], [{}, 1, 1], [{}, 2, 1], - [{}, -1, 1], [{}, -9999999999999999, 1], [{}, 9999999999999999, 1], [{}, 0.0, 1], [{}, inf, 1], - [{}, nan, 1], [{}, 3.141592653589793, 1], [{}, [], -1], [{}, [[]], -1], [{}, [1, 2, 3], -1], - [{}, '', -1], [{}, ' ', -1], [{}, '1', -1], [{}, 'a bee cd.', -1], [{}, '', -1], [{}, ' ', -1], - [{}, '1', -1], [{}, 'a bee cd.', -1], [{}, set([]), -1], [{}, set([1, 2, 3]), -1], - [{}, {5: 3}, -1], [{}, {'a': 99}, -1], [{}, {'a': 1, 'c': 3, 'b': 2}, -1], - [{}, {'a': 99, 'c': 3, 'b': 5}, -1], [{}, None, 1], [{'a': 99}, 0, 1], [{'a': 99}, 1, 1], - [{'a': 99}, 2, 1], [{'a': 99}, -1, 1], [{'a': 99}, -9999999999999999, 1], - [{'a': 99}, 9999999999999999, 1], [{'a': 99}, 0.0, 1], [{'a': 99}, inf, 1], [{'a': 99}, nan, 1], - [{'a': 99}, 3.141592653589793, 1], [{'a': 99}, [], -1], [{'a': 99}, [[]], -1], + [{5: 3}, 3.141592653589793, 1], [{5: 3}, [], -1], [{5: 3}, [[]], -1], [{5: 3}, [1, 2, 3], -1], + [{5: 3}, '', -1], [{5: 3}, ' ', -1], [{5: 3}, '1', -1], [{5: 3}, 'a bee cd.', -1], + [{5: 3}, '', -1], [{5: 3}, ' ', -1], [{5: 3}, '1', -1], [{5: 3}, 'a bee cd.', -1], + [{5: 3}, set([]), -1], [{5: 3}, set([1, 2, 3]), -1], [{5: 3}, {}, 1], [{5: 3}, {'a': 99}, -1], + [{5: 3}, {'a': 1, 'c': 3, 'b': 2}, -1], [{5: 3}, {'a': 99, 'c': 3, 'b': 5}, -1], [{5: 3}, None, 1], + [{}, 0, 1], [{}, 1, 1], [{}, 2, 1], [{}, -1, 1], [{}, -9999999999999999, 1], + [{}, 9999999999999999, 1], [{}, 0.0, 1], [{}, inf, 1], [{}, 3.141592653589793, 1], [{}, [], -1], + [{}, [[]], -1], [{}, [1, 2, 3], -1], [{}, '', -1], [{}, ' ', -1], [{}, '1', -1], + [{}, 'a bee cd.', -1], [{}, '', -1], [{}, ' ', -1], [{}, '1', -1], [{}, 'a bee cd.', -1], + [{}, set([]), -1], [{}, set([1, 2, 3]), -1], [{}, {5: 3}, -1], [{}, {'a': 99}, -1], + [{}, {'a': 1, 'c': 3, 'b': 2}, -1], [{}, {'a': 99, 'c': 3, 'b': 5}, -1], [{}, None, 1], + [{'a': 99}, 0, 1], [{'a': 99}, 1, 1], [{'a': 99}, 2, 1], [{'a': 99}, -1, 1], + [{'a': 99}, -9999999999999999, 1], [{'a': 99}, 9999999999999999, 1], [{'a': 99}, 0.0, 1], + [{'a': 99}, inf, 1], [{'a': 99}, 3.141592653589793, 1], [{'a': 99}, [], -1], [{'a': 99}, [[]], -1], [{'a': 99}, [1, 2, 3], -1], [{'a': 99}, '', -1], [{'a': 99}, ' ', -1], [{'a': 99}, '1', -1], [{'a': 99}, 'a bee cd.', -1], [{'a': 99}, '', -1], [{'a': 99}, ' ', -1], [{'a': 99}, '1', -1], [{'a': 99}, 'a bee cd.', -1], [{'a': 99}, set([]), -1], [{'a': 99}, set([1, 2, 3]), -1], @@ -205,36 +192,34 @@ [{'a': 1, 'c': 3, 'b': 2}, 1, 1], [{'a': 1, 'c': 3, 'b': 2}, 2, 1], [{'a': 1, 'c': 3, 'b': 2}, -1, 1], [{'a': 1, 'c': 3, 'b': 2}, -9999999999999999, 1], [{'a': 1, 'c': 3, 'b': 2}, 9999999999999999, 1], [{'a': 1, 'c': 3, 'b': 2}, 0.0, 1], - [{'a': 1, 'c': 3, 'b': 2}, inf, 1], [{'a': 1, 'c': 3, 'b': 2}, nan, 1], - [{'a': 1, 'c': 3, 'b': 2}, 3.141592653589793, 1], [{'a': 1, 'c': 3, 'b': 2}, [], -1], - [{'a': 1, 'c': 3, 'b': 2}, [[]], -1], [{'a': 1, 'c': 3, 'b': 2}, [1, 2, 3], -1], - [{'a': 1, 'c': 3, 'b': 2}, '', -1], [{'a': 1, 'c': 3, 'b': 2}, ' ', -1], - [{'a': 1, 'c': 3, 'b': 2}, '1', -1], [{'a': 1, 'c': 3, 'b': 2}, 'a bee cd.', -1], - [{'a': 1, 'c': 3, 'b': 2}, '', -1], [{'a': 1, 'c': 3, 'b': 2}, ' ', -1], - [{'a': 1, 'c': 3, 'b': 2}, '1', -1], [{'a': 1, 'c': 3, 'b': 2}, 'a bee cd.', -1], - [{'a': 1, 'c': 3, 'b': 2}, set([]), -1], [{'a': 1, 'c': 3, 'b': 2}, set([1, 2, 3]), -1], - [{'a': 1, 'c': 3, 'b': 2}, {5: 3}, 1], [{'a': 1, 'c': 3, 'b': 2}, {}, 1], - [{'a': 1, 'c': 3, 'b': 2}, {'a': 99}, 1], + [{'a': 1, 'c': 3, 'b': 2}, inf, 1], [{'a': 1, 'c': 3, 'b': 2}, 3.141592653589793, 1], + [{'a': 1, 'c': 3, 'b': 2}, [], -1], [{'a': 1, 'c': 3, 'b': 2}, [[]], -1], + [{'a': 1, 'c': 3, 'b': 2}, [1, 2, 3], -1], [{'a': 1, 'c': 3, 'b': 2}, '', -1], + [{'a': 1, 'c': 3, 'b': 2}, ' ', -1], [{'a': 1, 'c': 3, 'b': 2}, '1', -1], + [{'a': 1, 'c': 3, 'b': 2}, 'a bee cd.', -1], [{'a': 1, 'c': 3, 'b': 2}, '', -1], + [{'a': 1, 'c': 3, 'b': 2}, ' ', -1], [{'a': 1, 'c': 3, 'b': 2}, '1', -1], + [{'a': 1, 'c': 3, 'b': 2}, 'a bee cd.', -1], [{'a': 1, 'c': 3, 'b': 2}, set([]), -1], + [{'a': 1, 'c': 3, 'b': 2}, set([1, 2, 3]), -1], [{'a': 1, 'c': 3, 'b': 2}, {5: 3}, 1], + [{'a': 1, 'c': 3, 'b': 2}, {}, 1], [{'a': 1, 'c': 3, 'b': 2}, {'a': 99}, 1], [{'a': 1, 'c': 3, 'b': 2}, {'a': 99, 'c': 3, 'b': 5}, -1], [{'a': 1, 'c': 3, 'b': 2}, None, 1], [{'a': 99, 'c': 3, 'b': 5}, 0, 1], [{'a': 99, 'c': 3, 'b': 5}, 1, 1], [{'a': 99, 'c': 3, 'b': 5}, 2, 1], [{'a': 99, 'c': 3, 'b': 5}, -1, 1], [{'a': 99, 'c': 3, 'b': 5}, -9999999999999999, 1], [{'a': 99, 'c': 3, 'b': 5}, 9999999999999999, 1], [{'a': 99, 'c': 3, 'b': 5}, 0.0, 1], - [{'a': 99, 'c': 3, 'b': 5}, inf, 1], [{'a': 99, 'c': 3, 'b': 5}, nan, 1], - [{'a': 99, 'c': 3, 'b': 5}, 3.141592653589793, 1], [{'a': 99, 'c': 3, 'b': 5}, [], -1], - [{'a': 99, 'c': 3, 'b': 5}, [[]], -1], [{'a': 99, 'c': 3, 'b': 5}, [1, 2, 3], -1], - [{'a': 99, 'c': 3, 'b': 5}, '', -1], [{'a': 99, 'c': 3, 'b': 5}, ' ', -1], - [{'a': 99, 'c': 3, 'b': 5}, '1', -1], [{'a': 99, 'c': 3, 'b': 5}, 'a bee cd.', -1], - [{'a': 99, 'c': 3, 'b': 5}, '', -1], [{'a': 99, 'c': 3, 'b': 5}, ' ', -1], - [{'a': 99, 'c': 3, 'b': 5}, '1', -1], [{'a': 99, 'c': 3, 'b': 5}, 'a bee cd.', -1], - [{'a': 99, 'c': 3, 'b': 5}, set([]), -1], [{'a': 99, 'c': 3, 'b': 5}, set([1, 2, 3]), -1], - [{'a': 99, 'c': 3, 'b': 5}, {5: 3}, 1], [{'a': 99, 'c': 3, 'b': 5}, {}, 1], - [{'a': 99, 'c': 3, 'b': 5}, {'a': 99}, 1], + [{'a': 99, 'c': 3, 'b': 5}, inf, 1], [{'a': 99, 'c': 3, 'b': 5}, 3.141592653589793, 1], + [{'a': 99, 'c': 3, 'b': 5}, [], -1], [{'a': 99, 'c': 3, 'b': 5}, [[]], -1], + [{'a': 99, 'c': 3, 'b': 5}, [1, 2, 3], -1], [{'a': 99, 'c': 3, 'b': 5}, '', -1], + [{'a': 99, 'c': 3, 'b': 5}, ' ', -1], [{'a': 99, 'c': 3, 'b': 5}, '1', -1], + [{'a': 99, 'c': 3, 'b': 5}, 'a bee cd.', -1], [{'a': 99, 'c': 3, 'b': 5}, '', -1], + [{'a': 99, 'c': 3, 'b': 5}, ' ', -1], [{'a': 99, 'c': 3, 'b': 5}, '1', -1], + [{'a': 99, 'c': 3, 'b': 5}, 'a bee cd.', -1], [{'a': 99, 'c': 3, 'b': 5}, set([]), -1], + [{'a': 99, 'c': 3, 'b': 5}, set([1, 2, 3]), -1], [{'a': 99, 'c': 3, 'b': 5}, {5: 3}, 1], + [{'a': 99, 'c': 3, 'b': 5}, {}, 1], [{'a': 99, 'c': 3, 'b': 5}, {'a': 99}, 1], [{'a': 99, 'c': 3, 'b': 5}, {'a': 1, 'c': 3, 'b': 2}, 1], [{'a': 99, 'c': 3, 'b': 5}, None, 1], [None, 0, -1], [None, 1, -1], [None, 2, -1], [None, -1, -1], [None, -9999999999999999, -1], - [None, 9999999999999999, -1], [None, 0.0, -1], [None, inf, -1], [None, nan, -1], - [None, 3.141592653589793, -1], [None, [], -1], [None, [[]], -1], [None, [1, 2, 3], -1], - [None, '', -1], [None, ' ', -1], [None, '1', -1], [None, 'a bee cd.', -1], [None, '', -1], - [None, ' ', -1], [None, '1', -1], [None, 'a bee cd.', -1], [None, set([]), -1], - [None, set([1, 2, 3]), -1], [None, {5: 3}, -1], [None, {}, -1], [None, {'a': 99}, -1], - [None, {'a': 1, 'c': 3, 'b': 2}, -1], [None, {'a': 99, 'c': 3, 'b': 5}, -1]] + [None, 9999999999999999, -1], [None, 0.0, -1], [None, inf, -1], [None, 3.141592653589793, -1], + [None, [], -1], [None, [[]], -1], [None, [1, 2, 3], -1], [None, '', -1], [None, ' ', -1], + [None, '1', -1], [None, 'a bee cd.', -1], [None, '', -1], [None, ' ', -1], [None, '1', -1], + [None, 'a bee cd.', -1], [None, set([]), -1], [None, set([1, 2, 3]), -1], [None, {5: 3}, -1], + [None, {}, -1], [None, {'a': 99}, -1], [None, {'a': 1, 'c': 3, 'b': 2}, -1], + [None, {'a': 99, 'c': 3, 'b': 5}, -1]] From 3f1ff7eddff8df70137bcd7d627e7af59c8816ed Mon Sep 17 00:00:00 2001 From: "Roman A. Taycher" Date: Sun, 30 Aug 2020 22:01:21 -0700 Subject: [PATCH 360/425] fix 2.6 test --- tests/test_past/test_misc.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_past/test_misc.py b/tests/test_past/test_misc.py index db07896f..ab67bfdd 100644 --- a/tests/test_past/test_misc.py +++ b/tests/test_past/test_misc.py @@ -11,7 +11,7 @@ from contextlib import contextmanager from future.tests.base import unittest -from future.utils import PY3 +from future.utils import PY3, PY26 if PY3: from past.builtins import cmp @@ -29,6 +29,10 @@ def empty_context_manager(*args, **kwargs): class TestCmp(unittest.TestCase): def test_cmp(self): for x, y, cmp_python2_value in test_values.cmp_python2_value: + if PY26: + # set comparison works a bit differently in 2.6 + if isinstance(x, set) or isinstance(y, set): + continue # to get this to run on python <3.4 which lacks subTest with getattr(self, 'subTest', empty_context_manager)(x=x, y=y): try: From ddedcb9abf98fd1cc85fb43ee35a12b2e898a5d7 Mon Sep 17 00:00:00 2001 From: "Roman A. Taycher" Date: Sun, 30 Aug 2020 22:06:42 -0700 Subject: [PATCH 361/425] fix 2.6 test, better comment --- tests/test_past/test_misc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_past/test_misc.py b/tests/test_past/test_misc.py index ab67bfdd..0367b3db 100644 --- a/tests/test_past/test_misc.py +++ b/tests/test_past/test_misc.py @@ -30,7 +30,7 @@ class TestCmp(unittest.TestCase): def test_cmp(self): for x, y, cmp_python2_value in test_values.cmp_python2_value: if PY26: - # set comparison works a bit differently in 2.6 + # set cmp works a bit differently in 2.6, we try to emulate 2.7 behavior, so skip set cmp tests if isinstance(x, set) or isinstance(y, set): continue # to get this to run on python <3.4 which lacks subTest From 05bc771c8d8770da5b7e82db77078e6b2fef8146 Mon Sep 17 00:00:00 2001 From: Joseph Curtis Date: Wed, 16 Sep 2020 11:22:09 -0700 Subject: [PATCH 362/425] Update imp to importlib for py3 --- docs/other/auto2to3.py | 6 +++++- src/future/backports/test/support.py | 6 +++++- src/future/standard_library/__init__.py | 6 +++++- src/past/builtins/misc.py | 3 ++- src/past/translation/__init__.py | 8 ++++++-- tests/test_future/test_standard_library.py | 6 +++++- 6 files changed, 28 insertions(+), 7 deletions(-) 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/src/future/backports/test/support.py b/src/future/backports/test/support.py index 1999e208..4e05e972 100644 --- a/src/future/backports/test/support.py +++ b/src/future/backports/test/support.py @@ -28,7 +28,11 @@ # import collections.abc # not present on Py2.7 import re import subprocess -import imp +# imp was deprecated in python 3.6 +if sys.version_info >= (3, 6): + import importlib as imp +else: + import imp import time try: import sysconfig diff --git a/src/future/standard_library/__init__.py b/src/future/standard_library/__init__.py index cff02f95..0a447871 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -62,7 +62,11 @@ 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 diff --git a/src/past/builtins/misc.py b/src/past/builtins/misc.py index ba50aa9e..eaaef410 100644 --- a/src/past/builtins/misc.py +++ b/src/past/builtins/misc.py @@ -42,7 +42,8 @@ def oct(number): return '0' + builtins.oct(number)[2:] raw_input = input - from imp import reload + # imp was deprecated in python 3.6 + from importlib import reload unicode = str unichr = chr xrange = range diff --git a/src/past/translation/__init__.py b/src/past/translation/__init__.py index 7c678866..e70d4bcd 100644 --- a/src/past/translation/__init__.py +++ b/src/past/translation/__init__.py @@ -32,11 +32,15 @@ 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 diff --git a/tests/test_future/test_standard_library.py b/tests/test_future/test_standard_library.py index 3ac5d2d7..630a43f2 100644 --- a/tests/test_future/test_standard_library.py +++ b/tests/test_future/test_standard_library.py @@ -447,7 +447,11 @@ def test_reload(self): """ reload has been moved to the imp module """ - import imp + # imp was deprecated in python 3.6 + if sys.version_info >= (3, 6): + import importlib as imp + else: + import imp imp.reload(imp) self.assertTrue(True) From dbd3cd0270ce48bb27a633b9b88a1a9bb287011d Mon Sep 17 00:00:00 2001 From: Joseph Curtis Date: Wed, 16 Sep 2020 11:35:23 -0700 Subject: [PATCH 363/425] fix import reload for python<3.6 --- src/past/builtins/misc.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/past/builtins/misc.py b/src/past/builtins/misc.py index eaaef410..78210d37 100644 --- a/src/past/builtins/misc.py +++ b/src/past/builtins/misc.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import inspect +import sys from future.utils import PY2, PY3, exec_ @@ -43,7 +44,10 @@ def oct(number): raw_input = input # imp was deprecated in python 3.6 - from importlib import reload + if sys.version_info >= (3, 6): + from importlib import reload + else: + from imp import reload unicode = str unichr = chr xrange = range From bee0247d43a7e7fafaaa18a1557ab4815ed072d6 Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Thu, 8 Oct 2020 09:07:51 -0700 Subject: [PATCH 364/425] Add PY37_PLUS, PY38_PLUS, and PY39_PLUS This extend the set of existing PY3?_PLUS constants to include recent Python 3.x releases. --- src/future/utils/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/future/utils/__init__.py b/src/future/utils/__init__.py index 846d5da6..ec1b1027 100644 --- a/src/future/utils/__init__.py +++ b/src/future/utils/__init__.py @@ -61,6 +61,9 @@ 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) From c341d5497788923cc6ea0bd1358279f2147aa167 Mon Sep 17 00:00:00 2001 From: Alexander Shadchin Date: Sun, 15 Nov 2020 13:01:39 +0300 Subject: [PATCH 365/425] Add support Python 3.9 --- src/future/backports/xmlrpc/client.py | 7 ++++--- src/future/moves/_dummy_thread.py | 5 ++++- src/future/standard_library/__init__.py | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/future/backports/xmlrpc/client.py b/src/future/backports/xmlrpc/client.py index b78e5bad..0bcd90c9 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 diff --git a/src/future/moves/_dummy_thread.py b/src/future/moves/_dummy_thread.py index 688d249b..e5dca348 100644 --- a/src/future/moves/_dummy_thread.py +++ b/src/future/moves/_dummy_thread.py @@ -2,7 +2,10 @@ from future.utils import PY3 if PY3: - from _dummy_thread import * + try: + from _dummy_thread import * + except ImportError: + from _thread import * else: __future_module__ = True from dummy_thread import * diff --git a/src/future/standard_library/__init__.py b/src/future/standard_library/__init__.py index cff02f95..41c4f36d 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -125,7 +125,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 From c4411998f3935b5588ef0ba2fdff09b11edf2bcd Mon Sep 17 00:00:00 2001 From: Alexander Shadchin Date: Sun, 15 Nov 2020 13:04:51 +0300 Subject: [PATCH 366/425] Add Python 3.8 and 3.9 in .travis.yml --- .travis.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1be7fac8..e77b37aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,8 +22,13 @@ matrix: env: TOXENV=py36 - python: 3.7 env: TOXENV=py37 - dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069) - sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069) + dist: xenial + - python: 3.8 + env: TOXENV=py38 + dist: xenial + - python: 3.9 + env: TOXENV=py39 + dist: bionic install: - pip install typing==3.7.4.1 # required for Python 3.3 From 90e3e4d7146324bd88a5453ba17a43412174f013 Mon Sep 17 00:00:00 2001 From: Alexander Shadchin Date: Sun, 15 Nov 2020 13:09:44 +0300 Subject: [PATCH 367/425] Fix tests --- tests/test_future/test_standard_library.py | 3 ++- tests/test_future/test_urllib_toplevel.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_future/test_standard_library.py b/tests/test_future/test_standard_library.py index 3ac5d2d7..820bf47a 100644 --- a/tests/test_future/test_standard_library.py +++ b/tests/test_future/test_standard_library.py @@ -422,7 +422,8 @@ def test_urllib_imports_install_hooks(self): def test_underscore_prefixed_modules(self): import _thread - import _dummy_thread + if sys.version_info < (3, 9): + import _dummy_thread import _markupbase self.assertTrue(True) diff --git a/tests/test_future/test_urllib_toplevel.py b/tests/test_future/test_urllib_toplevel.py index 11e77201..68bc4c96 100644 --- a/tests/test_future/test_urllib_toplevel.py +++ b/tests/test_future/test_urllib_toplevel.py @@ -781,8 +781,9 @@ def test_unquoting(self): "%s" % result) self.assertRaises((TypeError, AttributeError), urllib_parse.unquote, None) self.assertRaises((TypeError, AttributeError), urllib_parse.unquote, ()) - with support.check_warnings(('', BytesWarning), quiet=True): - self.assertRaises((TypeError, AttributeError), urllib_parse.unquote, bytes(b'')) + if sys.version_info < (3, 9): + with support.check_warnings(('', BytesWarning), quiet=True): + self.assertRaises((TypeError, AttributeError), urllib_parse.unquote, bytes(b'')) def test_unquoting_badpercent(self): # Test unquoting on bad percent-escapes From 3e43aa2cdb7a25cf7a8c5cb382f3aa5ee8793918 Mon Sep 17 00:00:00 2001 From: Alexander Shadchin Date: Sun, 15 Nov 2020 13:16:00 +0300 Subject: [PATCH 368/425] Add Python 3.8 and 3.9 in tox.ini --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1ca3286b..48e17601 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - py{26,27,33,34,35,36,37}, + py{26,27,33,34,35,36,37,38,39}, docs [testenv] From 32641e1e8f22e326f8cb77dbcd8b2172ece797e2 Mon Sep 17 00:00:00 2001 From: Alexander Shadchin Date: Sun, 15 Nov 2020 13:30:06 +0300 Subject: [PATCH 369/425] Fix test_pow for Python 3.8+ --- tests/test_future/test_builtins.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_future/test_builtins.py b/tests/test_future/test_builtins.py index 3921a608..f5dbec64 100644 --- a/tests/test_future/test_builtins.py +++ b/tests/test_future/test_builtins.py @@ -1304,7 +1304,8 @@ def test_pow(self): self.assertAlmostEqual(pow(-1, 1/3), 0.5 + 0.8660254037844386j) # Raises TypeError in Python < v3.5, ValueError in v3.5: - self.assertRaises((TypeError, ValueError), pow, -1, -2, 3) + if sys.version_info < (3, 8): + self.assertRaises((TypeError, ValueError), pow, -1, -2, 3) self.assertRaises(ValueError, pow, 1, 2, 0) self.assertRaises(TypeError, pow) From e7a2f76afa12ac4201c25dac9f93197016a19a7c Mon Sep 17 00:00:00 2001 From: Alexander Shadchin Date: Sun, 15 Nov 2020 13:53:28 +0300 Subject: [PATCH 370/425] Fix test_ftp for Python 3.8+ --- tests/test_future/test_urllib2.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/test_future/test_urllib2.py b/tests/test_future/test_urllib2.py index 2d69dad1..bd8e75c5 100644 --- a/tests/test_future/test_urllib2.py +++ b/tests/test_future/test_urllib2.py @@ -691,10 +691,6 @@ def connect_ftp(self, user, passwd, host, port, dirs, h = NullFTPHandler(data) h.parent = MockOpener() - # MIME guessing works in Python 3.8! - guessed_mime = None - if sys.hexversion >= 0x03080000: - guessed_mime = "image/gif" for url, host, port, user, passwd, type_, dirs, filename, mimetype in [ ("ftp://localhost/foo/bar/baz.html", "localhost", ftplib.FTP_PORT, "", "", "I", @@ -713,7 +709,10 @@ def connect_ftp(self, user, passwd, host, port, dirs, ["foo", "bar"], "", None), ("ftp://localhost/baz.gif;type=a", "localhost", ftplib.FTP_PORT, "", "", "A", - [], "baz.gif", guessed_mime), + [], "baz.gif", None), + ("ftp://localhost/baz.gif", + "localhost", ftplib.FTP_PORT, "", "", "I", + [], "baz.gif", "image/gif"), ]: req = Request(url) req.timeout = None From 5f9893f1207380754a85be51e86652bc6d2b8ecc Mon Sep 17 00:00:00 2001 From: Liuyang Wan Date: Mon, 23 Nov 2020 22:25:52 +0800 Subject: [PATCH 371/425] Add docs/requirements.txt --- docs/requirements.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 docs/requirements.txt diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..c5e7e301 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,2 @@ +sphinx==3.2.1 +sphinx_bootstrap_theme==0.7.1 From 18ecc5a199c324b344bfc2f99f9456f806acde41 Mon Sep 17 00:00:00 2001 From: Liuyang Wan Date: Mon, 23 Nov 2020 22:36:39 +0800 Subject: [PATCH 372/425] Use same docs requirements in tox --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 1ca3286b..bc3300ab 100644 --- a/tox.ini +++ b/tox.ini @@ -12,6 +12,5 @@ commands = pytest {posargs} [testenv:docs] deps = - sphinx - sphinx_bootstrap_theme + -rdocs/requirements.txt commands = sphinx-build docs build From 3f40bd75d6cc3efee1e72413bc685e7bbf9b5a46 Mon Sep 17 00:00:00 2001 From: Liuyang Wan Date: Mon, 23 Nov 2020 22:50:38 +0800 Subject: [PATCH 373/425] Add docs build status badge to README.md --- README.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 5c090804..1ab43e53 100644 --- a/README.rst +++ b/README.rst @@ -3,6 +3,12 @@ Overview: Easy, clean, reliable Python 2/3 compatibility ======================================================== +.. image:: https://travis-ci.org/PythonCharmers/python-future.svg?branch=master + :target: https://travis-ci.org/PythonCharmers/python-future + +.. image:: https://readthedocs.org/projects/python-future/badge/?version=latest + :target: https://python-future.readthedocs.io/en/latest/?badge=latest + ``python-future`` is the missing compatibility layer between Python 2 and Python 3. It allows you to use a single, clean Python 3.x-compatible codebase to support both Python 2 and Python 3 with minimal overhead. @@ -22,9 +28,6 @@ are `Mezzanine `_ and `ObsPy Features -------- -.. image:: https://travis-ci.org/PythonCharmers/python-future.svg?branch=master - :target: https://travis-ci.org/PythonCharmers/python-future - - ``future.builtins`` package (also available as ``builtins`` on Py2) provides backports and remappings for 20 builtins with different semantics on Py3 versus Py2 From 42b30253a6968193965414d9eff82aca1d98094a Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 10 Jul 2020 00:42:47 +0000 Subject: [PATCH 374/425] Build System --- .gitignore | 2 ++ .travis.yml | 47 ++++++----------------------------------------- Dockerfile | 38 ++++++++++++++++++++++++++++++++++++++ TESTING.txt | 8 +++----- build.sh | 14 ++++++++++++++ lint.sh | 3 +++ setup.sh | 20 ++++++++++++++++++++ tox.ini | 16 ---------------- 8 files changed, 86 insertions(+), 62 deletions(-) create mode 100644 Dockerfile create mode 100755 build.sh create mode 100644 lint.sh create mode 100755 setup.sh delete mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index 3b7bce98..01c37c9c 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,8 @@ develop-eggs .installed.cfg lib lib64 +MANIFEST +MANIFEST.in # Backup files *.bak diff --git a/.travis.yml b/.travis.yml index 1be7fac8..d3a8b5ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,46 +1,11 @@ -sudo: false -language: python -cache: pip +language: generic - -matrix: - include: - - python: 2.6 - env: TOXENV=py26 - dist: trusty - - python: 2.7 - env: TOXENV=py27 - - python: 3.3 - env: TOXENV=py33 - dist: trusty - sudo: false - - python: 3.4 - env: TOXENV=py34 - - python: 3.5 - env: TOXENV=py35 - - python: 3.6 - env: TOXENV=py36 - - python: 3.7 - env: TOXENV=py37 - dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069) - sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069) - -install: - - pip install typing==3.7.4.1 # required for Python 3.3 - - pip install tox==2.9.1 - - pip install virtualenv==15.2.0 - - pip install py==1.4.30 - - pip install pluggy==0.5.2 +services: + - docker before_script: - # Run flake8 tests only on Python 2.7 and 3.7... - # 1) stop the build if there are Python syntax errors or undefined names - # 2) exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - - if [[ $TRAVIS_PYTHON_VERSION == *.7 ]]; then - pip install flake8; - flake8 . --count --exit-zero --select=E901,E999,F821,F822,F823 --show-source --statistics; - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics; - fi + - docker pull jmadler/python-future-builder:latest script: - - tox + - ./lint.sh + - ./build.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..5ed3387f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +FROM debian:9 +# This docker image has a copy of a wide array of Pythons installed +RUN apt-get update +RUN apt-get install --yes --no-install-recommends make build-essential zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev xz-utils tk-dev libxml2-dev libffi-dev liblzma-dev libssl1.0-dev +RUN apt-get install --yes git vim +RUN apt-get install --yes python3-pip +ENV PYENV_ROOT=/opt/pyenv +RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash +RUN echo export PATH="/opt/pyenv/bin:$PATH" >> ~/.bashrc +RUN echo 'eval "$(pyenv init -)"' >> ~/.bashrc +RUN echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bashrc +# venv 15.2.0 is the last to support Python 2.6. +RUN pip3 install virtualenv==15.2.0 +RUN PATH=/opt/pyenv/bin:$PATH pyenv install 2.6.9 +RUN virtualenv /root/py26 --python /opt/pyenv/versions/2.6.9/bin/python +RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.3.7 +RUN virtualenv /root/py33 --python /opt/pyenv/versions/3.3.7/bin/python +RUN pip3 install virtualenv==20.0.21 +RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.4.10 +RUN virtualenv /root/py34 --python /opt/pyenv/versions/3.4.10/bin/python +RUN apt-get install --yes libssl-dev libxmlsec1-dev +RUN PATH=/opt/pyenv/bin:$PATH pyenv install 2.7.18 +RUN virtualenv /root/py27 --python /opt/pyenv/versions/2.7.18/bin/python +RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.5.9 +RUN virtualenv /root/py35 --python /opt/pyenv/versions/3.5.9/bin/python +RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.6.10 +RUN virtualenv /root/py36 --python /opt/pyenv/versions/3.6.10/bin/python +RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.7.7 +RUN virtualenv /root/py37 --python /opt/pyenv/versions/3.7.7/bin/python +RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.8.3 +RUN virtualenv /root/py38 --python /opt/pyenv/versions/3.8.3/bin/python +RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.9.0 +RUN virtualenv /root/py39 --python /opt/pyenv/versions/3.9.0/bin/python +RUN ln -s /usr/bin/python3 /usr/bin/python +ENV LC_ALL=C.UTF-8 +ENV LANG=C.UTF-8 +WORKDIR /root/python-future +ADD . /root/python-future diff --git a/TESTING.txt b/TESTING.txt index 0e9b96a3..b2ad5c65 100644 --- a/TESTING.txt +++ b/TESTING.txt @@ -1,8 +1,6 @@ -Currently the tests are passing on OS X and Linux on Python 2.7 and 3.4. +A docker image, python-future-builder, is used to do testing and building. The test suite can be run with: -The test suite can be run with: - - $ tox + $ bash build.sh which tests the module under a number of different python versions, where available, or with: @@ -10,4 +8,4 @@ which tests the module under a number of different python versions, where availa To execute a single test: - $ pytest -k test_chained_exceptions_stacktrace \ No newline at end of file + $ pytest -k test_chained_exceptions_stacktrace diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..9dbba568 --- /dev/null +++ b/build.sh @@ -0,0 +1,14 @@ +# XXX: TODO: we should make this include -e once tests pass +set -xuo pipefail + +docker build . -t jmadler/python-future-builder + +version=0.18.2 + +for i in py26 py27 py33 py34 py35 py36 py37 py38 py39; do + docker run -ti -v $(realpath dist):/root/python-future/dist python-future-builder /root/python-future/setup.sh $version $(basename $i) +done + +python setup.py sdist +python setup.py clean +echo You may now run: "twine upload dist/*" diff --git a/lint.sh b/lint.sh new file mode 100644 index 00000000..234b3f14 --- /dev/null +++ b/lint.sh @@ -0,0 +1,3 @@ +# Run under Python 2.7 and 3.7 +flake8 . --count --exit-zero --select=E901,E999,F821,F822,F823 --show-source --statistics +flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics diff --git a/setup.sh b/setup.sh new file mode 100755 index 00000000..8e8dc150 --- /dev/null +++ b/setup.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -exo pipefail + +version=$1 +pytag=$2 + +if [ $pytag = 'py33' ]; then + pip3 install virtualenv==16.2.0 +fi + +source /root/$pytag/bin/activate + +if [ $pytag = 'py26' ]; then + pip install importlib +fi +pip install pytest unittest2 +python setup.py bdist_wheel --python-tag=$pytag +pip install dist/future-$version-$pytag-none-any.whl +pytest tests/ diff --git a/tox.ini b/tox.ini deleted file mode 100644 index bc3300ab..00000000 --- a/tox.ini +++ /dev/null @@ -1,16 +0,0 @@ -[tox] -envlist = - py{26,27,33,34,35,36,37}, - docs - -[testenv] -deps = - pytest - unittest2 - py26: importlib -commands = pytest {posargs} - -[testenv:docs] -deps = - -rdocs/requirements.txt -commands = sphinx-build docs build From 01e8440942888dd44a10bc8fe976633a707721e2 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 27 Nov 2020 11:13:19 -0800 Subject: [PATCH 375/425] Add docker push to optimize CI --- build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/build.sh b/build.sh index 9dbba568..04737215 100755 --- a/build.sh +++ b/build.sh @@ -2,6 +2,7 @@ set -xuo pipefail docker build . -t jmadler/python-future-builder +docker push jmadler/python-future-builder:latest version=0.18.2 From 58cc9849c7cbee1f6e9884190be82da55792d3ae Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 27 Nov 2020 13:16:11 -0800 Subject: [PATCH 376/425] Make lint.sh executable --- lint.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 lint.sh diff --git a/lint.sh b/lint.sh old mode 100644 new mode 100755 From 046ff1842a0b4464ebb0cdc936a24e626494968a Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 27 Nov 2020 17:36:13 -0800 Subject: [PATCH 377/425] Add flake8 to image --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 5ed3387f..6b94c0a8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,6 +31,8 @@ RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.8.3 RUN virtualenv /root/py38 --python /opt/pyenv/versions/3.8.3/bin/python RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.9.0 RUN virtualenv /root/py39 --python /opt/pyenv/versions/3.9.0/bin/python +# Lint tools +RUN pip3 install flake8 RUN ln -s /usr/bin/python3 /usr/bin/python ENV LC_ALL=C.UTF-8 ENV LANG=C.UTF-8 From f96a219a966d04f7b60a05501ecee52ca5326055 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 27 Nov 2020 18:52:15 -0800 Subject: [PATCH 378/425] fix order --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d3a8b5ad..3fe6a983 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,5 +7,5 @@ before_script: - docker pull jmadler/python-future-builder:latest script: - - ./lint.sh - ./build.sh + - ./lint.sh From 45cf38295a36fb8b59bd1921c926b940764492f3 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Fri, 27 Nov 2020 21:32:17 -0800 Subject: [PATCH 379/425] Update docker image and parcel out to constant variable. Add comment to update version constant --- build.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/build.sh b/build.sh index 04737215..d17fa7ce 100755 --- a/build.sh +++ b/build.sh @@ -1,13 +1,15 @@ # XXX: TODO: we should make this include -e once tests pass set -xuo pipefail -docker build . -t jmadler/python-future-builder -docker push jmadler/python-future-builder:latest - +DOCKER_IMAGE=jmadler/python-future-builder +# XXX: TODO: Perhaps this version shouldn't be hardcoded version=0.18.2 +docker build . -t $DOCKER_IMAGE +docker push $DOCKER_IMAGE:latest + for i in py26 py27 py33 py34 py35 py36 py37 py38 py39; do - docker run -ti -v $(realpath dist):/root/python-future/dist python-future-builder /root/python-future/setup.sh $version $(basename $i) + docker run -ti -v $(realpath dist):/root/python-future/dist $DOCKER_IMAGE /root/python-future/setup.sh $version $(basename $i) done python setup.py sdist From 2abe00d562445f93f72a7cdc00439b7539cc0402 Mon Sep 17 00:00:00 2001 From: Jordan Adler Date: Sat, 28 Nov 2020 09:46:28 -0800 Subject: [PATCH 380/425] Pass if lint fails --- lint.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lint.sh b/lint.sh index 234b3f14..667b258f 100755 --- a/lint.sh +++ b/lint.sh @@ -1,3 +1,3 @@ -# Run under Python 2.7 and 3.7 -flake8 . --count --exit-zero --select=E901,E999,F821,F822,F823 --show-source --statistics -flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics +# TODO: Run under Python 2.7 and 3.7 +flake8 . --count --exit-zero --select=E901,E999,F821,F822,F823 --show-source --statistics || true +flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics || true From c780bf5e15ce00939d0eb7b477b4236a54447c85 Mon Sep 17 00:00:00 2001 From: Tomer Chachamu Date: Tue, 29 Dec 2020 14:10:28 +0000 Subject: [PATCH 381/425] Correct __eq__ --- src/future/types/newrange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/types/newrange.py b/src/future/types/newrange.py index eda01a5a..6d4ebe2f 100644 --- a/src/future/types/newrange.py +++ b/src/future/types/newrange.py @@ -87,7 +87,7 @@ def __eq__(self, other): return (isinstance(other, newrange) and (self._len == 0 == other._len or (self._start, self._step, self._len) == - (other._start, other._step, self._len))) + (other._start, other._step, other._len))) def __len__(self): return self._len From 974eb1ff2fb3982498574bbb8d8084501c477527 Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Thu, 31 Dec 2020 07:30:41 +1100 Subject: [PATCH 382/425] docs: fix simple typo, reqest -> request There is a small typo in tests/test_future/test_urllibnet.py. Should read `request` rather than `reqest`. --- tests/test_future/test_urllibnet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_future/test_urllibnet.py b/tests/test_future/test_urllibnet.py index f9639bfc..6a7b6d64 100644 --- a/tests/test_future/test_urllibnet.py +++ b/tests/test_future/test_urllibnet.py @@ -38,7 +38,7 @@ def testURLread(self): class urlopenNetworkTests(unittest.TestCase): - """Tests urllib.reqest.urlopen using the network. + """Tests urllib.request.urlopen using the network. These tests are not exhaustive. Assuming that testing using files does a good job overall of some of the basic interface features. There are no From 6e27aacadfcee99607894b84e219ba4c571688ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20Andr=C3=A9?= Date: Wed, 20 Jan 2021 19:45:30 +0100 Subject: [PATCH 383/425] Fix bug in super() with metaclasses --- src/future/builtins/newsuper.py | 71 ++++++++++++++++----------------- tests/test_future/test_super.py | 12 ++++++ 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/src/future/builtins/newsuper.py b/src/future/builtins/newsuper.py index 5d3402bd..3e8cc80f 100644 --- a/src/future/builtins/newsuper.py +++ b/src/future/builtins/newsuper.py @@ -60,44 +60,15 @@ def newsuper(typ=_SENTINEL, type_or_obj=_SENTINEL, framedepth=1): raise RuntimeError('super() used in a function with no args') try: - # Get the MRO so we can crawl it. - mro = type_or_obj.__mro__ - except (AttributeError, RuntimeError): # see issue #160 + typ = find_owner(type_or_obj, f.f_code) + except (AttributeError, RuntimeError, TypeError): + # see issues #160, #267 try: - mro = type_or_obj.__class__.__mro__ + typ = find_owner(type_or_obj.__class__, f.f_code) except AttributeError: - raise RuntimeError('super() used with a non-newstyle class') - - # A ``for...else`` block? Yes! It's odd, but useful. - # If unfamiliar with for...else, see: - # - # http://psung.blogspot.com/2007/12/for-else-in-python.html - for typ in mro: - # Find the class that owns the currently-executing method. - for meth in typ.__dict__.values(): - # Drill down through any wrappers to the underlying func. - # This handles e.g. classmethod() and staticmethod(). - try: - while not isinstance(meth,FunctionType): - if isinstance(meth, property): - # Calling __get__ on the property will invoke - # user code which might throw exceptions or have - # side effects - meth = meth.fget - else: - try: - meth = meth.__func__ - except AttributeError: - meth = meth.__get__(type_or_obj, typ) - except (AttributeError, TypeError): - continue - if meth.func_code is f.f_code: - break # Aha! Found you. - else: - continue # Not found! Move onto the next class in MRO. - break # Found! Break out of the search loop. - else: - raise RuntimeError('super() called outside a method') + raise RuntimeError('super() used with an old-style class') + except TypeError: + raise RuntimeError('super() called outside a method') # Dispatch to builtin super(). if type_or_obj is not _SENTINEL: @@ -105,6 +76,34 @@ def newsuper(typ=_SENTINEL, type_or_obj=_SENTINEL, framedepth=1): return _builtin_super(typ) +def find_owner(cls, code): + '''Find the class that owns the currently-executing method. + ''' + for typ in cls.__mro__: + for meth in typ.__dict__.values(): + # Drill down through any wrappers to the underlying func. + # This handles e.g. classmethod() and staticmethod(). + try: + while not isinstance(meth,FunctionType): + if isinstance(meth, property): + # Calling __get__ on the property will invoke + # user code which might throw exceptions or have + # side effects + meth = meth.fget + else: + try: + meth = meth.__func__ + except AttributeError: + meth = meth.__get__(cls, typ) + except (AttributeError, TypeError): + continue + if meth.func_code is code: + return typ # Aha! Found you. + # Not found! Move onto the next class in MRO. + + raise TypeError + + def superm(*args, **kwds): f = sys._getframe(1) nm = f.f_code.co_name diff --git a/tests/test_future/test_super.py b/tests/test_future/test_super.py index 0376c1d8..3cb23d69 100644 --- a/tests/test_future/test_super.py +++ b/tests/test_future/test_super.py @@ -170,6 +170,18 @@ class Elite(Dangerous): self.assertEqual(Elite().walk(), 'Defused') + def test_metaclass(self): + class Meta(type): + def __init__(cls, name, bases, clsdict): + super().__init__(name, bases, clsdict) + + try: + class Base(object): + __metaclass__ = Meta + except Exception as e: + self.fail('raised %s with a custom metaclass' + % type(e).__name__) + class TestSuperFromTestDescrDotPy(unittest.TestCase): """ From fe645ba312a02e6d676265ece5dbf5aa31512e9a Mon Sep 17 00:00:00 2001 From: zihzihtw Date: Fri, 16 Apr 2021 16:36:27 +0800 Subject: [PATCH 384/425] Fix newint bool in py3 --- src/future/types/newint.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/future/types/newint.py b/src/future/types/newint.py index 748dba9d..04a411e9 100644 --- a/src/future/types/newint.py +++ b/src/future/types/newint.py @@ -284,6 +284,9 @@ def __bool__(self): """ So subclasses can override this, Py3-style """ + if PY3: + return super(newint, self).__bool__() + return super(newint, self).__nonzero__() def __native__(self): From 21ae5c76eb49edfff73d5a2319bf42ab0378da48 Mon Sep 17 00:00:00 2001 From: Alexander Shadchin Date: Fri, 11 Jun 2021 22:03:18 +0300 Subject: [PATCH 385/425] Fix tests --- tests/test_future/test_urllib_toplevel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_future/test_urllib_toplevel.py b/tests/test_future/test_urllib_toplevel.py index 68bc4c96..923b2e8a 100644 --- a/tests/test_future/test_urllib_toplevel.py +++ b/tests/test_future/test_urllib_toplevel.py @@ -120,7 +120,7 @@ def setUp(self): finally: f.close() self.pathname = support.TESTFN - self.returned_obj = urlopen("file:%s" % self.pathname) + self.returned_obj = urlopen("file:%s" % urllib_parse.quote(self.pathname)) def tearDown(self): """Shut down the open object""" @@ -167,7 +167,7 @@ def test_info(self): self.assertIsInstance(self.returned_obj.info(), email_message.Message) def test_geturl(self): - self.assertEqual(self.returned_obj.geturl(), self.pathname) + self.assertEqual(self.returned_obj.geturl(), urllib_parse.quote(self.pathname)) def test_getcode(self): self.assertIsNone(self.returned_obj.getcode()) From 3401099e77d5838715044902b1b6ef8ae118b2fd Mon Sep 17 00:00:00 2001 From: Andrew Bjonnes Date: Wed, 3 Nov 2021 21:02:35 -0400 Subject: [PATCH 386/425] Fix bug in fix_raise.py fixer The replacement node for the fix of a raise statement with an unknown value should inherit the prefix of the source node. --- src/libfuturize/fixes/fix_raise.py | 2 +- tests/test_future/test_libfuturize_fixers.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/libfuturize/fixes/fix_raise.py b/src/libfuturize/fixes/fix_raise.py index f7518416..d113401c 100644 --- a/src/libfuturize/fixes/fix_raise.py +++ b/src/libfuturize/fixes/fix_raise.py @@ -94,7 +94,7 @@ def transform(self, node, results): args = [exc, Comma(), val] if tb is not None: args += [Comma(), tb] - return Call(Name(u"raise_"), args) + return Call(Name(u"raise_"), args, prefix=node.prefix) if tb is not None: tb.prefix = "" diff --git a/tests/test_future/test_libfuturize_fixers.py b/tests/test_future/test_libfuturize_fixers.py index 4ac0b7e1..2080696a 100644 --- a/tests/test_future/test_libfuturize_fixers.py +++ b/tests/test_future/test_libfuturize_fixers.py @@ -767,6 +767,20 @@ def test_unknown_value_with_traceback_with_comments(self): raise_(E, Func(arg1, arg2, arg3), tb) # foo""" self.check(b, a) + def test_unknown_value_with_indent(self): + b = """ + while True: + print() # another expression in the same block triggers different parsing + raise E, V + """ + a = """ + from future.utils import raise_ + while True: + print() # another expression in the same block triggers different parsing + raise_(E, V) + """ + self.check(b, a) + # These should produce a warning def test_string_exc(self): From dffc579dbb7c882fc01fa0c0dfa6b59acef7827d Mon Sep 17 00:00:00 2001 From: Andrew Bjonnes Date: Wed, 10 Nov 2021 22:19:41 -0500 Subject: [PATCH 387/425] Fix bug in fix_print.py fixer When the print ends with a non-space whitespace character, an extra space character should not be printed at the end. --- src/libfuturize/fixes/fix_print.py | 10 +++++++ tests/test_future/test_libfuturize_fixers.py | 31 ++++++++++++++++++++ 2 files changed, 41 insertions(+) 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/tests/test_future/test_libfuturize_fixers.py b/tests/test_future/test_libfuturize_fixers.py index 2080696a..2146d1f2 100644 --- a/tests/test_future/test_libfuturize_fixers.py +++ b/tests/test_future/test_libfuturize_fixers.py @@ -307,6 +307,37 @@ def test_trailing_comma_3(self): a = """print(1, end=' ')""" self.check(b, a) + def test_trailing_comma_4(self): + b = """print "a ",""" + a = """print("a ", end=' ')""" + self.check(b, a) + + def test_trailing_comma_5(self): + b = r"""print "b\t",""" + a = r"""print("b\t", end='')""" + self.check(b, a) + + def test_trailing_comma_6(self): + b = r"""print "c\n",""" + a = r"""print("c\n", end='')""" + self.check(b, a) + + def test_trailing_comma_7(self): + b = r"""print "d\r",""" + a = r"""print("d\r", end='')""" + self.check(b, a) + + def test_trailing_comma_8(self): + b = r"""print "%s\n" % (1,),""" + a = r"""print("%s\n" % (1,), end='')""" + self.check(b, a) + + + def test_trailing_comma_9(self): + b = r"""print r"e\n",""" + a = r"""print(r"e\n", end=' ')""" + self.check(b, a) + # >> stuff def test_vargs_without_trailing_comma(self): From f16e1c18821532aa7635265a590fbd7ad967d514 Mon Sep 17 00:00:00 2001 From: Kian-Meng Ang Date: Wed, 12 Jan 2022 19:38:48 +0800 Subject: [PATCH 388/425] Fix typos --- docs/3rd-party-py3k-compat-code/jinja2_compat.py | 2 +- docs/notebooks/object special methods (next, bool, ...).ipynb | 2 +- docs/standard_library_imports.rst | 2 +- docs/whatsnew.rst | 4 ++-- src/future/backports/datetime.py | 2 +- src/future/backports/email/_header_value_parser.py | 2 +- src/future/backports/email/parser.py | 4 ++-- src/future/backports/http/cookiejar.py | 2 +- src/future/backports/xmlrpc/client.py | 2 +- src/future/standard_library/__init__.py | 2 +- src/future/types/newint.py | 2 +- src/future/types/newrange.py | 2 +- src/libfuturize/fixer_util.py | 2 +- src/libfuturize/fixes/fix_metaclass.py | 4 ++-- tests/test_future/test_http_cookiejar.py | 4 ++-- tests/test_future/test_standard_library.py | 2 +- tests/test_future/test_urllib.py | 4 ++-- tests/test_future/test_urllib_toplevel.py | 4 ++-- 18 files changed, 24 insertions(+), 24 deletions(-) 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/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/standard_library_imports.rst b/docs/standard_library_imports.rst index 60442541..c09e9e30 100644 --- a/docs/standard_library_imports.rst +++ b/docs/standard_library_imports.rst @@ -15,7 +15,7 @@ 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 +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``:: diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index c6fa8f86..c9df5120 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -33,7 +33,7 @@ This is a major bug-fix and feature release, including: - Fix an issue with copyreg import under Py3 that results in unexposed stdlib functionality - Export and document types in future.utils - Update behavior of newstr.__eq__() to match str.__eq__() as per reference docs -- Fix raising and the raising fixer to handle cases where the syntax is ambigious +- 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 @@ -65,7 +65,7 @@ This is a major bug-fix release, including: - 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 betwen newstrs and newbytes +- 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. 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/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/http/cookiejar.py b/src/future/backports/http/cookiejar.py index af3ef415..1d301b1c 100644 --- a/src/future/backports/http/cookiejar.py +++ b/src/future/backports/http/cookiejar.py @@ -1845,7 +1845,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/xmlrpc/client.py b/src/future/backports/xmlrpc/client.py index b78e5bad..cab75390 100644 --- a/src/future/backports/xmlrpc/client.py +++ b/src/future/backports/xmlrpc/client.py @@ -1251,7 +1251,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/standard_library/__init__.py b/src/future/standard_library/__init__.py index cff02f95..24124b26 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -17,7 +17,7 @@ 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 diff --git a/src/future/types/newint.py b/src/future/types/newint.py index 04a411e9..2c86ce18 100644 --- a/src/future/types/newint.py +++ b/src/future/types/newint.py @@ -318,7 +318,7 @@ def to_bytes(self, length, byteorder='big', signed=False): bits = length * 8 num = (2**bits) + self if num <= 0: - raise OverflowError("int too smal to convert") + raise OverflowError("int too small to convert") else: if self < 0: raise OverflowError("can't convert negative int to unsigned") diff --git a/src/future/types/newrange.py b/src/future/types/newrange.py index 6d4ebe2f..dc5eb802 100644 --- a/src/future/types/newrange.py +++ b/src/future/types/newrange.py @@ -105,7 +105,7 @@ def index(self, value): 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) diff --git a/src/libfuturize/fixer_util.py b/src/libfuturize/fixer_util.py index 48e4689d..cf646b61 100644 --- a/src/libfuturize/fixer_util.py +++ b/src/libfuturize/fixer_util.py @@ -116,7 +116,7 @@ def suitify(parent): """ 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 diff --git a/src/libfuturize/fixes/fix_metaclass.py b/src/libfuturize/fixes/fix_metaclass.py index 2ac41c97..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 diff --git a/tests/test_future/test_http_cookiejar.py b/tests/test_future/test_http_cookiejar.py index 079026bc..8a98ed68 100644 --- a/tests/test_future/test_http_cookiejar.py +++ b/tests/test_future/test_http_cookiejar.py @@ -380,7 +380,7 @@ class CookieTests(unittest.TestCase): ## comma-separated list, it'll be a headache to parse (at least my head ## starts hurting every time I think of that code). ## - Expires: You'll get all sorts of date formats in the expires, -## including emtpy expires attributes ("expires="). Be as flexible as you +## including empty expires attributes ("expires="). Be as flexible as you ## can, and certainly don't expect the weekday to be there; if you can't ## parse it, just ignore it and pretend it's a session cookie. ## - Domain-matching: Netscape uses the 2-dot rule for _all_ domains, not @@ -1734,7 +1734,7 @@ def test_session_cookies(self): key = "%s_after" % cookie.value counter[key] = counter[key] + 1 - # a permanent cookie got lost accidently + # a permanent cookie got lost accidentally self.assertEqual(counter["perm_after"], counter["perm_before"]) # a session cookie hasn't been cleared self.assertEqual(counter["session_after"], 0) diff --git a/tests/test_future/test_standard_library.py b/tests/test_future/test_standard_library.py index 3ac5d2d7..44c61a9b 100644 --- a/tests/test_future/test_standard_library.py +++ b/tests/test_future/test_standard_library.py @@ -591,7 +591,7 @@ def test_future_moves_dbm(self): from future.moves.dbm import ndbm -# Running the following tkinter test causes the following bizzare test failure: +# Running the following tkinter test causes the following bizarre test failure: # # ====================================================================== # FAIL: test_open_default_encoding (future.tests.test_builtins.BuiltinTest) diff --git a/tests/test_future/test_urllib.py b/tests/test_future/test_urllib.py index 278bafb5..64e89760 100644 --- a/tests/test_future/test_urllib.py +++ b/tests/test_future/test_urllib.py @@ -1,4 +1,4 @@ -"""Regresssion tests for urllib""" +"""Regression tests for urllib""" from __future__ import absolute_import, division, unicode_literals import io @@ -1229,7 +1229,7 @@ def open_spam(self, url): # Everywhere else they work ok, but on those machines, sometimes # fail in one of the tests, sometimes in other. I have a linux, and # the tests go ok. -# If anybody has one of the problematic enviroments, please help! +# If anybody has one of the problematic environments, please help! # . Facundo # # def server(evt): diff --git a/tests/test_future/test_urllib_toplevel.py b/tests/test_future/test_urllib_toplevel.py index 11e77201..86c75619 100644 --- a/tests/test_future/test_urllib_toplevel.py +++ b/tests/test_future/test_urllib_toplevel.py @@ -1,4 +1,4 @@ -"""Regresssion tests for urllib""" +"""Regression tests for urllib""" from __future__ import absolute_import, division, unicode_literals import io @@ -1244,7 +1244,7 @@ def open_spam(self, url): # Everywhere else they work ok, but on those machines, sometimes # fail in one of the tests, sometimes in other. I have a linux, and # the tests go ok. -# If anybody has one of the problematic enviroments, please help! +# If anybody has one of the problematic environments, please help! # . Facundo # # def server(evt): From 4cafc9d860d19135f5edb6e9d35ddf9f11b6d989 Mon Sep 17 00:00:00 2001 From: Linux User Date: Sun, 27 Feb 2022 05:30:15 +0000 Subject: [PATCH 389/425] modified fix_unpacking.py to support both the python2 unicode function and use the str function instead on python3 --- src/libpasteurize/fixes/fix_unpacking.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/libpasteurize/fixes/fix_unpacking.py b/src/libpasteurize/fixes/fix_unpacking.py index c2d3207a..e47efe0e 100644 --- a/src/libpasteurize/fixes/fix_unpacking.py +++ b/src/libpasteurize/fixes/fix_unpacking.py @@ -18,8 +18,14 @@ 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) + except NameError: + pre = str(num_pre) + try: + post = unicode(num_post) + except NameError: + 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: From 8e5ea186d385dbdc89b16cd81f58754ac05ad0a0 Mon Sep 17 00:00:00 2001 From: Linux User Date: Sun, 27 Feb 2022 06:42:43 +0000 Subject: [PATCH 390/425] condensed/refactored fix_unpacking.py change --- src/libpasteurize/fixes/fix_unpacking.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libpasteurize/fixes/fix_unpacking.py b/src/libpasteurize/fixes/fix_unpacking.py index e47efe0e..6e839e6b 100644 --- a/src/libpasteurize/fixes/fix_unpacking.py +++ b/src/libpasteurize/fixes/fix_unpacking.py @@ -20,11 +20,9 @@ def assignment_source(num_pre, num_post, LISTNAME, ITERNAME): children = [] try: pre = unicode(num_pre) - except NameError: - pre = str(num_pre) - try: 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. From 33977e4c10cd68cce2ab262a73d565173d06c0a6 Mon Sep 17 00:00:00 2001 From: Andrii Oriekhov Date: Mon, 28 Feb 2022 15:04:59 +0200 Subject: [PATCH 391/425] add GitHub URL for PyPi --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index 11d694c2..41b0df96 100755 --- a/setup.py +++ b/setup.py @@ -161,6 +161,9 @@ 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, From f1100c8b9eda51e7c242d6153b04946517080f3c Mon Sep 17 00:00:00 2001 From: matthew Date: Fri, 18 Mar 2022 09:31:05 -0700 Subject: [PATCH 392/425] future cannot install without setuptools Signed-off-by: matthew --- docs/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/requirements.txt b/docs/requirements.txt index c5e7e301..0f06b13f 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,3 @@ sphinx==3.2.1 sphinx_bootstrap_theme==0.7.1 +setuptools==0.18.2 From c91d70b34ef0402aef3e9d04364ba98509dca76f Mon Sep 17 00:00:00 2001 From: Will Shanks Date: Fri, 23 Dec 2022 13:38:26 -0500 Subject: [PATCH 393/425] Backport fix for bpo-38804 The regex http.cookiejar.LOOSE_HTTP_DATE_RE was vulnerable to regular expression denial of service (REDoS). The regex contained multiple overlapping \s* capture groups. A long sequence of spaces can trigger bad performance. See https://github.com/python/cpython/pull/17157 and https://pyup.io/posts/pyup-discovers-redos-vulnerabilities-in-top-python-packages/ --- src/future/backports/http/cookiejar.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/future/backports/http/cookiejar.py b/src/future/backports/http/cookiejar.py index af3ef415..0ad80a02 100644 --- a/src/future/backports/http/cookiejar.py +++ b/src/future/backports/http/cookiejar.py @@ -225,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. @@ -298,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: From 079ee9b75441d36447cec9981fa1b0032862f64d Mon Sep 17 00:00:00 2001 From: Liuyang Wan Date: Fri, 13 Jan 2023 11:11:30 +0800 Subject: [PATCH 394/425] Prepare for 0.18.3 release --- Dockerfile | 5 ++-- build.sh | 2 +- docs/whatsnew.rst | 56 ++++++++++++++++++++++++++++++++++++++++++ src/future/__init__.py | 2 +- 4 files changed, 61 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6b94c0a8..678de2e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,8 +11,9 @@ RUN echo 'eval "$(pyenv init -)"' >> ~/.bashrc RUN echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bashrc # venv 15.2.0 is the last to support Python 2.6. RUN pip3 install virtualenv==15.2.0 -RUN PATH=/opt/pyenv/bin:$PATH pyenv install 2.6.9 -RUN virtualenv /root/py26 --python /opt/pyenv/versions/2.6.9/bin/python +# Can't get python 2.6 to build anymore +# RUN PATH=/opt/pyenv/bin:$PATH pyenv install 2.6.9 +# RUN virtualenv /root/py26 --python /opt/pyenv/versions/2.6.9/bin/python RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.3.7 RUN virtualenv /root/py33 --python /opt/pyenv/versions/3.3.7/bin/python RUN pip3 install virtualenv==20.0.21 diff --git a/build.sh b/build.sh index d17fa7ce..df1f00f7 100755 --- a/build.sh +++ b/build.sh @@ -3,7 +3,7 @@ set -xuo pipefail DOCKER_IMAGE=jmadler/python-future-builder # XXX: TODO: Perhaps this version shouldn't be hardcoded -version=0.18.2 +version=0.18.3 docker build . -t $DOCKER_IMAGE docker push $DOCKER_IMAGE:latest diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index c6fa8f86..40f7191f 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -3,6 +3,62 @@ What's New ********** +What's new in version 0.18.3 (2023-01-13) +========================================= +This is a minor bug-fix release containing a number of fixes: + +- Backport fix for bpo-38804 (c91d70b) +- Fix bug in fix_print.py fixer (dffc579) +- Fix bug in fix_raise.py fixer (3401099) +- Fix newint bool in py3 (fe645ba) +- Fix bug in super() with metaclasses (6e27aac) +- docs: fix simple typo, reqest -> request (974eb1f) +- Correct __eq__ (c780bf5) +- Pass if lint fails (2abe00d) +- Update docker image and parcel out to constant variable. Add comment to update version constant (45cf382) +- fix order (f96a219) +- Add flake8 to image (046ff18) +- Make lint.sh executable (58cc984) +- Add docker push to optimize CI (01e8440) +- Build System (42b3025) +- Add docs build status badge to README.md (3f40bd7) +- Use same docs requirements in tox (18ecc5a) +- Add docs/requirements.txt (5f9893f) +- Add PY37_PLUS, PY38_PLUS, and PY39_PLUS (bee0247) +- fix 2.6 test, better comment (ddedcb9) +- fix 2.6 test (3f1ff7e) +- remove nan test (4dbded1) +- include list test values (e3f1a12) +- fix other python2 test issues (c051026) +- fix missing subTest (f006cad) +- import from old imp library on older python versions (fc84fa8) +- replace fstrings with format for python 3.4,3.5 (4a687ea) +- minor style/spelling fixes (8302d8c) +- improve cmp function, add unittest (0d95a40) +- Pin typing==3.7.4.1 for Python 3.3 compatiblity (1a48f1b) +- Fix various py26 unit test failures (9ca5a14) +- Add initial contributing guide with docs build instruction (e55f915) +- Add docs building to tox.ini (3ee9e7f) +- Support NumPy's specialized int types in builtins.round (b4b54f0) +- Added r""" to the docstring to avoid warnings in python3 (5f94572) +- Add __subclasscheck__ for past.types.basestring (c9bc0ff) +- Correct example in README (681e78c) +- Add simple documentation (6c6e3ae) +- Add pre-commit hooks (a9c6a37) +- Handling of __next__ and next by future.utils.get_next was reversed (52b0ff9) +- Add a test for our fix (461d77e) +- Compare headers to correct definition of str (3eaa8fd) +- #322 Add support for negative ndigits in round; additionally, fixing a bug so that it handles passing in Decimal properly (a4911b9) +- Add tkFileDialog to future.movers.tkinter (f6a6549) +- Sort before comparing dicts in TestChainMap (6126997) +- Fix typo (4dfa099) +- Fix formatting in "What's new" (1663dfa) +- Fix typo (4236061) +- Avoid DeprecationWarning caused by invalid escape (e4b7fa1) +- Fixup broken link to external django documentation re: porting to Python 3 and unicode_literals (d87713e) +- Fixed newdict checking version every time (99030ec) +- Add count from 2.7 to 2.6 (1b8ef51) + What's new in version 0.18.2 (2019-10-30) ========================================= This is a minor bug-fix release containing a number of fixes: diff --git a/src/future/__init__.py b/src/future/__init__.py index ad419d67..b609299a 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -87,7 +87,7 @@ __copyright__ = 'Copyright 2013-2019 Python Charmers Pty Ltd' __ver_major__ = 0 __ver_minor__ = 18 -__ver_patch__ = 2 +__ver_patch__ = 3 __ver_sub__ = '' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From a914e33e9f48a77356006cd54e538d9d36ba8629 Mon Sep 17 00:00:00 2001 From: slycordinator <68940237+slycordinator@users.noreply.github.com> Date: Thu, 23 Feb 2023 16:13:34 +0900 Subject: [PATCH 395/425] Fixes according to output from shellcheck * Added "!#" to top of files that didn't have it. * Added quotes around variables/commands as recommended * Changed some to be /bin/sh instead of /bin/bash for portability * removed -o pipefail, especiallly because the scripts seem to not have any piped commands, but also due to it being a bashism --- build.sh | 9 +++++---- lint.sh | 1 + setup.sh | 14 +++++++------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/build.sh b/build.sh index df1f00f7..71e1cdec 100755 --- a/build.sh +++ b/build.sh @@ -1,15 +1,16 @@ +#!/bin/sh # XXX: TODO: we should make this include -e once tests pass -set -xuo pipefail +set -xu DOCKER_IMAGE=jmadler/python-future-builder # XXX: TODO: Perhaps this version shouldn't be hardcoded version=0.18.3 -docker build . -t $DOCKER_IMAGE -docker push $DOCKER_IMAGE:latest +docker build . -t "$DOCKER_IMAGE" +docker push "$DOCKER_IMAGE:latest" for i in py26 py27 py33 py34 py35 py36 py37 py38 py39; do - docker run -ti -v $(realpath dist):/root/python-future/dist $DOCKER_IMAGE /root/python-future/setup.sh $version $(basename $i) + docker run -ti -v "$(realpath dist)":/root/python-future/dist "$DOCKER_IMAGE" /root/python-future/setup.sh "$version" "$i" done python setup.py sdist diff --git a/lint.sh b/lint.sh index 667b258f..b3c41cd4 100755 --- a/lint.sh +++ b/lint.sh @@ -1,3 +1,4 @@ +#!/bin/sh # TODO: Run under Python 2.7 and 3.7 flake8 . --count --exit-zero --select=E901,E999,F821,F822,F823 --show-source --statistics || true flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics || true diff --git a/setup.sh b/setup.sh index 8e8dc150..767cbd55 100755 --- a/setup.sh +++ b/setup.sh @@ -1,20 +1,20 @@ -#!/bin/bash +#!/bin/sh -set -exo pipefail +set -ex version=$1 pytag=$2 -if [ $pytag = 'py33' ]; then +if [ "$pytag" = 'py33' ]; then pip3 install virtualenv==16.2.0 fi -source /root/$pytag/bin/activate +. /root/"$pytag"/bin/activate -if [ $pytag = 'py26' ]; then +if [ "$pytag" = 'py26' ]; then pip install importlib fi pip install pytest unittest2 -python setup.py bdist_wheel --python-tag=$pytag -pip install dist/future-$version-$pytag-none-any.whl +python setup.py bdist_wheel --python-tag="$pytag" +pip install "dist/future-$version-$pytag-none-any.whl" pytest tests/ From 61f6cf1d999dddc8e1b1dddbbf53e929b0df1cdb Mon Sep 17 00:00:00 2001 From: lilinjie Date: Mon, 3 Apr 2023 10:36:37 +0800 Subject: [PATCH 396/425] fix typo Signed-off-by: lilinjie --- src/future/backports/http/cookiejar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/backports/http/cookiejar.py b/src/future/backports/http/cookiejar.py index 0ad80a02..a39242c0 100644 --- a/src/future/backports/http/cookiejar.py +++ b/src/future/backports/http/cookiejar.py @@ -1851,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. From a6135542dffb6b1b8254d6daac779d119d4fc08c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Hrn=C4=8Diar?= Date: Wed, 17 May 2023 14:03:26 +0200 Subject: [PATCH 397/425] Adjust tests to the repr changes in CPython --- tests/test_future/test_backports.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/test_future/test_backports.py b/tests/test_future/test_backports.py index 63b1afea..5d46b115 100644 --- a/tests/test_future/test_backports.py +++ b/tests/test_future/test_backports.py @@ -599,8 +599,12 @@ def test_yaml_linkage(self): def test_repr(self): od = OrderedDict([('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]) - self.assertEqual(repr(od), - "OrderedDict([('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)])") + if sys.version_info[0] == 3 and sys.version_info[1] >= 12: + self.assertEqual(repr(od), + "OrderedDict({'c': 1, 'b': 2, 'a': 3, 'd': 4, 'e': 5, 'f': 6})") + else: + self.assertEqual(repr(od), + "OrderedDict([('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)])") self.assertEqual(eval(repr(od)), od) self.assertEqual(repr(OrderedDict()), "OrderedDict()") @@ -608,8 +612,12 @@ def test_repr_recursive(self): # See issue #9826 od = OrderedDict.fromkeys('abc') od['x'] = od - self.assertEqual(repr(od), - "OrderedDict([('a', None), ('b', None), ('c', None), ('x', ...)])") + if sys.version_info[0] == 3 and sys.version_info[1] >= 12: + self.assertEqual(repr(od), + "OrderedDict({'a': None, 'b': None, 'c': None, 'x': ...})") + else: + self.assertEqual(repr(od), + "OrderedDict([('a', None), ('b', None), ('c', None), ('x', ...)])") def test_setdefault(self): pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] From d7dc44e88b77fea57b9001421428cd7d95abb3bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Hrn=C4=8Diar?= Date: Wed, 17 May 2023 14:42:09 +0200 Subject: [PATCH 398/425] Adjust test to the change in CPython, parser now raises SyntaxError instead of ValueError when source code contains null bytes --- tests/test_future/test_builtins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_future/test_builtins.py b/tests/test_future/test_builtins.py index 3921a608..d41d1254 100644 --- a/tests/test_future/test_builtins.py +++ b/tests/test_future/test_builtins.py @@ -523,8 +523,8 @@ def test_compile(self): self.assertRaises(TypeError, compile) self.assertRaises(ValueError, compile, 'print(42)\n', '', 'badmode') self.assertRaises(ValueError, compile, 'print(42)\n', '', 'single', 0xff) - # Raises TypeError in Python < v3.5, ValueError in v3.5: - self.assertRaises((TypeError, ValueError), compile, chr(0), 'f', 'exec') + # Raises TypeError in Python < v3.5, ValueError in v3.5, SyntaxError in >= 3.12: + self.assertRaises((TypeError, ValueError, SyntaxError), compile, chr(0), 'f', 'exec') self.assertRaises(TypeError, compile, 'pass', '?', 'exec', mode='eval', source='0', filename='tmp') compile('print("\xe5")\n', '', 'exec') From 331d5fb105a70bce1e2c415218fbaf5a002b1f9a Mon Sep 17 00:00:00 2001 From: Gabriela Gutierrez Date: Mon, 5 Jun 2023 15:04:38 -0300 Subject: [PATCH 399/425] Create SECURITY.md Signed-off-by: Gabriela Gutierrez --- .github/SECURITY.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/SECURITY.md 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. From 36ad5ba6df8c3e71b30c66d707e156b67c785eb5 Mon Sep 17 00:00:00 2001 From: Greg Roodt Date: Sun, 13 Aug 2023 14:47:27 +1000 Subject: [PATCH 400/425] Clarify how distributions are built and uploaded --- Dockerfile | 40 +--------------------------------------- build.sh | 4 ++-- setup.py | 5 ----- setup.sh | 25 ++++++++++--------------- 4 files changed, 13 insertions(+), 61 deletions(-) diff --git a/Dockerfile b/Dockerfile index 678de2e2..cf9ffd3c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,41 +1,3 @@ -FROM debian:9 -# This docker image has a copy of a wide array of Pythons installed -RUN apt-get update -RUN apt-get install --yes --no-install-recommends make build-essential zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev xz-utils tk-dev libxml2-dev libffi-dev liblzma-dev libssl1.0-dev -RUN apt-get install --yes git vim -RUN apt-get install --yes python3-pip -ENV PYENV_ROOT=/opt/pyenv -RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash -RUN echo export PATH="/opt/pyenv/bin:$PATH" >> ~/.bashrc -RUN echo 'eval "$(pyenv init -)"' >> ~/.bashrc -RUN echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bashrc -# venv 15.2.0 is the last to support Python 2.6. -RUN pip3 install virtualenv==15.2.0 -# Can't get python 2.6 to build anymore -# RUN PATH=/opt/pyenv/bin:$PATH pyenv install 2.6.9 -# RUN virtualenv /root/py26 --python /opt/pyenv/versions/2.6.9/bin/python -RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.3.7 -RUN virtualenv /root/py33 --python /opt/pyenv/versions/3.3.7/bin/python -RUN pip3 install virtualenv==20.0.21 -RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.4.10 -RUN virtualenv /root/py34 --python /opt/pyenv/versions/3.4.10/bin/python -RUN apt-get install --yes libssl-dev libxmlsec1-dev -RUN PATH=/opt/pyenv/bin:$PATH pyenv install 2.7.18 -RUN virtualenv /root/py27 --python /opt/pyenv/versions/2.7.18/bin/python -RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.5.9 -RUN virtualenv /root/py35 --python /opt/pyenv/versions/3.5.9/bin/python -RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.6.10 -RUN virtualenv /root/py36 --python /opt/pyenv/versions/3.6.10/bin/python -RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.7.7 -RUN virtualenv /root/py37 --python /opt/pyenv/versions/3.7.7/bin/python -RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.8.3 -RUN virtualenv /root/py38 --python /opt/pyenv/versions/3.8.3/bin/python -RUN PATH=/opt/pyenv/bin:$PATH pyenv install 3.9.0 -RUN virtualenv /root/py39 --python /opt/pyenv/versions/3.9.0/bin/python -# Lint tools -RUN pip3 install flake8 -RUN ln -s /usr/bin/python3 /usr/bin/python -ENV LC_ALL=C.UTF-8 -ENV LANG=C.UTF-8 +FROM quay.io/pypa/manylinux1_x86_64 WORKDIR /root/python-future ADD . /root/python-future diff --git a/build.sh b/build.sh index df1f00f7..15dac763 100755 --- a/build.sh +++ b/build.sh @@ -6,9 +6,9 @@ DOCKER_IMAGE=jmadler/python-future-builder version=0.18.3 docker build . -t $DOCKER_IMAGE -docker push $DOCKER_IMAGE:latest +#docker push $DOCKER_IMAGE:latest -for i in py26 py27 py33 py34 py35 py36 py37 py38 py39; do +for i in cp27-cp27m cp35-cp35m cp36-cp36m cp37-cp37m cp38-cp38 cp39-cp39; do docker run -ti -v $(realpath dist):/root/python-future/dist $DOCKER_IMAGE /root/python-future/setup.sh $version $(basename $i) done diff --git a/setup.py b/setup.py index 41b0df96..eef5d230 100755 --- a/setup.py +++ b/setup.py @@ -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", diff --git a/setup.sh b/setup.sh index 8e8dc150..047dd953 100755 --- a/setup.sh +++ b/setup.sh @@ -3,18 +3,13 @@ set -exo pipefail version=$1 -pytag=$2 - -if [ $pytag = 'py33' ]; then - pip3 install virtualenv==16.2.0 -fi - -source /root/$pytag/bin/activate - -if [ $pytag = 'py26' ]; then - pip install importlib -fi -pip install pytest unittest2 -python setup.py bdist_wheel --python-tag=$pytag -pip install dist/future-$version-$pytag-none-any.whl -pytest tests/ +pyabitag=$2 + +py="/opt/python/${pyabitag}/bin/python" +pytag=${pyabitag%-*} +pytag="${pytag//cp/py}" +$py -m pip install pytest unittest2 +$py setup.py bdist_wheel --python-tag=$pytag +$py -m pip install dist/future-$version-$pytag-none-any.whl +# Ignore test failures for now +$py -m pytest tests/ || true From 1901c1c347bcad603e8404b64656994eb2cc0439 Mon Sep 17 00:00:00 2001 From: David Bern Date: Tue, 10 Oct 2023 22:13:54 -0500 Subject: [PATCH 401/425] WIP Python 3.12 support for removal of imp module --- build.sh | 2 +- docs/whatsnew.rst | 6 ++++++ src/future/__init__.py | 2 +- src/future/backports/test/support.py | 11 +++++++---- src/future/standard_library/__init__.py | 12 +++++++++--- src/past/translation/__init__.py | 5 ++++- tests/test_future/test_standard_library.py | 11 ++++++++--- 7 files changed, 36 insertions(+), 13 deletions(-) diff --git a/build.sh b/build.sh index df1f00f7..ef52cb41 100755 --- a/build.sh +++ b/build.sh @@ -3,7 +3,7 @@ set -xuo pipefail DOCKER_IMAGE=jmadler/python-future-builder # XXX: TODO: Perhaps this version shouldn't be hardcoded -version=0.18.3 +version=0.18.4 docker build . -t $DOCKER_IMAGE docker push $DOCKER_IMAGE:latest diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 40f7191f..9018fdfe 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -3,6 +3,12 @@ What's New ********** +What's new in version 0.18.4 (2023-10-10) +========================================= +This is a minor bug-fix release containing a number of fixes: + +- Fix for Python 3.12's removal of the imp module + What's new in version 0.18.3 (2023-01-13) ========================================= This is a minor bug-fix release containing a number of fixes: diff --git a/src/future/__init__.py b/src/future/__init__.py index b609299a..64b66f43 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -87,7 +87,7 @@ __copyright__ = 'Copyright 2013-2019 Python Charmers Pty Ltd' __ver_major__ = 0 __ver_minor__ = 18 -__ver_patch__ = 3 +__ver_patch__ = 4 __ver_sub__ = '' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) diff --git a/src/future/backports/test/support.py b/src/future/backports/test/support.py index 1999e208..08b59829 100644 --- a/src/future/backports/test/support.py +++ b/src/future/backports/test/support.py @@ -28,7 +28,10 @@ # import collections.abc # not present on Py2.7 import re import subprocess -import imp +try: + from imp import cache_from_source +except ImportError: + from importlib.util import cache_from_source import time try: import sysconfig @@ -351,7 +354,7 @@ def make_legacy_pyc(source): 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) + pyc_file = 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) @@ -370,8 +373,8 @@ def forget(modname): # 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)) + unlink(cache_from_source(source, debug_override=True)) + unlink(cache_from_source(source, debug_override=False)) # On some platforms, should not run gui test even if it is allowed # in `use_resources'. diff --git a/src/future/standard_library/__init__.py b/src/future/standard_library/__init__.py index cff02f95..24d9287f 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -62,7 +62,10 @@ import sys import logging -import imp +try: + import importlib +except ImportError: + import imp import contextlib import types import copy @@ -297,8 +300,11 @@ def _find_and_load_module(self, name, path=None): flog.debug('What to do here?') name = bits[0] - module_info = imp.find_module(name, path) - return imp.load_module(name, *module_info) + try: + module_info = imp.find_module(name, path) + return imp.load_module(name, *module_info) + except AttributeError: + return importlib.import_module(name, path) class hooks(object): diff --git a/src/past/translation/__init__.py b/src/past/translation/__init__.py index 7c678866..6e6ccf74 100644 --- a/src/past/translation/__init__.py +++ b/src/past/translation/__init__.py @@ -32,7 +32,10 @@ Inspired by and based on ``uprefix`` by Vinay M. Sajip. """ -import imp +try: + import imp +except ImportError: + import importlib import logging import marshal import os diff --git a/tests/test_future/test_standard_library.py b/tests/test_future/test_standard_library.py index 3ac5d2d7..f3477210 100644 --- a/tests/test_future/test_standard_library.py +++ b/tests/test_future/test_standard_library.py @@ -447,9 +447,14 @@ def test_reload(self): """ reload has been moved to the imp module """ - import imp - imp.reload(imp) - self.assertTrue(True) + try: + import imp + imp.reload(imp) + self.assertTrue(True) + except ImportError: + import importlib + importlib.reload(importlib) + self.assertTrue(True) def test_install_aliases(self): """ From 4fcbe2e873100e1fdc064edd23f8501f65c4eae8 Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Fri, 19 Jan 2024 15:09:45 +0000 Subject: [PATCH 402/425] Use importlib instead of imp for Python 3.12 support Also remove some unused imports. --- src/future/backports/test/support.py | 35 --- src/future/standard_library/__init__.py | 15 +- src/past/translation/__init__.py | 254 +++++++++------------ tests/test_future/test_standard_library.py | 11 +- tests/test_past/test_builtins.py | 1 - tests/test_past/test_translation.py | 20 +- 6 files changed, 126 insertions(+), 210 deletions(-) diff --git a/src/future/backports/test/support.py b/src/future/backports/test/support.py index 08b59829..6639372b 100644 --- a/src/future/backports/test/support.py +++ b/src/future/backports/test/support.py @@ -28,10 +28,6 @@ # import collections.abc # not present on Py2.7 import re import subprocess -try: - from imp import cache_from_source -except ImportError: - from importlib.util import cache_from_source 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 = 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(cache_from_source(source, debug_override=True)) - unlink(cache_from_source(source, debug_override=False)) # On some platforms, should not run gui test even if it is allowed # in `use_resources'. diff --git a/src/future/standard_library/__init__.py b/src/future/standard_library/__init__.py index 24d9287f..2cee75db 100644 --- a/src/future/standard_library/__init__.py +++ b/src/future/standard_library/__init__.py @@ -62,12 +62,7 @@ import sys import logging -try: - import importlib -except ImportError: - import imp import contextlib -import types import copy import os @@ -82,6 +77,9 @@ from future.utils import PY2, PY3 +if PY2: + import imp + # The modules that are defined under the same names on Py3 but with # different contents in a significant way (e.g. submodules) are: # pickle (fast one) @@ -300,11 +298,8 @@ def _find_and_load_module(self, name, path=None): flog.debug('What to do here?') name = bits[0] - try: - module_info = imp.find_module(name, path) - return imp.load_module(name, *module_info) - except AttributeError: - return importlib.import_module(name, path) + module_info = imp.find_module(name, path) + return imp.load_module(name, *module_info) class hooks(object): diff --git a/src/past/translation/__init__.py b/src/past/translation/__init__.py index 6e6ccf74..db96982b 100644 --- a/src/past/translation/__init__.py +++ b/src/past/translation/__init__.py @@ -32,12 +32,7 @@ Inspired by and based on ``uprefix`` by Vinay M. Sajip. """ -try: - import imp -except ImportError: - import importlib import logging -import marshal import os import sys import copy @@ -46,6 +41,17 @@ from libfuturize import fixes +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) @@ -228,6 +234,81 @@ def detect_python2(source, pathname): 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 @@ -261,151 +342,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 - RTs.setup() - source += '\n' - try: - tree = RTs._rt.refactor_string(source, self.pathname) - except ParseError as e: - if e.msg != 'bad input' or e.value != '=': - raise - tree = RTs._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 - - # 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] + 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 - 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) - - code = compile(source, self.pathname, 'exec') - - dirname = os.path.dirname(cachename) - try: - if not os.path.exists(dirname): - os.makedirs(dirname) - 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() diff --git a/tests/test_future/test_standard_library.py b/tests/test_future/test_standard_library.py index f3477210..1028f6fc 100644 --- a/tests/test_future/test_standard_library.py +++ b/tests/test_future/test_standard_library.py @@ -9,7 +9,6 @@ import sys import tempfile -import os import copy import textwrap from subprocess import CalledProcessError @@ -448,13 +447,11 @@ def test_reload(self): reload has been moved to the imp module """ try: - import imp - imp.reload(imp) - self.assertTrue(True) + from importlib import reload except ImportError: - import importlib - importlib.reload(importlib) - self.assertTrue(True) + from imp import reload + reload(sys) + self.assertTrue(True) def test_install_aliases(self): """ diff --git a/tests/test_past/test_builtins.py b/tests/test_past/test_builtins.py index d16978ee..98d3c8c1 100644 --- a/tests/test_past/test_builtins.py +++ b/tests/test_past/test_builtins.py @@ -6,7 +6,6 @@ from past.builtins import apply, cmp, execfile, intern, raw_input from past.builtins import reduce, reload, unichr, unicode, xrange -from future import standard_library from future.backports.test.support import TESTFN #, run_unittest import tempfile import os diff --git a/tests/test_past/test_translation.py b/tests/test_past/test_translation.py index 2b442d96..58d8d000 100644 --- a/tests/test_past/test_translation.py +++ b/tests/test_past/test_translation.py @@ -7,18 +7,18 @@ import os import textwrap import sys -import pprint import tempfile -import os import io -from subprocess import Popen, PIPE - -from past import utils -from past.builtins import basestring, str as oldstr, unicode +from future.tests.base import ( + expectedFailurePY3, + unittest, +) +from past.builtins import ( + str as oldstr, + unicode, +) from past.translation import install_hooks, remove_hooks, common_substring -from future.tests.base import (unittest, CodeHandler, skip26, - expectedFailurePY3, expectedFailurePY26) class TestTranslate(unittest.TestCase): @@ -58,8 +58,8 @@ def write_and_import(self, code, modulename='mymodule'): sys.path.insert(0, self.tempdir) try: module = __import__(modulename) - except SyntaxError: - print('Bombed!') + except SyntaxError as e: + print('Import failed: %s' % e) else: print('Succeeded!') finally: From 74d834334d1f97cf5f874d601e88edae978da578 Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Fri, 19 Jan 2024 17:51:46 +0000 Subject: [PATCH 403/425] Test fixes --- src/future/moves/test/support.py | 9 +++++++++ tests/test_future/test_builtins.py | 7 +++++-- tests/test_future/test_urllib2.py | 6 +----- tests/test_future/test_urllib_toplevel.py | 7 +++++-- tests/test_future/test_utils.py | 2 +- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/future/moves/test/support.py b/src/future/moves/test/support.py index e9aa0f48..f70c9d7d 100644 --- a/src/future/moves/test/support.py +++ b/src/future/moves/test/support.py @@ -1,9 +1,18 @@ 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(): diff --git a/tests/test_future/test_builtins.py b/tests/test_future/test_builtins.py index 3921a608..0bf2a520 100644 --- a/tests/test_future/test_builtins.py +++ b/tests/test_future/test_builtins.py @@ -1303,8 +1303,11 @@ def test_pow(self): self.assertAlmostEqual(pow(-1, 0.5), 1j) self.assertAlmostEqual(pow(-1, 1/3), 0.5 + 0.8660254037844386j) - # Raises TypeError in Python < v3.5, ValueError in v3.5: - self.assertRaises((TypeError, ValueError), pow, -1, -2, 3) + # Raises TypeError in Python < v3.5, ValueError in v3.5-v3.7: + if sys.version_info[:2] < (3, 8): + self.assertRaises((TypeError, ValueError), pow, -1, -2, 3) + else: + self.assertEqual(pow(-1, -2, 3), 1) self.assertRaises(ValueError, pow, 1, 2, 0) self.assertRaises(TypeError, pow) diff --git a/tests/test_future/test_urllib2.py b/tests/test_future/test_urllib2.py index 2d69dad1..87bc585a 100644 --- a/tests/test_future/test_urllib2.py +++ b/tests/test_future/test_urllib2.py @@ -691,10 +691,6 @@ def connect_ftp(self, user, passwd, host, port, dirs, h = NullFTPHandler(data) h.parent = MockOpener() - # MIME guessing works in Python 3.8! - guessed_mime = None - if sys.hexversion >= 0x03080000: - guessed_mime = "image/gif" for url, host, port, user, passwd, type_, dirs, filename, mimetype in [ ("ftp://localhost/foo/bar/baz.html", "localhost", ftplib.FTP_PORT, "", "", "I", @@ -713,7 +709,7 @@ def connect_ftp(self, user, passwd, host, port, dirs, ["foo", "bar"], "", None), ("ftp://localhost/baz.gif;type=a", "localhost", ftplib.FTP_PORT, "", "", "A", - [], "baz.gif", guessed_mime), + [], "baz.gif", None), ]: req = Request(url) req.timeout = None diff --git a/tests/test_future/test_urllib_toplevel.py b/tests/test_future/test_urllib_toplevel.py index 11e77201..93364e6d 100644 --- a/tests/test_future/test_urllib_toplevel.py +++ b/tests/test_future/test_urllib_toplevel.py @@ -781,8 +781,11 @@ def test_unquoting(self): "%s" % result) self.assertRaises((TypeError, AttributeError), urllib_parse.unquote, None) self.assertRaises((TypeError, AttributeError), urllib_parse.unquote, ()) - with support.check_warnings(('', BytesWarning), quiet=True): - self.assertRaises((TypeError, AttributeError), urllib_parse.unquote, bytes(b'')) + if sys.version_info[:2] < (3, 9): + with support.check_warnings(('', BytesWarning), quiet=True): + self.assertRaises((TypeError, AttributeError), urllib_parse.unquote, bytes(b'')) + else: + self.assertEqual(urllib_parse.unquote(bytes(b"")), "") def test_unquoting_badpercent(self): # Test unquoting on bad percent-escapes diff --git a/tests/test_future/test_utils.py b/tests/test_future/test_utils.py index 46f5196c..a496bcaf 100644 --- a/tests/test_future/test_utils.py +++ b/tests/test_future/test_utils.py @@ -150,7 +150,7 @@ class Timeout(BaseException): self.assertRaises(Timeout, raise_, Timeout()) if PY3: - self.assertRaisesRegexp( + self.assertRaisesRegex( TypeError, "class must derive from BaseException", raise_, int) From 9ef05b386ce45dd40d2dab5915aec3ed78d81ed9 Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Mon, 22 Jan 2024 15:50:50 +0000 Subject: [PATCH 404/425] Add Python 3.8-3.12 classifiers --- setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.py b/setup.py index 41b0df96..9c62269b 100755 --- a/setup.py +++ b/setup.py @@ -103,6 +103,11 @@ "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", From 343f952f3a1eb6df902c201581d36268cf144a93 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 21 Feb 2024 13:14:03 +1100 Subject: [PATCH 405/425] Update instructions for uploading docs --- docs/other/upload_future_docs.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/other/upload_future_docs.sh b/docs/other/upload_future_docs.sh index d5c272d2..c5201f90 100644 --- a/docs/other/upload_future_docs.sh +++ b/docs/other/upload_future_docs.sh @@ -3,14 +3,14 @@ git checkout v0.16.0 # or whatever rm -Rf docs/build/ cd docs; make html -cp cheatsheet.pdf /shared/ +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 * -scp /shared/python-future-html-docs.zip python-future.org: -scp /shared/cheatsheet.pdf python-future.org: +scp ~/shared/python-future-html-docs.zip python-future.org: +scp ~/shared/cheatsheet.pdf python-future.org: ssh python-future.org From 8b930f5725fb1f4e9947c2364511c9abd528a33c Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 21 Feb 2024 13:59:30 +1100 Subject: [PATCH 406/425] Update whatsnew.rst --- docs/whatsnew.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 9018fdfe..a86c9bfd 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -3,11 +3,12 @@ What's New ********** -What's new in version 0.18.4 (2023-10-10) +What's new in version 0.18.4 (2024-02-21) ========================================= This is a minor bug-fix release containing a number of fixes: - Fix for Python 3.12's removal of the imp module +- Small updates to the docs What's new in version 0.18.3 (2023-01-13) ========================================= From 3f96b16ca5f220e07e0f90aa2480ac3cbce44e2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 03:16:14 +0000 Subject: [PATCH 407/425] Bump setuptools from 0.18.2 to 65.5.1 in /docs Bumps [setuptools](https://github.com/pypa/setuptools) from 0.18.2 to 65.5.1. - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/commits/v65.5.1) --- updated-dependencies: - dependency-name: setuptools dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 0f06b13f..de74d8b2 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,3 @@ sphinx==3.2.1 sphinx_bootstrap_theme==0.7.1 -setuptools==0.18.2 +setuptools==65.5.1 From d6785dd102876eecbbf5ae8b4ff47bc66b14fba6 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 21 Feb 2024 14:45:01 +1100 Subject: [PATCH 408/425] Add more meaningful comments about Python deprecations Thanks to Bruno Alla (@browniebroke) --- src/future/moves/_dummy_thread.py | 2 ++ tests/test_future/test_builtins.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/future/moves/_dummy_thread.py b/src/future/moves/_dummy_thread.py index e5dca348..96cf99e1 100644 --- a/src/future/moves/_dummy_thread.py +++ b/src/future/moves/_dummy_thread.py @@ -2,6 +2,8 @@ from future.utils import PY3 if PY3: + # _dummy_thread and dummy_threading modules were both deprecated in + # Python 3.7 and removed in Python 3.9 try: from _dummy_thread import * except ImportError: diff --git a/tests/test_future/test_builtins.py b/tests/test_future/test_builtins.py index d4e4f977..0da3fc2d 100644 --- a/tests/test_future/test_builtins.py +++ b/tests/test_future/test_builtins.py @@ -1307,6 +1307,9 @@ def test_pow(self): if sys.version_info[:2] < (3, 8): self.assertRaises((TypeError, ValueError), pow, -1, -2, 3) else: + # Changed in version 3.8: For int operands, the three-argument form + # of pow now allows the second argument to be negative, permitting + # computation of modular inverses. self.assertEqual(pow(-1, -2, 3), 1) self.assertRaises(ValueError, pow, 1, 2, 0) From 8cd11e8cdb8e096989a384787c739fa5c82739d3 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 21 Feb 2024 15:00:32 +1100 Subject: [PATCH 409/425] Move CI to GitHub Actions & split Python versions into jobs (#603) Thanks to Bruno Alla (@browniebroke) --- .github/workflows/ci.yml | 46 +++++++++++++++++++++++++++++++ .travis.yml | 11 -------- 2.6.Dockerfile | 26 +++++++++++++++++ Dockerfile | 6 +++- README.rst | 4 +-- build.sh | 18 ------------ lint.sh | 4 --- setup.sh | 21 -------------- src/future/moves/_dummy_thread.py | 10 +++---- test.sh | 18 ++++++++++++ 10 files changed, 102 insertions(+), 62 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml create mode 100644 2.6.Dockerfile delete mode 100755 build.sh delete mode 100755 lint.sh delete mode 100755 setup.sh create mode 100755 test.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..fb7f5064 --- /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@v3 + - 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 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3fe6a983..00000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: generic - -services: - - docker - -before_script: - - docker pull jmadler/python-future-builder:latest - -script: - - ./build.sh - - ./lint.sh 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 index cf9ffd3c..c859757f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,7 @@ -FROM quay.io/pypa/manylinux1_x86_64 +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/README.rst b/README.rst index 1ab43e53..cce7605c 100644 --- a/README.rst +++ b/README.rst @@ -3,8 +3,8 @@ Overview: Easy, clean, reliable Python 2/3 compatibility ======================================================== -.. image:: https://travis-ci.org/PythonCharmers/python-future.svg?branch=master - :target: https://travis-ci.org/PythonCharmers/python-future +.. image:: https://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 .. image:: https://readthedocs.org/projects/python-future/badge/?version=latest :target: https://python-future.readthedocs.io/en/latest/?badge=latest diff --git a/build.sh b/build.sh deleted file mode 100755 index d4b92d9a..00000000 --- a/build.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh -# XXX: TODO: we should make this include -e once tests pass -set -xu - -DOCKER_IMAGE=jmadler/python-future-builder -# XXX: TODO: Perhaps this version shouldn't be hardcoded -version=0.18.4 - -docker build . -t "$DOCKER_IMAGE" -#docker push "$DOCKER_IMAGE:latest" - -for i in cp27-cp27m cp35-cp35m cp36-cp36m cp37-cp37m cp38-cp38 cp39-cp39; do - docker run -ti -v "$(realpath dist)":/root/python-future/dist "$DOCKER_IMAGE" /root/python-future/setup.sh "$version" $(basename $i) -done - -python setup.py sdist -python setup.py clean -echo You may now run: "twine upload dist/*" diff --git a/lint.sh b/lint.sh deleted file mode 100755 index b3c41cd4..00000000 --- a/lint.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -# TODO: Run under Python 2.7 and 3.7 -flake8 . --count --exit-zero --select=E901,E999,F821,F822,F823 --show-source --statistics || true -flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics || true diff --git a/setup.sh b/setup.sh deleted file mode 100755 index fa89d431..00000000 --- a/setup.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh - -set -ex - -version=$1 -pytag=$2 - -if [ "$pytag" = 'py33' ]; then - pip3 install virtualenv==16.2.0 -fi - -. /root/"$pytag"/bin/activate - -if [ "$pytag" = 'py26' ]; then - pip install importlib -fi -pip install pytest unittest2 -python setup.py bdist_wheel --python-tag="$pytag" -pip install "dist/future-$version-$pytag-none-any.whl" -# Ignore test failures for now -pytest tests/ || true diff --git a/src/future/moves/_dummy_thread.py b/src/future/moves/_dummy_thread.py index 96cf99e1..6633f42e 100644 --- a/src/future/moves/_dummy_thread.py +++ b/src/future/moves/_dummy_thread.py @@ -1,13 +1,13 @@ from __future__ import absolute_import -from future.utils import PY3 +from future.utils import PY3, PY39_PLUS -if PY3: + +if PY39_PLUS: # _dummy_thread and dummy_threading modules were both deprecated in # Python 3.7 and removed in Python 3.9 - try: + from _thread import * +elif PY3: from _dummy_thread import * - except ImportError: - from _thread import * else: __future_module__ = True from dummy_thread 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/ From e8806ebe3b690c06dadf1dc6c18fc85ef6bdf311 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 21 Feb 2024 15:37:42 +1100 Subject: [PATCH 410/425] Update Trove classifier to "Mature" --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 961e1b8c..13b0f435 100755 --- a/setup.py +++ b/setup.py @@ -105,7 +105,7 @@ "Programming Language :: Python :: 3.12", "License :: OSI Approved", "License :: OSI Approved :: MIT License", - "Development Status :: 4 - Beta", + "Development Status :: 6 - Mature", "Intended Audience :: Developers", ] From 87f7ed425814e40e010bae7cc860480504850cbb Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 21 Feb 2024 15:41:20 +1100 Subject: [PATCH 411/425] Remove references to dead `python-porting` mailing list (issue #615) --- docs/faq.rst | 3 +-- docs/quickstart.rst | 4 +--- docs/stdlib_incompatibilities.rst | 3 +-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index 9b1eab00..046c0167 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -287,8 +287,7 @@ Support Is there a mailing list? ------------------------ -Yes, please ask any questions on the `python-porting -`_ mailing list. +There was (`python-porting`), but it's now dead. .. _contributing: diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 6042e059..8461a1a2 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -138,9 +138,7 @@ code:: 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/stdlib_incompatibilities.rst b/docs/stdlib_incompatibilities.rst index 5f2217d2..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: From a7dd2cb2e9d24a77c8a448bce4ba09b4e91099e8 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 21 Feb 2024 15:45:48 +1100 Subject: [PATCH 412/425] Fix pow() with negative newint (issue #568) --- src/future/types/newint.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/future/types/newint.py b/src/future/types/newint.py index 2c86ce18..ebc5715e 100644 --- a/src/future/types/newint.py +++ b/src/future/types/newint.py @@ -223,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): From 3bc1977d32e9f6c7f2cf1a0fd022959325e8e454 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 21 Feb 2024 06:00:43 +0100 Subject: [PATCH 413/425] Upgrade to actions/checkout@v4 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb7f5064..4458e9d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - if: ${{ matrix.versions.python != '2.6' }} run: | docker build \ @@ -43,4 +43,4 @@ jobs: docker run \ -e PYTHON_VERSION=${{ matrix.versions.python }} \ jmadler/python-future-builder:${{ matrix.versions.python }} \ - /root/python-future/test.sh \ No newline at end of file + /root/python-future/test.sh From 6eef83de7db1086633c98ba2e104de1b471944d0 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 21 Feb 2024 17:03:50 +1100 Subject: [PATCH 414/425] Update copyright dates, add end-of-life notice --- LICENSE.txt | 2 +- README.rst | 28 ++++++++--- docs/compatible_idioms.rst | 2 +- docs/credits.rst | 50 +++++++++++++++---- .../Writing Python 2-3 compatible code.ipynb | 2 +- docs/whatsnew.rst | 8 +-- futurize.py | 2 +- pasteurize.py | 2 +- src/future/__init__.py | 7 ++- src/past/__init__.py | 4 +- 10 files changed, 76 insertions(+), 31 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 4c904dba..275cafd3 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2013-2019 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/README.rst b/README.rst index cce7605c..65999990 100644 --- a/README.rst +++ b/README.rst @@ -19,9 +19,21 @@ 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 2/3 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: @@ -223,6 +235,9 @@ 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. @@ -304,12 +319,11 @@ Licensing :Author: Ed Schofield, Jordan M. Adler, et al -:Copyright: 2013-2019 Python Charmers Pty Ltd, Australia. +:Copyright: 2013-2024 Python Charmers, Australia. -:Sponsors: Python Charmers Pty Ltd, Australia, and Python Charmers Pte - Ltd, Singapore. http://pythoncharmers.com +:Sponsors: Python Charmers: https://pythoncharmers.com - Pinterest https://opensource.pinterest.com/ + Pinterest https://opensource.pinterest.com :Licence: MIT. See ``LICENSE.txt`` or `here `_. diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index b0cb05a3..f7087699 100644 --- a/docs/compatible_idioms.rst +++ b/docs/compatible_idioms.rst @@ -3,7 +3,7 @@ Cheat Sheet: Writing Python 2-3 compatible code =============================================== -- **Copyright (c):** 2013-2019 Python Charmers Pty Ltd, Australia. +- **Copyright (c):** 2013-2024 Python Charmers, Australia. - **Author:** Ed Schofield. - **Licence:** Creative Commons Attribution. diff --git a/docs/credits.rst b/docs/credits.rst index 275e148e..4c029efd 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -8,7 +8,7 @@ Licence The software is distributed under an MIT licence. The text is as follows (from ``LICENSE.txt``):: - Copyright (c) 2013-2019 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 @@ -32,39 +32,59 @@ The software is distributed under an MIT licence. The text is as follows Sponsors -------- -Python Charmers Pty Ltd, Australia, and Python Charmers Pte Ltd, Singapore. -http://pythoncharmers.com -Pinterest https://opensource.pinterest.com/ +Python Charmers: https://pythoncharmers.com .. _authors: -Maintainer ----------- -Python-Future is currently maintained by Jordan M. Adler . - -Authors +Author ------- -Python-Future is largely written by Ed Schofield with the help of various contributors: + +Python-Future was largely written by Ed Schofield . + +Maintainers +----------- + +The project is no longer being actively maintained. Like Python 2, it should be +considered end-of-life. + +Past maintainers include: + +- 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 @@ -84,22 +104,31 @@ Python-Future is largely written by Ed Schofield with th - 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 - Tim Tröndle - Brad Walker +- Liuyang Wan - Andrew Wason - Jeff Widman - Dan Yeaw @@ -111,6 +140,7 @@ Python-Future is largely written by Ed Schofield with th - urain39 (GitHub user) - 9seconds (GitHub user) - Varriount (GitHub user) +- zihzihtw (GitHub user) Suggestions and Feedback ~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/notebooks/Writing Python 2-3 compatible code.ipynb b/docs/notebooks/Writing Python 2-3 compatible code.ipynb index 0f585d29..e2af9c7e 100644 --- a/docs/notebooks/Writing Python 2-3 compatible code.ipynb +++ b/docs/notebooks/Writing Python 2-3 compatible code.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "- **Copyright (c):** 2013-2019 Python Charmers Pty Ltd, Australia.\n", + "- **Copyright (c):** 2013-2024 Python Charmers, Australia.\n", "- **Author:** Ed Schofield.\n", "- **Licence:** Creative Commons Attribution.\n", "\n", diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index cc7fc242..1514bf40 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -3,12 +3,14 @@ What's New ********** -What's new in version 0.18.4 (2024-02-21) -========================================= -This is a minor bug-fix release containing a number of fixes: +What's new in version 0.8.4 (2024-02-21) +======================================== +This is a minor bug-fix release containing a small number of fixes: - Fix for Python 3.12's removal of the imp module - Small updates to the docs +- Add a note to the docs that the package, like Python 2, is done. +- Fix pasteurize: NameError: name 'unicode' is not defined (de68c10) What's new in version 0.18.3 (2023-01-13) ========================================= diff --git a/futurize.py b/futurize.py index cb446ab2..09feaf59 100755 --- a/futurize.py +++ b/futurize.py @@ -13,7 +13,7 @@ Licensing --------- -Copyright 2013-2019 Python Charmers Pty Ltd, Australia. +Copyright 2013-2024 Python Charmers, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ diff --git a/pasteurize.py b/pasteurize.py index 2b98327c..658955f6 100755 --- a/pasteurize.py +++ b/pasteurize.py @@ -12,7 +12,7 @@ Licensing --------- -Copyright 2013-2019 Python Charmers Pty Ltd, Australia. +Copyright 2013-2024 Python Charmers, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ diff --git a/src/future/__init__.py b/src/future/__init__.py index 64b66f43..3bac3563 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -69,14 +69,13 @@ ------- :Author: Ed Schofield, Jordan M. Adler, et al -:Sponsor: Python Charmers Pty Ltd, Australia, and Python Charmers Pte - Ltd, Singapore. http://pythoncharmers.com +:Sponsor: Python Charmers: https://pythoncharmers.com :Others: See docs/credits.rst or http://python-future.org/credits.html Licensing --------- -Copyright 2013-2019 Python Charmers Pty Ltd, Australia. +Copyright 2013-2024 Python Charmers, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ @@ -84,7 +83,7 @@ __title__ = 'future' __author__ = 'Ed Schofield' __license__ = 'MIT' -__copyright__ = 'Copyright 2013-2019 Python Charmers Pty Ltd' +__copyright__ = 'Copyright 2013-2024 Python Charmers (https://pythoncharmers.com)' __ver_major__ = 0 __ver_minor__ = 18 __ver_patch__ = 4 diff --git a/src/past/__init__.py b/src/past/__init__.py index 14713039..54619e0a 100644 --- a/src/past/__init__.py +++ b/src/past/__init__.py @@ -75,12 +75,12 @@ ------- :Author: Ed Schofield, Jordan M. Adler, et al -:Sponsor: Python Charmers Pty Ltd, Australia: http://pythoncharmers.com +:Sponsor: Python Charmers: https://pythoncharmers.com Licensing --------- -Copyright 2013-2019 Python Charmers Pty Ltd, Australia. +Copyright 2013-2024 Python Charmers, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ From 9f33c2ca45c96a3991cb7573d2c038eb121b1e1e Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 21 Feb 2024 17:06:38 +1100 Subject: [PATCH 415/425] Bump version to 1.0.0 --- docs/whatsnew.rst | 4 ++-- src/future/__init__.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 1514bf40..b92de81b 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -3,8 +3,8 @@ What's New ********** -What's new in version 0.8.4 (2024-02-21) -======================================== +What's new in version 1.0.0 (2024-02-21) +========================================= This is a minor bug-fix release containing a small number of fixes: - Fix for Python 3.12's removal of the imp module diff --git a/src/future/__init__.py b/src/future/__init__.py index 3bac3563..ec2623f7 100644 --- a/src/future/__init__.py +++ b/src/future/__init__.py @@ -84,9 +84,9 @@ __author__ = 'Ed Schofield' __license__ = 'MIT' __copyright__ = 'Copyright 2013-2024 Python Charmers (https://pythoncharmers.com)' -__ver_major__ = 0 -__ver_minor__ = 18 -__ver_patch__ = 4 +__ver_major__ = 1 +__ver_minor__ = 0 +__ver_patch__ = 0 __ver_sub__ = '' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) From 70b36a8a514e45a2582589991be4a2c1fd1944b7 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 21 Feb 2024 17:09:10 +1100 Subject: [PATCH 416/425] Disable Python 2.6 CI tests for now --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb7f5064..e436f3cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: versions: - - python: "2.6" + # - python: "2.6" - python: "2.7" - python: "3.3" - python: "3.4" @@ -43,4 +43,4 @@ jobs: docker run \ -e PYTHON_VERSION=${{ matrix.versions.python }} \ jmadler/python-future-builder:${{ matrix.versions.python }} \ - /root/python-future/test.sh \ No newline at end of file + /root/python-future/test.sh From f4a1f0406b47e8ef91838fcff9d679ee6fe186d9 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Wed, 21 Feb 2024 17:19:55 +1100 Subject: [PATCH 417/425] Docs: replace http links with https --- README.rst | 14 +++++++------- docs/_templates/sidebarintro.html | 2 +- docs/compatible_idioms.rst | 8 ++++---- docs/faq.rst | 4 ++-- .../Writing Python 2-3 compatible code.ipynb | 10 +++++----- src/future/__init__.py | 6 +++--- src/future/builtins/__init__.py | 2 +- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/README.rst b/README.rst index 65999990..ca533976 100644 --- a/README.rst +++ b/README.rst @@ -6,9 +6,6 @@ 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 -.. image:: https://readthedocs.org/projects/python-future/badge/?version=latest - :target: https://python-future.readthedocs.io/en/latest/?badge=latest - ``python-future`` is the missing compatibility layer between Python 2 and Python 3. It allows you to use a single, clean Python 3.x-compatible codebase to support both Python 2 and Python 3 with minimal overhead. @@ -325,16 +322,19 @@ Licensing Pinterest https://opensource.pinterest.com -:Licence: MIT. See ``LICENSE.txt`` or `here `_. +:Licence: MIT. See ``LICENSE.txt`` or `here `_. -:Other credits: See `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/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index e4433221..1e1a512f 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

Easy, clean, reliable Python 2/3 compatibility

- Table of Contents + Table of Contents