Add Python 3 support.

This commit is contained in:
Berker Peksag 2013-06-11 09:15:41 +03:00
parent 95b30e6207
commit b40ccfcba3
18 changed files with 164 additions and 90 deletions

View File

@ -1,11 +1,16 @@
language: python
python:
- "2.6"
- "2.7"
- "2.6"
- "2.7"
- "3.3"
env:
- DJANGO_VERSION=1.4.5
- DJANGO_VERSION=1.5.1
- DJANGO_VERSION=1.4.6
- DJANGO_VERSION=1.5.2
install:
- pip install -q Django==${DJANGO_VERSION} --use-mirrors
- pip install -q -r requirements.txt --use-mirrors
script: fab test
- pip install Django==${DJANGO_VERSION}
- pip install -r requirements.txt
matrix:
exclude:
- python: "3.3"
env: DJANGO_VERSION=1.4.6
script: python run_tests.py

View File

@ -1,6 +1,11 @@
CHANGES
=======
v0.x.y
------
* Added Python 3 support.
v0.6.2
------

View File

@ -190,6 +190,11 @@ Usage::
Testing
-------
Testing is handle via fabric::
To run the test suite, you need to define ``DJANGO_SETTINGS_MODULE`` first::
fab test
$ export DJANGO_SETTINGS_MODULE="fake_settings"
$ nosetests
or simply run::
$ python run_tests.py

View File

@ -154,9 +154,9 @@ could create a link if I knew how to do that.
The other method uses Jinja's ``trans`` tag::
{% trans user=review.user|user_link, date=review.created|datetime %}
by {{ user }} on {{ date }}
{% endtrans %}
{% trans user=review.user|user_link, date=review.created|datetime %}
by {{ user }} on {{ date }}
{% endtrans %}
``trans`` is nice when you have a lot of text or want to inject some variables
directly. Both methods are useful, pick the one that makes you happy.
@ -174,6 +174,11 @@ not recognize.
Testing
-------
Testing is handle via fabric::
To run the test suite, you need to define ``DJANGO_SETTINGS_MODULE`` first::
fab test
$ export DJANGO_SETTINGS_MODULE="fake_settings"
$ nosetests
or simply run::
$ python run_tests.py

40
fabfile.py vendored
View File

@ -1,40 +0,0 @@
"""
Creating standalone Django apps is a PITA because you're not in a project, so
you don't have a settings.py file. I can never remember to define
DJANGO_SETTINGS_MODULE, so I run these commands which get the right env
automatically.
"""
import functools
import os
from fabric.api import local, cd, env
from fabric.contrib.project import rsync_project
NAME = os.path.basename(os.path.dirname(__file__))
ROOT = os.path.abspath(os.path.dirname(__file__))
os.environ['DJANGO_SETTINGS_MODULE'] = 'fake_settings'
os.environ['PYTHONPATH'] = os.pathsep.join([ROOT,
os.path.join(ROOT, 'examples')])
env.hosts = ['jbalogh.me']
local = functools.partial(local, capture=False)
def doc(kind='html'):
with cd('docs'):
local('make clean %s' % kind)
def shell():
local('django-admin.py shell')
def test():
local('nosetests')
def cover():
local('nosetests --with-coverage')
def updoc():
doc('dirhtml')
rsync_project('p/%s' % NAME, 'docs/_build/dirhtml/', delete=True)

View File

@ -1,4 +1,7 @@
"""Adapter for using Jinja2 with Django."""
from __future__ import unicode_literals
import functools
import imp
import logging

View File

@ -1,6 +1,14 @@
# coding: utf-8
from __future__ import unicode_literals, print_function
from django.utils import six
from django.utils.translation import ugettext as _
from django.template.defaulttags import CsrfTokenNode
from django.utils.encoding import smart_unicode
try:
from django.utils.encoding import smart_unicode as smart_text
except ImportError:
from django.utils.encoding import smart_text
from django.core.urlresolvers import reverse
import jinja2
@ -23,7 +31,6 @@ def f(string, *args, **kwargs):
>>> {{ "{0} arguments and {x} arguments"|f('positional', x='keyword') }}
"positional arguments and keyword arguments"
"""
string = unicode(string)
return string.format(*args, **kwargs)
@ -32,12 +39,12 @@ def fe(string, *args, **kwargs):
"""Format a safe string with potentially unsafe arguments, then return a
safe string."""
string = unicode(string)
string = six.text_type(string)
args = [jinja2.escape(smart_unicode(v)) for v in args]
args = [jinja2.escape(smart_text(v)) for v in args]
for k in kwargs:
kwargs[k] = jinja2.escape(smart_unicode(kwargs[k]))
kwargs[k] = jinja2.escape(smart_text(kwargs[k]))
return jinja2.Markup(string.format(*args, **kwargs))
@ -54,8 +61,12 @@ def nl2br(string):
def datetime(t, fmt=None):
"""Call ``datetime.strftime`` with the given format string."""
if fmt is None:
fmt = _('%B %e, %Y')
return smart_unicode(t.strftime(fmt.encode('utf-8'))) if t else u''
fmt = _(u'%B %e, %Y')
if not six.PY3:
# The datetime.strftime function strictly does not
# support Unicode in Python 2 but is Unicode only in 3.x.
fmt = fmt.encode('utf-8')
return smart_text(t.strftime(fmt)) if t else ''
@register.filter

View File

@ -21,9 +21,13 @@ from the nuggets project at
https://github.com/mozilla/nuggets/blob/master/safe_django_forms.py
"""
from __future__ import unicode_literals
import django.utils.encoding
import django.utils.html
import django.utils.safestring
from django.utils import six
# This function gets directly imported within Django, so this change needs to
@ -47,7 +51,7 @@ def __html__(self):
Allows interoperability with other template engines.
"""
return unicode(self)
return six.text_type(self)
# Django uses StrAndUnicode for classes like Form, BoundField, Widget which
@ -57,7 +61,7 @@ class SafeStrAndUnicode(django.utils.encoding.StrAndUnicode):
"""A class whose __str__ and __html__ returns __unicode__."""
def __html__(self):
return unicode(self)
return six.text_type(self)
def patch():

View File

@ -1,8 +1,13 @@
from __future__ import unicode_literals
from django.shortcuts import render
import jinja2
from nose.tools import eq_
from mock import Mock, patch, sentinel
try:
from unittest.mock import Mock, patch, sentinel
except ImportError:
from mock import Mock, patch, sentinel
import jingo

View File

@ -1,20 +1,23 @@
# -*- coding: utf-8 -*-
"""Tests for the jingo's builtin helpers."""
from __future__ import unicode_literals
from datetime import datetime
from collections import namedtuple
from django.utils import six
from jinja2 import Markup
from mock import patch
try:
from unittest.mock import patch
except ImportError:
from mock import patch
from nose.tools import eq_
import jingo
from jingo import helpers
from jingo import register
def render(s, context={}):
t = jingo.env.from_string(s)
return t.render(context)
from .utils import htmleq_, render
def test_f():
@ -35,9 +38,9 @@ def test_fe_positional():
def test_fe_unicode():
context = {'var': u'Français'}
context = {'var': 'Français'}
template = '{{ "Speak {0}"|fe(var) }}'
eq_(u'Speak Français', render(template, context))
eq_('Speak Français', render(template, context))
def test_fe_markup():
@ -113,7 +116,7 @@ def test_field_attrs():
def __str__(self):
attrs = self.field.widget.attrs
attr_str = ' '.join('%s="%s"' % (k, v)
for (k, v) in attrs.iteritems())
for (k, v) in six.iteritems(attrs))
return Markup('<input %s />' % attr_str)
def __html__(self):
@ -122,7 +125,7 @@ def test_field_attrs():
f = field()
s = render('{{ field|field_attrs(class="bar",name="baz") }}',
{'field': f})
eq_(s, '<input class="bar" name="baz" />')
htmleq_(s, '<input class="bar" name="baz" />')
def test_url():
@ -157,14 +160,15 @@ def test_custom_url(s):
def test_filter_override():
def f(s):
return s.upper()
f.__name__ = 'a'
# See issue 7688: http://bugs.python.org/issue7688
f.__name__ = 'a' if six.PY3 else b'a'
register.filter(f)
s = render('{{ s|a }}', {'s': 'Str'})
eq_(s, 'STR')
def g(s):
return s.lower()
g.__name__ = 'a'
g.__name__ = 'a' if six.PY3 else b'a'
register.filter(override=False)(g)
s = render('{{ s|a }}', {'s': 'Str'})
eq_(s, 'STR')

View File

@ -1,34 +1,39 @@
from __future__ import unicode_literals
from django.shortcuts import render
from nose.tools import eq_
from mock import Mock
try:
from unittest.mock import Mock
except ImportError:
from mock import Mock
def test_render():
r = render(Mock(), 'jinja_app/test.html', {})
eq_(r.content, 'HELLO')
eq_(r.content, b'HELLO')
def test_render_no_toplevel_override():
r = render(Mock(), 'jinja_app/test_nonoverride.html', {})
eq_(r.content, 'HELLO')
eq_(r.content, b'HELLO')
def test_render_toplevel_override():
r = render(Mock(), 'jinja_app/test_override.html', {})
eq_(r.content, 'HELLO')
eq_(r.content, b'HELLO')
def test_render_django():
r = render(Mock(), 'django_app/test.html', {})
eq_(r.content, 'HELLO ...\n')
eq_(r.content, b'HELLO ...\n')
def test_render_django_no_toplevel_override():
r = render(Mock(), 'django_app/test_nonoverride.html', {})
eq_(r.content, 'HELLO ...\n')
eq_(r.content, b'HELLO ...\n')
def test_render_django_toplevel_override():
r = render(Mock(), 'django_app/test_override.html', {})
eq_(r.content, 'HELLO ...\n')
eq_(r.content, b'HELLO ...\n')

View File

@ -1,11 +1,14 @@
from __future__ import unicode_literals
from django import forms
from django.utils import six
from jinja2 import escape
from nose.tools import eq_
import jingo
import jingo.monkey
from test_helpers import render
from .utils import render
class MyForm(forms.Form):
@ -23,5 +26,5 @@ def test_monkey_patch():
jingo.monkey.patch()
eq_(html, render(t, context))
s = unicode(form['email'])
s = six.text_type(form['email'])
eq_(s, render('{{ form.email }}', {'form': form}))

View File

@ -1,6 +1,11 @@
from __future__ import unicode_literals
from django.utils import translation
from mock import sentinel
try:
from unittest.mock import sentinel
except ImportError:
from mock import sentinel
from nose.tools import eq_
from jingo import get_env, render_to_string

View File

@ -1,3 +1,5 @@
from __future__ import unicode_literals
from django.conf.urls import patterns

35
jingo/tests/utils.py Normal file
View File

@ -0,0 +1,35 @@
from django.test.html import HTMLParseError, parse_html
from nose.tools import eq_
from jingo import env
def htmleq_(html1, html2, msg=None):
"""
Asserts that two HTML snippets are semantically the same.
Whitespace in most cases is ignored, and attribute ordering is not
significant. The passed-in arguments must be valid HTML.
See ticket 16921: https://code.djangoproject.com/ticket/16921
"""
dom1 = assert_and_parse_html(html1, msg,
'First argument is not valid HTML:')
dom2 = assert_and_parse_html(html2, msg,
'Second argument is not valid HTML:')
eq_(dom1, dom2)
def assert_and_parse_html(html, user_msg, msg):
try:
dom = parse_html(html)
except HTMLParseError as e:
standard_msg = '%s\n%s\n%s' % (user_msg, msg, e.msg)
raise AssertionError(standard_msg)
return dom
def render(s, context={}):
t = env.from_string(s)
return t.render(context)

View File

@ -1,7 +1,6 @@
# These are the reqs to build docs and run tests.
Django==1.5.1
Django==1.5.2
sphinx
jinja2
jinja2==2.7
nose
mock
fabric

13
run_tests.py Normal file
View File

@ -0,0 +1,13 @@
import os
import nose
NAME = os.path.basename(os.path.dirname(__file__))
ROOT = os.path.abspath(os.path.dirname(__file__))
os.environ['DJANGO_SETTINGS_MODULE'] = 'fake_settings'
os.environ['PYTHONPATH'] = os.pathsep.join([ROOT,
os.path.join(ROOT, 'examples')])
if __name__ == '__main__':
nose.main()

View File

@ -24,6 +24,11 @@ setup(
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Topic :: Software Development :: Libraries :: Python Modules',
]
)