diff options
| author | Andrew Godwin <andrew@aeracode.org> | 2012-08-10 12:40:37 +0100 |
|---|---|---|
| committer | Andrew Godwin <andrew@aeracode.org> | 2012-08-10 12:40:37 +0100 |
| commit | 184cf9ab798d5b25d855649ddb2ca580949778df (patch) | |
| tree | 8512633ec04a6979b0953e32e73c9a43d09e5805 /django/utils | |
| parent | c4b2a3262cc79383d6562cfc7e9af20135c8e0bf (diff) | |
| parent | 7275576235ae2e87f3de7b0facb3f9b0a2368f28 (diff) | |
Merge branch 'master' into schema-alteration
Diffstat (limited to 'django/utils')
| -rw-r--r-- | django/utils/_os.py | 20 | ||||
| -rw-r--r-- | django/utils/archive.py | 7 | ||||
| -rw-r--r-- | django/utils/cache.py | 4 | ||||
| -rw-r--r-- | django/utils/crypto.py | 17 | ||||
| -rw-r--r-- | django/utils/datastructures.py | 2 | ||||
| -rw-r--r-- | django/utils/dateformat.py | 6 | ||||
| -rw-r--r-- | django/utils/dateparse.py | 7 | ||||
| -rw-r--r-- | django/utils/dictconfig.py | 2 | ||||
| -rw-r--r-- | django/utils/encoding.py | 68 | ||||
| -rw-r--r-- | django/utils/feedgenerator.py | 37 | ||||
| -rw-r--r-- | django/utils/functional.py | 64 | ||||
| -rw-r--r-- | django/utils/html.py | 26 | ||||
| -rw-r--r-- | django/utils/http.py | 24 | ||||
| -rw-r--r-- | django/utils/regex_helper.py | 11 | ||||
| -rw-r--r-- | django/utils/safestring.py | 4 | ||||
| -rw-r--r-- | django/utils/six.py | 1 | ||||
| -rw-r--r-- | django/utils/termcolors.py | 4 | ||||
| -rw-r--r-- | django/utils/text.py | 24 | ||||
| -rw-r--r-- | django/utils/translation/__init__.py | 4 | ||||
| -rw-r--r-- | django/utils/translation/trans_null.py | 6 | ||||
| -rw-r--r-- | django/utils/translation/trans_real.py | 41 | ||||
| -rw-r--r-- | django/utils/tree.py | 3 | ||||
| -rw-r--r-- | django/utils/tzinfo.py | 4 |
23 files changed, 222 insertions, 164 deletions
diff --git a/django/utils/_os.py b/django/utils/_os.py index 884689fefc..9eb5e5e8ea 100644 --- a/django/utils/_os.py +++ b/django/utils/_os.py @@ -1,7 +1,8 @@ import os import stat from os.path import join, normcase, normpath, abspath, isabs, sep -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text +from django.utils import six try: WindowsError = WindowsError @@ -10,13 +11,12 @@ except NameError: pass -# Define our own abspath function that can handle joining -# unicode paths to a current working directory that has non-ASCII -# characters in it. This isn't necessary on Windows since the -# Windows version of abspath handles this correctly. The Windows -# abspath also handles drive letters differently than the pure -# Python implementation, so it's best not to replace it. -if os.name == 'nt': +# Under Python 2, define our own abspath function that can handle joining +# unicode paths to a current working directory that has non-ASCII characters +# in it. This isn't necessary on Windows since the Windows version of abspath +# handles this correctly. It also handles drive letters differently than the +# pure Python implementation, so it's best not to replace it. +if six.PY3 or os.name == 'nt': abspathu = abspath else: def abspathu(path): @@ -37,8 +37,8 @@ def safe_join(base, *paths): The final path must be located inside of the base path component (otherwise a ValueError is raised). """ - base = force_unicode(base) - paths = [force_unicode(p) for p in paths] + base = force_text(base) + paths = [force_text(p) for p in paths] final_path = abspathu(join(base, *paths)) base_path = abspathu(base) base_path_len = len(base_path) diff --git a/django/utils/archive.py b/django/utils/archive.py index 6b5d73290f..829b55dd28 100644 --- a/django/utils/archive.py +++ b/django/utils/archive.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ import os import shutil -import sys import tarfile import zipfile @@ -147,11 +146,11 @@ class TarArchive(BaseArchive): else: try: extracted = self._archive.extractfile(member) - except (KeyError, AttributeError): + except (KeyError, AttributeError) as exc: # Some corrupt tar files seem to produce this # (specifically bad symlinks) - print ("In the tar file %s the member %s is invalid: %s" % - (name, member.name, sys.exc_info()[1])) + print("In the tar file %s the member %s is invalid: %s" % + (name, member.name, exc)) else: dirname = os.path.dirname(filename) if dirname and not os.path.exists(dirname): diff --git a/django/utils/cache.py b/django/utils/cache.py index 8b81a2ffa2..42b0de4ce6 100644 --- a/django/utils/cache.py +++ b/django/utils/cache.py @@ -23,7 +23,7 @@ import time from django.conf import settings from django.core.cache import get_cache -from django.utils.encoding import iri_to_uri, force_unicode +from django.utils.encoding import iri_to_uri, force_text from django.utils.http import http_date from django.utils.timezone import get_current_timezone_name from django.utils.translation import get_language @@ -169,7 +169,7 @@ def _i18n_cache_key_suffix(request, cache_key): # Windows is known to use non-standard, locale-dependant names. # User-defined tzinfo classes may return absolutely anything. # Hence this paranoid conversion to create a valid cache key. - tz_name = force_unicode(get_current_timezone_name(), errors='ignore') + tz_name = force_text(get_current_timezone_name(), errors='ignore') cache_key += '.%s' % tz_name.encode('ascii', 'ignore').replace(' ', '_') return cache_key diff --git a/django/utils/crypto.py b/django/utils/crypto.py index 9d46bdd793..70a07e7fde 100644 --- a/django/utils/crypto.py +++ b/django/utils/crypto.py @@ -23,7 +23,7 @@ except NotImplementedError: using_sysrandom = False from django.conf import settings -from django.utils.encoding import smart_str +from django.utils.encoding import smart_bytes from django.utils.six.moves import xrange @@ -50,7 +50,7 @@ def salted_hmac(key_salt, value, secret=None): # line is redundant and could be replaced by key = key_salt + secret, since # the hmac module does the same thing for keys longer than the block size. # However, we need to ensure that we *always* do this. - return hmac.new(key, msg=value, digestmod=hashlib.sha1) + return hmac.new(key, msg=smart_bytes(value), digestmod=hashlib.sha1) def get_random_string(length=12, @@ -99,7 +99,7 @@ def _bin_to_long(x): This is a clever optimization for fast xor vector math """ - return int(x.encode('hex'), 16) + return int(binascii.hexlify(x), 16) def _long_to_bin(x, hex_format_string): @@ -112,13 +112,14 @@ def _long_to_bin(x, hex_format_string): def _fast_hmac(key, msg, digest): """ - A trimmed down version of Python's HMAC implementation + A trimmed down version of Python's HMAC implementation. + + This function operates on bytes. """ dig1, dig2 = digest(), digest() - key = smart_str(key) if len(key) > dig1.block_size: key = digest(key).digest() - key += chr(0) * (dig1.block_size - len(key)) + key += b'\x00' * (dig1.block_size - len(key)) dig1.update(key.translate(_trans_36)) dig1.update(msg) dig2.update(key.translate(_trans_5c)) @@ -141,8 +142,8 @@ def pbkdf2(password, salt, iterations, dklen=0, digest=None): assert iterations > 0 if not digest: digest = hashlib.sha256 - password = smart_str(password) - salt = smart_str(salt) + password = smart_bytes(password) + salt = smart_bytes(salt) hlen = digest().digest_size if not dklen: dklen = hlen diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index bbd31ad36c..ad17573104 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -129,7 +129,7 @@ class SortedDict(dict): data = list(data) super(SortedDict, self).__init__(data) if isinstance(data, dict): - self.keyOrder = list(six.iterkeys(data)) + self.keyOrder = list(data) else: self.keyOrder = [] seen = set() diff --git a/django/utils/dateformat.py b/django/utils/dateformat.py index c9a6138aed..6a91a370e5 100644 --- a/django/utils/dateformat.py +++ b/django/utils/dateformat.py @@ -20,7 +20,7 @@ import datetime from django.utils.dates import MONTHS, MONTHS_3, MONTHS_ALT, MONTHS_AP, WEEKDAYS, WEEKDAYS_ABBR from django.utils.tzinfo import LocalTimezone from django.utils.translation import ugettext as _ -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils import six from django.utils.timezone import is_aware, is_naive @@ -30,9 +30,9 @@ re_escaped = re.compile(r'\\(.)') class Formatter(object): def format(self, formatstr): pieces = [] - for i, piece in enumerate(re_formatchars.split(force_unicode(formatstr))): + for i, piece in enumerate(re_formatchars.split(force_text(formatstr))): if i % 2: - pieces.append(force_unicode(getattr(self, piece)())) + pieces.append(force_text(getattr(self, piece)())) elif piece: pieces.append(re_escaped.sub(r'\1', piece)) return ''.join(pieces) diff --git a/django/utils/dateparse.py b/django/utils/dateparse.py index 532bb259c3..032eb493b6 100644 --- a/django/utils/dateparse.py +++ b/django/utils/dateparse.py @@ -7,6 +7,7 @@ import datetime import re +from django.utils import six from django.utils.timezone import utc from django.utils.tzinfo import FixedOffset @@ -34,7 +35,7 @@ def parse_date(value): """ match = date_re.match(value) if match: - kw = dict((k, int(v)) for k, v in match.groupdict().iteritems()) + kw = dict((k, int(v)) for k, v in six.iteritems(match.groupdict())) return datetime.date(**kw) def parse_time(value): @@ -53,7 +54,7 @@ def parse_time(value): kw = match.groupdict() if kw['microsecond']: kw['microsecond'] = kw['microsecond'].ljust(6, '0') - kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None) + kw = dict((k, int(v)) for k, v in six.iteritems(kw) if v is not None) return datetime.time(**kw) def parse_datetime(value): @@ -80,6 +81,6 @@ def parse_datetime(value): if tzinfo[0] == '-': offset = -offset tzinfo = FixedOffset(offset) - kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None) + kw = dict((k, int(v)) for k, v in six.iteritems(kw) if v is not None) kw['tzinfo'] = tzinfo return datetime.datetime(**kw) diff --git a/django/utils/dictconfig.py b/django/utils/dictconfig.py index b4d6d66b3c..c7b39819b5 100644 --- a/django/utils/dictconfig.py +++ b/django/utils/dictconfig.py @@ -363,7 +363,7 @@ class DictConfigurator(BaseConfigurator): #which were in the previous configuration but #which are not in the new configuration. root = logging.root - existing = root.manager.loggerDict.keys() + existing = list(root.manager.loggerDict) #The list needs to be sorted so that we can #avoid disabling child loggers of explicitly #named loggers. With a sorted list it is easier diff --git a/django/utils/encoding.py b/django/utils/encoding.py index f2295444bf..eb60cfde8b 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -24,9 +24,13 @@ class DjangoUnicodeDecodeError(UnicodeDecodeError): class StrAndUnicode(object): """ - A class whose __str__ returns its __unicode__ as a UTF-8 bytestring. + A class that derives __str__ from __unicode__. - Useful as a mix-in. + On Python 2, __str__ returns the output of __unicode__ encoded as a UTF-8 + bytestring. On Python 3, __str__ returns the output of __unicode__. + + Useful as a mix-in. If you support Python 2 and 3 with a single code base, + you can inherit this mix-in and just define __unicode__. """ if six.PY3: def __str__(self): @@ -35,37 +39,36 @@ class StrAndUnicode(object): def __str__(self): return self.__unicode__().encode('utf-8') -def smart_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): +def smart_text(s, encoding='utf-8', strings_only=False, errors='strict'): """ - Returns a unicode object representing 's'. Treats bytestrings using the - 'encoding' codec. + Returns a text object representing 's' -- unicode on Python 2 and str on + Python 3. Treats bytestrings using the 'encoding' codec. If strings_only is True, don't convert (some) non-string-like objects. """ if isinstance(s, Promise): # The input is the result of a gettext_lazy() call. return s - return force_unicode(s, encoding, strings_only, errors) + return force_text(s, encoding, strings_only, errors) def is_protected_type(obj): """Determine if the object instance is of a protected type. Objects of protected types are preserved as-is when passed to - force_unicode(strings_only=True). + force_text(strings_only=True). """ 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'): +def force_text(s, encoding='utf-8', strings_only=False, errors='strict'): """ - Similar to smart_unicode, except that lazy instances are resolved to + Similar to smart_text, except that lazy instances are resolved to strings, rather than kept as lazy objects. If strings_only is True, don't convert (some) non-string-like objects. """ - # 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. + # Handle the common case first, saves 30-40% when s is an instance of + # six.text_type. This function gets called often in that setting. if isinstance(s, six.text_type): return s if strings_only and is_protected_type(s): @@ -92,9 +95,9 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): # without raising a further exception. We do an # approximation to what the Exception's standard str() # output should be. - s = ' '.join([force_unicode(arg, encoding, strings_only, + s = ' '.join([force_text(arg, encoding, strings_only, errors) for arg in s]) - elif not isinstance(s, six.text_type): + else: # 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. @@ -108,21 +111,26 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): # working unicode method. Try to handle this without raising a # further exception by individually forcing the exception args # to unicode. - s = ' '.join([force_unicode(arg, encoding, strings_only, + s = ' '.join([force_text(arg, encoding, strings_only, errors) for arg in s]) return s -def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'): +def smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict'): """ Returns a bytestring version of 's', encoded as specified in 'encoding'. If strings_only is True, don't convert (some) non-string-like objects. """ + if isinstance(s, bytes): + if encoding == 'utf-8': + return s + else: + return s.decode('utf-8', errors).encode(encoding, errors) if strings_only and (s is None or isinstance(s, int)): return s if isinstance(s, Promise): return six.text_type(s).encode(encoding, errors) - elif not isinstance(s, six.string_types): + if not isinstance(s, six.string_types): try: if six.PY3: return six.text_type(s).encode(encoding) @@ -133,15 +141,25 @@ def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'): # An Exception subclass containing non-ASCII data that doesn't # know how to print itself properly. We shouldn't raise a # further exception. - return ' '.join([smart_str(arg, encoding, strings_only, + return ' '.join([smart_bytes(arg, encoding, strings_only, errors) for arg in s]) 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) else: - return s + return s.encode(encoding, errors) + +if six.PY3: + smart_str = smart_text +else: + smart_str = smart_bytes + # backwards compatibility for Python 2 + smart_unicode = smart_text + force_unicode = force_text + +smart_str.__doc__ = """\ +Apply smart_text in Python 3 and smart_bytes in Python 2. + +This is suitable for writing to sys.stdout (for instance). +""" def iri_to_uri(iri): """ @@ -168,7 +186,7 @@ def iri_to_uri(iri): # converted. if iri is None: return iri - return quote(smart_str(iri), safe=b"/#%[]=:;$&()+,!?*@'~") + return quote(smart_bytes(iri), safe=b"/#%[]=:;$&()+,!?*@'~") def filepath_to_uri(path): """Convert an file system path to a URI portion that is suitable for @@ -187,7 +205,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 quote(smart_str(path).replace("\\", "/"), safe=b"/~!*()'") + return quote(smart_bytes(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 6498aaf57c..f9126a6782 100644 --- a/django/utils/feedgenerator.py +++ b/django/utils/feedgenerator.py @@ -29,8 +29,10 @@ try: 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.encoding import force_text, iri_to_uri from django.utils import datetime_safe +from django.utils import six +from django.utils.six import StringIO from django.utils.timezone import is_aware def rfc2822_date(date): @@ -44,25 +46,29 @@ def rfc2822_date(date): dow = days[date.weekday()] month = months[date.month - 1] time_str = date.strftime('%s, %%d %s %%Y %%H:%%M:%%S ' % (dow, month)) + if not six.PY3: # strftime returns a byte string in Python 2 + time_str = time_str.decode('utf-8') if is_aware(date): offset = date.tzinfo.utcoffset(date) timezone = (offset.days * 24 * 60) + (offset.seconds // 60) hour, minute = divmod(timezone, 60) - return time_str + "%+03d%02d" % (hour, minute) + return time_str + '%+03d%02d' % (hour, minute) else: return time_str + '-0000' def rfc3339_date(date): # Support datetime objects older than 1900 date = datetime_safe.new_datetime(date) + time_str = date.strftime('%Y-%m-%dT%H:%M:%S') + if not six.PY3: # strftime returns a byte string in Python 2 + time_str = time_str.decode('utf-8') if is_aware(date): - time_str = date.strftime('%Y-%m-%dT%H:%M:%S') offset = date.tzinfo.utcoffset(date) timezone = (offset.days * 24 * 60) + (offset.seconds // 60) hour, minute = divmod(timezone, 60) - return time_str + "%+03d:%02d" % (hour, minute) + return time_str + '%+03d:%02d' % (hour, minute) else: - return date.strftime('%Y-%m-%dT%H:%M:%SZ') + return time_str + 'Z' def get_tag_uri(url, date): """ @@ -81,12 +87,12 @@ class SyndicationFeed(object): def __init__(self, title, link, description, language=None, author_email=None, author_name=None, author_link=None, subtitle=None, categories=None, feed_url=None, feed_copyright=None, feed_guid=None, ttl=None, **kwargs): - to_unicode = lambda s: force_unicode(s, strings_only=True) + to_unicode = lambda s: force_text(s, strings_only=True) if categories: - categories = [force_unicode(c) for c in categories] + categories = [force_text(c) for c in categories] if ttl is not None: # Force ints to unicode - ttl = force_unicode(ttl) + ttl = force_text(ttl) self.feed = { 'title': to_unicode(title), 'link': iri_to_uri(link), @@ -114,12 +120,12 @@ class SyndicationFeed(object): objects except pubdate, which is a datetime.datetime object, and enclosure, which is an instance of the Enclosure class. """ - to_unicode = lambda s: force_unicode(s, strings_only=True) + to_unicode = lambda s: force_text(s, strings_only=True) if categories: categories = [to_unicode(c) for c in categories] if ttl is not None: # Force ints to unicode - ttl = force_unicode(ttl) + ttl = force_text(ttl) item = { 'title': to_unicode(title), 'link': iri_to_uri(link), @@ -178,8 +184,7 @@ class SyndicationFeed(object): """ Returns the feed in the given encoding as a string. """ - from io import BytesIO - s = BytesIO() + s = StringIO() self.write(s, encoding) return s.getvalue() @@ -237,7 +242,7 @@ class RssFeed(SyndicationFeed): handler.addQuickElement("category", cat) if self.feed['feed_copyright'] is not None: handler.addQuickElement("copyright", self.feed['feed_copyright']) - handler.addQuickElement("lastBuildDate", rfc2822_date(self.latest_post_date()).decode('utf-8')) + handler.addQuickElement("lastBuildDate", rfc2822_date(self.latest_post_date())) if self.feed['ttl'] is not None: handler.addQuickElement("ttl", self.feed['ttl']) @@ -271,7 +276,7 @@ class Rss201rev2Feed(RssFeed): handler.addQuickElement("dc:creator", item["author_name"], {"xmlns:dc": "http://purl.org/dc/elements/1.1/"}) if item['pubdate'] is not None: - handler.addQuickElement("pubDate", rfc2822_date(item['pubdate']).decode('utf-8')) + handler.addQuickElement("pubDate", rfc2822_date(item['pubdate'])) if item['comments'] is not None: handler.addQuickElement("comments", item['comments']) if item['unique_id'] is not None: @@ -314,7 +319,7 @@ class Atom1Feed(SyndicationFeed): if self.feed['feed_url'] is not None: handler.addQuickElement("link", "", {"rel": "self", "href": self.feed['feed_url']}) handler.addQuickElement("id", self.feed['id']) - handler.addQuickElement("updated", rfc3339_date(self.latest_post_date()).decode('utf-8')) + handler.addQuickElement("updated", rfc3339_date(self.latest_post_date())) if self.feed['author_name'] is not None: handler.startElement("author", {}) handler.addQuickElement("name", self.feed['author_name']) @@ -340,7 +345,7 @@ class Atom1Feed(SyndicationFeed): handler.addQuickElement("title", item['title']) handler.addQuickElement("link", "", {"href": item['link'], "rel": "alternate"}) if item['pubdate'] is not None: - handler.addQuickElement("updated", rfc3339_date(item['pubdate']).decode('utf-8')) + handler.addQuickElement("updated", rfc3339_date(item['pubdate'])) # Author information. if item['author_name'] is not None: diff --git a/django/utils/functional.py b/django/utils/functional.py index 2dca182b99..085ec40b63 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -93,13 +93,19 @@ def lazy(func, *resultclasses): if hasattr(cls, k): continue setattr(cls, k, meth) - 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 - elif cls._delegate_str: - cls.__str__ = cls.__str_cast + cls._delegate_bytes = bytes in resultclasses + cls._delegate_text = six.text_type in resultclasses + assert not (cls._delegate_bytes and cls._delegate_text), "Cannot call lazy() with both bytes and text return types." + if cls._delegate_text: + if six.PY3: + cls.__str__ = cls.__text_cast + else: + cls.__unicode__ = cls.__text_cast + elif cls._delegate_bytes: + if six.PY3: + cls.__bytes__ = cls.__bytes_cast + else: + cls.__str__ = cls.__bytes_cast __prepare_class__ = classmethod(__prepare_class__) def __promise__(cls, klass, funcname, method): @@ -120,17 +126,17 @@ def lazy(func, *resultclasses): return __wrapper__ __promise__ = classmethod(__promise__) - def __unicode_cast(self): + def __text_cast(self): return func(*self.__args, **self.__kw) - def __str_cast(self): - return str(func(*self.__args, **self.__kw)) + def __bytes_cast(self): + return bytes(func(*self.__args, **self.__kw)) def __cast(self): - if self._delegate_str: - return self.__str_cast() - elif self._delegate_unicode: - return self.__unicode_cast() + if self._delegate_bytes: + return self.__bytes_cast() + elif self._delegate_text: + return self.__text_cast() else: return func(*self.__args, **self.__kw) @@ -144,10 +150,12 @@ def lazy(func, *resultclasses): other = other.__cast() return self.__cast() < other + __hash__ = object.__hash__ + def __mod__(self, rhs): - if self._delegate_str: - return str(self) % rhs - elif self._delegate_unicode: + if self._delegate_bytes and not six.PY3: + return bytes(self) % rhs + elif self._delegate_text: return six.text_type(self) % rhs else: raise AssertionError('__mod__ not supported for non-string types') @@ -178,7 +186,7 @@ def allow_lazy(func, *resultclasses): """ @wraps(func) def wrapper(*args, **kwargs): - for arg in list(args) + kwargs.values(): + for arg in list(args) + list(six.itervalues(kwargs)): if isinstance(arg, Promise): break else: @@ -234,6 +242,9 @@ class LazyObject(object): __dir__ = new_method_proxy(dir) +# Workaround for http://bugs.python.org/issue12370 +_super = super + class SimpleLazyObject(LazyObject): """ A lazy object initialised from any function. @@ -251,13 +262,17 @@ class SimpleLazyObject(LazyObject): value. """ self.__dict__['_setupfunc'] = func - super(SimpleLazyObject, self).__init__() + _super(SimpleLazyObject, self).__init__() def _setup(self): self._wrapped = self._setupfunc() - __str__ = new_method_proxy(bytes) - __unicode__ = new_method_proxy(six.text_type) + if six.PY3: + __bytes__ = new_method_proxy(bytes) + __str__ = new_method_proxy(str) + else: + __str__ = new_method_proxy(str) + __unicode__ = new_method_proxy(unicode) def __deepcopy__(self, memo): if self._wrapped is empty: @@ -284,7 +299,8 @@ class SimpleLazyObject(LazyObject): __class__ = property(new_method_proxy(operator.attrgetter("__class__"))) __eq__ = new_method_proxy(operator.eq) __hash__ = new_method_proxy(hash) - __nonzero__ = new_method_proxy(bool) + __bool__ = new_method_proxy(bool) # Python 3 + __nonzero__ = __bool__ # Python 2 class lazy_property(property): @@ -312,8 +328,8 @@ def partition(predicate, values): Splits the values into two sets, based on the return value of the function (True/False). e.g.: - >>> partition(lambda: x > 3, range(5)) - [1, 2, 3], [4] + >>> partition(lambda x: x > 3, range(5)) + [0, 1, 2, 3], [4] """ results = ([], []) for item in values: diff --git a/django/utils/html.py b/django/utils/html.py index ca3340ccb1..7dd53a1353 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -11,7 +11,7 @@ except ImportError: # Python 2 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.encoding import smart_bytes, force_text from django.utils.functional import allow_lazy from django.utils import six from django.utils.text import normalize_newlines @@ -33,13 +33,13 @@ link_target_attribute_re = re.compile(r'(<a [^>]*?)target=[^\s>]+') html_gunk_re = re.compile(r'(?:<br clear="all">|<i><\/i>|<b><\/b>|<em><\/em>|<strong><\/strong>|<\/?smallcaps>|<\/?uppercase>)', re.IGNORECASE) hard_coded_bullets_re = re.compile(r'((?:<p>(?:%s).*?[a-zA-Z].*?</p>\s*)+)' % '|'.join([re.escape(x) for x in DOTS]), re.DOTALL) trailing_empty_content_re = re.compile(r'(?:<p>(?: |\s|<br \/>)*?</p>\s*)+\Z') -del x # Temporary variable + def escape(text): """ Returns the given text with ampersands, quotes and angle brackets encoded for use in HTML. """ - return mark_safe(force_unicode(text).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''')) + return mark_safe(force_text(text).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''')) escape = allow_lazy(escape, six.text_type) _base_js_escapes = ( @@ -63,7 +63,7 @@ _js_escapes = (_base_js_escapes + def escapejs(value): """Hex encodes characters for use in JavaScript strings.""" for bad, good in _js_escapes: - value = mark_safe(force_unicode(value).replace(bad, good)) + value = mark_safe(force_text(value).replace(bad, good)) return value escapejs = allow_lazy(escapejs, six.text_type) @@ -84,7 +84,7 @@ def format_html(format_string, *args, **kwargs): """ args_safe = map(conditional_escape, args) kwargs_safe = dict([(k, conditional_escape(v)) for (k, v) in - kwargs.iteritems()]) + six.iteritems(kwargs)]) return mark_safe(format_string.format(*args_safe, **kwargs_safe)) def format_html_join(sep, format_string, args_generator): @@ -120,22 +120,22 @@ linebreaks = allow_lazy(linebreaks, six.text_type) def strip_tags(value): """Returns the given HTML with all tags stripped.""" - return re.sub(r'<[^>]*?>', '', force_unicode(value)) + return re.sub(r'<[^>]*?>', '', force_text(value)) 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)) + return re.sub(r'>\s+<', '><', force_text(value)) 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)) + return re.sub(r'&(?:\w+|#\d+);', '', force_text(value)) 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)) + return unencoded_ampersands_re.sub('&', force_text(value)) fix_ampersands = allow_lazy(fix_ampersands, six.text_type) def smart_urlquote(url): @@ -153,9 +153,9 @@ def smart_urlquote(url): # 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 = quote(smart_str(url), safe=b'!*\'();:@&=+$,/?#[]~') + url = quote(smart_bytes(url), safe=b'!*\'();:@&=+$,/?#[]~') - return force_unicode(url) + return force_text(url) def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False): """ @@ -176,7 +176,7 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False): """ trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x safe_input = isinstance(text, SafeData) - words = word_split_re.split(force_unicode(text)) + words = word_split_re.split(force_text(text)) for i, word in enumerate(words): match = None if '.' in word or '@' in word or ':' in word: @@ -245,7 +245,7 @@ def clean_html(text): bottom of the text. """ from django.utils.text import normalize_newlines - text = normalize_newlines(force_unicode(text)) + text = normalize_newlines(force_text(text)) text = re.sub(r'<(/?)\s*b\s*>', '<\\1strong>', text) text = re.sub(r'<(/?)\s*i\s*>', '<\\1em>', text) text = fix_ampersands(text) diff --git a/django/utils/http.py b/django/utils/http.py index f3a3dce58c..22e81a33d7 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -13,7 +13,7 @@ except ImportError: # Python 2 from email.utils import formatdate from django.utils.datastructures import MultiValueDict -from django.utils.encoding import smart_str, force_unicode +from django.utils.encoding import force_text, smart_str from django.utils.functional import allow_lazy from django.utils import six @@ -37,7 +37,7 @@ 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_parse.quote(smart_str(url), smart_str(safe))) + return force_text(urllib_parse.quote(smart_str(url), smart_str(safe))) urlquote = allow_lazy(urlquote, six.text_type) def urlquote_plus(url, safe=''): @@ -47,7 +47,7 @@ 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_parse.quote_plus(smart_str(url), smart_str(safe))) + return force_text(urllib_parse.quote_plus(smart_str(url), smart_str(safe))) urlquote_plus = allow_lazy(urlquote_plus, six.text_type) def urlunquote(quoted_url): @@ -55,7 +55,7 @@ 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_parse.unquote(smart_str(quoted_url))) + return force_text(urllib_parse.unquote(smart_str(quoted_url))) urlunquote = allow_lazy(urlunquote, six.text_type) def urlunquote_plus(quoted_url): @@ -63,7 +63,7 @@ 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_parse.unquote_plus(smart_str(quoted_url))) + return force_text(urllib_parse.unquote_plus(smart_str(quoted_url))) urlunquote_plus = allow_lazy(urlunquote_plus, six.text_type) def urlencode(query, doseq=0): @@ -167,8 +167,9 @@ def base36_to_int(s): if len(s) > 13: raise ValueError("Base36 input too large") value = int(s, 36) - # ... then do a final check that the value will fit into an int. - if value > sys.maxint: + # ... then do a final check that the value will fit into an int to avoid + # returning a long (#15067). The long type was removed in Python 3. + if not six.PY3 and value > sys.maxint: raise ValueError("Base36 input too large") return value @@ -178,8 +179,13 @@ def int_to_base36(i): """ digits = "0123456789abcdefghijklmnopqrstuvwxyz" factor = 0 - if not 0 <= i <= sys.maxint: - raise ValueError("Base36 conversion input too large or incorrect type.") + if i < 0: + raise ValueError("Negative base36 conversion input.") + if not six.PY3: + if not isinstance(i, six.integer_types): + raise TypeError("Non-integer base36 conversion input.") + if i > sys.maxint: + raise ValueError("Base36 conversion input too large.") # Find starting factor while True: factor += 1 diff --git a/django/utils/regex_helper.py b/django/utils/regex_helper.py index 8953a21e95..7b40d141de 100644 --- a/django/utils/regex_helper.py +++ b/django/utils/regex_helper.py @@ -8,6 +8,7 @@ should be good enough for a large class of URLS, however. from __future__ import unicode_literals from django.utils import six +from django.utils.six.moves import zip # 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 @@ -44,8 +45,8 @@ class NonCapture(list): def normalize(pattern): """ - Given a reg-exp pattern, normalizes it to a list of forms that suffice for - reverse matching. This does the following: + Given a reg-exp pattern, normalizes it to an iterable of forms that + suffice for reverse matching. This does the following: (1) For any repeating sections, keeps the minimum number of occurrences permitted (this means zero for optional groups). @@ -80,7 +81,7 @@ def normalize(pattern): try: ch, escaped = next(pattern_iter) except StopIteration: - return zip([''], [[]]) + return [('', [])] try: while True: @@ -193,9 +194,9 @@ def normalize(pattern): pass except NotImplementedError: # A case of using the disjunctive form. No results for you! - return zip([''], [[]]) + return [('', [])] - return zip(*flatten_result(result)) + return list(zip(*flatten_result(result))) def next_char(input_iter): """ diff --git a/django/utils/safestring.py b/django/utils/safestring.py index 1599fc2a66..bfaefd07ee 100644 --- a/django/utils/safestring.py +++ b/django/utils/safestring.py @@ -96,7 +96,7 @@ def mark_safe(s): """ if isinstance(s, SafeData): return s - if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_str): + if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes): return SafeString(s) if isinstance(s, (six.text_type, Promise)): return SafeUnicode(s) @@ -112,7 +112,7 @@ def mark_for_escaping(s): """ if isinstance(s, (SafeData, EscapeData)): return s - if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_str): + if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes): return EscapeString(s) if isinstance(s, (six.text_type, Promise)): return EscapeUnicode(s) diff --git a/django/utils/six.py b/django/utils/six.py index e226bba09e..ceb5d70e58 100644 --- a/django/utils/six.py +++ b/django/utils/six.py @@ -113,6 +113,7 @@ class _MovedItems(types.ModuleType): _moved_attributes = [ MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), MovedAttribute("map", "itertools", "builtins", "imap", "map"), MovedAttribute("reload_module", "__builtin__", "imp", "reload"), MovedAttribute("reduce", "__builtin__", "functools"), diff --git a/django/utils/termcolors.py b/django/utils/termcolors.py index 1eebaa2316..4f74b564a5 100644 --- a/django/utils/termcolors.py +++ b/django/utils/termcolors.py @@ -2,6 +2,8 @@ termcolors.py """ +from django.utils import six + color_names = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white') foreground = dict([(color_names[x], '3%s' % x) for x in range(8)]) background = dict([(color_names[x], '4%s' % x) for x in range(8)]) @@ -41,7 +43,7 @@ def colorize(text='', opts=(), **kwargs): code_list = [] if text == '' and len(opts) == 1 and opts[0] == 'reset': return '\x1b[%sm' % RESET - for k, v in kwargs.iteritems(): + for k, v in six.iteritems(kwargs): if k == 'fg': code_list.append(foreground[v]) elif k == 'bg': diff --git a/django/utils/text.py b/django/utils/text.py index 43056aa634..0838d79c65 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -7,13 +7,13 @@ from gzip import GzipFile from django.utils.six.moves import html_entities from io import BytesIO -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text 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 = lambda x: x and force_text(x)[0].upper() + force_text(x)[1:] capfirst = allow_lazy(capfirst, six.text_type) # Set up regular expressions @@ -26,7 +26,7 @@ def wrap(text, width): A word-wrap function that preserves existing line breaks and most spaces in the text. Expects that existing line breaks are posix newlines. """ - text = force_unicode(text) + text = force_text(text) def _generator(): it = iter(text.split(' ')) word = next(it) @@ -55,14 +55,14 @@ class Truncator(SimpleLazyObject): An object used to truncate text, either by characters or words. """ def __init__(self, text): - super(Truncator, self).__init__(lambda: force_unicode(text)) + super(Truncator, self).__init__(lambda: force_text(text)) def add_truncation_text(self, text, truncate=None): if truncate is None: truncate = pgettext( 'String to return when truncating text', '%(truncated_text)s...') - truncate = force_unicode(truncate) + truncate = force_text(truncate) if '%(truncated_text)s' in truncate: return truncate % {'truncated_text': text} # The truncation text didn't contain the %(truncated_text)s string @@ -226,7 +226,7 @@ def get_valid_filename(s): >>> get_valid_filename("john's portrait in 2004.jpg") 'johns_portrait_in_2004.jpg' """ - s = force_unicode(s).strip().replace(' ', '_') + s = force_text(s).strip().replace(' ', '_') return re.sub(r'(?u)[^-\w.]', '', s) get_valid_filename = allow_lazy(get_valid_filename, six.text_type) @@ -244,20 +244,20 @@ def get_text_list(list_, last_word=ugettext_lazy('or')): '' """ if len(list_) == 0: return '' - if len(list_) == 1: return force_unicode(list_[0]) + if len(list_) == 1: return force_text(list_[0]) return '%s %s %s' % ( # 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])) + _(', ').join([force_text(i) for i in list_][:-1]), + force_text(last_word), force_text(list_[-1])) 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)) + return force_text(re.sub(r'\r\n|\r|\n', '\n', text)) normalize_newlines = allow_lazy(normalize_newlines, six.text_type) def recapitalize(text): "Recapitalizes text, placing caps after end-of-sentence punctuation." - text = force_unicode(text).lower() + text = force_text(text).lower() capsRE = re.compile(r'(?:^|(?<=[\.\?\!] ))([a-z])') text = capsRE.sub(lambda x: x.group(1).upper(), text) return text @@ -330,7 +330,7 @@ def smart_split(text): >>> list(smart_split(r'A "\"funky\" style" test.')) ['A', '"\\"funky\\" style"', 'test.'] """ - text = force_unicode(text) + text = force_text(text) for bit in smart_split_re.finditer(text): yield bit.group(0) smart_split = allow_lazy(smart_split, six.text_type) diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py index d31a7aebf1..febde404a5 100644 --- a/django/utils/translation/__init__.py +++ b/django/utils/translation/__init__.py @@ -3,7 +3,7 @@ Internationalization support. """ from __future__ import unicode_literals -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.functional import lazy from django.utils import six @@ -139,7 +139,7 @@ def _string_concat(*strings): Lazy variant of string concatenation, needed for translations that are constructed from multiple parts. """ - return ''.join([force_unicode(s) for s in strings]) + return ''.join([force_text(s) for s in strings]) string_concat = lazy(_string_concat, six.text_type) def get_language_info(lang_code): diff --git a/django/utils/translation/trans_null.py b/django/utils/translation/trans_null.py index 0fabc707ce..5c51468204 100644 --- a/django/utils/translation/trans_null.py +++ b/django/utils/translation/trans_null.py @@ -3,7 +3,7 @@ # settings.USE_I18N = False can use this module rather than trans_real.py. from django.conf import settings -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.safestring import mark_safe, SafeData def ngettext(singular, plural, number): @@ -12,7 +12,7 @@ def ngettext(singular, plural, number): ngettext_lazy = ngettext def ungettext(singular, plural, number): - return force_unicode(ngettext(singular, plural, number)) + return force_text(ngettext(singular, plural, number)) def pgettext(context, message): return ugettext(message) @@ -44,7 +44,7 @@ def gettext(message): return result def ugettext(message): - return force_unicode(gettext(message)) + return force_text(gettext(message)) gettext_noop = gettext_lazy = _ = gettext diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index 9ebcbf5441..9e6eadcd48 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -9,7 +9,9 @@ import gettext as gettext_module from threading import local from django.utils.importlib import import_module +from django.utils.encoding import smart_str, smart_text from django.utils.safestring import mark_safe, SafeData +from django.utils import six from django.utils.six import StringIO @@ -259,12 +261,14 @@ def do_translate(message, translation_function): def gettext(message): return do_translate(message, 'gettext') -def ugettext(message): - return do_translate(message, 'ugettext') +if six.PY3: + ugettext = gettext +else: + def ugettext(message): + return do_translate(message, 'ugettext') def pgettext(context, message): - result = do_translate( - "%s%s%s" % (context, CONTEXT_SEPARATOR, message), 'ugettext') + result = ugettext("%s%s%s" % (context, CONTEXT_SEPARATOR, message)) if CONTEXT_SEPARATOR in result: # Translation not found result = message @@ -297,20 +301,23 @@ def ngettext(singular, plural, number): """ return do_ntranslate(singular, plural, number, 'ngettext') -def ungettext(singular, plural, number): - """ - Returns a unicode strings of the translation of either the singular or - plural, based on the number. - """ - return do_ntranslate(singular, plural, number, 'ungettext') +if six.PY3: + ungettext = ngettext +else: + def ungettext(singular, plural, number): + """ + Returns a unicode strings of the translation of either the singular or + plural, based on the number. + """ + return do_ntranslate(singular, plural, number, 'ungettext') def npgettext(context, singular, plural, number): - result = do_ntranslate("%s%s%s" % (context, CONTEXT_SEPARATOR, singular), - "%s%s%s" % (context, CONTEXT_SEPARATOR, plural), - number, 'ungettext') + result = ungettext("%s%s%s" % (context, CONTEXT_SEPARATOR, singular), + "%s%s%s" % (context, CONTEXT_SEPARATOR, plural), + number) if CONTEXT_SEPARATOR in result: # Translation not found - result = do_ntranslate(singular, plural, number, 'ungettext') + result = ungettext(singular, plural, number) return result def all_locale_paths(): @@ -440,7 +447,7 @@ def templatize(src, origin=None): from django.conf import settings from django.template import (Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK, TOKEN_COMMENT, TRANSLATOR_COMMENT_MARK) - src = src.decode(settings.FILE_CHARSET) + src = smart_text(src, settings.FILE_CHARSET) out = StringIO() message_context = None intrans = False @@ -455,7 +462,7 @@ def templatize(src, origin=None): content = ''.join(comment) translators_comment_start = None for lineno, line in enumerate(content.splitlines(True)): - if line.lstrip().startswith(TRANSLATOR_COMMENT_MARK): + if line.lstrip().startswith(smart_text(TRANSLATOR_COMMENT_MARK)): translators_comment_start = lineno for lineno, line in enumerate(content.splitlines(True)): if translators_comment_start is not None and lineno >= translators_comment_start: @@ -570,7 +577,7 @@ def templatize(src, origin=None): out.write(' # %s' % t.contents) else: out.write(blankout(t.contents, 'X')) - return out.getvalue().encode('utf-8') + return smart_str(out.getvalue()) def parse_accept_lang_header(lang_string): """ diff --git a/django/utils/tree.py b/django/utils/tree.py index 36b5977942..717181d2b9 100644 --- a/django/utils/tree.py +++ b/django/utils/tree.py @@ -68,11 +68,12 @@ class Node(object): """ return len(self.children) - def __nonzero__(self): + def __bool__(self): """ For truth value testing. """ return bool(self.children) + __nonzero__ = __bool__ # Python 2 def __contains__(self, other): """ diff --git a/django/utils/tzinfo.py b/django/utils/tzinfo.py index 05f4aa6d2f..208b7e7191 100644 --- a/django/utils/tzinfo.py +++ b/django/utils/tzinfo.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import time from datetime import timedelta, tzinfo -from django.utils.encoding import smart_unicode, smart_str, DEFAULT_LOCALE_ENCODING +from django.utils.encoding import smart_text, smart_str, DEFAULT_LOCALE_ENCODING # Python's doc say: "A tzinfo subclass must have an __init__() method that can # be called with no arguments". FixedOffset and LocalTimezone don't honor this @@ -72,7 +72,7 @@ class LocalTimezone(tzinfo): def tzname(self, dt): try: - return smart_unicode(time.tzname[self._isdst(dt)], + return smart_text(time.tzname[self._isdst(dt)], DEFAULT_LOCALE_ENCODING) except UnicodeDecodeError: return None |
