diff --git a/docs/compatible_idioms.rst b/docs/compatible_idioms.rst index 0402b915..9fa0a19e 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", diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst index 2450d23e..46b49bbf 100644 --- a/docs/whatsnew.rst +++ b/docs/whatsnew.rst @@ -5,7 +5,15 @@ 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.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) ========================================== This release removes the ``configparser`` package as an alias for @@ -26,18 +34,18 @@ 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) +- 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) diff --git a/src/future/standard_library/__init__.py b/src/future/standard_library/__init__.py index 05ac4ec7..cff02f95 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'), 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 48fd85d6..3ac5d2d7 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):