Skip to content

Commit c865899

Browse files
committed
Enable conservative basestring fixer from past.builtins (issues #127 and #156)
1 parent ae19794 commit c865899

File tree

4 files changed

+99
-42
lines changed

4 files changed

+99
-42
lines changed

docs/whatsnew.rst

+3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ Bug fixes:
3737
- Improve robustness of test suite against opening .pyc files as text on Py2
3838
- Update backports of ``Counter`` and ``OrderedDict`` to use the newer
3939
implementations from Py3.4. This fixes ``.copy()`` preserving subclasses etc.
40+
- ``futurize`` no longer breaks working Py2 code by changing ``basestring`` to
41+
``str``. Instead it imports the ``basestring`` forward-port from
42+
``past.builtins`` (issues #127 and #156)
4043

4144

4245
What's new in version 0.14.3 (2014-12-15)

src/libfuturize/fixes/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
# The following fixers add a dependency on the ``future`` package on order to
4141
# support Python 2:
4242
lib2to3_fix_names_stage2 = set([
43-
'lib2to3.fixes.fix_basestring',
4443
# 'lib2to3.fixes.fix_buffer', # perhaps not safe. Test this.
4544
# 'lib2to3.fixes.fix_callable', # not needed in Py3.2+
4645
'lib2to3.fixes.fix_dict', # TODO: add support for utils.viewitems() etc. and move to stage2
@@ -79,6 +78,7 @@
7978
])
8079

8180
libfuturize_fix_names_stage2 = set([
81+
'libfuturize.fixes.fix_basestring',
8282
# 'libfuturize.fixes.fix_add__future__imports_except_unicode_literals', # just in case
8383
'libfuturize.fixes.fix_cmp',
8484
'libfuturize.fixes.fix_division_safe',
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""
2+
Fixer that adds ``from past.builtins import basestring`` if there is a
3+
reference to ``basestring``
4+
"""
5+
6+
from lib2to3 import fixer_base
7+
8+
from libfuturize.fixer_util import touch_import_top
9+
10+
11+
class FixBasestring(fixer_base.BaseFix):
12+
BM_compatible = True
13+
14+
PATTERN = "'basestring'"
15+
16+
def transform(self, node, results):
17+
touch_import_top(u'past.builtins', 'basestring', node)
18+

tests/test_future/test_futurize.py

+77-41
Original file line numberDiff line numberDiff line change
@@ -1144,14 +1144,10 @@ def test_range_necessary_list_calls(self):
11441144
"""
11451145
self.convert_check(before, after)
11461146

1147-
1148-
class TestConservativeFuturize(CodeHandler):
1149-
@unittest.expectedFailure
11501147
def test_basestring(self):
11511148
"""
1152-
In conservative mode, futurize would not modify "basestring"
1153-
but merely import it, and the following code would still run on
1154-
both Py2 and Py3.
1149+
The 2to3 basestring fixer breaks working Py2 code that uses basestring.
1150+
This tests whether something sensible is done instead.
11551151
"""
11561152
before = """
11571153
assert isinstance('hello', basestring)
@@ -1164,41 +1160,7 @@ def test_basestring(self):
11641160
assert isinstance(u'hello', basestring)
11651161
assert isinstance(b'hello', basestring)
11661162
"""
1167-
self.convert_check(before, after, conservative=True)
1168-
1169-
@unittest.expectedFailure
1170-
def test_open(self):
1171-
"""
1172-
In conservative mode, futurize would not import io.open because
1173-
this changes the default return type from bytes to text.
1174-
"""
1175-
before = """
1176-
filename = 'temp_file_open.test'
1177-
contents = 'Temporary file contents. Delete me.'
1178-
with open(filename, 'w') as f:
1179-
f.write(contents)
1180-
1181-
with open(filename, 'r') as f:
1182-
data = f.read()
1183-
assert isinstance(data, str)
1184-
assert data == contents
1185-
"""
1186-
after = """
1187-
from past.builtins import open, str as oldbytes, unicode
1188-
filename = oldbytes(b'temp_file_open.test')
1189-
contents = oldbytes(b'Temporary file contents. Delete me.')
1190-
with open(filename, oldbytes(b'w')) as f:
1191-
f.write(contents)
1192-
1193-
with open(filename, oldbytes(b'r')) as f:
1194-
data = f.read()
1195-
assert isinstance(data, oldbytes)
1196-
assert data == contents
1197-
assert isinstance(oldbytes(b'hello'), basestring)
1198-
assert isinstance(unicode(u'hello'), basestring)
1199-
assert isinstance(oldbytes(b'hello'), basestring)
1200-
"""
1201-
self.convert_check(before, after, conservative=True)
1163+
self.convert_check(before, after)
12021164

12031165
def test_safe_division(self):
12041166
"""
@@ -1255,6 +1217,80 @@ def __truediv__(self, other):
12551217
"""
12561218
self.convert_check(before, after)
12571219

1220+
def test_basestring_issue_156(self):
1221+
before = """
1222+
x = str(3)
1223+
allowed_types = basestring, int
1224+
assert isinstance('', allowed_types)
1225+
assert isinstance(u'', allowed_types)
1226+
assert isinstance(u'foo', basestring)
1227+
"""
1228+
after = """
1229+
from builtins import str
1230+
from past.builtins import basestring
1231+
x = str(3)
1232+
allowed_types = basestring, int
1233+
assert isinstance('', allowed_types)
1234+
assert isinstance(u'', allowed_types)
1235+
assert isinstance(u'foo', basestring)
1236+
"""
1237+
self.convert_check(before, after)
1238+
1239+
1240+
class TestConservativeFuturize(CodeHandler):
1241+
@unittest.expectedFailure
1242+
def test_basestring(self):
1243+
"""
1244+
In conservative mode, futurize would not modify "basestring"
1245+
but merely import it from ``past``, and the following code would still
1246+
run on both Py2 and Py3.
1247+
"""
1248+
before = """
1249+
assert isinstance('hello', basestring)
1250+
assert isinstance(u'hello', basestring)
1251+
assert isinstance(b'hello', basestring)
1252+
"""
1253+
after = """
1254+
from past.builtins import basestring
1255+
assert isinstance('hello', basestring)
1256+
assert isinstance(u'hello', basestring)
1257+
assert isinstance(b'hello', basestring)
1258+
"""
1259+
self.convert_check(before, after, conservative=True)
1260+
1261+
@unittest.expectedFailure
1262+
def test_open(self):
1263+
"""
1264+
In conservative mode, futurize would not import io.open because
1265+
this changes the default return type from bytes to text.
1266+
"""
1267+
before = """
1268+
filename = 'temp_file_open.test'
1269+
contents = 'Temporary file contents. Delete me.'
1270+
with open(filename, 'w') as f:
1271+
f.write(contents)
1272+
1273+
with open(filename, 'r') as f:
1274+
data = f.read()
1275+
assert isinstance(data, str)
1276+
assert data == contents
1277+
"""
1278+
after = """
1279+
from past.builtins import open, str as oldbytes, unicode
1280+
filename = oldbytes(b'temp_file_open.test')
1281+
contents = oldbytes(b'Temporary file contents. Delete me.')
1282+
with open(filename, oldbytes(b'w')) as f:
1283+
f.write(contents)
1284+
1285+
with open(filename, oldbytes(b'r')) as f:
1286+
data = f.read()
1287+
assert isinstance(data, oldbytes)
1288+
assert data == contents
1289+
assert isinstance(oldbytes(b'hello'), basestring)
1290+
assert isinstance(unicode(u'hello'), basestring)
1291+
assert isinstance(oldbytes(b'hello'), basestring)
1292+
"""
1293+
self.convert_check(before, after, conservative=True)
12581294

12591295
class TestFuturizeAllImports(CodeHandler):
12601296
"""

0 commit comments

Comments
 (0)