summaryrefslogtreecommitdiff
path: root/django/utils
diff options
context:
space:
mode:
authorAndrew Godwin <andrew@aeracode.org>2012-07-26 18:58:10 +0100
committerAndrew Godwin <andrew@aeracode.org>2012-07-26 18:58:10 +0100
commit4a2e80fff44d0eb1856b593ac5f31ab1492b3e45 (patch)
tree9bc87a682dc488e6555792c1d4bb53f5f3cdc880 /django/utils
parent959a3f9791d780062c4efe8765404a8ef95e87f0 (diff)
parentab6cd1c839b136cbc94178da433b2e97ab7f6061 (diff)
Merge branch 'master' of github.com:django/django into schema-alteration
Conflicts: django/db/backends/postgresql_psycopg2/base.py
Diffstat (limited to 'django/utils')
-rw-r--r--django/utils/archive.py4
-rw-r--r--django/utils/autoreload.py2
-rw-r--r--django/utils/baseconv.py2
-rw-r--r--django/utils/checksums.py4
-rw-r--r--django/utils/crypto.py7
-rw-r--r--django/utils/daemonize.py4
-rw-r--r--django/utils/datastructures.py186
-rw-r--r--django/utils/dateformat.py5
-rw-r--r--django/utils/dictconfig.py4
-rw-r--r--django/utils/encoding.py61
-rw-r--r--django/utils/feedgenerator.py11
-rw-r--r--django/utils/formats.py11
-rw-r--r--django/utils/functional.py11
-rw-r--r--django/utils/html.py77
-rw-r--r--django/utils/html_parser.py24
-rw-r--r--django/utils/http.py31
-rw-r--r--django/utils/importlib.py2
-rw-r--r--django/utils/ipv6.py1
-rw-r--r--django/utils/itercompat.py6
-rw-r--r--django/utils/numberformat.py5
-rw-r--r--django/utils/py3.py109
-rw-r--r--django/utils/regex_helper.py4
-rw-r--r--django/utils/safestring.py33
-rw-r--r--django/utils/six.py367
-rw-r--r--django/utils/text.py29
-rw-r--r--django/utils/timezone.py5
-rw-r--r--django/utils/translation/__init__.py15
-rw-r--r--django/utils/translation/trans_real.py2
28 files changed, 684 insertions, 338 deletions
diff --git a/django/utils/archive.py b/django/utils/archive.py
index 70e1f9ba59..6b5d73290f 100644
--- a/django/utils/archive.py
+++ b/django/utils/archive.py
@@ -27,6 +27,8 @@ import sys
import tarfile
import zipfile
+from django.utils import six
+
class ArchiveException(Exception):
"""
@@ -58,7 +60,7 @@ class Archive(object):
@staticmethod
def _archive_cls(file):
cls = None
- if isinstance(file, basestring):
+ if isinstance(file, six.string_types):
filename = file
else:
try:
diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py
index 85d9907856..b6c055383c 100644
--- a/django/utils/autoreload.py
+++ b/django/utils/autoreload.py
@@ -33,7 +33,7 @@ import os, sys, time, signal
try:
import thread
except ImportError:
- import dummy_thread as thread
+ from django.utils.six.moves import _dummy_thread as thread
# This import does nothing, but it's necessary to avoid some race conditions
# in the threading module. See http://code.djangoproject.com/ticket/2330 .
diff --git a/django/utils/baseconv.py b/django/utils/baseconv.py
index 8a6181bb2d..053ce3e97e 100644
--- a/django/utils/baseconv.py
+++ b/django/utils/baseconv.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2010 Taurinus Collective. All rights reserved.
+# Copyright (c) 2010 Guilherme Gondim. All rights reserved.
# Copyright (c) 2009 Simon Willison. All rights reserved.
# Copyright (c) 2002 Drew Perttula. All rights reserved.
#
diff --git a/django/utils/checksums.py b/django/utils/checksums.py
index 970f563f38..6bbdccc58c 100644
--- a/django/utils/checksums.py
+++ b/django/utils/checksums.py
@@ -4,6 +4,8 @@ Common checksum routines (used in multiple localflavor/ cases, for example).
__all__ = ['luhn',]
+from django.utils import six
+
LUHN_ODD_LOOKUP = (0, 2, 4, 6, 8, 1, 3, 5, 7, 9) # sum_of_digits(index * 2)
def luhn(candidate):
@@ -12,7 +14,7 @@ def luhn(candidate):
algorithm (used in validation of, for example, credit cards).
Both numeric and string candidates are accepted.
"""
- if not isinstance(candidate, basestring):
+ if not isinstance(candidate, six.string_types):
candidate = str(candidate)
try:
evens = sum([int(c) for c in candidate[-1::-2]])
diff --git a/django/utils/crypto.py b/django/utils/crypto.py
index cc59e2e39f..9d46bdd793 100644
--- a/django/utils/crypto.py
+++ b/django/utils/crypto.py
@@ -24,10 +24,11 @@ except NotImplementedError:
from django.conf import settings
from django.utils.encoding import smart_str
+from django.utils.six.moves import xrange
-_trans_5c = b"".join([chr(x ^ 0x5C) for x in xrange(256)])
-_trans_36 = b"".join([chr(x ^ 0x36) for x in xrange(256)])
+_trans_5c = bytearray([(x ^ 0x5C) for x in xrange(256)])
+_trans_36 = bytearray([(x ^ 0x36) for x in xrange(256)])
def salted_hmac(key_salt, value, secret=None):
@@ -98,7 +99,7 @@ def _bin_to_long(x):
This is a clever optimization for fast xor vector math
"""
- return long(x.encode('hex'), 16)
+ return int(x.encode('hex'), 16)
def _long_to_bin(x, hex_format_string):
diff --git a/django/utils/daemonize.py b/django/utils/daemonize.py
index a9d5128b33..763a9db752 100644
--- a/django/utils/daemonize.py
+++ b/django/utils/daemonize.py
@@ -3,7 +3,7 @@ import sys
if os.name == 'posix':
def become_daemon(our_home_dir='.', out_log='/dev/null',
- err_log='/dev/null', umask=022):
+ err_log='/dev/null', umask=0o022):
"Robustly turn into a UNIX daemon, running in our_home_dir."
# First fork
try:
@@ -33,7 +33,7 @@ if os.name == 'posix':
# Set custom file descriptors so that they get proper buffering.
sys.stdout, sys.stderr = so, se
else:
- def become_daemon(our_home_dir='.', out_log=None, err_log=None, umask=022):
+ def become_daemon(our_home_dir='.', out_log=None, err_log=None, umask=0o022):
"""
If we're not running under a POSIX system, just simulate the daemon
mode by doing redirections and directory changing.
diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py
index f7042f7061..bbd31ad36c 100644
--- a/django/utils/datastructures.py
+++ b/django/utils/datastructures.py
@@ -1,5 +1,8 @@
import copy
+import warnings
from types import GeneratorType
+from django.utils import six
+
class MergeDict(object):
"""
@@ -29,38 +32,48 @@ class MergeDict(object):
except KeyError:
return default
+ # This is used by MergeDicts of MultiValueDicts.
def getlist(self, key):
for dict_ in self.dicts:
- if key in dict_.keys():
+ if key in dict_:
return dict_.getlist(key)
return []
- def iteritems(self):
+ def _iteritems(self):
seen = set()
for dict_ in self.dicts:
- for item in dict_.iteritems():
- k, v = item
+ for item in six.iteritems(dict_):
+ k = item[0]
if k in seen:
continue
seen.add(k)
yield item
- def iterkeys(self):
- for k, v in self.iteritems():
+ def _iterkeys(self):
+ for k, v in self._iteritems():
yield k
- def itervalues(self):
- for k, v in self.iteritems():
+ def _itervalues(self):
+ for k, v in self._iteritems():
yield v
- def items(self):
- return list(self.iteritems())
+ if six.PY3:
+ items = _iteritems
+ keys = _iterkeys
+ values = _itervalues
+ else:
+ iteritems = _iteritems
+ iterkeys = _iterkeys
+ itervalues = _itervalues
- def keys(self):
- return list(self.iterkeys())
+ def items(self):
+ return list(self.iteritems())
- def values(self):
- return list(self.itervalues())
+ def keys(self):
+ return list(self.iterkeys())
+
+ def values(self):
+ return list(self.itervalues())
def has_key(self, key):
for dict_ in self.dicts:
@@ -69,7 +82,8 @@ class MergeDict(object):
return False
__contains__ = has_key
- __iter__ = iterkeys
+
+ __iter__ = _iterkeys
def copy(self):
"""Returns a copy of this object."""
@@ -115,7 +129,7 @@ class SortedDict(dict):
data = list(data)
super(SortedDict, self).__init__(data)
if isinstance(data, dict):
- self.keyOrder = data.keys()
+ self.keyOrder = list(six.iterkeys(data))
else:
self.keyOrder = []
seen = set()
@@ -126,7 +140,7 @@ class SortedDict(dict):
def __deepcopy__(self, memo):
return self.__class__([(key, copy.deepcopy(value, memo))
- for key, value in self.iteritems()])
+ for key, value in six.iteritems(self)])
def __copy__(self):
# The Python's default copy implementation will alter the state
@@ -160,28 +174,38 @@ class SortedDict(dict):
self.keyOrder.remove(result[0])
return result
- def items(self):
- return zip(self.keyOrder, self.values())
-
- def iteritems(self):
+ def _iteritems(self):
for key in self.keyOrder:
yield key, self[key]
- def keys(self):
- return self.keyOrder[:]
-
- def iterkeys(self):
- return iter(self.keyOrder)
-
- def values(self):
- return map(self.__getitem__, self.keyOrder)
+ def _iterkeys(self):
+ for key in self.keyOrder:
+ yield key
- def itervalues(self):
+ def _itervalues(self):
for key in self.keyOrder:
yield self[key]
+ if six.PY3:
+ items = _iteritems
+ keys = _iterkeys
+ values = _itervalues
+ else:
+ iteritems = _iteritems
+ iterkeys = _iterkeys
+ itervalues = _itervalues
+
+ def items(self):
+ return list(self.iteritems())
+
+ def keys(self):
+ return list(self.iterkeys())
+
+ def values(self):
+ return list(self.itervalues())
+
def update(self, dict_):
- for k, v in dict_.iteritems():
+ for k, v in six.iteritems(dict_):
self[k] = v
def setdefault(self, key, default):
@@ -191,10 +215,21 @@ class SortedDict(dict):
def value_for_index(self, index):
"""Returns the value of the item at the given zero-based index."""
+ # This, and insert() are deprecated because they cannot be implemented
+ # using collections.OrderedDict (Python 2.7 and up), which we'll
+ # eventually switch to
+ warnings.warn(
+ "SortedDict.value_for_index is deprecated", PendingDeprecationWarning,
+ stacklevel=2
+ )
return self[self.keyOrder[index]]
def insert(self, index, key, value):
"""Inserts the key, value pair before the item with the given index."""
+ warnings.warn(
+ "SortedDict.insert is deprecated", PendingDeprecationWarning,
+ stacklevel=2
+ )
if key in self.keyOrder:
n = self.keyOrder.index(key)
del self.keyOrder[n]
@@ -213,7 +248,7 @@ class SortedDict(dict):
Replaces the normal dict.__repr__ with a version that returns the keys
in their sorted order.
"""
- return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()])
+ return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in six.iteritems(self)])
def clear(self):
super(SortedDict, self).clear()
@@ -326,7 +361,8 @@ class MultiValueDict(dict):
def setdefault(self, key, default=None):
if key not in self:
self[key] = default
- return default
+ # Do not return default here because __setitem__() may store
+ # another value -- QueryDict.__setitem__() does. Look it up.
return self[key]
def setlistdefault(self, key, default_list=None):
@@ -334,45 +370,49 @@ class MultiValueDict(dict):
if default_list is None:
default_list = []
self.setlist(key, default_list)
- return default_list
+ # Do not return default_list here because setlist() may store
+ # another value -- QueryDict.setlist() does. Look it up.
return self.getlist(key)
def appendlist(self, key, value):
"""Appends an item to the internal list associated with key."""
self.setlistdefault(key).append(value)
- def items(self):
- """
- Returns a list of (key, value) pairs, where value is the last item in
- the list associated with the key.
- """
- return [(key, self[key]) for key in self.keys()]
-
- def iteritems(self):
+ def _iteritems(self):
"""
Yields (key, value) pairs, where value is the last item in the list
associated with the key.
"""
- for key in self.keys():
- yield (key, self[key])
-
- def lists(self):
- """Returns a list of (key, list) pairs."""
- return super(MultiValueDict, self).items()
+ for key in self:
+ yield key, self[key]
- def iterlists(self):
+ def _iterlists(self):
"""Yields (key, list) pairs."""
- return super(MultiValueDict, self).iteritems()
+ return six.iteritems(super(MultiValueDict, self))
- def values(self):
- """Returns a list of the last value on every key list."""
- return [self[key] for key in self.keys()]
-
- def itervalues(self):
+ def _itervalues(self):
"""Yield the last value on every key list."""
- for key in self.iterkeys():
+ for key in self:
yield self[key]
+ if six.PY3:
+ items = _iteritems
+ lists = _iterlists
+ values = _itervalues
+ else:
+ iteritems = _iteritems
+ iterlists = _iterlists
+ itervalues = _itervalues
+
+ def items(self):
+ return list(self.iteritems())
+
+ def lists(self):
+ return list(self.iterlists())
+
+ def values(self):
+ return list(self.itervalues())
+
def copy(self):
"""Returns a shallow copy of this object."""
return copy.copy(self)
@@ -395,7 +435,7 @@ class MultiValueDict(dict):
self.setlistdefault(key).append(value)
except TypeError:
raise ValueError("MultiValueDict.update() takes either a MultiValueDict or dictionary")
- for key, value in kwargs.iteritems():
+ for key, value in six.iteritems(kwargs):
self.setlistdefault(key).append(value)
def dict(self):
@@ -404,38 +444,6 @@ class MultiValueDict(dict):
"""
return dict((key, self[key]) for key in self)
-class DotExpandedDict(dict):
- """
- A special dictionary constructor that takes a dictionary in which the keys
- may contain dots to specify inner dictionaries. It's confusing, but this
- example should make sense.
-
- >>> d = DotExpandedDict({'person.1.firstname': ['Simon'], \
- 'person.1.lastname': ['Willison'], \
- 'person.2.firstname': ['Adrian'], \
- 'person.2.lastname': ['Holovaty']})
- >>> d
- {'person': {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}}
- >>> d['person']
- {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}
- >>> d['person']['1']
- {'lastname': ['Willison'], 'firstname': ['Simon']}
-
- # Gotcha: Results are unpredictable if the dots are "uneven":
- >>> DotExpandedDict({'c.1': 2, 'c.2': 3, 'c': 1})
- {'c': 1}
- """
- def __init__(self, key_to_list_mapping):
- for k, v in key_to_list_mapping.items():
- current = self
- bits = k.split('.')
- for bit in bits[:-1]:
- current = current.setdefault(bit, {})
- # Now assign value to current position
- try:
- current[bits[-1]] = v
- except TypeError: # Special-case if current isn't a dict.
- current = {bits[-1]: v}
class ImmutableList(tuple):
"""
diff --git a/django/utils/dateformat.py b/django/utils/dateformat.py
index d410bce63e..c9a6138aed 100644
--- a/django/utils/dateformat.py
+++ b/django/utils/dateformat.py
@@ -21,6 +21,7 @@ from django.utils.dates import MONTHS, MONTHS_3, MONTHS_ALT, MONTHS_AP, WEEKDAYS
from django.utils.tzinfo import LocalTimezone
from django.utils.translation import ugettext as _
from django.utils.encoding import force_unicode
+from django.utils import six
from django.utils.timezone import is_aware, is_naive
re_formatchars = re.compile(r'(?<!\\)([aAbBcdDeEfFgGhHiIjlLmMnNoOPrsStTUuwWyYzZ])')
@@ -236,7 +237,7 @@ class DateFormat(TimeFormat):
name = self.timezone and self.timezone.tzname(self.data) or None
if name is None:
name = self.format('O')
- return unicode(name)
+ return six.text_type(name)
def U(self):
"Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)"
@@ -277,7 +278,7 @@ class DateFormat(TimeFormat):
def y(self):
"Year, 2 digits; e.g. '99'"
- return unicode(self.data.year)[2:]
+ return six.text_type(self.data.year)[2:]
def Y(self):
"Year, 4 digits; e.g. '1999'"
diff --git a/django/utils/dictconfig.py b/django/utils/dictconfig.py
index ae797afcc5..b4d6d66b3c 100644
--- a/django/utils/dictconfig.py
+++ b/django/utils/dictconfig.py
@@ -23,6 +23,8 @@ import re
import sys
import types
+from django.utils import six
+
IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
def valid_ident(s):
@@ -231,7 +233,7 @@ class BaseConfigurator(object):
isinstance(value, tuple):
value = ConvertingTuple(value)
value.configurator = self
- elif isinstance(value, basestring): # str for py3k
+ elif isinstance(value, six.string_types): # str for py3k
m = self.CONVERT_PATTERN.match(value)
if m:
d = m.groupdict()
diff --git a/django/utils/encoding.py b/django/utils/encoding.py
index 80e456ba2a..f2295444bf 100644
--- a/django/utils/encoding.py
+++ b/django/utils/encoding.py
@@ -1,12 +1,16 @@
from __future__ import unicode_literals
-import urllib
-import locale
-import datetime
import codecs
+import datetime
from decimal import Decimal
+import locale
+try:
+ from urllib.parse import quote
+except ImportError: # Python 2
+ from urllib import quote
from django.utils.functional import Promise
+from django.utils import six
class DjangoUnicodeDecodeError(UnicodeDecodeError):
def __init__(self, obj, *args):
@@ -24,8 +28,12 @@ class StrAndUnicode(object):
Useful as a mix-in.
"""
- def __str__(self):
- return self.__unicode__().encode('utf-8')
+ if six.PY3:
+ def __str__(self):
+ return self.__unicode__()
+ else:
+ def __str__(self):
+ return self.__unicode__().encode('utf-8')
def smart_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
"""
@@ -45,12 +53,8 @@ def is_protected_type(obj):
Objects of protected types are preserved as-is when passed to
force_unicode(strings_only=True).
"""
- return isinstance(obj, (
- type(None),
- int, long,
- datetime.datetime, datetime.date, datetime.time,
- float, Decimal)
- )
+ return isinstance(obj, six.integer_types + (type(None), float, Decimal,
+ datetime.datetime, datetime.date, datetime.time))
def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
"""
@@ -62,17 +66,23 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
# Handle the common case first, saves 30-40% in performance when s
# is an instance of unicode. This function gets called often in that
# setting.
- if isinstance(s, unicode):
+ if isinstance(s, six.text_type):
return s
if strings_only and is_protected_type(s):
return s
try:
- if not isinstance(s, basestring,):
+ if not isinstance(s, six.string_types):
if hasattr(s, '__unicode__'):
- s = unicode(s)
+ s = s.__unicode__()
else:
try:
- s = unicode(str(s), encoding, errors)
+ if six.PY3:
+ if isinstance(s, bytes):
+ s = six.text_type(s, encoding, errors)
+ else:
+ s = six.text_type(s)
+ else:
+ s = six.text_type(bytes(s), encoding, errors)
except UnicodeEncodeError:
if not isinstance(s, Exception):
raise
@@ -84,8 +94,8 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
# output should be.
s = ' '.join([force_unicode(arg, encoding, strings_only,
errors) for arg in s])
- elif not isinstance(s, unicode):
- # Note: We use .decode() here, instead of unicode(s, encoding,
+ elif not isinstance(s, six.text_type):
+ # Note: We use .decode() here, instead of six.text_type(s, encoding,
# errors), so that if s is a SafeString, it ends up being a
# SafeUnicode at the end.
s = s.decode(encoding, errors)
@@ -111,10 +121,13 @@ def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
if strings_only and (s is None or isinstance(s, int)):
return s
if isinstance(s, Promise):
- return unicode(s).encode(encoding, errors)
- elif not isinstance(s, basestring):
+ return six.text_type(s).encode(encoding, errors)
+ elif not isinstance(s, six.string_types):
try:
- return str(s)
+ if six.PY3:
+ return six.text_type(s).encode(encoding)
+ else:
+ return bytes(s)
except UnicodeEncodeError:
if isinstance(s, Exception):
# An Exception subclass containing non-ASCII data that doesn't
@@ -122,8 +135,8 @@ def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
# further exception.
return ' '.join([smart_str(arg, encoding, strings_only,
errors) for arg in s])
- return unicode(s).encode(encoding, errors)
- elif isinstance(s, unicode):
+ return six.text_type(s).encode(encoding, errors)
+ elif isinstance(s, six.text_type):
return s.encode(encoding, errors)
elif s and encoding != 'utf-8':
return s.decode('utf-8', errors).encode(encoding, errors)
@@ -155,7 +168,7 @@ def iri_to_uri(iri):
# converted.
if iri is None:
return iri
- return urllib.quote(smart_str(iri), safe=b"/#%[]=:;$&()+,!?*@'~")
+ return quote(smart_str(iri), safe=b"/#%[]=:;$&()+,!?*@'~")
def filepath_to_uri(path):
"""Convert an file system path to a URI portion that is suitable for
@@ -174,7 +187,7 @@ def filepath_to_uri(path):
return path
# I know about `os.sep` and `os.altsep` but I want to leave
# some flexibility for hardcoding separators.
- return urllib.quote(smart_str(path).replace("\\", "/"), safe=b"/~!*()'")
+ return quote(smart_str(path).replace("\\", "/"), safe=b"/~!*()'")
# The encoding of the default system locale but falls back to the
# given fallback encoding if the encoding is unsupported by python or could
diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py
index 52f40fb5f1..6498aaf57c 100644
--- a/django/utils/feedgenerator.py
+++ b/django/utils/feedgenerator.py
@@ -19,12 +19,15 @@ Sample usage:
... feed.write(fp, 'utf-8')
For definitions of the different versions of RSS, see:
-http://diveintomark.org/archives/2004/02/04/incompatible-rss
+http://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004/02/04/incompatible-rss
"""
from __future__ import unicode_literals
import datetime
-import urlparse
+try:
+ from urllib.parse import urlparse
+except ImportError: # Python 2
+ from urlparse import urlparse
from django.utils.xmlutils import SimplerXMLGenerator
from django.utils.encoding import force_unicode, iri_to_uri
from django.utils import datetime_safe
@@ -65,9 +68,9 @@ def get_tag_uri(url, date):
"""
Creates a TagURI.
- See http://diveintomark.org/archives/2004/05/28/howto-atom-id
+ See http://web.archive.org/web/20110514113830/http://diveintomark.org/archives/2004/05/28/howto-atom-id
"""
- bits = urlparse.urlparse(url)
+ bits = urlparse(url)
d = ''
if date is not None:
d = ',%s' % datetime_safe.new_datetime(date).strftime('%Y-%m-%d')
diff --git a/django/utils/formats.py b/django/utils/formats.py
index e283490b17..2e54d792fa 100644
--- a/django/utils/formats.py
+++ b/django/utils/formats.py
@@ -7,6 +7,7 @@ from django.utils.importlib import import_module
from django.utils.encoding import smart_str
from django.utils.functional import lazy
from django.utils.safestring import mark_safe
+from django.utils import six
from django.utils.translation import get_language, to_locale, check_for_language
# format_cache is a mapping from (format_type, lang) to the format string.
@@ -88,7 +89,7 @@ def get_format(format_type, lang=None, use_l10n=None):
_format_cache[cache_key] = None
return getattr(settings, format_type)
-get_format_lazy = lazy(get_format, unicode, list, tuple)
+get_format_lazy = lazy(get_format, six.text_type, list, tuple)
def date_format(value, format=None, use_l10n=None):
"""
@@ -138,8 +139,8 @@ def localize(value, use_l10n=None):
be localized (or not), overriding the value of settings.USE_L10N.
"""
if isinstance(value, bool):
- return mark_safe(unicode(value))
- elif isinstance(value, (decimal.Decimal, float, int, long)):
+ return mark_safe(six.text_type(value))
+ elif isinstance(value, (decimal.Decimal, float) + six.integer_types):
return number_format(value, use_l10n=use_l10n)
elif isinstance(value, datetime.datetime):
return date_format(value, 'DATETIME_FORMAT', use_l10n=use_l10n)
@@ -155,7 +156,7 @@ def localize_input(value, default=None):
Checks if an input value is a localizable type and returns it
formatted with the appropriate formatting string of the current locale.
"""
- if isinstance(value, (decimal.Decimal, float, int, long)):
+ if isinstance(value, (decimal.Decimal, float) + six.integer_types):
return number_format(value)
elif isinstance(value, datetime.datetime):
value = datetime_safe.new_datetime(value)
@@ -177,7 +178,7 @@ def sanitize_separators(value):
"""
if settings.USE_L10N:
decimal_separator = get_format('DECIMAL_SEPARATOR')
- if isinstance(value, basestring):
+ if isinstance(value, six.string_types):
parts = []
if decimal_separator in value:
value, decimals = value.split(decimal_separator, 1)
diff --git a/django/utils/functional.py b/django/utils/functional.py
index 31466ee8b8..2dca182b99 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -3,6 +3,7 @@ import operator
from functools import wraps, update_wrapper
import sys
+from django.utils import six
# You can't trivially replace this `functools.partial` because this binds to
# classes and returns bound instances, whereas functools.partial (on CPython)
@@ -92,8 +93,8 @@ def lazy(func, *resultclasses):
if hasattr(cls, k):
continue
setattr(cls, k, meth)
- cls._delegate_str = str in resultclasses
- cls._delegate_unicode = unicode in resultclasses
+ cls._delegate_str = bytes in resultclasses
+ cls._delegate_unicode = six.text_type in resultclasses
assert not (cls._delegate_str and cls._delegate_unicode), "Cannot call lazy() with both str and unicode return types."
if cls._delegate_unicode:
cls.__unicode__ = cls.__unicode_cast
@@ -147,7 +148,7 @@ def lazy(func, *resultclasses):
if self._delegate_str:
return str(self) % rhs
elif self._delegate_unicode:
- return unicode(self) % rhs
+ return six.text_type(self) % rhs
else:
raise AssertionError('__mod__ not supported for non-string types')
@@ -255,8 +256,8 @@ class SimpleLazyObject(LazyObject):
def _setup(self):
self._wrapped = self._setupfunc()
- __str__ = new_method_proxy(str)
- __unicode__ = new_method_proxy(unicode)
+ __str__ = new_method_proxy(bytes)
+ __unicode__ = new_method_proxy(six.text_type)
def __deepcopy__(self, memo):
if self._wrapped is empty:
diff --git a/django/utils/html.py b/django/utils/html.py
index 014d837bbb..ca3340ccb1 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -4,16 +4,20 @@ from __future__ import unicode_literals
import re
import string
-import urllib
-import urlparse
+try:
+ from urllib.parse import quote, urlsplit, urlunsplit
+except ImportError: # Python 2
+ from urllib import quote
+ from urlparse import urlsplit, urlunsplit
from django.utils.safestring import SafeData, mark_safe
from django.utils.encoding import smart_str, force_unicode
from django.utils.functional import allow_lazy
+from django.utils import six
from django.utils.text import normalize_newlines
# Configuration for urlize() function.
-TRAILING_PUNCTUATION = ['.', ',', ':', ';']
+TRAILING_PUNCTUATION = ['.', ',', ':', ';', '.)']
WRAPPING_PUNCTUATION = [('(', ')'), ('<', '>'), ('&lt;', '&gt;')]
# List of possible strings used for bullets in bulleted lists.
@@ -31,12 +35,12 @@ hard_coded_bullets_re = re.compile(r'((?:<p>(?:%s).*?[a-zA-Z].*?</p>\s*)+)' % '|
trailing_empty_content_re = re.compile(r'(?:<p>(?:&nbsp;|\s|<br \/>)*?</p>\s*)+\Z')
del x # Temporary variable
-def escape(html):
+def escape(text):
"""
- Returns the given HTML with ampersands, quotes and angle brackets encoded.
+ Returns the given text with ampersands, quotes and angle brackets encoded for use in HTML.
"""
- return mark_safe(force_unicode(html).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;'))
-escape = allow_lazy(escape, unicode)
+ return mark_safe(force_unicode(text).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;'))
+escape = allow_lazy(escape, six.text_type)
_base_js_escapes = (
('\\', '\\u005C'),
@@ -61,16 +65,47 @@ def escapejs(value):
for bad, good in _js_escapes:
value = mark_safe(force_unicode(value).replace(bad, good))
return value
-escapejs = allow_lazy(escapejs, unicode)
+escapejs = allow_lazy(escapejs, six.text_type)
-def conditional_escape(html):
+def conditional_escape(text):
"""
Similar to escape(), except that it doesn't operate on pre-escaped strings.
"""
- if isinstance(html, SafeData):
- return html
+ if isinstance(text, SafeData):
+ return text
else:
- return escape(html)
+ return escape(text)
+
+def format_html(format_string, *args, **kwargs):
+ """
+ Similar to str.format, but passes all arguments through conditional_escape,
+ and calls 'mark_safe' on the result. This function should be used instead
+ of str.format or % interpolation to build up small HTML fragments.
+ """
+ args_safe = map(conditional_escape, args)
+ kwargs_safe = dict([(k, conditional_escape(v)) for (k, v) in
+ kwargs.iteritems()])
+ return mark_safe(format_string.format(*args_safe, **kwargs_safe))
+
+def format_html_join(sep, format_string, args_generator):
+ """
+ A wrapper format_html, for the common case of a group of arguments that need
+ to be formatted using the same format string, and then joined using
+ 'sep'. 'sep' is also passed through conditional_escape.
+
+ 'args_generator' should be an iterator that returns the sequence of 'args'
+ that will be passed to format_html.
+
+ Example:
+
+ format_html_join('\n', "<li>{0} {1}</li>", ((u.first_name, u.last_name)
+ for u in users))
+
+ """
+ return mark_safe(conditional_escape(sep).join(
+ format_html(format_string, *tuple(args))
+ for args in args_generator))
+
def linebreaks(value, autoescape=False):
"""Converts newlines into <p> and <br />s."""
@@ -81,7 +116,7 @@ def linebreaks(value, autoescape=False):
else:
paras = ['<p>%s</p>' % p.replace('\n', '<br />') for p in paras]
return '\n\n'.join(paras)
-linebreaks = allow_lazy(linebreaks, unicode)
+linebreaks = allow_lazy(linebreaks, six.text_type)
def strip_tags(value):
"""Returns the given HTML with all tags stripped."""
@@ -91,34 +126,34 @@ strip_tags = allow_lazy(strip_tags)
def strip_spaces_between_tags(value):
"""Returns the given HTML with spaces between tags removed."""
return re.sub(r'>\s+<', '><', force_unicode(value))
-strip_spaces_between_tags = allow_lazy(strip_spaces_between_tags, unicode)
+strip_spaces_between_tags = allow_lazy(strip_spaces_between_tags, six.text_type)
def strip_entities(value):
"""Returns the given HTML with all entities (&something;) stripped."""
return re.sub(r'&(?:\w+|#\d+);', '', force_unicode(value))
-strip_entities = allow_lazy(strip_entities, unicode)
+strip_entities = allow_lazy(strip_entities, six.text_type)
def fix_ampersands(value):
"""Returns the given HTML with all unencoded ampersands encoded correctly."""
return unencoded_ampersands_re.sub('&amp;', force_unicode(value))
-fix_ampersands = allow_lazy(fix_ampersands, unicode)
+fix_ampersands = allow_lazy(fix_ampersands, six.text_type)
def smart_urlquote(url):
"Quotes a URL if it isn't already quoted."
# Handle IDN before quoting.
- scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
+ scheme, netloc, path, query, fragment = urlsplit(url)
try:
netloc = netloc.encode('idna') # IDN -> ACE
except UnicodeError: # invalid domain part
pass
else:
- url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
+ url = urlunsplit((scheme, netloc, path, query, fragment))
# An URL is considered unquoted if it contains no % characters or
# contains a % not followed by two hexadecimal digits. See #9655.
if '%' not in url or unquoted_percents_re.search(url):
# See http://bugs.python.org/issue2637
- url = urllib.quote(smart_str(url), safe=b'!*\'();:@&=+$,/?#[]~')
+ url = quote(smart_str(url), safe=b'!*\'();:@&=+$,/?#[]~')
return force_unicode(url)
@@ -195,7 +230,7 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
elif autoescape:
words[i] = escape(word)
return ''.join(words)
-urlize = allow_lazy(urlize, unicode)
+urlize = allow_lazy(urlize, six.text_type)
def clean_html(text):
"""
@@ -229,4 +264,4 @@ def clean_html(text):
# of the text.
text = trailing_empty_content_re.sub('', text)
return text
-clean_html = allow_lazy(clean_html, unicode)
+clean_html = allow_lazy(clean_html, six.text_type)
diff --git a/django/utils/html_parser.py b/django/utils/html_parser.py
index b28005705e..ee56c01aec 100644
--- a/django/utils/html_parser.py
+++ b/django/utils/html_parser.py
@@ -1,25 +1,28 @@
-import HTMLParser as _HTMLParser
+from django.utils.six.moves import html_parser as _html_parser
import re
+tagfind = re.compile('([a-zA-Z][-.a-zA-Z0-9:_]*)(?:\s|/(?!>))*')
-class HTMLParser(_HTMLParser.HTMLParser):
+HTMLParseError = _html_parser.HTMLParseError
+
+class HTMLParser(_html_parser.HTMLParser):
"""
Patched version of stdlib's HTMLParser with patch from:
http://bugs.python.org/issue670664
"""
def __init__(self):
- _HTMLParser.HTMLParser.__init__(self)
+ _html_parser.HTMLParser.__init__(self)
self.cdata_tag = None
def set_cdata_mode(self, tag):
try:
- self.interesting = _HTMLParser.interesting_cdata
+ self.interesting = _html_parser.interesting_cdata
except AttributeError:
self.interesting = re.compile(r'</\s*%s\s*>' % tag.lower(), re.I)
self.cdata_tag = tag.lower()
def clear_cdata_mode(self):
- self.interesting = _HTMLParser.interesting_normal
+ self.interesting = _html_parser.interesting_normal
self.cdata_tag = None
# Internal -- handle starttag, return end or -1 if not terminated
@@ -33,13 +36,13 @@ class HTMLParser(_HTMLParser.HTMLParser):
# Now parse the data between i+1 and j into a tag and attrs
attrs = []
- match = _HTMLParser.tagfind.match(rawdata, i + 1)
+ match = tagfind.match(rawdata, i + 1)
assert match, 'unexpected call to parse_starttag()'
k = match.end()
- self.lasttag = tag = rawdata[i + 1:k].lower()
+ self.lasttag = tag = match.group(1).lower()
while k < endpos:
- m = _HTMLParser.attrfind.match(rawdata, k)
+ m = _html_parser.attrfind.match(rawdata, k)
if not m:
break
attrname, rest, attrvalue = m.group(1, 2, 3)
@@ -48,6 +51,7 @@ class HTMLParser(_HTMLParser.HTMLParser):
elif attrvalue[:1] == '\'' == attrvalue[-1:] or \
attrvalue[:1] == '"' == attrvalue[-1:]:
attrvalue = attrvalue[1:-1]
+ if attrvalue:
attrvalue = self.unescape(attrvalue)
attrs.append((attrname.lower(), attrvalue))
k = m.end()
@@ -76,11 +80,11 @@ class HTMLParser(_HTMLParser.HTMLParser):
def parse_endtag(self, i):
rawdata = self.rawdata
assert rawdata[i:i + 2] == "</", "unexpected call to parse_endtag"
- match = _HTMLParser.endendtag.search(rawdata, i + 1) # >
+ match = _html_parser.endendtag.search(rawdata, i + 1) # >
if not match:
return -1
j = match.end()
- match = _HTMLParser.endtagfind.match(rawdata, i) # </ + tag + >
+ match = _html_parser.endtagfind.match(rawdata, i) # </ + tag + >
if not match:
if self.cdata_tag is not None: # *** add ***
self.handle_data(rawdata[i:j]) # *** add ***
diff --git a/django/utils/http.py b/django/utils/http.py
index 87db284416..f3a3dce58c 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -2,13 +2,20 @@ import calendar
import datetime
import re
import sys
-import urllib
-import urlparse
+try:
+ from urllib import parse as urllib_parse
+except ImportError: # Python 2
+ import urllib as urllib_parse
+ import urlparse
+ urllib_parse.urlparse = urlparse.urlparse
+
+
from email.utils import formatdate
from django.utils.datastructures import MultiValueDict
from django.utils.encoding import smart_str, force_unicode
from django.utils.functional import allow_lazy
+from django.utils import six
ETAG_MATCH = re.compile(r'(?:W/)?"((?:\\.|[^"])*)"')
@@ -30,8 +37,8 @@ def urlquote(url, safe='/'):
can safely be used as part of an argument to a subsequent iri_to_uri() call
without double-quoting occurring.
"""
- return force_unicode(urllib.quote(smart_str(url), smart_str(safe)))
-urlquote = allow_lazy(urlquote, unicode)
+ return force_unicode(urllib_parse.quote(smart_str(url), smart_str(safe)))
+urlquote = allow_lazy(urlquote, six.text_type)
def urlquote_plus(url, safe=''):
"""
@@ -40,24 +47,24 @@ def urlquote_plus(url, safe=''):
returned string can safely be used as part of an argument to a subsequent
iri_to_uri() call without double-quoting occurring.
"""
- return force_unicode(urllib.quote_plus(smart_str(url), smart_str(safe)))
-urlquote_plus = allow_lazy(urlquote_plus, unicode)
+ return force_unicode(urllib_parse.quote_plus(smart_str(url), smart_str(safe)))
+urlquote_plus = allow_lazy(urlquote_plus, six.text_type)
def urlunquote(quoted_url):
"""
A wrapper for Python's urllib.unquote() function that can operate on
the result of django.utils.http.urlquote().
"""
- return force_unicode(urllib.unquote(smart_str(quoted_url)))
-urlunquote = allow_lazy(urlunquote, unicode)
+ return force_unicode(urllib_parse.unquote(smart_str(quoted_url)))
+urlunquote = allow_lazy(urlunquote, six.text_type)
def urlunquote_plus(quoted_url):
"""
A wrapper for Python's urllib.unquote_plus() function that can operate on
the result of django.utils.http.urlquote_plus().
"""
- return force_unicode(urllib.unquote_plus(smart_str(quoted_url)))
-urlunquote_plus = allow_lazy(urlunquote_plus, unicode)
+ return force_unicode(urllib_parse.unquote_plus(smart_str(quoted_url)))
+urlunquote_plus = allow_lazy(urlunquote_plus, six.text_type)
def urlencode(query, doseq=0):
"""
@@ -69,7 +76,7 @@ def urlencode(query, doseq=0):
query = query.lists()
elif hasattr(query, 'items'):
query = query.items()
- return urllib.urlencode(
+ return urllib_parse.urlencode(
[(smart_str(k),
[smart_str(i) for i in v] if isinstance(v, (list,tuple)) else smart_str(v))
for k, v in query],
@@ -211,5 +218,5 @@ def same_origin(url1, url2):
"""
Checks if two URLs are 'same-origin'
"""
- p1, p2 = urlparse.urlparse(url1), urlparse.urlparse(url2)
+ p1, p2 = urllib_parse.urlparse(url1), urllib_parse.urlparse(url2)
return (p1.scheme, p1.hostname, p1.port) == (p2.scheme, p2.hostname, p2.port)
diff --git a/django/utils/importlib.py b/django/utils/importlib.py
index ef4d0e4a27..703ba7f6d8 100644
--- a/django/utils/importlib.py
+++ b/django/utils/importlib.py
@@ -6,7 +6,7 @@ def _resolve_name(name, package, level):
if not hasattr(package, 'rindex'):
raise ValueError("'package' not set to a string")
dot = len(package)
- for x in xrange(level, 1, -1):
+ for x in range(level, 1, -1):
try:
dot = package.rindex('.', 0, dot)
except ValueError:
diff --git a/django/utils/ipv6.py b/django/utils/ipv6.py
index e80a0e205f..2ccae8cdd0 100644
--- a/django/utils/ipv6.py
+++ b/django/utils/ipv6.py
@@ -2,6 +2,7 @@
# Copyright 2007 Google Inc. http://code.google.com/p/ipaddr-py/
# Licensed under the Apache License, Version 2.0 (the "License").
from django.core.exceptions import ValidationError
+from django.utils.six.moves import xrange
def clean_ipv6_address(ip_str, unpack_ipv4=False,
error_message="This is not a valid IPv6 address"):
diff --git a/django/utils/itercompat.py b/django/utils/itercompat.py
index 2f016b1c3f..aa329c152e 100644
--- a/django/utils/itercompat.py
+++ b/django/utils/itercompat.py
@@ -4,7 +4,7 @@ Where possible, we try to use the system-native version and only fall back to
these implementations if necessary.
"""
-import __builtin__
+from django.utils.six.moves import builtins
import itertools
import warnings
@@ -25,9 +25,9 @@ def product(*args, **kwds):
def all(iterable):
warnings.warn("django.utils.itercompat.all is deprecated; use the native version instead",
DeprecationWarning)
- return __builtin__.all(iterable)
+ return builtins.all(iterable)
def any(iterable):
warnings.warn("django.utils.itercompat.any is deprecated; use the native version instead",
DeprecationWarning)
- return __builtin__.any(iterable)
+ return builtins.any(iterable)
diff --git a/django/utils/numberformat.py b/django/utils/numberformat.py
index 924d06511b..d51b230823 100644
--- a/django/utils/numberformat.py
+++ b/django/utils/numberformat.py
@@ -1,5 +1,6 @@
from django.conf import settings
from django.utils.safestring import mark_safe
+from django.utils import six
def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='',
@@ -18,13 +19,13 @@ def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='',
use_grouping = use_grouping and grouping > 0
# Make the common case fast
if isinstance(number, int) and not use_grouping and not decimal_pos:
- return mark_safe(unicode(number))
+ return mark_safe(six.text_type(number))
# sign
if float(number) < 0:
sign = '-'
else:
sign = ''
- str_number = unicode(number)
+ str_number = six.text_type(number)
if str_number[0] == '-':
str_number = str_number[1:]
# decimal part
diff --git a/django/utils/py3.py b/django/utils/py3.py
deleted file mode 100644
index 8a5c37e976..0000000000
--- a/django/utils/py3.py
+++ /dev/null
@@ -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
diff --git a/django/utils/regex_helper.py b/django/utils/regex_helper.py
index 4b8ecea721..8953a21e95 100644
--- a/django/utils/regex_helper.py
+++ b/django/utils/regex_helper.py
@@ -7,6 +7,8 @@ should be good enough for a large class of URLS, however.
"""
from __future__ import unicode_literals
+from django.utils import six
+
# Mapping of an escape character to a representative of that class. So, e.g.,
# "\w" is replaced by "x" in a reverse URL. A value of None means to ignore
# this sequence. Any missing key is mapped to itself.
@@ -302,7 +304,7 @@ def flatten_result(source):
result_args = [[]]
pos = last = 0
for pos, elt in enumerate(source):
- if isinstance(elt, basestring):
+ if isinstance(elt, six.string_types):
continue
piece = ''.join(source[last:pos])
if isinstance(elt, Group):
diff --git a/django/utils/safestring.py b/django/utils/safestring.py
index 2e31c23676..1599fc2a66 100644
--- a/django/utils/safestring.py
+++ b/django/utils/safestring.py
@@ -5,17 +5,18 @@ that the producer of the string has already turned characters that should not
be interpreted by the HTML engine (e.g. '<') into the appropriate entities.
"""
from django.utils.functional import curry, Promise
+from django.utils import six
class EscapeData(object):
pass
-class EscapeString(str, EscapeData):
+class EscapeString(bytes, EscapeData):
"""
A string that should be HTML-escaped when output.
"""
pass
-class EscapeUnicode(unicode, EscapeData):
+class EscapeUnicode(six.text_type, EscapeData):
"""
A unicode object that should be HTML-escaped when output.
"""
@@ -24,7 +25,7 @@ class EscapeUnicode(unicode, EscapeData):
class SafeData(object):
pass
-class SafeString(str, SafeData):
+class SafeString(bytes, SafeData):
"""
A string subclass that has been specifically marked as "safe" (requires no
further escaping) for HTML output purposes.
@@ -40,7 +41,7 @@ class SafeString(str, SafeData):
elif isinstance(rhs, SafeString):
return SafeString(t)
return t
-
+
def _proxy_method(self, *args, **kwargs):
"""
Wrap a call to a normal unicode method up so that we return safe
@@ -49,14 +50,14 @@ class SafeString(str, SafeData):
"""
method = kwargs.pop('method')
data = method(self, *args, **kwargs)
- if isinstance(data, str):
+ if isinstance(data, bytes):
return SafeString(data)
else:
return SafeUnicode(data)
- decode = curry(_proxy_method, method = str.decode)
+ decode = curry(_proxy_method, method=bytes.decode)
-class SafeUnicode(unicode, SafeData):
+class SafeUnicode(six.text_type, SafeData):
"""
A unicode subclass that has been specifically marked as "safe" for HTML
output purposes.
@@ -70,7 +71,7 @@ class SafeUnicode(unicode, SafeData):
if isinstance(rhs, SafeData):
return SafeUnicode(t)
return t
-
+
def _proxy_method(self, *args, **kwargs):
"""
Wrap a call to a normal unicode method up so that we return safe
@@ -79,12 +80,12 @@ class SafeUnicode(unicode, SafeData):
"""
method = kwargs.pop('method')
data = method(self, *args, **kwargs)
- if isinstance(data, str):
+ if isinstance(data, bytes):
return SafeString(data)
else:
return SafeUnicode(data)
- encode = curry(_proxy_method, method = unicode.encode)
+ encode = curry(_proxy_method, method=six.text_type.encode)
def mark_safe(s):
"""
@@ -95,11 +96,11 @@ def mark_safe(s):
"""
if isinstance(s, SafeData):
return s
- if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str):
+ if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_str):
return SafeString(s)
- if isinstance(s, (unicode, Promise)):
+ if isinstance(s, (six.text_type, Promise)):
return SafeUnicode(s)
- return SafeString(str(s))
+ return SafeString(bytes(s))
def mark_for_escaping(s):
"""
@@ -111,9 +112,9 @@ def mark_for_escaping(s):
"""
if isinstance(s, (SafeData, EscapeData)):
return s
- if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str):
+ if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_str):
return EscapeString(s)
- if isinstance(s, (unicode, Promise)):
+ if isinstance(s, (six.text_type, Promise)):
return EscapeUnicode(s)
- return EscapeString(str(s))
+ return EscapeString(bytes(s))
diff --git a/django/utils/six.py b/django/utils/six.py
new file mode 100644
index 0000000000..e226bba09e
--- /dev/null
+++ b/django/utils/six.py
@@ -0,0 +1,367 @@
+"""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["django.utils.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,), {})
+
+
+### Additional customizations for Django ###
+
+if PY3:
+ _iterlists = "lists"
+else:
+ _iterlists = "iterlists"
+
+def iterlists(d):
+ """Return an iterator over the values of a MultiValueDict."""
+ return getattr(d, _iterlists)()
+
+add_move(MovedModule("_dummy_thread", "dummy_thread"))
diff --git a/django/utils/text.py b/django/utils/text.py
index 2546f770b5..43056aa634 100644
--- a/django/utils/text.py
+++ b/django/utils/text.py
@@ -4,16 +4,17 @@ import re
import unicodedata
import warnings
from gzip import GzipFile
-from htmlentitydefs import name2codepoint
+from django.utils.six.moves import html_entities
from io import BytesIO
from django.utils.encoding import force_unicode
from django.utils.functional import allow_lazy, SimpleLazyObject
+from django.utils import six
from django.utils.translation import ugettext_lazy, ugettext as _, pgettext
# Capitalizes the first letter of a string.
capfirst = lambda x: x and force_unicode(x)[0].upper() + force_unicode(x)[1:]
-capfirst = allow_lazy(capfirst, unicode)
+capfirst = allow_lazy(capfirst, six.text_type)
# Set up regular expressions
re_words = re.compile(r'&.*?;|<.*?>|(\w[\w-]*)', re.U|re.S)
@@ -46,7 +47,7 @@ def wrap(text, width):
pos = len(lines[-1])
yield word
return ''.join(_generator())
-wrap = allow_lazy(wrap, unicode)
+wrap = allow_lazy(wrap, six.text_type)
class Truncator(SimpleLazyObject):
@@ -207,14 +208,14 @@ def truncate_words(s, num, end_text='...'):
'in django.utils.text instead.', category=DeprecationWarning)
truncate = end_text and ' %s' % end_text or ''
return Truncator(s).words(num, truncate=truncate)
-truncate_words = allow_lazy(truncate_words, unicode)
+truncate_words = allow_lazy(truncate_words, six.text_type)
def truncate_html_words(s, num, end_text='...'):
warnings.warn('This function has been deprecated. Use the Truncator class '
'in django.utils.text instead.', category=DeprecationWarning)
truncate = end_text and ' %s' % end_text or ''
return Truncator(s).words(num, truncate=truncate, html=True)
-truncate_html_words = allow_lazy(truncate_html_words, unicode)
+truncate_html_words = allow_lazy(truncate_html_words, six.text_type)
def get_valid_filename(s):
"""
@@ -227,7 +228,7 @@ def get_valid_filename(s):
"""
s = force_unicode(s).strip().replace(' ', '_')
return re.sub(r'(?u)[^-\w.]', '', s)
-get_valid_filename = allow_lazy(get_valid_filename, unicode)
+get_valid_filename = allow_lazy(get_valid_filename, six.text_type)
def get_text_list(list_, last_word=ugettext_lazy('or')):
"""
@@ -248,11 +249,11 @@ def get_text_list(list_, last_word=ugettext_lazy('or')):
# Translators: This string is used as a separator between list elements
_(', ').join([force_unicode(i) for i in list_][:-1]),
force_unicode(last_word), force_unicode(list_[-1]))
-get_text_list = allow_lazy(get_text_list, unicode)
+get_text_list = allow_lazy(get_text_list, six.text_type)
def normalize_newlines(text):
return force_unicode(re.sub(r'\r\n|\r|\n', '\n', text))
-normalize_newlines = allow_lazy(normalize_newlines, unicode)
+normalize_newlines = allow_lazy(normalize_newlines, six.text_type)
def recapitalize(text):
"Recapitalizes text, placing caps after end-of-sentence punctuation."
@@ -288,9 +289,9 @@ def javascript_quote(s, quote_double_quotes=False):
def fix(match):
return b"\u%04x" % ord(match.group(1))
- if type(s) == str:
+ if type(s) == bytes:
s = s.decode('utf-8')
- elif type(s) != unicode:
+ elif type(s) != six.text_type:
raise TypeError(s)
s = s.replace('\\', '\\\\')
s = s.replace('\r', '\\r')
@@ -300,7 +301,7 @@ def javascript_quote(s, quote_double_quotes=False):
if quote_double_quotes:
s = s.replace('"', '&quot;')
return str(ustring_re.sub(fix, s))
-javascript_quote = allow_lazy(javascript_quote, unicode)
+javascript_quote = allow_lazy(javascript_quote, six.text_type)
# Expression to match some_token and some_token="with spaces" (and similarly
# for single-quoted strings).
@@ -332,7 +333,7 @@ def smart_split(text):
text = force_unicode(text)
for bit in smart_split_re.finditer(text):
yield bit.group(0)
-smart_split = allow_lazy(smart_split, unicode)
+smart_split = allow_lazy(smart_split, six.text_type)
def _replace_entity(match):
text = match.group(1)
@@ -348,7 +349,7 @@ def _replace_entity(match):
return match.group(0)
else:
try:
- return unichr(name2codepoint[text])
+ return unichr(html_entities.name2codepoint[text])
except (ValueError, KeyError):
return match.group(0)
@@ -356,7 +357,7 @@ _entity_re = re.compile(r"&(#?[xX]?(?:[0-9a-fA-F]+|\w{1,8}));")
def unescape_entities(text):
return _entity_re.sub(_replace_entity, text)
-unescape_entities = allow_lazy(unescape_entities, unicode)
+unescape_entities = allow_lazy(unescape_entities, six.text_type)
def unescape_string_literal(s):
r"""
diff --git a/django/utils/timezone.py b/django/utils/timezone.py
index d9d9636023..68c214d26f 100644
--- a/django/utils/timezone.py
+++ b/django/utils/timezone.py
@@ -13,6 +13,7 @@ except ImportError:
pytz = None
from django.conf import settings
+from django.utils import six
__all__ = [
'utc', 'get_default_timezone', 'get_current_timezone',
@@ -107,7 +108,7 @@ def get_default_timezone():
"""
global _localtime
if _localtime is None:
- if isinstance(settings.TIME_ZONE, basestring) and pytz is not None:
+ if isinstance(settings.TIME_ZONE, six.string_types) and pytz is not None:
_localtime = pytz.timezone(settings.TIME_ZONE)
else:
_localtime = LocalTimezone()
@@ -160,7 +161,7 @@ def activate(timezone):
"""
if isinstance(timezone, tzinfo):
_active.value = timezone
- elif isinstance(timezone, basestring) and pytz is not None:
+ elif isinstance(timezone, six.string_types) and pytz is not None:
_active.value = pytz.timezone(timezone)
else:
raise ValueError("Invalid timezone: %r" % timezone)
diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py
index 0f1f28e5c4..d31a7aebf1 100644
--- a/django/utils/translation/__init__.py
+++ b/django/utils/translation/__init__.py
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
from django.utils.encoding import force_unicode
from django.utils.functional import lazy
+from django.utils import six
__all__ = [
@@ -78,12 +79,12 @@ def pgettext(context, message):
def npgettext(context, singular, plural, number):
return _trans.npgettext(context, singular, plural, number)
-ngettext_lazy = lazy(ngettext, str)
-gettext_lazy = lazy(gettext, str)
-ungettext_lazy = lazy(ungettext, unicode)
-ugettext_lazy = lazy(ugettext, unicode)
-pgettext_lazy = lazy(pgettext, unicode)
-npgettext_lazy = lazy(npgettext, unicode)
+ngettext_lazy = lazy(ngettext, bytes)
+gettext_lazy = lazy(gettext, bytes)
+ungettext_lazy = lazy(ungettext, six.text_type)
+ugettext_lazy = lazy(ugettext, six.text_type)
+pgettext_lazy = lazy(pgettext, six.text_type)
+npgettext_lazy = lazy(npgettext, six.text_type)
def activate(language):
return _trans.activate(language)
@@ -139,7 +140,7 @@ def _string_concat(*strings):
constructed from multiple parts.
"""
return ''.join([force_unicode(s) for s in strings])
-string_concat = lazy(_string_concat, unicode)
+string_concat = lazy(_string_concat, six.text_type)
def get_language_info(lang_code):
from django.conf.locale import LANG_INFO
diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
index 0cd13fd6f5..9ebcbf5441 100644
--- a/django/utils/translation/trans_real.py
+++ b/django/utils/translation/trans_real.py
@@ -6,11 +6,11 @@ import os
import re
import sys
import gettext as gettext_module
-from io import StringIO
from threading import local
from django.utils.importlib import import_module
from django.utils.safestring import mark_safe, SafeData
+from django.utils.six import StringIO
# Translations are cached in a dictionary for every language+app tuple.