diff --git a/.travis.yml b/.travis.yml index f447312..c3c57cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: python env: - TOXENV=py27 + - TOXENV=py33 - TOXENV=flake8 - TOXENV=sphinx - TOXENV=readme diff --git a/demo/demoproject/virtual/views.py b/demo/demoproject/virtual/views.py index 8bb7020..805651f 100644 --- a/demo/demoproject/virtual/views.py +++ b/demo/demoproject/virtual/views.py @@ -1,4 +1,4 @@ -from StringIO import StringIO +from six import StringIO from django.core.files.base import ContentFile @@ -10,7 +10,7 @@ from django_downloadview import StringIteratorIO class TextDownloadView(VirtualDownloadView): def get_file(self): """Return :class:`django.core.files.base.ContentFile` object.""" - return ContentFile(u"Hello world!\n", name='hello-world.txt') + return ContentFile(b"Hello world!\n", name='hello-world.txt') class StringIODownloadView(VirtualDownloadView): diff --git a/django_downloadview/apache/tests.py b/django_downloadview/apache/tests.py index b0ac5db..0aa17b0 100644 --- a/django_downloadview/apache/tests.py +++ b/django_downloadview/apache/tests.py @@ -1,3 +1,4 @@ +from six import iteritems from django_downloadview.apache.response import XSendfileResponse @@ -21,7 +22,7 @@ class XSendfileValidator(object): """ self.assert_x_sendfile_response(test_case, response) - for key, value in assertions.iteritems(): + for key, value in iteritems(assertions): assert_func = getattr(self, 'assert_%s' % key) assert_func(test_case, response, value) diff --git a/django_downloadview/files.py b/django_downloadview/files.py index 41ebb6b..e41534d 100644 --- a/django_downloadview/files.py +++ b/django_downloadview/files.py @@ -2,7 +2,7 @@ """File wrappers for use as exchange data between views and responses.""" from __future__ import absolute_import from io import BytesIO -from urlparse import urlparse +from six.moves.urllib.parse import urlparse from django.core.files.base import File from django.utils.encoding import force_bytes diff --git a/django_downloadview/nginx/tests.py b/django_downloadview/nginx/tests.py index 0cf7349..02bfec9 100644 --- a/django_downloadview/nginx/tests.py +++ b/django_downloadview/nginx/tests.py @@ -1,3 +1,4 @@ +from six import iteritems from django_downloadview.nginx.response import XAccelRedirectResponse @@ -35,7 +36,7 @@ class XAccelRedirectValidator(object): """ self.assert_x_accel_redirect_response(test_case, response) - for key, value in assertions.iteritems(): + for key, value in iteritems(assertions): assert_func = getattr(self, 'assert_%s' % key) assert_func(test_case, response, value) diff --git a/django_downloadview/response.py b/django_downloadview/response.py index 9cb7de7..40f0a0e 100644 --- a/django_downloadview/response.py +++ b/django_downloadview/response.py @@ -4,7 +4,8 @@ import os import mimetypes import re import unicodedata -import urllib +import six +from six.moves import urllib from django.conf import settings from django.http import HttpResponse, StreamingHttpResponse @@ -14,28 +15,31 @@ from django.utils.encoding import force_str def encode_basename_ascii(value): """Return US-ASCII encoded ``value`` for use in Content-Disposition header. - >>> encode_basename_ascii(unicode('éà', 'utf-8')) + >>> encode_basename_ascii('éà') # doctest: +IGNORE_UNICODE u'ea' Spaces are converted to underscores. - >>> encode_basename_ascii(' ') + >>> encode_basename_ascii(' ') # doctest: +IGNORE_UNICODE u'_' Text with non US-ASCII characters is expected to be unicode. - >>> encode_basename_ascii('éà') # doctest: +ELLIPSIS + >>> from six import b + >>> encode_basename_ascii(b('éà')) # doctest: +ELLIPSIS Traceback (most recent call last): ... - UnicodeDecodeError: \'ascii\' codec can\'t decode byte ... + UnicodeDecodeError: ... Of course, ASCII values are not modified. - >>> encode_basename_ascii('ea') + >>> encode_basename_ascii('ea') # doctest: +IGNORE_UNICODE u'ea' """ - ascii_basename = unicode(value) + if isinstance(value, six.binary_type): + value = value.decode('utf-8') + ascii_basename = six.text_type(value) ascii_basename = unicodedata.normalize('NFKD', ascii_basename) ascii_basename = ascii_basename.encode('ascii', 'ignore') ascii_basename = ascii_basename.decode('ascii') @@ -49,11 +53,11 @@ def encode_basename_utf8(value): >>> encode_basename_utf8(u' .txt') '%20.txt' - >>> encode_basename_utf8(unicode('éà', 'utf-8')) + >>> encode_basename_utf8(u'éà') '%C3%A9%C3%A0' """ - return urllib.quote(force_str(value)) + return urllib.parse.quote(force_str(value)) def content_disposition(filename): @@ -70,7 +74,7 @@ def content_disposition(filename): If filename contains non US-ASCII characters, the returned value contains UTF-8 encoded filename and US-ASCII fallback. - >>> content_disposition(unicode('é.txt', 'utf-8')) + >>> content_disposition(u'é.txt') "attachment; filename=e.txt; filename*=UTF-8''%C3%A9.txt" """ diff --git a/django_downloadview/test.py b/django_downloadview/test.py index e9fc454..714e8c2 100644 --- a/django_downloadview/test.py +++ b/django_downloadview/test.py @@ -1,5 +1,6 @@ """Testing utilities.""" import shutil +from six import iteritems import tempfile from django.conf import settings @@ -101,7 +102,7 @@ class DownloadResponseValidator(object): """ self.assert_download_response(test_case, response) - for key, value in assertions.iteritems(): + for key, value in iteritems(assertions): assert_func = getattr(self, 'assert_%s' % key) assert_func(test_case, response, value) @@ -138,7 +139,9 @@ class DownloadResponseValidator(object): test_case.assertTrue(response['Content-Type'].startswith(value)) def assert_content(self, test_case, response, value): - test_case.assertEqual(''.join(response.streaming_content), value) + test_case.assertEqual( + ''.join([s.decode('utf-8') for s in response.streaming_content]), + value) def assert_attachment(self, test_case, response, value): test_case.assertEqual('attachment;' in response['Content-Disposition'], diff --git a/django_downloadview/tests/api.py b/django_downloadview/tests/api.py index 4113043..ce2ebfc 100644 --- a/django_downloadview/tests/api.py +++ b/django_downloadview/tests/api.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """Test suite around :mod:`django_downloadview.api` and deprecation plan.""" import unittest +from six.moves import reload_module as reload import warnings from django.core.exceptions import ImproperlyConfigured diff --git a/django_downloadview/utils.py b/django_downloadview/utils.py index f5b3326..ea08f44 100644 --- a/django_downloadview/utils.py +++ b/django_downloadview/utils.py @@ -26,7 +26,7 @@ def url_basename(url, content_type): If URL contains extension, it is kept as-is. - >>> url_basename(u'/path/to/somefile.rst', 'text/plain') + >>> url_basename(u'/path/to/somefile.rst', 'text/plain') # doctest: +IGNORE_UNICODE u'somefile.rst' """ @@ -43,5 +43,5 @@ def import_member(import_string): """ module_name, factory_name = str(import_string).rsplit('.', 1) - module = __import__(module_name, globals(), locals(), [factory_name], -1) + module = __import__(module_name, globals(), locals(), [factory_name], 0) return getattr(module, factory_name) diff --git a/setup.py b/setup.py index 12809e6..d6f037b 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ KEYWORDS = ['file', 'mod_xsendfile', 'offload'] PACKAGES = [NAME.replace('-', '_')] -REQUIREMENTS = ['setuptools', 'Django>=1.5', 'requests'] +REQUIREMENTS = ['setuptools', 'Django>=1.5', 'requests', 'six'] if IS_PYTHON2: REQUIREMENTS.append('mock') ENTRY_POINTS = {} diff --git a/tox.ini b/tox.ini index 266a4d5..8bfcb8b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,12 @@ [tox] -envlist = py27,flake8,sphinx,readme +envlist = py27,py33,flake8,sphinx,readme [testenv] deps = nose rednose coverage + doctest-ignore-unicode commands = pip install -e ./ pip install -e demo/ @@ -28,6 +29,7 @@ deps = nose rednose Sphinx + doctest-ignore-unicode commands = make --directory=docs clean html doctest whitelist_externals =