From 0b25d3bdd61d126cc32f737ed7f8f2df5ee89cd0 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 10 Jan 2014 01:14:20 +1100 Subject: [PATCH 0001/1190] Initial commit of ``past`` package --- docs/changelog.rst | 45 +- docs/overview.rst | 30 +- docs/quickstart.rst | 13 +- future/__init__.py | 1 - past/__init__.py | 93 ++++ past/builtins/__init__.py | 24 + past/builtins/noniterators.py | 59 +++ past/builtins/types/__init__.py | 15 + past/builtins/types/basestring.py | 40 ++ past/builtins/types/olddict.py | 97 ++++ past/builtins/types/oldstr.py | 125 +++++ past/tests/__init__.py | 0 past/tests/test_basestring.py | 24 + past/tests/test_noniterators.py | 34 ++ past/tests/test_olddict.py | 792 ++++++++++++++++++++++++++++++ past/tests/test_oldstr.py | 44 ++ setup.py | 4 + 17 files changed, 1409 insertions(+), 31 deletions(-) create mode 100644 past/__init__.py create mode 100644 past/builtins/__init__.py create mode 100644 past/builtins/noniterators.py create mode 100644 past/builtins/types/__init__.py create mode 100644 past/builtins/types/basestring.py create mode 100644 past/builtins/types/olddict.py create mode 100644 past/builtins/types/oldstr.py create mode 100644 past/tests/__init__.py create mode 100644 past/tests/test_basestring.py create mode 100644 past/tests/test_noniterators.py create mode 100644 past/tests/test_olddict.py create mode 100644 past/tests/test_oldstr.py diff --git a/docs/changelog.rst b/docs/changelog.rst index b96890eb..c55b6deb 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,15 +7,40 @@ What's new What's new in version 0.11 ========================== -More robust implementation of standard_library hooks ----------------------------------------------------- +``past`` package +---------------- + +The python-future project now provides a ``past`` package in addition to the +``future`` package. The structure reflects that of ``future``, with +``past.builtins`` and ``past.utils``. + +The primary purpose of ``past`` is to ease module-by-module upgrades to +codebases from Python 2. It can also help with enabling Python 2 libraries to +support Python 3 without breaking the API they provide. (For example, user code +may expect these libraries to pass them Python 2's 8-bit strings, rather than +Python 3's ``bytes`` object.) + +Currently ``past.builtins`` provides forward-ports of Python 2's ``str`` and +``dict`` objects, ``basestring``, and list-producing iterator functions. + + +No import hooks by default with ``future.standard_library`` +----------------------------------------------------------- ``future.standard_library`` now no longer installs import hooks by default. These were bleeding into surrounding code, causing incompatibilities with modules like ``requests`` (issue #19). -Now ``future.standard_library`` provides the context manager -``enable_hooks()``. Use it as follows:: +*Note*: this is a backward-incompatible change. + +This feature may be resurrected in a later version if a safe implementation can be found. + + +New context manager for standard_library hooks +---------------------------------------------- + +``future.standard_library`` provides a new context manager called +``enable_hooks``. Use it as follows:: >>> from future import standard_library >>> with standard_library.enable_hooks(): @@ -25,16 +50,8 @@ Now ``future.standard_library`` provides the context manager >>> import requests >>> # etc. -If you prefer, the following imports are also available directly:: - - >>> from future.standard_library import queue - >>> from future.standard_library import socketserver - >>> from future.standard_library.http.client import HTTPConnection - - As usual, this has no effect on Python 3. -*Note*: this is a backward-incompatible change. Simpler imports --------------- @@ -58,8 +75,8 @@ for raising exceptions. Thanks to Joel Tratner for the contribution of these. -Deprecated ``isinstance`` replacement removed ---------------------------------------------- +Deprecated ``isinstance`` removed +--------------------------------- ``future`` v0.8.2 briefly introduced a replacement for the ``isinstance`` builtin. This was then removed and its use was deprecated as of v0.9.0. diff --git a/docs/overview.rst b/docs/overview.rst index 692ac483..6d9e0d61 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -3,26 +3,36 @@ Overview ======== -``future`` is the missing compatibility layer between Python 3 and Python -2. It allows you to maintain a single, clean Python 3.x-compatible -codebase with minimal cruft and run it easily on Python 2 mostly unchanged. +``python-future`` is the missing compatibility layer between Python 3 and Python +2. -``future`` comes with ``futurize``, a script that helps you to transition -to supporting both Python 2 and 3 in a single codebase, module by module. +It allows you to maintain a single, clean Python 3.x-compatible codebase with +minimal cruft and run it easily on Python 2 mostly unchanged. + +It also provides a tools designed to help with module-by-module upgrades (e.g. +of libraries) from Python 2 to a compatible 2/3 codebase without breaking the +existing API. + +``python-future`` comes with ``futurize``, a customized 2to3-based script that +helps you to transition to supporting both Python 2 and 3 in a single codebase, +module by module, from either Python 2 or Python 3. .. _features: Features -------- -- provides backports and remappings for 16 builtins with different - semantics on Py3 versus Py2 -- provides backports and remappings from the Py3 standard library +- ``future`` package provides backports and remappings for 16 builtins with + different semantics on Py3 versus Py2 +- ``future`` package provides backports and remappings from the Py3 standard + library +- ``past`` package provides forward-ports of Python 2 types and resurrects + some Python 2 builtins (to aid with per-module code migrations) - 300+ unit tests - ``futurize`` script based on ``2to3``, ``3to2`` and parts of - ``python-modernize`` for automatic conversion from either Py2 or Py3 to a + ``python-modernize``, for automatic conversion from either Py2 or Py3 to a clean single-source codebase compatible with Python 2.6+ and Python 3.3+. -- a consistent set of utility functions and decorators selected from +- a comprehensive set of utility functions and decorators selected from Py2/3 compatibility interfaces from projects like ``six``, ``IPython``, ``Jinja2``, ``Django``, and ``Pandas``. diff --git a/docs/quickstart.rst b/docs/quickstart.rst index d9d6d847..65d8bbfd 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -47,6 +47,7 @@ module:: from __future__ import unicode_literals from future.builtins import open from future.builtins import str + from past.utils import div # etc., as needed and converts a few Python 3-only constructs to a form compatible with @@ -124,12 +125,12 @@ Utilities --------- :mod:`future` also provides some useful functions and decorators to ease -backward compatibility with Py2 in the :mod:`future.utils` module. These -are a selection of the most useful functions from ``six`` and various -home-grown Py2/3 compatibility modules from popular Python projects, such as -Jinja2, Pandas, IPython, and Django. The goal is to consolidate these in one -place, tested and documented, obviating the need for every project to repeat -this work. +backward compatibility with Py2 in the :mod:`future.utils` and +:mod:`past.utils` modules. These are a selection of the most useful functions +from ``six`` and various home-grown Py2/3 compatibility modules from popular +Python projects, such as Jinja2, Pandas, IPython, and Django. The goal is to +consolidate these in one place, tested and documented, obviating the need for +every project to repeat this work. Examples:: diff --git a/future/__init__.py b/future/__init__.py index 7f599b39..6c3787cd 100644 --- a/future/__init__.py +++ b/future/__init__.py @@ -83,7 +83,6 @@ """ -from future import standard_library, utils from future.builtins import * __title__ = 'future' diff --git a/past/__init__.py b/past/__init__.py new file mode 100644 index 00000000..2f3d03fc --- /dev/null +++ b/past/__init__.py @@ -0,0 +1,93 @@ +# coding=utf-8 +""" +past: an implementation of Python 2 constructs in Python 3 +========================================================== + +``past`` is a package to aid with Python 2/3 compatibility. Whereas ``future`` +contains backports of Python 3 constructs to Python 2, ``past`` provides +implementations of some Python 2 constructs in Python 3. It is intended to be +used sparingly, primarily for libraries: + +- as a step in porting a Python 2 codebase to Python 3 (e.g. with the ``futurize`` script) +- to provide Python 3 support for previously Python 2-only libraries with the + same APIs as on Python 2 -- particularly with regard to 8-bit strings (the + ``past.builtins.str`` type). +- to aid in providing minimal-effort Python 3 support for applications using + libraries that do not yet wish to upgrade their code properly to Python 3, or + wish to upgrade it gradually to Python 3 style. + + +Here are some examples that run identically on Python 3 and 2:: + + >>> from past.builtins import str as py2str + + >>> confucius = py2str(b'\xe5\xad\x94\xe5\xad\x90') + >>> # This now behaves like a Py2 byte-string on both Py2 and Py3. + >>> # For example, indexing returns a Python 2-like string object, not + >>> # an integer: + >>> confucius[0] + '\xe5' + >>> type(confucius[0]) + + + >>> # The div() function behaves like Python 2's / operator + >>> # without "from __future__ import division" + >>> from past.utils import div + >>> div(3, 2) # like 3/2 in Py2 + 0 + >>> div(3, 2.0) # like 3/2.0 in Py2 + 1.5 + + >>> # List-producing versions of range, reduce, map, filter + >>> from past.builtins import range, reduce + >>> range(10) + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) + 15 + + >>> # Other functions removed in Python 3 are resurrected ... + >>> from past.builtins import execfile + >>> execfile('myfile.py') + + >>> from past.builtins import raw_input + >>> name = raw_input('What is your name? ') + What is your name? [cursor] + + >>> from past.builtins import reload + >>> reload(mymodule) # equivalent to imp.reload(mymodule) in Python 3 + + >>> from past.builtins import xrange + >>> for i in xrange(10): + ... pass + + + +Credits +------- + +:Author: Ed Schofield +:Sponsor: Python Charmers Pty Ltd, Australia, and Python Charmers Pte + Ltd, Singapore. http://pythoncharmers.com + + +Licensing +--------- +Copyright 2013-2014 Python Charmers Pty Ltd, Australia. +The software is distributed under an MIT licence. See LICENSE.txt. + +""" + +# from past.builtins import * + +__title__ = 'past' +__author__ = 'Ed Schofield' +__license__ = 'MIT' +__copyright__ = 'Copyright 2014 Python Charmers Pty Ltd' +__ver_major__ = 0 +__ver_minor__ = 11 +__ver_patch__ = 0 +__ver_sub__ = '-dev' +__version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, + __ver_patch__, __ver_sub__) + + diff --git a/past/builtins/__init__.py b/past/builtins/__init__.py new file mode 100644 index 00000000..00650a3f --- /dev/null +++ b/past/builtins/__init__.py @@ -0,0 +1,24 @@ +from past.builtins.noniterators import (filter, map, range, zip) +# from past.builtins.misc import (ascii, chr, hex, input, oct, open, raw_input, unichr) +# from past.builtins.types import (basestring, dict, str, unicode) +from past.builtins.types import basestring, dict, str + +from future import utils + + +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', 'zip', + 'basestring', 'dict', 'str', + # 'ascii', 'chr', 'hex', 'input', 'oct', 'open', 'unichr', + # 'bytes', 'dict', 'int', 'range', 'round', 'str', 'super', + ] + +else: + # No namespace pollution on Py2 + __all__ = [] + + # TODO: add 'callable' for Py3.0 and Py3.1? diff --git a/past/builtins/noniterators.py b/past/builtins/noniterators.py new file mode 100644 index 00000000..4e5ef1f4 --- /dev/null +++ b/past/builtins/noniterators.py @@ -0,0 +1,59 @@ +""" +This module is designed to be used as follows:: + + from past.builtins.noniterators import filter, map, range, reduce, zip + +And then, for example:: + + assert isinstance(range(5), list) + +The list-producing functions this brings in are:: + +- ``filter`` +- ``map`` +- ``range`` +- ``reduce`` +- ``zip`` + +""" + +from __future__ import division, absolute_import, print_function + +import itertools +from future.utils import PY3 + +if PY3: + import builtins + + # list-producing versions of the major Python iterating functions + def oldfilter(*args, **kwargs): + return list(builtins.filter(*args, **kwargs)) + + def oldmap(*args, **kwargs): + return list(builtins.map(*args, **kwargs)) + + def oldrange(*args, **kwargs): + return list(builtins.range(*args, **kwargs)) + + # def reduce(*args, **kwargs): + # return list(reduce(*args, **kwargs)) + + def oldzip(*args, **kwargs): + return list(builtins.zip(*args, **kwargs)) + + filter = oldfilter + map = oldmap + range = oldrange + zip = oldzip + __all__ = ['filter', 'map', 'range', 'zip'] + +else: + import __builtin__ + # Python 2-builtin ranges produce lists + filter = __builtin__.filter + map = __builtin__.map + # reduce = __builtin__.reduce + range = __builtin__.range + zip = __builtin__.zip + __all__ = [] + diff --git a/past/builtins/types/__init__.py b/past/builtins/types/__init__.py new file mode 100644 index 00000000..d1b27efb --- /dev/null +++ b/past/builtins/types/__init__.py @@ -0,0 +1,15 @@ +from future import utils + +if utils.PY2: + import __builtin__ + basestring = __builtin__.basestring + dict = __builtin__.dict + str = __builtin__.str + __all__ = [] +else: + from .basestring import basestring + from .olddict import olddict as dict + from .oldstr import oldstr as str + # from .unicode import unicode + __all__ = ['basestring', 'dict', 'str'] + diff --git a/past/builtins/types/basestring.py b/past/builtins/types/basestring.py new file mode 100644 index 00000000..d2ae6d47 --- /dev/null +++ b/past/builtins/types/basestring.py @@ -0,0 +1,40 @@ +""" +An implementation of the basestring type for Python 3 + +Example use: + +>>> s = b'abc' +>>> assert isinstance(s, basestring) +>>> from past.builtins.types import str as oldstr +>>> s2 = oldstr(b'abc') +>>> assert isinstance(s2, basestring) + +""" + +import sys + +from future.utils import with_metaclass, PY2 + +if PY2: + str = unicode + +ver = sys.version_info[:2] + + +class BaseBaseString(type): + def __instancecheck__(cls, instance): + return isinstance(instance, (bytes, str)) + + def __subclasshook__(cls, thing): + # TODO: What should go here? + raise NotImplemented + + +class basestring(with_metaclass(BaseBaseString)): + """ + A minimal backport of the Python 2 basestring type to Py3 + """ + + +__all__ = ['basestring'] + diff --git a/past/builtins/types/olddict.py b/past/builtins/types/olddict.py new file mode 100644 index 00000000..58385ce0 --- /dev/null +++ b/past/builtins/types/olddict.py @@ -0,0 +1,97 @@ +""" +A dict subclass for Python 3 that behaves like Python 2's dict + +Example use: + +>>> from future.builtins import dict +>>> d1 = dict() # instead of {} for an empty dict +>>> d2 = dict(key1='value1', key2='value2') + +The keys, values and items methods now return lists on Python 3.x and there are +methods for iterkeys, itervalues, iteritems, and viewkeys etc. + +>>> for d in (d1, d2): +... assert isinstance(d.keys(), list) +... assert isinstance(d.values(), list) +... assert isinstance(d.items(), list) +""" + +import sys + +from future.utils import with_metaclass + + +_builtin_dict = dict +ver = sys.version_info[:2] + + +class BaseOldDict(type): + def __instancecheck__(cls, instance): + return isinstance(instance, _builtin_dict) + + +class olddict(with_metaclass(BaseOldDict, _builtin_dict)): + """ + A backport of the Python 3 dict object to Py2 + """ + iterkeys = _builtin_dict.keys + viewkeys = _builtin_dict.keys + + def keys(self): + return list(super().keys()) + + itervalues = _builtin_dict.values + viewvalues = _builtin_dict.values + + def values(self): + return list(super().values()) + + iteritems = _builtin_dict.items + viewitems = _builtin_dict.items + + def items(self): + return list(super().items()) + + def has_key(self, k): + """ + D.has_key(k) -> True if D has a key k, else False + """ + return k in self + + # def __new__(cls, *args, **kwargs): + # """ + # dict() -> new empty dictionary + # dict(mapping) -> new dictionary initialized from a mapping object's + # (key, value) pairs + # dict(iterable) -> new dictionary initialized as if via: + # d = {} + # for k, v in iterable: + # d[k] = v + # dict(**kwargs) -> new dictionary initialized with the name=value pairs + # 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): + # # We use type() instead of the above because we're redefining + # # this to be True for all unicode string subclasses. Warning: + # # This may render newstr un-subclassable. + # elif type(args[0]) == olddict: + # return args[0] + # # elif isinstance(args[0], _builtin_dict): + # # value = args[0] + # else: + # value = args[0] + # return super(olddict, cls).__new__(cls, value) + + def __native__(self): + """ + Hook for the future.utils.native() function + """ + return super(oldbytes, self) + + +__all__ = ['olddict'] + diff --git a/past/builtins/types/oldstr.py b/past/builtins/types/oldstr.py new file mode 100644 index 00000000..7121c503 --- /dev/null +++ b/past/builtins/types/oldstr.py @@ -0,0 +1,125 @@ +""" +Pure-Python implementation of a Python 2-like str object for Python 3. +""" + +from collections import Iterable +from numbers import Integral + +from future.utils import PY2, with_metaclass +from future.builtins.backports import no, issubset + + +_builtin_bytes = bytes + + +class BaseOldStr(type): + def __instancecheck__(cls, instance): + return isinstance(instance, _builtin_bytes) + + +def collapse_double_backslashes(s): + """ + Takes and returns a native string. Example: + + >>> s = collapse_double_backslashes(r'abc\\def') # i.e. 'abc\\\\def' + >>> print(s) + 'abc\def' + """ + return s.replace(r'\\', '\\') + + +class oldstr(with_metaclass(BaseOldStr, _builtin_bytes)): + """ + A forward port of the Python 2 8-bit string object to Py3 + """ + # Python 2 strings have no __iter__ method: + @property + def __iter__(self): + raise AttributeError + + def __dir__(self): + return [thing for thing in dir(_builtin_bytes) if thing != '__iter__'] + + # def __new__(cls, *args, **kwargs): + # """ + # From the Py3 bytes docstring: + + # bytes(iterable_of_ints) -> bytes + # bytes(string, encoding[, errors]) -> bytes + # 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): + # # We use type() instead of the above because we're redefining + # # this to be True for all unicode string subclasses. Warning: + # # This may render newstr un-subclassable. + # elif type(args[0]) == newbytes: + # return args[0] + # elif isinstance(args[0], _builtin_bytes): + # value = args[0] + # elif isinstance(args[0], unicode): + # if 'encoding' not in kwargs: + # raise TypeError('unicode string argument without an encoding') + # ### + # # Was: value = args[0].encode(**kwargs) + # # Python 2.6 string encode() method doesn't take kwargs: + # # Use this instead: + # newargs = [kwargs['encoding']] + # 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? + # raise ValueError('unknown argument type') + # elif len(args[0]) > 0 and isinstance(args[0][0], Integral): + # # It's a list of integers + # value = b''.join([chr(x) for x in args[0]]) + # else: + # raise ValueError('item cannot be interpreted as an integer') + # elif isinstance(args[0], Integral): + # if args[0] < 0: + # raise ValueError('negative count') + # value = b'\x00' * args[0] + # else: + # value = args[0] + # return super(newbytes, cls).__new__(cls, value) + + def __repr__(self): + s = super().__repr__() # e.g. b'abc' + return s[1:] + + def __str__(self): + s = super().__str__() # e.g. "b'abc'" or "b'abc\\ndef' + # TODO: fix this: + return collapse_double_backslashes(s[2:-1]) # e.g. 'abc' or 'abc\ndef' + + def __getitem__(self, y): + if isinstance(y, Integral): + return super().__getitem__(slice(y, y+1)) + else: + return super().__getitem__(y) + + def __getslice__(self, *args): + return self.__getitem__(slice(*args)) + + def __contains__(self, key): + if isinstance(key, int): + return False + + def __native__(self): + return bytes(self) + + +__all__ = ['oldstr'] diff --git a/past/tests/__init__.py b/past/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/past/tests/test_basestring.py b/past/tests/test_basestring.py new file mode 100644 index 00000000..dc0c1fba --- /dev/null +++ b/past/tests/test_basestring.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +""" +Tests for the Py2-like class:`basestring` type. +""" + +from __future__ import absolute_import, unicode_literals, print_function +import os + +from future import utils +from future.tests.base import unittest +from past.builtins import basestring, str as oldstr + + +class TestBaseString(unittest.TestCase): + + def test_isinstance(self): + s = b'abc' + self.assertTrue(isinstance(s, basestring)) + s2 = oldstr(b'abc') + self.assertTrue(isinstance(s2, basestring)) + + +if __name__ == '__main__': + unittest.main() diff --git a/past/tests/test_noniterators.py b/past/tests/test_noniterators.py new file mode 100644 index 00000000..3d991997 --- /dev/null +++ b/past/tests/test_noniterators.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +""" +Tests for the Py2-like list-producing functions +""" + +from __future__ import absolute_import, unicode_literals, print_function +import os + +from future import utils +from future.tests.base import unittest +from past.builtins import filter, map, range, zip + + +class TestNonIterators(unittest.TestCase): + + def test_noniterators_produce_lists(self): + l = range(10) + self.assertTrue(isinstance(l, list)) + + l2 = zip(l, list('ABCDE')*2) + self.assertTrue(isinstance(l2, list)) + + double = lambda x: x*2 + l3 = map(double, l) + self.assertTrue(isinstance(l3, list)) + + is_odd = lambda x: x % 2 == 1 + l4 = filter(is_odd, range(10)) + self.assertEqual(l4, [1, 3, 5, 7, 9]) + self.assertTrue(isinstance(l4, list)) + + +if __name__ == '__main__': + unittest.main() diff --git a/past/tests/test_olddict.py b/past/tests/test_olddict.py new file mode 100644 index 00000000..a0005ad8 --- /dev/null +++ b/past/tests/test_olddict.py @@ -0,0 +1,792 @@ +# -*- coding: utf-8 -*- +""" +Tests for the resurrected Py2-like class:`dict` class. +""" + +from __future__ import absolute_import, unicode_literals, print_function +import os +import sys + +from future import utils +from future.tests.base import unittest +from past.builtins import dict + + +class TestOldDict(unittest.TestCase): + def setUp(self): + self.d1 = dict({'C': 1, 'B': 2, 'A': 3}) + self.d2 = dict(key1='value1', key2='value2') + + def test_dict_empty(self): + """ + dict() -> {} + """ + self.assertEqual(dict(), {}) + + def test_dict_eq(self): + d = self.d1 + self.assertEqual(dict(d), d) + + def test_dict_keys(self): + """ + The keys, values and items methods should now return lists on + Python 3.x. + """ + d = self.d1 + self.assertEqual(set(dict(d)), set(d)) + self.assertEqual(set(dict(d).keys()), set(d.keys())) + keys = dict(d).keys() + assert isinstance(keys, list) + key0 = keys[0] + + def test_dict_values(self): + d = self.d1 + self.assertEqual(set(dict(d).values()), set(d.values())) + values = dict(d).values() + assert isinstance(values, list) + val0 = values[0] + + def test_dict_items(self): + d = self.d1 + self.assertEqual(set(dict(d).items()), set(d.items())) + items = dict(d).items() + assert isinstance(items, list) + item0 = items[0] + + def test_isinstance_dict(self): + self.assertTrue(isinstance(self.d1, dict)) + + def test_dict_getitem(self): + d = dict({'C': 1, 'B': 2, 'A': 3}) + self.assertEqual(d['C'], 1) + self.assertEqual(d['B'], 2) + self.assertEqual(d['A'], 3) + with self.assertRaises(KeyError): + self.assertEqual(d['D']) + + def test_methods_produce_lists(self): + for d in (dict(self.d1), self.d2): + assert isinstance(d.keys(), list) + assert isinstance(d.values(), list) + assert isinstance(d.items(), list) + + @unittest.skipIf(sys.version_info[:2] == (2, 6), + 'set-like behaviour of dict methods is only available in Py2.7+') + def test_set_like_behaviour(self): + d1, d2 = self.d1, self.d2 + self.assertEqual(dict(d1).viewkeys() & dict(d2).viewkeys(), set()) + self.assertEqual(dict(d1).viewkeys() | dict(d2).viewkeys(), + {'key1', 'key2', 'C', 'B', 'A'}) + self.assertTrue(isinstance(d1.viewvalues() | d2.viewkeys(), set)) + self.assertTrue(isinstance(d1.viewitems() | d2.viewitems(), set)) + + with self.assertRaises(TypeError): + d1.values() | d2.values() + d1.keys() | d2.keys() + d1.items() | d2.items() + + @unittest.expectedFailure + def test_braces_create_newdict_object(self): + """ + It would nice if the {} dict syntax could be coaxed + into producing our new dict objects somehow ... + """ + d = self.d1 + self.assertTrue(type(d) == dict) + + +import unittest + +# import UserDict +import random, string +import gc, weakref + + +class Py2DictTest(unittest.TestCase): + """ + These are Py2/3-compatible ports of the unit tests from Python 2.7's + tests/test_dict.py + """ + + def test_constructor(self): + # calling built-in types without argument must return empty + self.assertEqual(dict(), {}) + self.assertIsNot(dict(), {}) + + def test_literal_constructor(self): + # check literal constructor for different sized dicts + # (to exercise the BUILD_MAP oparg). + for n in (0, 1, 6, 256, 400): + items = [(''.join(random.sample(string.ascii_letters, 8)), i) + for i in range(n)] + random.shuffle(items) + formatted_items = ('{!r}: {:d}'.format(k, v) for k, v in items) + dictliteral = '{' + ', '.join(formatted_items) + '}' + self.assertEqual(eval(dictliteral), dict(items)) + + def test_bool(self): + self.assertIs(not dict(), True) + self.assertTrue(dict({1: 2})) + self.assertIs(bool(dict({})), False) + self.assertIs(bool(dict({1: 2})), True) + + def test_keys(self): + d = dict() + self.assertEqual(d.keys(), []) + d = dict({'a': 1, 'b': 2}) + k = d.keys() + self.assertTrue(d.has_key('a')) + self.assertTrue(d.has_key('b')) + + self.assertRaises(TypeError, d.keys, None) + + def test_values(self): + d = dict() + self.assertEqual(d.values(), []) + d = dict({1:2}) + self.assertEqual(d.values(), [2]) + + self.assertRaises(TypeError, d.values, None) + + def test_items(self): + d = dict() + self.assertEqual(d.items(), []) + + d = dict({1:2}) + self.assertEqual(d.items(), [(1, 2)]) + + self.assertRaises(TypeError, d.items, None) + + def test_has_key(self): + d = dict() + self.assertFalse(d.has_key('a')) + d = dict({'a': 1, 'b': 2}) + k = d.keys() + k.sort() + self.assertEqual(k, ['a', 'b']) + + self.assertRaises(TypeError, d.has_key) + + def test_contains(self): + d = dict() + self.assertNotIn('a', d) + self.assertFalse('a' in d) + self.assertTrue('a' not in d) + d = dict({'a': 1, 'b': 2}) + self.assertIn('a', d) + self.assertIn('b', d) + self.assertNotIn('c', d) + + self.assertRaises(TypeError, d.__contains__) + + def test_len(self): + d = dict() + self.assertEqual(len(d), 0) + d = dict({'a': 1, 'b': 2}) + self.assertEqual(len(d), 2) + + def test_getitem(self): + d = dict({'a': 1, 'b': 2}) + self.assertEqual(d['a'], 1) + self.assertEqual(d['b'], 2) + d['c'] = 3 + d['a'] = 4 + self.assertEqual(d['c'], 3) + self.assertEqual(d['a'], 4) + del d['b'] + self.assertEqual(d, dict({'a': 4, 'c': 3})) + + self.assertRaises(TypeError, d.__getitem__) + + class BadEq(object): + def __eq__(self, other): + raise Exc() + def __hash__(self): + return 24 + + d = dict() + d[BadEq()] = 42 + self.assertRaises(KeyError, d.__getitem__, 23) + + class Exc(Exception): pass + + class BadHash(object): + fail = False + def __hash__(self): + if self.fail: + raise Exc() + else: + return 42 + + x = BadHash() + d[x] = 42 + x.fail = True + self.assertRaises(Exc, d.__getitem__, x) + + def test_clear(self): + d = dict({1:1, 2:2, 3:3}) + d.clear() + self.assertEqual(d, {}) + + self.assertRaises(TypeError, d.clear, None) + + def test_update(self): + d = dict() + d.update({1:100}) + d.update(dict({2:20})) + d.update({1:1, 2:2, 3:3}) + self.assertEqual(d, {1:1, 2:2, 3:3}) + + d.update() + self.assertEqual(d, {1:1, 2:2, 3:3}) + + self.assertRaises((TypeError, AttributeError), d.update, None) + + class SimpleUserDict: + def __init__(self): + self.d = dict({1:1, 2:2, 3:3}) + def keys(self): + return self.d.keys() + def __getitem__(self, i): + return self.d[i] + d.clear() + d.update(SimpleUserDict()) + self.assertEqual(d, {1:1, 2:2, 3:3}) + + class Exc(Exception): pass + + d.clear() + class FailingUserDict: + def keys(self): + raise Exc + self.assertRaises(Exc, d.update, FailingUserDict()) + + class FailingUserDict: + def keys(self): + class BogonIter: + def __init__(self): + self.i = 1 + def __iter__(self): + return self + def __next__(self): + if self.i: + self.i = 0 + return 'a' + raise Exc + return BogonIter() + def __getitem__(self, key): + return key + self.assertRaises(Exc, d.update, FailingUserDict()) + + class FailingUserDict: + def keys(self): + class BogonIter: + def __init__(self): + self.i = ord('a') + def __iter__(self): + return self + def __next__(self): + if self.i <= ord('z'): + rtn = chr(self.i) + self.i += 1 + return rtn + raise StopIteration + return BogonIter() + def __getitem__(self, key): + raise Exc + self.assertRaises(Exc, d.update, FailingUserDict()) + + class badseq(object): + def __iter__(self): + return self + def __next__(self): + raise Exc() + + self.assertRaises(Exc, {}.update, badseq()) + + self.assertRaises(ValueError, {}.update, [(1, 2, 3)]) + + def test_fromkeys(self): + self.assertEqual(dict.fromkeys('abc'), {'a':None, 'b':None, 'c':None}) + d = {} + self.assertIsNot(d.fromkeys('abc'), d) + self.assertEqual(d.fromkeys('abc'), {'a':None, 'b':None, 'c':None}) + self.assertEqual(d.fromkeys((4,5),0), {4:0, 5:0}) + self.assertEqual(d.fromkeys([]), {}) + def g(): + yield 1 + self.assertEqual(d.fromkeys(g()), {1:None}) + self.assertRaises(TypeError, dict().fromkeys, 3) + class dictlike(dict): pass + self.assertEqual(dictlike.fromkeys('a'), {'a':None}) + self.assertEqual(dictlike().fromkeys('a'), {'a':None}) + self.assertIsInstance(dictlike.fromkeys('a'), dictlike) + self.assertIsInstance(dictlike().fromkeys('a'), dictlike) + # class mydict(dict): + # def __new__(cls): + # return UserDict.UserDict() + # ud = mydict.fromkeys('ab') + # self.assertEqual(ud, {'a':None, 'b':None}) + # self.assertIsInstance(ud, UserDict.UserDict) + # self.assertRaises(TypeError, dict.fromkeys) + + class Exc(Exception): pass + + class baddict1(dict): + def __init__(self): + raise Exc() + + self.assertRaises(Exc, baddict1.fromkeys, [1]) + + class BadSeq(object): + def __iter__(self): + return self + def __next__(self): + raise Exc() + + self.assertRaises(Exc, dict.fromkeys, BadSeq()) + + class baddict2(dict): + def __setitem__(self, key, value): + raise Exc() + + self.assertRaises(Exc, baddict2.fromkeys, [1]) + + # test fast path for dictionary inputs + d = dict(zip(range(6), range(6))) + self.assertEqual(dict.fromkeys(d, 0), dict(zip(range(6), [0]*6))) + + class baddict3(dict): + def __new__(cls): + return d + d = dict({i : i for i in range(10)}) + res = d.copy() + res.update(a=None, b=None, c=None) + self.assertEqual(baddict3.fromkeys({"a", "b", "c"}), res) + + def test_copy(self): + d = dict({1:1, 2:2, 3:3}) + self.assertEqual(d.copy(), {1:1, 2:2, 3:3}) + self.assertEqual({}.copy(), {}) + self.assertRaises(TypeError, d.copy, None) + + def test_get(self): + d = dict() + self.assertIs(d.get('c'), None) + self.assertEqual(d.get('c', 3), 3) + d = dict({'a': 1, 'b': 2}) + self.assertIs(d.get('c'), None) + self.assertEqual(d.get('c', 3), 3) + self.assertEqual(d.get('a'), 1) + self.assertEqual(d.get('a', 3), 1) + self.assertRaises(TypeError, d.get) + self.assertRaises(TypeError, d.get, None, None, None) + + def test_setdefault(self): + # dict.setdefault() + d = dict() + self.assertIs(d.setdefault('key0'), None) + d.setdefault('key0', []) + self.assertIs(d.setdefault('key0'), None) + d.setdefault('key', []).append(3) + self.assertEqual(d['key'][0], 3) + d.setdefault('key', []).append(4) + self.assertEqual(len(d['key']), 2) + self.assertRaises(TypeError, d.setdefault) + + class Exc(Exception): pass + + class BadHash(object): + fail = False + def __hash__(self): + if self.fail: + raise Exc() + else: + return 42 + + x = BadHash() + d[x] = 42 + x.fail = True + self.assertRaises(Exc, d.setdefault, x, []) + + def test_setdefault_atomic(self): + # Issue #13521: setdefault() calls __hash__ and __eq__ only once. + class Hashed(object): + def __init__(self): + self.hash_count = 0 + self.eq_count = 0 + def __hash__(self): + self.hash_count += 1 + return 42 + def __eq__(self, other): + self.eq_count += 1 + return id(self) == id(other) + hashed1 = Hashed() + y = dict({hashed1: 5}) + hashed2 = Hashed() + y.setdefault(hashed2, []) + self.assertEqual(hashed1.hash_count, 1) + self.assertEqual(hashed2.hash_count, 1) + self.assertEqual(hashed1.eq_count + hashed2.eq_count, 1) + + def test_popitem(self): + # dict.popitem() + for copymode in -1, +1: + # -1: b has same structure as a + # +1: b is a.copy() + for log2size in range(12): + size = 2**log2size + a = dict() + b = dict() + for i in range(size): + a[repr(i)] = i + if copymode < 0: + b[repr(i)] = i + if copymode > 0: + b = a.copy() + for i in range(size): + ka, va = ta = a.popitem() + self.assertEqual(va, int(ka)) + kb, vb = tb = b.popitem() + self.assertEqual(vb, int(kb)) + self.assertFalse(copymode < 0 and ta != tb) + self.assertFalse(a) + self.assertFalse(b) + + d = dict() + self.assertRaises(KeyError, d.popitem) + + def test_pop(self): + # Tests for pop with specified key + d = dict() + k, v = 'abc', 'def' + d[k] = v + self.assertRaises(KeyError, d.pop, 'ghi') + + self.assertEqual(d.pop(k), v) + self.assertEqual(len(d), 0) + + self.assertRaises(KeyError, d.pop, k) + + self.assertEqual(d.pop(k, v), v) + d[k] = v + self.assertEqual(d.pop(k, 1), v) + + self.assertRaises(TypeError, d.pop) + + class Exc(Exception): pass + + class BadHash(object): + fail = False + def __hash__(self): + if self.fail: + raise Exc() + else: + return 42 + + x = BadHash() + d[x] = 42 + x.fail = True + self.assertRaises(Exc, d.pop, x) + + def test_mutatingiteration(self): + # changing dict size during iteration + d = dict() + d[1] = 1 + with self.assertRaises(RuntimeError): + for i in d: + d[i+1] = 1 + + def test_repr(self): + d = dict() + self.assertEqual(repr(d), '{}') + d[1] = 2 + self.assertEqual(repr(d), '{1: 2}') + d = dict() + d[1] = d + self.assertEqual(repr(d), '{1: {...}}') + + class Exc(Exception): pass + + class BadRepr(object): + def __repr__(self): + raise Exc() + + d = dict({1: BadRepr()}) + self.assertRaises(Exc, repr, d) + + @unittest.skip('Comparing dicts for order has not been forward-ported') + def test_le(self): + self.assertFalse(dict() < {}) + self.assertFalse(dict() < dict()) + self.assertFalse(dict({1: 2}) < {1: 2}) + + class Exc(Exception): pass + + class BadCmp(object): + def __eq__(self, other): + raise Exc() + def __hash__(self): + return 42 + + d1 = dict({BadCmp(): 1}) + d2 = dict({1: 1}) + + with self.assertRaises(Exc): + d1 < d2 + + def test_missing(self): + # Make sure dict doesn't have a __missing__ method + self.assertFalse(hasattr(dict, "__missing__")) + self.assertFalse(hasattr(dict(), "__missing__")) + # Test several cases: + # (D) subclass defines __missing__ method returning a value + # (E) subclass defines __missing__ method raising RuntimeError + # (F) subclass sets __missing__ instance variable (no effect) + # (G) subclass doesn't define __missing__ at a all + class D(dict): + def __missing__(self, key): + return 42 + d = D({1: 2, 3: 4}) + self.assertEqual(d[1], 2) + self.assertEqual(d[3], 4) + self.assertNotIn(2, d) + self.assertNotIn(2, d.keys()) + self.assertEqual(d[2], 42) + + class E(dict): + def __missing__(self, key): + raise RuntimeError(key) + e = E() + with self.assertRaises(RuntimeError) as c: + e[42] + self.assertEqual(c.exception.args, (42,)) + + class F(dict): + def __init__(self): + # An instance variable __missing__ should have no effect + self.__missing__ = lambda key: None + f = F() + with self.assertRaises(KeyError) as c: + f[42] + self.assertEqual(c.exception.args, (42,)) + + class G(dict): + pass + g = G() + with self.assertRaises(KeyError) as c: + g[42] + self.assertEqual(c.exception.args, (42,)) + + def test_tuple_keyerror(self): + # SF #1576657 + d = dict() + with self.assertRaises(KeyError) as c: + d[(1,)] + self.assertEqual(c.exception.args, ((1,),)) + + # def test_bad_key(self): + # # Dictionary lookups should fail if __cmp__() raises an exception. + # class CustomException(Exception): + # pass + + # class BadDictKey: + # def __hash__(self): + # return hash(self.__class__) + + # def __cmp__(self, other): + # if isinstance(other, self.__class__): + # raise CustomException + # return other + + # d = dict() + # x1 = BadDictKey() + # x2 = BadDictKey() + # d[x1] = 1 + # for stmt in ['d[x2] = 2', + # 'z = d[x2]', + # 'x2 in d', + # 'd.has_key(x2)', + # 'd.get(x2)', + # 'd.setdefault(x2, 42)', + # 'd.pop(x2)', + # '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 + # # release build. Unfortunately, provoking this kind of stuff requires + # # a mix of inserts and deletes hitting exactly the right hash codes in + # # exactly the right order, and I can't think of a randomized approach + # # that would be *likely* to hit a failing case in reasonable time. + + # d = {} + # for i in range(5): + # d[i] = i + # for i in range(5): + # del d[i] + # for i in range(5, 9): # i==8 was the problem + # d[i] = i + + # def test_resize2(self): + # # Another dict resizing bug (SF bug #1456209). + # # This caused Segmentation faults or Illegal instructions. + + # class X(object): + # def __hash__(self): + # return 5 + # def __eq__(self, other): + # if resizing: + # d.clear() + # return False + # d = {} + # resizing = False + # d[X()] = 1 + # d[X()] = 2 + # d[X()] = 3 + # d[X()] = 4 + # d[X()] = 5 + # # now trigger a resize + # resizing = True + # d[9] = 6 + + # def test_empty_presized_dict_in_freelist(self): + # # Bug #3537: if an empty but presized dict with a size larger + # # than 7 was in the freelist, it triggered an assertion failure + # with self.assertRaises(ZeroDivisionError): + # d = {'a': 1 // 0, 'b': None, 'c': None, 'd': None, 'e': None, + # 'f': None, 'g': None, 'h': None} + # d = {} + + # def test_container_iterator(self): + # # Bug #3680: tp_traverse was not implemented for dictiter objects + # class C(object): + # pass + # iterators = (dict.iteritems, dict.itervalues, dict.iterkeys) + # for i in iterators: + # obj = C() + # ref = weakref.ref(obj) + # container = {obj: 1} + # obj.x = i(container) + # del obj, container + # gc.collect() + # self.assertIs(ref(), None, "Cycle was not collected") + + # def _not_tracked(self, t): + # # Nested containers can take several collections to untrack + # gc.collect() + # gc.collect() + # self.assertFalse(gc.is_tracked(t), t) + + # def _tracked(self, t): + # self.assertTrue(gc.is_tracked(t), t) + # gc.collect() + # gc.collect() + # self.assertTrue(gc.is_tracked(t), t) + + # @test_support.cpython_only + # def test_track_literals(self): + # # Test GC-optimization of dict literals + # x, y, z, w = 1.5, "a", (1, None), [] + + # self._not_tracked({}) + # self._not_tracked({x:(), y:x, z:1}) + # self._not_tracked({1: "a", "b": 2}) + # self._not_tracked({1: 2, (None, True, False, ()): int}) + # self._not_tracked({1: object()}) + + # # Dicts with mutable elements are always tracked, even if those + # # elements are not tracked right now. + # self._tracked({1: []}) + # self._tracked({1: ([],)}) + # self._tracked({1: {}}) + # self._tracked({1: set()}) + + # @test_support.cpython_only + # def test_track_dynamic(self): + # # Test GC-optimization of dynamically-created dicts + # class MyObject(object): + # pass + # x, y, z, w, o = 1.5, "a", (1, object()), [], MyObject() + + # d = dict() + # self._not_tracked(d) + # d[1] = "a" + # self._not_tracked(d) + # d[y] = 2 + # self._not_tracked(d) + # d[z] = 3 + # self._not_tracked(d) + # self._not_tracked(d.copy()) + # d[4] = w + # self._tracked(d) + # self._tracked(d.copy()) + # d[4] = None + # self._not_tracked(d) + # self._not_tracked(d.copy()) + + # # dd isn't tracked right now, but it may mutate and therefore d + # # which contains it must be tracked. + # d = dict() + # dd = dict() + # d[1] = dd + # self._not_tracked(dd) + # self._tracked(d) + # dd[1] = d + # self._tracked(dd) + + # d = dict.fromkeys([x, y, z]) + # self._not_tracked(d) + # dd = dict() + # dd.update(d) + # self._not_tracked(dd) + # d = dict.fromkeys([x, y, z, o]) + # self._tracked(d) + # dd = dict() + # dd.update(d) + # self._tracked(dd) + + # d = dict(x=x, y=y, z=z) + # self._not_tracked(d) + # d = dict(x=x, y=y, z=z, w=w) + # self._tracked(d) + # d = dict() + # d.update(x=x, y=y, z=z) + # self._not_tracked(d) + # d.update(w=w) + # self._tracked(d) + + # d = dict([(x, y), (z, 1)]) + # self._not_tracked(d) + # d = dict([(x, y), (z, w)]) + # self._tracked(d) + # d = dict() + # d.update([(x, y), (z, 1)]) + # self._not_tracked(d) + # d.update([(x, y), (z, w)]) + # self._tracked(d) + + # @test_support.cpython_only + # def test_track_subtypes(self): + # # Dict subtypes are always tracked + # class MyDict(dict): + # pass + # self._tracked(MyDict()) + + +def test_main(): + if utils.PY3: + from test import support as test_support + else: + from test import test_support + + test_support.run_unittest( + TestOldDict, + Py2DictTest, + ) + + +if __name__ == '__main__': + test_main() diff --git a/past/tests/test_oldstr.py b/past/tests/test_oldstr.py new file mode 100644 index 00000000..4d23c310 --- /dev/null +++ b/past/tests/test_oldstr.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +""" +Tests for the resurrected Py2-like 8-bit string object +""" + +from __future__ import absolute_import, unicode_literals, print_function + +from numbers import Integral +from future.tests.base import unittest +from past.builtins.types.oldstr import oldstr, collapse_double_backslashes + + +class TestOldStr(unittest.TestCase): + def test_repr(self): + s1 = oldstr(b'abc') + assert repr(s1) == "'abc'" + s2 = oldstr(b'abc\ndef') + assert repr(s2) == "'abc\\ndef'" + + def test_str(self): + s1 = oldstr(b'abc') + assert str(s1) == 'abc' + s2 = oldstr(b'abc\ndef') + assert str(s2) == 'abc\ndef' + + def test_collapse_double_backslashes(s): + s = collapse_double_backslashes(r'a\\b\c\\d') # i.e. 'a\\\\b\\c\\\\d' + assert str(s) == r'a\b\c\d' + s2 = collapse_double_backslashes(r'abc\\ndef') # i.e. 'abc\\\\ndef' + assert str(s2) == r'abc\ndef' + + def test_getitem(self): + s = oldstr(b'abc') + + assert s[0] != 97 + assert s[0] == b'a' + assert s[0] == oldstr(b'a') + + assert s[1:] == b'bc' + assert s[1:] == oldstr(b'bc') + + +if __name__ == '__main__': + unittest.main() diff --git a/setup.py b/setup.py index 47783245..c8edeabb 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,10 @@ "future.standard_library.http", "future.standard_library.test", "future.utils", + "past", + "past.builtins", + "past.builtins.types", + "past.tests", "libfuturize", "libfuturize.fixes2", "libfuturize.fixes3"] From c1d8efff34e929bc30f631bde7721007a3b67f62 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 10 Jan 2014 01:46:14 +1100 Subject: [PATCH 0002/1190] Update README.rst and tweak overview.rst --- README.rst | 110 ++++++++++++++++++++++++++++++++++++++++++++++ docs/overview.rst | 17 +++---- 2 files changed, 117 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index 5d679deb..5f8cfbb8 100644 --- a/README.rst +++ b/README.rst @@ -5,6 +5,116 @@ future: an easier, safer, cleaner upgrade path to Python 3 Overview ======== +``python-future`` is the missing compatibility layer between Python 3 and +Python 2. It allows you to maintain a single, clean Python 3.x-compatible +codebase with minimal cruft and run it easily on Python 2 mostly unchanged. + +It provides ``future`` and ``past`` packages with backports and forward ports +of features from Python 3 and 2. It also comes with ``futurize``, a customized +2to3-based script that helps you to transition to supporting both Python 2 and +3 in a single codebase, module by module, from either Python 2 or Python 3. + + +.. _features: + +Features +-------- + +- ``future`` package provides backports and remappings for 16 builtins with + different semantics on Py3 versus Py2 +- ``future`` package provides backports and remappings from the Py3 standard + library +- ``past`` package provides forward-ports of Python 2 types and resurrects + some Python 2 builtins (to aid with per-module code migrations) +- 300+ unit tests +- ``futurize`` script based on ``2to3``, ``3to2`` and parts of + ``python-modernize``, for automatic conversion from either Py2 or Py3 to a + clean single-source codebase compatible with Python 2.6+ and Python 3.3+. +- a comprehensive set of utility functions and decorators selected from + Py2/3 compatibility interfaces from projects like ``six``, ``IPython``, + ``Jinja2``, ``Django``, and ``Pandas``. + + +.. _code-examples: + +Code examples +------------- + +``future`` is designed to be imported at the top of each Python module +together with Python's built-in ``__future__`` module. For example, this +code behaves the same way on Python 2.6/2.7 after these imports as it does +on Python 3:: + + from __future__ import absolute_import, division, print_function + from future import bytes, str, open, super, zip, round, input, int + + # Backported Py3 bytes object + b = bytes(b'ABCD') + assert list(b) == [65, 66, 67, 68] + assert repr(b) == "b'ABCD'" + # These raise TypeErrors: + # b + u'EFGH' + # bytes(b',').join([u'Fred', u'Bill']) + + # Backported Py3 str object + s = str(u'ABCD') + assert s != bytes(b'ABCD') + assert isinstance(s.encode('utf-8'), bytes) + assert isinstance(b.decode('utf-8'), str) + assert repr(s) == 'ABCD' # consistent repr with Py3 (no u prefix) + # These raise TypeErrors: + # bytes(b'B') in s + # s.find(bytes(b'A')) + + # Extra arguments for the open() function + f = open('japanese.txt', encoding='utf-8', errors='replace') + + # New simpler super() function: + class VerboseList(list): + def append(self, item): + print('Adding an item') + super().append(item) + + # 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) + + # Compatible output from isinstance() across Py2/3: + assert isinstance(2**64, int) # long integers + assert isinstance(u'blah', str) + assert isinstance('blah', str) # if unicode_literals is in effect + +There is also support for renamed standard library modules in the form of a context manager that provides import hooks:: + + from future import standard_library + + with standard_library.enable_hooks(): + from http.client import HttpConnection + from itertools import filterfalse + import html.parser + import queue + + +Next steps +---------- +Check out the :ref:`quickstart-guide`. + +***** +Overview +======== + ``future`` is the missing compatibility layer between Python 3 and Python 2. It allows you to maintain a single, clean Python 3.x-compatible codebase with minimal cruft and run it easily on Python 2 without further diff --git a/docs/overview.rst b/docs/overview.rst index 6d9e0d61..a06c83f8 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -3,19 +3,16 @@ Overview ======== -``python-future`` is the missing compatibility layer between Python 3 and Python -2. -It allows you to maintain a single, clean Python 3.x-compatible codebase with -minimal cruft and run it easily on Python 2 mostly unchanged. +``python-future`` is the missing compatibility layer between Python 3 and +Python 2. It allows you to maintain a single, clean Python 3.x-compatible +codebase with minimal cruft and run it easily on Python 2 mostly unchanged. -It also provides a tools designed to help with module-by-module upgrades (e.g. -of libraries) from Python 2 to a compatible 2/3 codebase without breaking the -existing API. +It provides ``future`` and ``past`` packages with backports and forward ports +of features from Python 3 and 2. It also comes with ``futurize``, a customized +2to3-based script that helps you to transition to supporting both Python 2 and +3 in a single codebase, module by module, from either Python 2 or Python 3. -``python-future`` comes with ``futurize``, a customized 2to3-based script that -helps you to transition to supporting both Python 2 and 3 in a single codebase, -module by module, from either Python 2 or Python 3. .. _features: From ada86aa0ccea9bb2d76225e79a35017df5b31091 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Fri, 10 Jan 2014 01:48:36 +1100 Subject: [PATCH 0003/1190] Fix README.rst formatting (hopefully) --- README.rst | 109 ----------------------------------------------------- 1 file changed, 109 deletions(-) diff --git a/README.rst b/README.rst index 5f8cfbb8..92f5ea82 100644 --- a/README.rst +++ b/README.rst @@ -15,8 +15,6 @@ of features from Python 3 and 2. It also comes with ``futurize``, a customized 3 in a single codebase, module by module, from either Python 2 or Python 3. -.. _features: - Features -------- @@ -35,8 +33,6 @@ Features ``Jinja2``, ``Django``, and ``Pandas``. -.. _code-examples: - Code examples ------------- @@ -107,111 +103,6 @@ There is also support for renamed standard library modules in the form of a cont import queue -Next steps ----------- -Check out the :ref:`quickstart-guide`. - -***** -Overview -======== - -``future`` is the missing compatibility layer between Python 3 and Python -2. It allows you to maintain a single, clean Python 3.x-compatible -codebase with minimal cruft and run it easily on Python 2 without further -modification. - -``future`` comes with ``futurize``, a script that helps you to transition -to supporting both Python 2 and 3 in a single codebase, module by module. - - -Features --------- - -- provides backports and remappings for 15 builtins with different - semantics on Py3 versus Py2 -- provides backports and remappings from the Py3 standard library -- 300+ unit tests -- ``futurize`` script based on ``2to3``, ``3to2`` and parts of - ``python-modernize`` for automatic conversion from either Py2 or Py3 to a - clean single-source codebase compatible with Python 2.6+ and Python 3.3+. -- a consistent set of utility functions and decorators selected from - Py2/3 compatibility interfaces from projects like ``six``, ``IPython``, - ``Jinja2``, ``Django``, and ``Pandas``. - - -Code examples -------------- - -``future`` is designed to be imported at the top of each Python module -together with Python's built-in ``__future__`` module like this:: - - from __future__ import (absolute_import, division, - print_function, unicode_literals) - from future import standard_library - from future.builtins import * - -followed by standard Python 3 code. The imports have no effect on Python -3 but allow the code to run mostly unchanged on Python 3 and Python 2.6/2.7. - -For example, this code behaves the same way on Python 2.6/2.7 after these -imports as it normally does on Python 3:: - - # Support for renamed standard library modules via import hooks - from http.client import HttpConnection - from itertools import filterfalse - import html.parser - import queue - - # Backported Py3 bytes object - b = bytes(b'ABCD') - assert list(b) == [65, 66, 67, 68] - assert repr(b) == "b'ABCD'" - # These raise TypeErrors: - # b + u'EFGH' - # bytes(b',').join([u'Fred', u'Bill']) - - # Backported Py3 str object - s = str(u'ABCD') - assert s != bytes(b'ABCD') - assert isinstance(s.encode('utf-8'), bytes) - assert isinstance(b.decode('utf-8'), str) - assert repr(s) == 'ABCD' # consistent repr with Py3 (no u prefix) - # These raise TypeErrors: - # bytes(b'B') in s - # s.find(bytes(b'A')) - - # Extra arguments for the open() function - f = open('japanese.txt', encoding='utf-8', errors='replace') - - # New simpler super() function: - class VerboseList(list): - def append(self, item): - print('Adding an item') - super().append(item) - - # 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) - - # Compatible output from isinstance() across Py2/3: - assert isinstance(2**64, int) # long integers - assert isinstance(u'blah', str) - assert isinstance('blah', str) # with unicode_literals in effect - assert isinstance(b'bytestring', bytes) - - Documentation ------------- From 04b8aaa9c3b7c1e01d71f8810f45369b236dc4d7 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 11 Jan 2014 08:55:09 +1100 Subject: [PATCH 0004/1190] Docs: fix google analytics --- docs/_templates/layout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html index 09cdb2d2..128031cc 100644 --- a/docs/_templates/layout.html +++ b/docs/_templates/layout.html @@ -10,7 +10,7 @@ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - ga('create', 'UA-blah', 'python-future.org'); + ga('create', 'UA-19344199-2', 'python-future.org'); ga('send', 'pageview'); From 13c9d08d45466b0b8bc668e8626290d96af086d8 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 11 Jan 2014 09:42:07 +1100 Subject: [PATCH 0005/1190] Show the complete version string in the Sphinx docs --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 1b2f5411..bff19704 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,7 +53,7 @@ # if 'dev' in release: # release = release.split('dev')[0] + 'dev' release = '0.11.0-dev' -version = '.'.join(release.split('.')[:2]) +version = release # was: '.'.join(release.split('.')[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From abea4f322461e9cafcc8808c5c9f3b63014d2ff1 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 11 Jan 2014 09:44:11 +1100 Subject: [PATCH 0006/1190] Update docs --- docs/changelog.rst | 39 ++++++++++++++------------- docs/credits.rst | 2 +- docs/quickstart.rst | 2 -- docs/reference.rst | 27 ++++++++++++------- future/builtins/backports/newbytes.py | 14 +--------- 5 files changed, 41 insertions(+), 43 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index b96890eb..af78c64b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -14,8 +14,8 @@ More robust implementation of standard_library hooks These were bleeding into surrounding code, causing incompatibilities with modules like ``requests`` (issue #19). -Now ``future.standard_library`` provides the context manager -``enable_hooks()``. Use it as follows:: +``future.standard_library`` now provides a context manager +``enable_hooks``. Use it as follows:: >>> from future import standard_library >>> with standard_library.enable_hooks(): @@ -25,28 +25,31 @@ Now ``future.standard_library`` provides the context manager >>> import requests >>> # etc. -If you prefer, the following imports are also available directly:: +``future.standard_library`` still also supports the ``install_hooks`` and +``remove_hooks`` functions as an alternative. - >>> from future.standard_library import queue - >>> from future.standard_library import socketserver - >>> from future.standard_library.http.client import HTTPConnection +.. If you prefer, the following imports are also available directly:: +.. +.. >>> from future.standard_library import queue +.. >>> from future.standard_library import socketserver +.. >>> from future.standard_library.http.client import HTTPConnection -As usual, this has no effect on Python 3. +As usual, these have no effect on Python 3. *Note*: this is a backward-incompatible change. -Simpler imports ---------------- - -It is now possible to import builtins directly from the ``future`` -namespace as follows:: - - >>> from future import * - -or just those you need:: - - >>> from future import open, str +.. Simpler imports +.. --------------- +.. +.. It is now possible to import builtins directly from the ``future`` +.. namespace as follows:: +.. +.. >>> from future import * +.. +.. or just those you need:: +.. +.. >>> from future import open, str Utility functions for raising exceptions with a traceback portably diff --git a/docs/credits.rst b/docs/credits.rst index be1d6e25..b52fac28 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -22,7 +22,7 @@ Credits Licensing --------- The software is distributed under an MIT licence. The text is as follows -(from LICENSE.txt): +(from LICENSE.txt):: Copyright (c) 2013-2014 Python Charmers Pty Ltd, Australia diff --git a/docs/quickstart.rst b/docs/quickstart.rst index d9d6d847..c81587ed 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -90,7 +90,6 @@ be accessed under their Python 3 names and locations in Python 2:: :mod:`future` also includes backports for these stdlib modules from Py3 that were heavily refactored versus Py2:: - with standard_library.enable_hooks(): import html import html.entities import html.parser @@ -102,7 +101,6 @@ that were heavily refactored versus Py2:: These modules are currently not supported, but we aim to support them in the future:: - with standard_library.enable_hooks(): import http.cookies import http.cookiejar diff --git a/docs/reference.rst b/docs/reference.rst index ca41a900..844a48ed 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -1,10 +1,11 @@ -############### -Reference Guide -############### +############# +API Reference +############# -.. *NOTE: These are still a work in progress... We need to go through our -.. docstrings and make them sphinx-compliant, and figure out how to improve -.. formatting with the sphinx-bootstrap-theme plugin.* +*NOTE: This page is still a work in progress... We need to go through our +docstrings and make them sphinx-compliant, and figure out how to improve +formatting with the sphinx-bootstrap-theme plugin. Pull requests would be +very welcome.* .. contents:: @@ -35,8 +36,16 @@ future.utils Interface Backported types ================ -.. autoclass:: future.builtins.backports.newbytes -.. autoclass:: future.builtins.backports.newstr -.. autoclass:: future.builtins.backports.newint +bytes +----- +.. automodule:: future.builtins.backports.newbytes + +str +--- +.. automodule:: future.builtins.backports.newstr + +int +--- +.. automodule:: future.builtins.backports.newint diff --git a/future/builtins/backports/newbytes.py b/future/builtins/backports/newbytes.py index 9162f877..3100e94c 100644 --- a/future/builtins/backports/newbytes.py +++ b/future/builtins/backports/newbytes.py @@ -2,19 +2,7 @@ Pure-Python implementation of a Python 3-like bytes object for Python 2. Why do this? Without it, the Python 2 bytes object is a very, very -different beast to the Python 3 bytes object. Running the -test_bytes_from_py33.py script from the Python 3.3 test suite using -Python 2 with its default str-aliased bytes object (after the appropriate -import fixes, and using the backported test.support module) yields this: - ------------------------------------------------------------------ - Ran 203 tests in 0.214s - - FAILED (failures=31, errors=55, skipped=1) - ------------------------------------------------------------------ -when running - - $ python -m future.tests.test_bytes_from_py33 - +different beast to the Python 3 bytes object. """ from collections import Iterable From 62e2ec6c5e830da3349233e3f6cbc2044a17390c Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 11 Jan 2014 09:44:45 +1100 Subject: [PATCH 0007/1190] Disable imports from ``future`` base package again for now --- future/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/future/__init__.py b/future/__init__.py index 7f599b39..9ca31414 100644 --- a/future/__init__.py +++ b/future/__init__.py @@ -83,8 +83,8 @@ """ -from future import standard_library, utils -from future.builtins import * +# from future import standard_library, utils +# from future.builtins import * __title__ = 'future' __author__ = 'Ed Schofield' From 88ed26f11e0b1329bd9e36225dbed38a09d29cd0 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 11 Jan 2014 09:45:43 +1100 Subject: [PATCH 0008/1190] TODO: make ``futurize`` add a standard_library.install_hooks() call --- libfuturize/fixes2/fix_add_future_standard_library_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libfuturize/fixes2/fix_add_future_standard_library_import.py b/libfuturize/fixes2/fix_add_future_standard_library_import.py index 5c077f19..b40d510d 100644 --- a/libfuturize/fixes2/fix_add_future_standard_library_import.py +++ b/libfuturize/fixes2/fix_add_future_standard_library_import.py @@ -19,4 +19,4 @@ class FixAddFutureStandardLibraryImport(fixer_base.BaseFix): 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) - + # TODO: also add standard_library.install_hooks() From 8d63c29b82faa1d2951966f5afddb0387828c7ca Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 11 Jan 2014 10:00:08 +1100 Subject: [PATCH 0009/1190] Rename enable_hooks and disable_hooks -> install_hooks and remove_hooks --- future/standard_library/__init__.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/future/standard_library/__init__.py b/future/standard_library/__init__.py index f6275444..df9952ec 100644 --- a/future/standard_library/__init__.py +++ b/future/standard_library/__init__.py @@ -208,7 +208,7 @@ class RenameImport(object): # Different RenameImport classes are created when importing this module from # different source files. This causes isinstance(hook, RenameImport) checks # to produce inconsistent results. We add this RENAMER attribute here so - # disable_hooks() and enable_hooks() can find instances of these classes + # remove_hooks() and install_hooks() can find instances of these classes # easily: RENAMER = True @@ -325,7 +325,7 @@ def _find_and_load_module(self, name, path=None): ] -def enable_hooks(): +def install_hooks(): if utils.PY3: return for (newmodname, newobjname, oldmodname, oldobjname) in MOVES: @@ -340,13 +340,16 @@ def enable_hooks(): sys.meta_path.append(newhook) -def disable_hooks(): +def remove_hooks(): if not utils.PY3: # Loop backwards, so deleting items keeps the ordering: for i, hook in list(enumerate(sys.meta_path))[::-1]: if hasattr(hook, 'RENAMER'): del sys.meta_path[i] +# For backward compatibility with version 0.10: +disable_hooks = remove_hooks + @contextlib.contextmanager def suspend_hooks(): @@ -360,4 +363,4 @@ def suspend_hooks(): if not utils.PY3: - enable_hooks() + install_hooks() From f5a9e3d9aa73258d83001137a6f022b17875ec36 Mon Sep 17 00:00:00 2001 From: Jeff Tratner Date: Fri, 3 Jan 2014 08:12:42 -0800 Subject: [PATCH 0010/1190] Fix reraise on Python 2 and Python 3 Python 2: be a real function, rather than a nested one Python 3: need to check that value was an Exception --- future/tests/test_utils.py | 39 +++++++++++++++++++++++++++++++++++++- future/utils/__init__.py | 29 ++++++++++++++++++---------- 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/future/tests/test_utils.py b/future/tests/test_utils.py index e9ad07c5..bfebd09a 100644 --- a/future/tests/test_utils.py +++ b/future/tests/test_utils.py @@ -4,8 +4,11 @@ """ from __future__ import absolute_import, unicode_literals, print_function +import sys from future.builtins import * -from future.utils import old_div, istext, isbytes, native, PY2, PY3, native_str +from future.utils import (old_div, istext, isbytes, native, PY2, PY3, + native_str, reraise) + from numbers import Integral from future.tests.base import unittest @@ -96,6 +99,40 @@ def test_isbytes(self): self.assertFalse(isbytes(self.s)) self.assertFalse(isbytes(self.s2)) + def test_reraise(self): + def valerror(): + try: + raise ValueError("Apples!") + except Exception as e: + reraise(e) + + self.assertRaises(ValueError, valerror) + + def with_value(): + reraise(IOError, "This is an error") + + self.assertRaises(IOError, with_value) + + try: + with_value() + except IOError as e: + self.assertEqual(str(e), "This is an error") + + def with_traceback(): + try: + raise ValueError("An error") + except Exception as e: + _, _, traceback = sys.exc_info() + reraise(IOError, str(e), traceback) + + self.assertRaises(IOError, with_traceback) + + try: + with_traceback() + except IOError as e: + self.assertEqual(str(e), "An error") + + if __name__ == '__main__': unittest.main() diff --git a/future/utils/__init__.py b/future/utils/__init__.py index 8d5512e4..2aff13aa 100644 --- a/future/utils/__init__.py +++ b/future/utils/__init__.py @@ -312,17 +312,26 @@ def getexception(): return sys.exc_info()[1] +if PY3: + def reraise(tp, value=None, tb=None): + """ + Create a raise_ method that allows re-raising exceptions with the cls + value and traceback on Python2 & Python3. + """ + 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: + exc = tp + if exc.__traceback__ is not tb: + raise exc.with_traceback(tb) + +else: + exec(''' def reraise(tp, value=None, tb=None): - """ - Create a raise_ method that allows re-raising exceptions with the cls - value and traceback on Python2 & Python3. - """ - if PY3: - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - else: - exec('def reraise(tp, value=None, tb=None):\n raise tp, value, tb') + raise tp, value, tb +'''.strip()) def implements_iterator(cls): From e706344b805ecd543c09ec3ee2edbad232ba0207 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Thu, 9 Jan 2014 00:28:47 +1100 Subject: [PATCH 0011/1190] Rename future.utils.reraise to future.utils.raise_ --- future/tests/test_utils.py | 10 +++++----- future/utils/__init__.py | 9 +++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/future/tests/test_utils.py b/future/tests/test_utils.py index bfebd09a..37fceb3c 100644 --- a/future/tests/test_utils.py +++ b/future/tests/test_utils.py @@ -7,7 +7,7 @@ import sys from future.builtins import * from future.utils import (old_div, istext, isbytes, native, PY2, PY3, - native_str, reraise) + native_str, raise_) from numbers import Integral @@ -99,17 +99,17 @@ def test_isbytes(self): self.assertFalse(isbytes(self.s)) self.assertFalse(isbytes(self.s2)) - def test_reraise(self): + def test_raise_(self): def valerror(): try: raise ValueError("Apples!") except Exception as e: - reraise(e) + raise_(e) self.assertRaises(ValueError, valerror) def with_value(): - reraise(IOError, "This is an error") + raise_(IOError, "This is an error") self.assertRaises(IOError, with_value) @@ -123,7 +123,7 @@ def with_traceback(): raise ValueError("An error") except Exception as e: _, _, traceback = sys.exc_info() - reraise(IOError, str(e), traceback) + raise_(IOError, str(e), traceback) self.assertRaises(IOError, with_traceback) diff --git a/future/utils/__init__.py b/future/utils/__init__.py index 2aff13aa..a0a65159 100644 --- a/future/utils/__init__.py +++ b/future/utils/__init__.py @@ -313,10 +313,11 @@ def getexception(): if PY3: - def reraise(tp, value=None, tb=None): + def raise_(tp, value=None, tb=None): """ - Create a raise_ method that allows re-raising exceptions with the cls - value and traceback on Python2 & Python3. + A function that matches the Python 2.x ``raise`` statement. This + 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") @@ -329,7 +330,7 @@ def reraise(tp, value=None, tb=None): else: exec(''' -def reraise(tp, value=None, tb=None): +def raise_(tp, value=None, tb=None): raise tp, value, tb '''.strip()) From 379b35090d81f6b226c5af1fd31991ada5405797 Mon Sep 17 00:00:00 2001 From: Ed Schofield Date: Sat, 11 Jan 2014 10:15:18 +1100 Subject: [PATCH 0012/1190] Bring in updated docs from master branch --- docs/_templates/layout.html | 2 +- docs/_templates/navbar.html | 2 +- docs/automatic_conversion.rst | 130 +++++++++--- docs/changelog.rst | 30 ++- docs/conf.py | 6 +- docs/contents.rst.inc | 2 +- docs/credits.rst | 4 +- docs/dict_object.rst | 17 +- docs/imports.rst | 300 +++++++++++++++++++++++----- docs/isinstance.rst | 7 +- docs/open_function.rst | 7 +- docs/overview.rst | 43 ++-- docs/quickstart.rst | 26 ++- docs/reference.rst | 27 ++- docs/roadmap.rst | 4 +- future/standard_library/__init__.py | 4 +- 16 files changed, 462 insertions(+), 149 deletions(-) diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html index 09cdb2d2..128031cc 100644 --- a/docs/_templates/layout.html +++ b/docs/_templates/layout.html @@ -10,7 +10,7 @@ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - ga('create', 'UA-blah', 'python-future.org'); + ga('create', 'UA-19344199-2', 'python-future.org'); ga('send', 'pageview'); diff --git a/docs/_templates/navbar.html b/docs/_templates/navbar.html index 85e819d4..7edada2a 100644 --- a/docs/_templates/navbar.html +++ b/docs/_templates/navbar.html @@ -1,4 +1,4 @@ -Fork me on GitHub +Fork me on GitHub