Introduced support of Python >= 3.3. Closes #46.

This commit is contained in:
Benoît Bryon 2014-03-02 19:35:20 +01:00
commit 3b725c2ab4
13 changed files with 49 additions and 83 deletions

View file

@ -1,6 +1,7 @@
language: python
env:
- TOXENV=py27
- TOXENV=py33
- TOXENV=flake8
- TOXENV=sphinx
- TOXENV=readme

View file

@ -8,6 +8,8 @@ future releases, check `milestones`_ and :doc:`/about/vision`.
1.6 (unreleased)
----------------
- Feature #46: introduced support for Python>=3.3.
- Feature #74: the Makefile in project's repository no longer creates a
virtualenv. Developers setup the environment as they like, i.e. using
virtualenv, virtualenvwrapper or whatever. Tests are run with tox.

View file

@ -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):

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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
@ -12,30 +13,27 @@ from django.utils.encoding import force_str
def encode_basename_ascii(value):
"""Return US-ASCII encoded ``value`` for use in Content-Disposition header.
u"""Return US-ASCII encoded ``value`` for Content-Disposition header.
>>> encode_basename_ascii(unicode('éà', 'utf-8'))
u'ea'
>>> print(encode_basename_ascii(u'éà'))
ea
Spaces are converted to underscores.
>>> encode_basename_ascii(' ')
u'_'
Text with non US-ASCII characters is expected to be unicode.
>>> encode_basename_ascii('éà') # doctest: +ELLIPSIS
Traceback (most recent call last):
...
UnicodeDecodeError: \'ascii\' codec can\'t decode byte ...
>>> print(encode_basename_ascii(' '))
_
Of course, ASCII values are not modified.
>>> encode_basename_ascii('ea')
u'ea'
>>> print(encode_basename_ascii('ea'))
ea
>>> print(encode_basename_ascii(b'ea'))
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')
@ -44,34 +42,34 @@ def encode_basename_ascii(value):
def encode_basename_utf8(value):
"""Return UTF-8 encoded ``value`` for use in Content-Disposition header.
u"""Return UTF-8 encoded ``value`` for use in Content-Disposition header.
>>> encode_basename_utf8(u' .txt')
'%20.txt'
>>> print(encode_basename_utf8(u' .txt'))
%20.txt
>>> encode_basename_utf8(unicode('éà', 'utf-8'))
'%C3%A9%C3%A0'
>>> print(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):
"""Return value of ``Content-Disposition`` header with 'attachment'.
u"""Return value of ``Content-Disposition`` header with 'attachment'.
>>> content_disposition('demo.txt')
'attachment; filename=demo.txt'
>>> print(content_disposition('demo.txt'))
attachment; filename=demo.txt
If filename is empty, only "attachment" is returned.
>>> content_disposition('')
'attachment'
>>> print(content_disposition(''))
attachment
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'))
"attachment; filename=e.txt; filename*=UTF-8''%C3%A9.txt"
>>> print(content_disposition(u'é.txt'))
attachment; filename=e.txt; filename*=UTF-8''%C3%A9.txt
"""
if not filename:

View file

@ -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'],

View file

@ -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

View file

@ -26,8 +26,8 @@ def url_basename(url, content_type):
If URL contains extension, it is kept as-is.
>>> url_basename(u'/path/to/somefile.rst', 'text/plain')
u'somefile.rst'
>>> print(url_basename(u'/path/to/somefile.rst', 'text/plain'))
somefile.rst
"""
return url.split('/')[-1]
@ -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)

View file

@ -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 = {}

View file

@ -50,44 +50,3 @@ class VersionTestCase(unittest.TestCase):
'You may need to run ``make develop`` to update the '
'installed version in development environment.'
% (self.get_version(), file_version))
class ReadMeTestCase(unittest.TestCase):
"""Test suite around README file."""
def test_readme_build(self):
"""README builds to HTML without errors."""
# Run build.
import docutils.core
import docutils.io
source = open(os.path.join(project_dir, 'README.rst')).read()
writer_name = 'html'
import sys
from StringIO import StringIO
stderr_backup = sys.stderr
sys.stderr = StringIO()
output, pub = docutils.core.publish_programmatically(
source=source,
source_class=docutils.io.StringInput,
source_path=None,
destination_class=docutils.io.StringOutput,
destination=None,
destination_path=None,
reader=None,
reader_name='standalone',
parser=None,
parser_name='restructuredtext',
writer=None,
writer_name=writer_name,
settings=None,
settings_spec=None,
settings_overrides=None,
config_section=None,
enable_exit_status=False)
sys.stderr = stderr_backup
errors = pub._stderr.stream.getvalue()
# Check result.
self.assertFalse(errors, "Docutils reported errors while building "
"readme content from reStructuredText to "
"HTML. So PyPI would display the readme as "
"text instead of HTML. Errors are:\n%s"
% errors)

View file

@ -1,5 +1,5 @@
[tox]
envlist = py27,flake8,sphinx,readme
envlist = py27,py33,flake8,sphinx,readme
[testenv]
deps =
@ -39,6 +39,6 @@ deps =
pygments
commands =
mkdir -p var/docs
rst2html.py --exit-status=2 README.rst var/docs/README.html
rst2html.py --exit-status=2 README.rst var/docs/README.html
whitelist_externals =
mkdir