Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

[py3] Bundled six for Python 3 compatibility.

Refs #18363.
  • Loading branch information...
commit 8b0190984116873158ee627c7a033a7bd4c3a199 1 parent 01c3926
@aaugustin aaugustin authored
Showing with 378 additions and 337 deletions.
  1. +0 −109 django/utils/py3.py
  2. +353 −0 django/utils/six.py
  3. +25 −228 docs/topics/python3.txt
View
109 django/utils/py3.py
@@ -1,109 +0,0 @@
-# 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
View
353 django/utils/six.py
@@ -0,0 +1,353 @@
+"""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,), {})
View
253 docs/topics/python3.txt
@@ -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
Please sign in to comment.
Something went wrong with that request. Please try again.