Ticket #18363: py3_layer.diff

File py3_layer.diff, 14.3 KB (added by Claude Paroz, 12 years ago)

Add Python 3 compatibility layer

  • new file django/utils/py3.py

    diff --git a/django/utils/py3.py b/django/utils/py3.py
    new file mode 100644
    index 0000000..ff31468
    - +  
     1# Compatibility layer for running Django both in 2.x and 3.x
     2
     3import sys
     4
     5if sys.version_info[0] < 3:
     6    PY3 = False
     7    # Changed module locations
     8    from urlparse import (urlparse, urlunparse, urljoin, urlsplit, urlunsplit,
     9                          urldefrag, parse_qsl)
     10    from urllib import (quote, unquote, quote_plus, urlopen, urlencode,
     11                        url2pathname, urlretrieve, unquote_plus)
     12    from urllib2 import (Request, OpenerDirector, UnknownHandler, HTTPHandler,
     13                         HTTPSHandler, HTTPDefaultErrorHandler, FTPHandler,
     14                         HTTPError, HTTPErrorProcessor)
     15    import urllib2
     16    import Cookie as cookies
     17    try:
     18        import cPickle as pickle
     19    except ImportError:
     20        import pickle
     21    try:
     22        import thread
     23    except ImportError:
     24        import dummy_thread as thread
     25    from htmlentitydefs import name2codepoint
     26    import HTMLParser
     27    from os import getcwdu
     28    from itertools import izip as zip
     29    unichr = unichr
     30    xrange = xrange
     31    maxsize = sys.maxint
     32
     33    # Type aliases
     34    string_types = basestring,
     35    text_type = unicode
     36    integer_types = int, long
     37    long_type = long
     38
     39    from io import BytesIO as OutputIO
     40
     41    # Glue code for syntax differences
     42    def exec_(code, globs=None, locs=None):
     43        """Execute code in a namespace."""
     44        if globs is None:
     45            frame = sys._getframe(1)
     46            globs = frame.f_globals
     47            if locs is None:
     48                locs = frame.f_locals
     49            del frame
     50        elif locs is None:
     51            locs = globs
     52        exec("""exec code in globs, locs""")
     53    exec_("""def reraise(tp, value, tb=None):
     54    raise tp, value, tb
     55""")
     56
     57    def with_metaclass(meta, base=object):
     58        class _DjangoBase(base):
     59            __metaclass__ = meta
     60        return _DjangoBase
     61
     62    iteritems = lambda o: o.iteritems()
     63    itervalues = lambda o: o.itervalues()
     64    iterkeys = lambda o: o.iterkeys()
     65
     66    # n() is useful when python3 needs a str (unicode), and python2 str (bytes)
     67    def n(s, encoding='utf-8'):
     68        return s.encode(encoding)
     69
     70else:
     71    PY3 = True
     72    import builtins
     73
     74    # Changed module locations
     75    from urllib.parse import (urlparse, urlunparse, urlencode, urljoin,
     76                              urlsplit, urlunsplit, quote, unquote,
     77                              quote_plus, unquote_plus, parse_qsl,
     78                              urldefrag)
     79    from urllib.request import (urlopen, url2pathname, Request, OpenerDirector,
     80                                UnknownHandler, HTTPHandler, HTTPSHandler,
     81                                HTTPDefaultErrorHandler, FTPHandler,
     82                                HTTPError, HTTPErrorProcessor, urlretrieve)
     83    import urllib.request as urllib2
     84    import http.cookies as cookies
     85    import pickle
     86    try:
     87        import _thread as thread
     88    except ImportError:
     89        import _dummy_thread as thread
     90    from html.entities import name2codepoint
     91    import html.parser as HTMLParser
     92    from os import getcwd as getcwdu
     93    zip = zip
     94    unichr = chr
     95    xrange = range
     96    maxsize = sys.maxsize
     97
     98    # Type aliases
     99    string_types = str,
     100    text_type = str
     101    integer_types = int,
     102    long_type = int
     103
     104    from io import StringIO as OutputIO
     105
     106    # Glue code for syntax differences
     107    def reraise(tp, value, tb=None):
     108        if value.__traceback__ is not tb:
     109            raise value.with_traceback(tb)
     110        raise value
     111
     112    exec_ = getattr(builtins, 'exec')
     113    def with_metaclass(meta, base=object):
     114        ns = dict(base=base, meta=meta)
     115        exec_("""class _DjangoBase(base, metaclass=meta):
     116    pass""", ns)
     117        return ns["_DjangoBase"]
     118
     119    iteritems = lambda o: o.items()
     120    itervalues = lambda o: o.values()
     121    iterkeys = lambda o: o.keys()
     122
     123    n = lambda s: s
  • docs/index.txt

    diff --git a/docs/index.txt b/docs/index.txt
    index d596511..d85aabe 100644
    a b Other batteries included  
    183183* :doc:`Logging <topics/logging>`
    184184* :doc:`Messages <ref/contrib/messages>`
    185185* :doc:`Pagination <topics/pagination>`
     186* :doc:`Python 3 compatibility <topics/python3>`
    186187* :doc:`Redirects <ref/contrib/redirects>`
    187188* :doc:`Security <topics/security>`
    188189* :doc:`Serialization <topics/serialization>`
  • docs/topics/index.txt

    diff --git a/docs/topics/index.txt b/docs/topics/index.txt
    index 95ca76e..00647bc 100644
    a b Introductions to all the key parts of Django you'll need to know:  
    2222   i18n/index
    2323   logging
    2424   pagination
     25   python3
    2526   security
    2627   serialization
    2728   settings
  • new file docs/topics/python3.txt

    diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt
    new file mode 100644
    index 0000000..50d1a99
    - +  
     1======================
     2Python 3 compatibility
     3======================
     4
     5Django 1.5 introduces a compatibility layer that allows the code to be run both
     6in Python 2 (2.6/2.7) and Python 3 (>= 3.2) (*work in progress*).
     7
     8This document is not meant as a complete Python 2 to Python 3 migration guide.
     9There are many existing resources you can read. But we describe some utilities
     10and guidelines that we recommend you should use when you want to ensure your
     11code can be run with both Python 2 and 3.
     12
     13* http://docs.python.org/py3k/howto/pyporting.html
     14* http://python3porting.com/
     15
     16django.utils.py3
     17================
     18
     19Whenever a symbol or module has different semantics or different locations on
     20Python 2 and Python 3, you can import it from ``django.utils.py3`` where it
     21will be automatically converted depending on your current Python version.
     22
     23PY3
     24---
     25
     26If you need to know anywhere in your code if you are running Python 3 or a
     27previous Python 2 version, you can check the ``PY3`` boolean variable::
     28
     29    from django.utils.py3 import PY3
     30
     31    if PY3:
     32        # Do stuff Python 3-wise
     33    else:
     34        # Do stuff Python 2-wise
     35
     36String handling
     37===============
     38
     39In Python 3, all strings are considered Unicode strings by default. Byte strings
     40have to be prefixed with the letter 'b'. To mimic the same behaviour in Python 2,
     41we recommend you import ``unicode_literals`` from the ``__future__`` library::
     42
     43    from __future__ import unicode_literals
     44
     45    my_string = "This is an unicode literal"
     46    my_bytestring = b"This is a bytestring"
     47
     48Be cautious if you have to slice bytestrings.
     49See http://docs.python.org/py3k/howto/pyporting.html#bytes-literals
     50
     51Different expected strings
     52--------------------------
     53
     54Some method parameters have changed the expected string type of a parameter.
     55For example, ``strftime`` format parameter expects a bytestring on Python 2 but
     56a normal (Unicode) string on Python 3. For these cases, ``django.utils.py3``
     57provides a ``n()`` function which encodes the string parameter only with
     58Python 2.
     59
     60    >>> from __future__ import unicode_literals
     61    >>> from datetime import datetime
     62
     63    >>> print(datetime.date(2012, 5, 21).strftime(n("%m → %Y")))
     64    05 → 2012
     65
     66Renamed types
     67=============
     68
     69Several types are named differently in Python 2 and Python 3. In order to keep
     70compatibility while using those types, import their corresponding aliases from
     71``django.utils.py3``.
     72
     73===========  =========  =====================
     74Python 2     Python 3   django.utils.py3
     75===========  =========  =====================
     76basestring,  str,       string_types (tuple)
     77unicode      str        text_type
     78int, long    int,       integer_types (tuple)
     79long         int        long_type
     80===========  =========  =====================
     81
     82String aliases
     83--------------
     84
     85Code sample::
     86
     87    if isinstance(foo, basestring):
     88        print("foo is a string")
     89
     90    # I want to convert a number to a Unicode string
     91    bar = 45
     92    bar_string = unicode(bar)
     93
     94Should be replaced by::
     95
     96    from django.utils.py3 import string_types, text_type
     97
     98    if isinstance(foo, string_types):
     99        print("foo is a string")
     100
     101    # I want to convert a number to a Unicode string
     102    bar = 45
     103    bar_string = text_type(bar)
     104
     105No more long type
     106-----------------
     107``long`` and ``int`` types have been unified in Python 3, meaning that  ``long``
     108is no more available. ``django.utils.py3`` provides both ``long_type`` and
     109``integer_types`` aliases. For example:
     110
     111.. code-block:: python
     112
     113    # Old Python 2 code
     114    my_var = long(333463247234623)
     115    if isinstance(my_var, (int, long)):
     116        # ...
     117
     118Should be replaced by:
     119
     120.. code-block:: python
     121
     122    from django.utils.py3 import long_type, integer_types
     123
     124    my_var = long_type(333463247234623)
     125    if isinstance(my_var, integer_types):
     126        # ...
     127
     128
     129Changed module locations
     130========================
     131
     132The following modules have changed their location in Python 3. Therefore, it is
     133recommended to import them from the ``django.utils.py3`` compatibility layer:
     134
     135=============================== ======================================  ======================
     136Python 2                        Python3                                 django.utils.py3
     137=============================== ======================================  ======================
     138Cookie                          http.cookies                            cookies
     139
     140urlparse.urlparse               urllib.parse.urlparse                   urlparse
     141urlparse.urlunparse             urllib.parse.urlunparse                 urlunparse
     142urlparse.urljoin                urllib.parse.urljoin                    urljoin
     143urlparse.urlsplit               urllib.parse.urlsplit                   urlsplit
     144urlparse.urlunsplit             urllib.parse.urlunsplit                 urlunsplit
     145urlparse.urldefrag              urllib.parse.urldefrag                  urldefrag
     146urlparse.parse_qsl              urllib.parse.parse_qsl                  parse_qsl
     147urllib.quote                    urllib.parse.quote                      quote
     148urllib.unquote                  urllib.parse.unquote                    unquote
     149urllib.quote_plus               urllib.parse.quote_plus                 quote_plus
     150urllib.unquote_plus             urllib.parse.unquote_plus               unquote_plus
     151urllib.urlencode                urllib.parse.urlencode                  urlencode
     152urllib.urlopen                  urllib.request.urlopen                  urlopen
     153urllib.url2pathname             urllib.request.url2pathname             url2pathname
     154urllib.urlretrieve              urllib.request.urlretrieve              urlretrieve
     155urllib2                         urllib.request                          urllib2
     156urllib2.Request                 urllib.request.Request                  Request
     157urllib2.OpenerDirector          urllib.request.OpenerDirector           OpenerDirector
     158urllib2.UnknownHandler          urllib.request.UnknownHandler           UnknownHandler
     159urllib2.HTTPHandler             urllib.request.HTTPHandler              HTTPHandler
     160urllib2.HTTPSHandler            urllib.request.HTTPSHandler             HTTPSHandler
     161urllib2.HTTPDefaultErrorHandler urllib.request.HTTPDefaultErrorHandler  HTTPDefaultErrorHandler
     162urllib2.FTPHandler              urllib.request.FTPHandler               FTPHandler
     163urllib2.HTTPError               urllib.request.HTTPError                HTTPError
     164urllib2.HTTPErrorProcessor      urllib.request.HTTPErrorProcessor       HTTPErrorProcessor
     165
     166htmlentitydefs.name2codepoint   html.entities.name2codepoint            name2codepoint
     167HTMLParser                      html.parser                             HTMLParser
     168cPickle/pickle                  pickle                                  pickle
     169thread/dummy_thread             _thread/_dummy_thread                   thread
     170
     171os.getcwdu                      os.getcwd                               getcwdu
     172itertools.izip                  zip                                     zip
     173sys.maxint                      sys.maxsize                             maxsize
     174unichr                          chr                                     unichr
     175xrange                          range                                   xrange
     176=============================== ======================================  ======================
     177
     178
     179Ouptut encoding now Unicode
     180===========================
     181
     182If you want to catch stdout/stderr output, the output content is UTF-8 encoded
     183in Python 2, while it is Unicode strings in Python 3. You can use the OutputIO
     184stream to capture this output::
     185
     186    from django.utils.py3 import OutputIO
     187
     188    try:
     189        old_stdout = sys.stdout
     190        out = OutputIO()
     191        sys.stdout = out
     192        # Do stuff which produces standard output
     193        result = out.getvalue()
     194    finally:
     195        sys.stdout = old_stdout
     196
     197Dict iteritems/itervalues/iterkeys
     198==================================
     199
     200The iteritems(), itervalues() and iterkeys() methods of dictionaries do not
     201exist any more in Python 3, simply because they represent the default items()
     202values() and keys() behavior in Python 3. Therefore, to keep compatibility,
     203use similar functions from ``django.utils.py3``::
     204
     205    from django.utils.py3 import iteritems, itervalues, iterkeys
     206
     207    my_dict = {'a': 21, 'b': 42}
     208    for key, value in iteritems(my_dict):
     209        # ...
     210    for value in itervalues(my_dict):
     211        # ...
     212    for key in iterkeys(my_dict):
     213        # ...
     214
     215Note that in Python 3, dict.keys(), dict.items() and dict.values() return
     216"views" instead of lists. Wrap them into list() if you really need their return
     217values to be in a list.
     218
     219http://docs.python.org/release/3.0.1/whatsnew/3.0.html#views-and-iterators-instead-of-lists
     220
     221Metaclass
     222=========
     223
     224The syntax for declaring metaclasses has changed in Python 3.
     225``django.utils.py3`` offers a compatible way to declare metaclasses::
     226
     227    from django.utils.py3 import with_metaclass
     228
     229    class MyClass(with_metaclass(SubClass1, SubClass2,...)):
     230        # ...
     231
     232Re-raising exceptions
     233=====================
     234
     235One of the syntaxes to raise exceptions (raise E, V, T) is gone in Python 3.
     236This is especially used in very specific cases where you want to re-raise a
     237different exception that the initial one, while keeping the original traceback.
     238So, instead of::
     239
     240    raise Exception, Exception(msg), traceback
     241
     242Use::
     243
     244    from django.utils.py3 import reraise
     245
     246    reraise(Exception, Exception(msg), traceback)
     247
Back to Top