diff options
| author | Andrew Godwin <andrew@aeracode.org> | 2012-07-26 18:58:10 +0100 |
|---|---|---|
| committer | Andrew Godwin <andrew@aeracode.org> | 2012-07-26 18:58:10 +0100 |
| commit | 4a2e80fff44d0eb1856b593ac5f31ab1492b3e45 (patch) | |
| tree | 9bc87a682dc488e6555792c1d4bb53f5f3cdc880 /django/utils | |
| parent | 959a3f9791d780062c4efe8765404a8ef95e87f0 (diff) | |
| parent | ab6cd1c839b136cbc94178da433b2e97ab7f6061 (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')
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 = [('(', ')'), ('<', '>'), ('<', '>')] # 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>(?: |\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('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''')) -escape = allow_lazy(escape, unicode) + return mark_safe(force_unicode(text).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''')) +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('&', 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('"', '"') 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. |
