Commit 8b019098 authored by Aymeric Augustin's avatar Aymeric Augustin
Browse files

[py3] Bundled six for Python 3 compatibility.

Refs #18363.
parent 01c39262
Loading
Loading
Loading
Loading

django/utils/py3.py

deleted100644 → 0
+0 −109
Original line number Diff line number Diff line
# Compatibility layer for running Django both in 2.x and 3.x

import sys

if sys.version_info[0] < 3:
    PY3 = False
    # Changed module locations
    from urlparse import (urlparse, urlunparse, urljoin, urlsplit, urlunsplit,
                          urldefrag, parse_qsl)
    from urllib import (quote, unquote, quote_plus, urlopen, urlencode,
                        url2pathname, urlretrieve, unquote_plus)
    from urllib2 import (Request, OpenerDirector, UnknownHandler, HTTPHandler,
                         HTTPSHandler, HTTPDefaultErrorHandler, FTPHandler,
                         HTTPError, HTTPErrorProcessor)
    import urllib2
    import Cookie as cookies
    try:
        import cPickle as pickle
    except ImportError:
        import pickle
    try:
        import thread
    except ImportError:
        import dummy_thread as thread
    from htmlentitydefs import name2codepoint
    import HTMLParser
    from os import getcwdu
    from itertools import izip as zip
    unichr = unichr
    xrange = xrange
    maxsize = sys.maxint

    # Type aliases
    string_types = basestring,
    text_type = unicode
    integer_types = int, long
    long_type = long

    from io import BytesIO as OutputIO

    # Glue code for syntax differences
    def reraise(tp, value, tb=None):
        exec("raise tp, value, tb")

    def with_metaclass(meta, base=object):
        class _DjangoBase(base):
            __metaclass__ = meta
        return _DjangoBase

    iteritems = lambda o: o.iteritems()
    itervalues = lambda o: o.itervalues()
    iterkeys = lambda o: o.iterkeys()

    # n() is useful when python3 needs a str (unicode), and python2 str (bytes)
    n = lambda s: s.encode('utf-8')

else:
    PY3 = True
    import builtins

    # Changed module locations
    from urllib.parse import (urlparse, urlunparse, urlencode, urljoin,
                              urlsplit, urlunsplit, quote, unquote,
                              quote_plus, unquote_plus, parse_qsl,
                              urldefrag)
    from urllib.request import (urlopen, url2pathname, Request, OpenerDirector,
                                UnknownHandler, HTTPHandler, HTTPSHandler,
                                HTTPDefaultErrorHandler, FTPHandler,
                                HTTPError, HTTPErrorProcessor, urlretrieve)
    import urllib.request as urllib2
    import http.cookies as cookies
    import pickle
    try:
        import _thread as thread
    except ImportError:
        import _dummy_thread as thread
    from html.entities import name2codepoint
    import html.parser as HTMLParser
    from os import getcwd as getcwdu
    zip = zip
    unichr = chr
    xrange = range
    maxsize = sys.maxsize

    # Type aliases
    string_types = str,
    text_type = str
    integer_types = int,
    long_type = int

    from io import StringIO as OutputIO

    # Glue code for syntax differences
    def reraise(tp, value, tb=None):
        if value.__traceback__ is not tb:
            raise value.with_traceback(tb)
        raise value

    def with_metaclass(meta, base=object):
        ns = dict(base=base, meta=meta)
        exec("""class _DjangoBase(base, metaclass=meta):
    pass""", ns)
        return ns["_DjangoBase"]

    iteritems = lambda o: o.items()
    itervalues = lambda o: o.values()
    iterkeys = lambda o: o.keys()

    n = lambda s: s

django/utils/six.py

0 → 100644
+353 −0
Original line number Diff line number Diff line
"""Utilities for writing code that runs on Python 2 and 3"""

import operator
import sys
import types

__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.1.0"


# True if we are running on Python 3.
PY3 = sys.version_info[0] == 3

if PY3:
    string_types = str,
    integer_types = int,
    class_types = type,
    text_type = str
    binary_type = bytes

    MAXSIZE = sys.maxsize
else:
    string_types = basestring,
    integer_types = (int, long)
    class_types = (type, types.ClassType)
    text_type = unicode
    binary_type = str

    # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
    class X(object):
        def __len__(self):
            return 1 << 31
    try:
        len(X())
    except OverflowError:
        # 32-bit
        MAXSIZE = int((1 << 31) - 1)
    else:
        # 64-bit
        MAXSIZE = int((1 << 63) - 1)
    del X


def _add_doc(func, doc):
    """Add documentation to a function."""
    func.__doc__ = doc


def _import_module(name):
    """Import module, returning the module after the last dot."""
    __import__(name)
    return sys.modules[name]


class _LazyDescr(object):

    def __init__(self, name):
        self.name = name

    def __get__(self, obj, tp):
        result = self._resolve()
        setattr(obj, self.name, result)
        # This is a bit ugly, but it avoids running this again.
        delattr(tp, self.name)
        return result


class MovedModule(_LazyDescr):

    def __init__(self, name, old, new=None):
        super(MovedModule, self).__init__(name)
        if PY3:
            if new is None:
                new = name
            self.mod = new
        else:
            self.mod = old

    def _resolve(self):
        return _import_module(self.mod)


class MovedAttribute(_LazyDescr):

    def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
        super(MovedAttribute, self).__init__(name)
        if PY3:
            if new_mod is None:
                new_mod = name
            self.mod = new_mod
            if new_attr is None:
                if old_attr is None:
                    new_attr = name
                else:
                    new_attr = old_attr
            self.attr = new_attr
        else:
            self.mod = old_mod
            if old_attr is None:
                old_attr = name
            self.attr = old_attr

    def _resolve(self):
        module = _import_module(self.mod)
        return getattr(module, self.attr)



class _MovedItems(types.ModuleType):
    """Lazy loading of moved objects"""


_moved_attributes = [
    MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
    MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
    MovedAttribute("map", "itertools", "builtins", "imap", "map"),
    MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
    MovedAttribute("reduce", "__builtin__", "functools"),
    MovedAttribute("StringIO", "StringIO", "io"),
    MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
    MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),

    MovedModule("builtins", "__builtin__"),
    MovedModule("configparser", "ConfigParser"),
    MovedModule("copyreg", "copy_reg"),
    MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
    MovedModule("http_cookies", "Cookie", "http.cookies"),
    MovedModule("html_entities", "htmlentitydefs", "html.entities"),
    MovedModule("html_parser", "HTMLParser", "html.parser"),
    MovedModule("http_client", "httplib", "http.client"),
    MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
    MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
    MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
    MovedModule("cPickle", "cPickle", "pickle"),
    MovedModule("queue", "Queue"),
    MovedModule("reprlib", "repr"),
    MovedModule("socketserver", "SocketServer"),
    MovedModule("tkinter", "Tkinter"),
    MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
    MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
    MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
    MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
    MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
    MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
    MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
    MovedModule("tkinter_colorchooser", "tkColorChooser",
                "tkinter.colorchooser"),
    MovedModule("tkinter_commondialog", "tkCommonDialog",
                "tkinter.commondialog"),
    MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
    MovedModule("tkinter_font", "tkFont", "tkinter.font"),
    MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
    MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
                "tkinter.simpledialog"),
    MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
    MovedModule("winreg", "_winreg"),
]
for attr in _moved_attributes:
    setattr(_MovedItems, attr.name, attr)
del attr

moves = sys.modules["six.moves"] = _MovedItems("moves")


def add_move(move):
    """Add an item to six.moves."""
    setattr(_MovedItems, move.name, move)


def remove_move(name):
    """Remove item from six.moves."""
    try:
        delattr(_MovedItems, name)
    except AttributeError:
        try:
            del moves.__dict__[name]
        except KeyError:
            raise AttributeError("no such move, %r" % (name,))


if PY3:
    _meth_func = "__func__"
    _meth_self = "__self__"

    _func_code = "__code__"
    _func_defaults = "__defaults__"

    _iterkeys = "keys"
    _itervalues = "values"
    _iteritems = "items"
else:
    _meth_func = "im_func"
    _meth_self = "im_self"

    _func_code = "func_code"
    _func_defaults = "func_defaults"

    _iterkeys = "iterkeys"
    _itervalues = "itervalues"
    _iteritems = "iteritems"


if PY3:
    def get_unbound_function(unbound):
        return unbound


    advance_iterator = next

    def callable(obj):
        return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
else:
    def get_unbound_function(unbound):
        return unbound.im_func


    def advance_iterator(it):
        return it.next()

    callable = callable
_add_doc(get_unbound_function,
         """Get the function out of a possibly unbound function""")


get_method_function = operator.attrgetter(_meth_func)
get_method_self = operator.attrgetter(_meth_self)
get_function_code = operator.attrgetter(_func_code)
get_function_defaults = operator.attrgetter(_func_defaults)


def iterkeys(d):
    """Return an iterator over the keys of a dictionary."""
    return getattr(d, _iterkeys)()

def itervalues(d):
    """Return an iterator over the values of a dictionary."""
    return getattr(d, _itervalues)()

def iteritems(d):
    """Return an iterator over the (key, value) pairs of a dictionary."""
    return getattr(d, _iteritems)()


if PY3:
    def b(s):
        return s.encode("latin-1")
    def u(s):
        return s
    if sys.version_info[1] <= 1:
        def int2byte(i):
            return bytes((i,))
    else:
        # This is about 2x faster than the implementation above on 3.2+
        int2byte = operator.methodcaller("to_bytes", 1, "big")
    import io
    StringIO = io.StringIO
    BytesIO = io.BytesIO
else:
    def b(s):
        return s
    def u(s):
        return unicode(s, "unicode_escape")
    int2byte = chr
    import StringIO
    StringIO = BytesIO = StringIO.StringIO
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")


if PY3:
    import builtins
    exec_ = getattr(builtins, "exec")


    def reraise(tp, value, tb=None):
        if value.__traceback__ is not tb:
            raise value.with_traceback(tb)
        raise value


    print_ = getattr(builtins, "print")
    del builtins

else:
    def exec_(code, globs=None, locs=None):
        """Execute code in a namespace."""
        if globs is None:
            frame = sys._getframe(1)
            globs = frame.f_globals
            if locs is None:
                locs = frame.f_locals
            del frame
        elif locs is None:
            locs = globs
        exec("""exec code in globs, locs""")


    exec_("""def reraise(tp, value, tb=None):
    raise tp, value, tb
""")


    def print_(*args, **kwargs):
        """The new-style print function."""
        fp = kwargs.pop("file", sys.stdout)
        if fp is None:
            return
        def write(data):
            if not isinstance(data, basestring):
                data = str(data)
            fp.write(data)
        want_unicode = False
        sep = kwargs.pop("sep", None)
        if sep is not None:
            if isinstance(sep, unicode):
                want_unicode = True
            elif not isinstance(sep, str):
                raise TypeError("sep must be None or a string")
        end = kwargs.pop("end", None)
        if end is not None:
            if isinstance(end, unicode):
                want_unicode = True
            elif not isinstance(end, str):
                raise TypeError("end must be None or a string")
        if kwargs:
            raise TypeError("invalid keyword arguments to print()")
        if not want_unicode:
            for arg in args:
                if isinstance(arg, unicode):
                    want_unicode = True
                    break
        if want_unicode:
            newline = unicode("\n")
            space = unicode(" ")
        else:
            newline = "\n"
            space = " "
        if sep is None:
            sep = space
        if end is None:
            end = newline
        for i, arg in enumerate(args):
            if i:
                write(sep)
            write(arg)
        write(end)

_add_doc(reraise, """Reraise an exception.""")


def with_metaclass(meta, base=object):
    """Create a base class with a metaclass."""
    return meta("NewBase", (base,), {})
+25 −228
Original line number Diff line number Diff line
@@ -2,251 +2,48 @@
Python 3 compatibility
======================

Django 1.5 introduces a compatibility layer that allows the code to be run both
in Python 2 (2.6/2.7) and Python 3 (>= 3.2) (*work in progress*).
Django 1.5 is the first version of Django to support Python 3.

This document is not meant as a complete Python 2 to Python 3 migration guide.
There are many existing resources you can read. But we describe some utilities
and guidelines that we recommend you should use when you want to ensure your
code can be run with both Python 2 and 3.
The same code runs both on Python 2 (≥2.6.5) and Python 3 (≥3.2). To
achieve this:

* http://docs.python.org/py3k/howto/pyporting.html
* http://python3porting.com/
- wherever possible, Django uses the six_ compatibility layer,
- all modules declare ``from __future__ import unicode_literals``.

django.utils.py3
================
.. _six: http://packages.python.org/six/

This document is not meant as a Python 2 to Python 3 migration guide. There
are many existing resources, including `Python's official porting guide`_. But
it describes guidelines that apply to Django's code and are recommended for
pluggable apps that run with both Python 2 and 3.

Whenever a symbol or module has different semantics or different locations on
Python 2 and Python 3, you can import it from ``django.utils.py3`` where it
will be automatically converted depending on your current Python version.
.. _Python's official porting guide: http://docs.python.org/py3k/howto/pyporting.html

PY3
---
.. module: django.utils.six

If you need to know anywhere in your code if you are running Python 3 or a
previous Python 2 version, you can check the ``PY3`` boolean variable::
django.utils.six
================

    from django.utils.py3 import PY3
Read the documentation of six_. It's the canonical compatibility library for
supporting Python 2 and 3 in a single codebase.

    if PY3:
        # Do stuff Python 3-wise
    else:
        # Do stuff Python 2-wise
``six`` is bundled with Django: you can import it as :mod:`django.utils.six`.

This should be considered as a last resort solution when it is not possible
to import a compatible name from django.utils.py3, as described in the sections
below.
.. _string-handling:

String handling
===============

In Python 3, all strings are considered Unicode strings by default. Byte strings
have to be prefixed with the letter 'b'. To mimic the same behaviour in Python 2,
we recommend you import ``unicode_literals`` from the ``__future__`` library::
In Python 3, all strings are considered Unicode strings by default. Byte
strings must be prefixed with the letter ``b``. In order to enable the same
behavior in Python 2, every module must import ``unicode_literals`` from
``__future__``::

    from __future__ import unicode_literals

    my_string = "This is an unicode literal"
    my_bytestring = b"This is a bytestring"

Be cautious if you have to slice bytestrings.
See http://docs.python.org/py3k/howto/pyporting.html#bytes-literals

Different expected strings
--------------------------

Some method parameters have changed the expected string type of a parameter.
For example, ``strftime`` format parameter expects a bytestring on Python 2 but
a normal (Unicode) string on Python 3. For these cases, ``django.utils.py3``
provides a ``n()`` function which encodes the string parameter only with
Python 2.

    >>> from __future__ import unicode_literals
    >>> from datetime import datetime

    >>> print(datetime.date(2012, 5, 21).strftime(n("%m → %Y")))
    05 → 2012

Renamed types
=============

Several types are named differently in Python 2 and Python 3. In order to keep
compatibility while using those types, import their corresponding aliases from
``django.utils.py3``.

===========  =========  =====================
Python 2     Python 3   django.utils.py3
===========  =========  =====================
basestring,  str,       string_types (tuple)
unicode      str        text_type
int, long    int,       integer_types (tuple)
long         int        long_type
===========  =========  =====================

String aliases
--------------

Code sample::

    if isinstance(foo, basestring):
        print("foo is a string")

    # I want to convert a number to a Unicode string
    bar = 45
    bar_string = unicode(bar)

Should be replaced by::

    from django.utils.py3 import string_types, text_type

    if isinstance(foo, string_types):
        print("foo is a string")

    # I want to convert a number to a Unicode string
    bar = 45
    bar_string = text_type(bar)

No more long type
-----------------

``long`` and ``int`` types have been unified in Python 3, meaning that  ``long``
is no longer available. ``django.utils.py3`` provides both ``long_type`` and
``integer_types`` aliases. For example:

.. code-block:: python

    # Old Python 2 code
    my_var = long(333463247234623)
    if isinstance(my_var, (int, long)):
        # ...

Should be replaced by:

.. code-block:: python

    from django.utils.py3 import long_type, integer_types

    my_var = long_type(333463247234623)
    if isinstance(my_var, integer_types):
        # ...


Changed module locations
========================

The following modules have changed their location in Python 3. Therefore, it is
recommended to import them from the ``django.utils.py3`` compatibility layer:

=============================== ======================================  ======================
Python 2                        Python3                                 django.utils.py3
=============================== ======================================  ======================
Cookie                          http.cookies                            cookies

urlparse.urlparse               urllib.parse.urlparse                   urlparse
urlparse.urlunparse             urllib.parse.urlunparse                 urlunparse
urlparse.urljoin                urllib.parse.urljoin                    urljoin
urlparse.urlsplit               urllib.parse.urlsplit                   urlsplit
urlparse.urlunsplit             urllib.parse.urlunsplit                 urlunsplit
urlparse.urldefrag              urllib.parse.urldefrag                  urldefrag
urlparse.parse_qsl              urllib.parse.parse_qsl                  parse_qsl
urllib.quote                    urllib.parse.quote                      quote
urllib.unquote                  urllib.parse.unquote                    unquote
urllib.quote_plus               urllib.parse.quote_plus                 quote_plus
urllib.unquote_plus             urllib.parse.unquote_plus               unquote_plus
urllib.urlencode                urllib.parse.urlencode                  urlencode
urllib.urlopen                  urllib.request.urlopen                  urlopen
urllib.url2pathname             urllib.request.url2pathname             url2pathname
urllib.urlretrieve              urllib.request.urlretrieve              urlretrieve
urllib2                         urllib.request                          urllib2
urllib2.Request                 urllib.request.Request                  Request
urllib2.OpenerDirector          urllib.request.OpenerDirector           OpenerDirector
urllib2.UnknownHandler          urllib.request.UnknownHandler           UnknownHandler
urllib2.HTTPHandler             urllib.request.HTTPHandler              HTTPHandler
urllib2.HTTPSHandler            urllib.request.HTTPSHandler             HTTPSHandler
urllib2.HTTPDefaultErrorHandler urllib.request.HTTPDefaultErrorHandler  HTTPDefaultErrorHandler
urllib2.FTPHandler              urllib.request.FTPHandler               FTPHandler
urllib2.HTTPError               urllib.request.HTTPError                HTTPError
urllib2.HTTPErrorProcessor      urllib.request.HTTPErrorProcessor       HTTPErrorProcessor

htmlentitydefs.name2codepoint   html.entities.name2codepoint            name2codepoint
HTMLParser                      html.parser                             HTMLParser
cPickle/pickle                  pickle                                  pickle
thread/dummy_thread             _thread/_dummy_thread                   thread

os.getcwdu                      os.getcwd                               getcwdu
itertools.izip                  zip                                     zip
sys.maxint                      sys.maxsize                             maxsize
unichr                          chr                                     unichr
xrange                          range                                   xrange
=============================== ======================================  ======================


Output encoding now Unicode
===========================

If you want to catch stdout/stderr output, the output content is UTF-8 encoded
in Python 2, while it is Unicode strings in Python 3. You can use the OutputIO
stream to capture this output::

    from django.utils.py3 import OutputIO

    try:
        old_stdout = sys.stdout
        out = OutputIO()
        sys.stdout = out
        # Do stuff which produces standard output
        result = out.getvalue()
    finally:
        sys.stdout = old_stdout

Dict iteritems/itervalues/iterkeys
==================================

The iteritems(), itervalues() and iterkeys() methods of dictionaries do not
exist any more in Python 3, simply because they represent the default items()
values() and keys() behavior in Python 3. Therefore, to keep compatibility,
use similar functions from ``django.utils.py3``::

    from django.utils.py3 import iteritems, itervalues, iterkeys

    my_dict = {'a': 21, 'b': 42}
    for key, value in iteritems(my_dict):
        # ...
    for value in itervalues(my_dict):
        # ...
    for key in iterkeys(my_dict):
        # ...

Note that in Python 3, dict.keys(), dict.items() and dict.values() return
"views" instead of lists. Wrap them into list() if you really need their return
values to be in a list.

http://docs.python.org/release/3.0.1/whatsnew/3.0.html#views-and-iterators-instead-of-lists

Metaclass
=========

The syntax for declaring metaclasses has changed in Python 3.
``django.utils.py3`` offers a compatible way to declare metaclasses::

    from django.utils.py3 import with_metaclass

    class MyClass(with_metaclass(SubClass1, SubClass2,...)):
        # ...

Re-raising exceptions
=====================

One of the syntaxes to raise exceptions (raise E, V, T) is gone in Python 3.
This is especially used in very specific cases where you want to re-raise a
different exception that the initial one, while keeping the original traceback.
So, instead of::

    raise Exception, Exception(msg), traceback

Use::

    from django.utils.py3 import reraise

    reraise(Exception, Exception(msg), traceback)
Be cautious if you have to `slice bytestrings`_.

.. _slice bytestrings: http://docs.python.org/py3k/howto/pyporting.html#bytes-literals