Skip to content

Commit 440d06b

Browse files
committed
Improve str_is_unicode module and tests
1 parent 8ec65bc commit 440d06b

2 files changed

Lines changed: 82 additions & 7 deletions

File tree

future/str_is_unicode.py

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""
2-
This module redefines str on Python 2.x to be the unicode type.
2+
This module redefines str on Python 2.x to be the unicode type and
3+
provides a decorator called python_2_unicode_compatible to be applied to
4+
classes.
35
46
It is designed to be used together with the unicode_literals import as
57
follows:
@@ -25,20 +27,63 @@
2527
2628
This module is designed to be imported together with unicode_literals on
2729
Python 2 to bring the meaning of str() back into alignment with
28-
unprefixed
29-
string literals.
30+
unprefixed string literals.
31+
32+
Note that str() would then normally call the __unicode__ method on
33+
objects in Python 2. Therefore this module also defines a simple
34+
decorator called python_2_unicode_compatible (borrowed from
35+
django.utils.encoding) which defines __unicode__ and __str__ methods
36+
under Python 2. To support Python 2 and 3 with a single code base, simply
37+
define a __str__ method returning text and apply the
38+
python_2_unicode_compatible decorator to the class like this:::
39+
40+
from future import str_is_unicode
41+
42+
@python_2_unicode_compatible
43+
class MyClass(object):
44+
def __str__(self):
45+
return u'Unicode string: \u5b54\u5b50'
46+
47+
a = MyClass()
48+
49+
Then this is True on both Python 3 and 2:::
50+
51+
str(a) == bytes(a).decode('utf-8')
52+
53+
and, on a Unicode-enabled terminal with the right fonts, these both print
54+
the Chinese name of Confucius:::
55+
56+
print(a)
57+
print(str(a))
3058
31-
Note that str() will then call the __unicode__ method on objects in
32-
Python 2, whereas print() will call __str__.
3359
"""
3460

3561
from __future__ import unicode_literals
3662

63+
import __builtin__
3764
import inspect
65+
import imp
66+
import logging
3867

3968
from . import six
4069

4170

71+
def python_2_unicode_compatible(klass):
72+
"""
73+
A decorator that defines __unicode__ and __str__ methods under Python
74+
2. Under Python 3 it does nothing.
75+
76+
To support Python 2 and 3 with a single code base, define a __str__
77+
method returning text and apply this decorator to the class.
78+
79+
The implementation comes from django.utils.encoding.
80+
"""
81+
if not six.PY3:
82+
klass.__unicode__ = klass.__str__
83+
klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
84+
return klass
85+
86+
4287
if not six.PY3:
4388
caller = inspect.currentframe().f_back
4489
caller.f_globals['str'] = unicode
Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,42 @@
1-
from __future__ import absolute_import
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Tests for the future.str_is_unicode module
4+
"""
25

6+
from __future__ import absolute_import, unicode_literals, print_function
37
from future import str_is_unicode
48

9+
from future import six
10+
511
import unittest
612

7-
class TestIterators(unittest.TestCase):
13+
14+
class TestStrIsUnicode(unittest.TestCase):
815
def test_str(self):
916
self.assertIsNot(str, bytes) # Py2: assertIsNot only in 2.7
1017
self.assertEqual(str('blah'), u'blah') # Py3.3 and Py2 only
1118

19+
def test_python_2_unicode_compatible_decorator(self):
20+
# With the decorator:
21+
@str_is_unicode.python_2_unicode_compatible
22+
class A(object):
23+
def __str__(self):
24+
return u'Unicode string: \u5b54\u5b50'
25+
a = A()
26+
self.assertEqual(str(a), bytes(a).decode('utf-8'))
27+
assert len(str(a)) == 18
28+
if not six.PY3:
29+
assert hasattr(a, '__unicode__')
30+
print(str(a))
31+
32+
# Manual equivalent without the decorator:
33+
class B(object):
34+
def __unicode__(self):
35+
return u'Unicode string: \u5b54\u5b50'
36+
def __str__(self):
37+
return unicode(self).encode('utf-8')
38+
b = B()
39+
assert str(a) == str(b)
40+
41+
1242
unittest.main()

0 commit comments

Comments
 (0)