Skip to content
This repository was archived by the owner on May 4, 2020. It is now read-only.

Commit 1ef7513

Browse files
committed
Python 3 compatibility.
1 parent 849ed28 commit 1ef7513

19 files changed

+258
-102
lines changed

.travis.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ sudo: false
44
language: python
55

66
python:
7+
- "3.5"
8+
- "3.4"
9+
- "3.3"
710
- "2.7"
811
- "2.6"
912

docs/api.rst

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ API Reference
1313

1414
>>> from xlutils.display import quoted_sheet_name
1515
>>> quoted_sheet_name(u'Price(\xa3)','utf-8')
16-
'Price(\xc2\xa3)'
16+
b'Price(\xc2\xa3)'
1717

1818
It also quotes the sheet name if it contains spaces:
1919

2020
>>> quoted_sheet_name(u'My Sheet')
21-
"'My Sheet'"
21+
b"'My Sheet'"
2222

2323
Single quotes are replaced with double quotes:
2424

2525
>>> quoted_sheet_name(u"John's Sheet")
26-
"'John''s Sheet'"
26+
b"'John''s Sheet'"
2727

2828
.. autofunction:: xlutils.display.cell_display
2929

@@ -33,6 +33,7 @@ API Reference
3333
>>> import xlrd
3434
>>> from xlrd.sheet import Cell
3535
>>> from xlutils.display import cell_display
36+
>>> from xlutils.compat import PY3
3637

3738
>>> cell_display(Cell(xlrd.XL_CELL_EMPTY, ''))
3839
'undefined'
@@ -73,14 +74,23 @@ API Reference
7374
If non-unicode characters are to be displayed, they will be masked
7475
out:
7576

76-
>>> cell_display(Cell(xlrd.XL_CELL_TEXT,u'Price (\xa3)'))
77-
'text (Price (?))'
77+
>>> cd = cell_display(Cell(xlrd.XL_CELL_TEXT,u'Price (\xa3)'))
78+
>>> if PY3:
79+
... str(cd) == "text (b'Price (?)')"
80+
... else:
81+
... str(cd) == 'text (Price (?))'
82+
True
83+
7884

7985
If you want to see these characters, specify an encoding for the
8086
output string:
8187

82-
>>> cell_display(Cell(xlrd.XL_CELL_TEXT,u'Price (\xa3)'), encoding='utf-8')
83-
'text (Price (\xc2\xa3))'
88+
>>> cd = cell_display(Cell(xlrd.XL_CELL_TEXT,u'Price (\xa3)'), encoding='utf-8')
89+
>>> if PY3:
90+
... str(cd) == "text (b'Price (\\xc2\\xa3)')"
91+
... else:
92+
... str(cd) == 'text (Price (\xc2\xa3))'
93+
True
8494

8595
Error cells will have their textual description displayed:
8696

docs/filter.rst

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -56,37 +56,38 @@ methods on the next filter.
5656
Here's an example filter that does nothing but print messages when its
5757
methods are called and then call the next filter in the chain:
5858

59+
>>> from __future__ import print_function
5960
>>> class MyFilter:
6061
...
6162
... def __init__(self,name):
6263
... self.name = name
6364
...
6465
... def start(self):
65-
... print self.name,'start'
66+
... print(self.name, 'start')
6667
... self.next.start()
6768
...
6869
... def workbook(self,rdbook,wtbook_name):
69-
... print self.name,'workbook',rdbook,wtbook_name
70-
... self.next.workbook(rdbook,wtbook_name)
70+
... print(self.name, 'workbook', rdbook, wtbook_name)
71+
... self.next.workbook(rdbook, wtbook_name)
7172
...
7273
... def sheet(self,rdsheet,wtsheet_name):
73-
... print self.name,'sheet',rdsheet,wtsheet_name
74-
... self.next.sheet(rdsheet,wtsheet_name)
74+
... print(self.name, 'sheet', rdsheet, wtsheet_name)
75+
... self.next.sheet(rdsheet, wtsheet_name)
7576
...
7677
... def set_rdsheet(self,rdsheet):
77-
... print self.name,'set_rdsheet',rdsheet
78-
... self.next.sheet(rdsheet,wtsheet_name)
78+
... print(self.name, 'set_rdsheet', rdsheet)
79+
... self.next.sheet(rdsheet, wtsheet_name)
7980
...
8081
... def row(self,rdrowx,wtrowx):
81-
... print self.name,'row',rdrowx,wtrowx
82-
... self.next.row(rdrowx,wtrowx)
82+
... print(self.name, 'row', rdrowx,wtrowx)
83+
... self.next.row(rdrowx, wtrowx)
8384
...
8485
... def cell(self,rdrowx,rdcolx,wtrowx,wtcolx):
85-
... print self.name,'cell',rdrowx,rdcolx,wtrowx,wtcolx
86-
... self.next.cell(rdrowx,rdcolx,wtrowx,wtcolx)
86+
... print(self.name, 'cell', rdrowx, rdcolx, wtrowx, wtcolx)
87+
... self.next.cell(rdrowx, rdcolx, wtrowx, wtcolx)
8788
...
8889
... def finish(self):
89-
... print self.name, 'finish'
90+
... print(self.name, 'finish')
9091
... self.next.finish()
9192

9293
For full details of when each of these methods are called, see the
@@ -237,12 +238,12 @@ directory specified:
237238
>>> os.listdir(temp_dir)
238239
[]
239240
>>> f = w.get_stream('test.xls')
240-
>>> f.write('some \r\n data')
241+
>>> _ = f.write(b'some \r\n data')
241242
>>> f.close()
242243
>>> os.listdir(temp_dir)
243244
['test.xls']
244245
>>> open(os.path.join(temp_dir,'test.xls'),'rb').read()
245-
'some \r\n data'
246+
b'some \r\n data'
246247

247248
StreamWriter
248249
------------
@@ -264,10 +265,10 @@ The :meth:`~StreamWriter.get_stream` method makes sure the excel data is written
264265
the stream provided:
265266

266267
>>> f = w.get_stream('test.xls')
267-
>>> f.write('xls data')
268-
>>> tf.seek(0)
268+
>>> _ = f.write(b'xls data')
269+
>>> _ = tf.seek(0)
269270
>>> tf.read()
270-
'xls data'
271+
b'xls data'
271272

272273
.. note:: Only one file may be written to a :class:`StreamWriter`,
273274
further attempts will result in an exception being raised:
@@ -281,7 +282,7 @@ Exception: Attempt to write more than one workbook
281282

282283
>>> tf = TemporaryFile()
283284
>>> process(TestReader(('Sheet1',[['R0C0']])),StreamWriter(tf))
284-
>>> tf.seek(0)
285+
>>> _ = tf.seek(0)
285286
>>> len(tf.read())
286287
5632
287288

@@ -373,11 +374,11 @@ When sheets are trimmed, a message is also logged to aid debugging:
373374
>>> from testfixtures import LogCapture
374375
>>> l = LogCapture()
375376
>>> process(r, ColumnTrimmer(), c)
376-
>>> print l
377+
>>> print(l)
377378
xlutils.filter DEBUG
378-
Number of columns trimmed from 3 to 2 for sheet 'Sheet1'
379+
Number of columns trimmed from 3 to 2 for sheet b'Sheet1'
379380
xlutils.filter DEBUG
380-
Number of columns trimmed from 3 to 1 for sheet 'Sheet3'
381+
Number of columns trimmed from 3 to 1 for sheet b'Sheet3'
381382

382383
The definition of 'no useful data' can also be controlled by passing
383384
in a function that returns ``True`` or ``False`` for each value:
@@ -444,7 +445,7 @@ passed on to the next filter:
444445
As well as the error message logged, we can also see the :class:`ErrorFilter`
445446
logs an error to that that the method calls have not been passed on:
446447

447-
>>> print h
448+
>>> print(h)
448449
theLogger ERROR
449450
a message
450451
xlutils.filter ERROR
@@ -456,7 +457,7 @@ instantiated:
456457

457458
>>> f = ErrorFilter(message='wingnuts! errors have occurred!')
458459
>>> process(MyReader('test.xls'), Log(logging.ERROR), f, c)
459-
>>> print h
460+
>>> print(h)
460461
theLogger ERROR
461462
a message
462463
xlutils.filter ERROR
@@ -481,9 +482,9 @@ also cause all methods to be filtered:
481482
>>> process(r,ErrorFilter(),c)
482483
>>> len(c.method_calls)
483484
0
484-
>>> print h
485+
>>> print(h)
485486
xlutils.filter ERROR
486-
Cell A1 of sheet 'Price(?)' contains a bad value: error (#NULL!)
487+
Cell A1 of sheet b'Price(?)' contains a bad value: error (#NULL!)
487488
xlutils.filter ERROR
488489
No output as errors have occurred.
489490

docs/save.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ Here's a simple example:
1717

1818
You can also save the data to a stream that you provide:
1919

20-
>>> from StringIO import StringIO
21-
>>> s = StringIO()
20+
>>> from xlutils.compat import BytesIO
21+
>>> s = BytesIO()
2222
>>> save(wb,s)
2323
>>> len(s.getvalue())
2424
5632

docs/view.rst

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,32 @@ workbook:
1313
>>> def print_data(rows):
1414
... for row in rows:
1515
... for value in row:
16-
... print value,
17-
... print
16+
... print(value, end=' ')
17+
... print()
1818

1919
>>> from os.path import join
2020
>>> from xlutils.view import View
2121
>>> view = View(join(test_files,'testall.xls'))
2222
>>> print_data(view[0])
23-
R0C0 R0C1
24-
R1C0 R1C1
25-
A merged cell
23+
R0C0 R0C1
24+
R1C0 R1C1
25+
A merged cell
2626
<BLANKLINE>
2727
<BLANKLINE>
28-
More merged cells
28+
More merged cells
2929

3030
You can also get a sheet by name:
3131

3232
>>> print_data(view['Sheet2'])
33-
R0C0 R0C1
34-
R1C0 R1C1
33+
R0C0 R0C1
34+
R1C0 R1C1
3535

3636
One helpful feature is that dates are converted to
3737
:class:`~datetime.datetime` objects rather than being left as numbers:
3838

3939
>>> for row in View(join(test_files,'datetime.xls'))[0]:
4040
... for value in row:
41-
... print repr(value)
41+
... print(repr(value))
4242
datetime.datetime(2012, 4, 13, 0, 0)
4343
datetime.time(12, 54, 37)
4444
datetime.datetime(2014, 2, 14, 4, 56, 23)
@@ -47,8 +47,8 @@ Now, things get really interesting when you start slicing the view of
4747
a sheet:
4848

4949
>>> print_data(view['Sheet1'][:2, :1])
50-
R0C0
51-
R1C0
50+
R0C0
51+
R1C0
5252

5353
As you can see, these behave exactly as slices into lists would, with
5454
the first slice being on rows and the second slice being on columns.
@@ -60,8 +60,8 @@ are inclusive. For example:
6060

6161
>>> from xlutils.view import Row, Col
6262
>>> print_data(view['Sheet1'][Row(1):Row(2), Col('A'):Col('B')])
63-
R0C0 R0C1
64-
R1C0 R1C1
63+
R0C0 R0C1
64+
R1C0 R1C1
6565

6666
Finally, to aid with automated tests, there is a :class:`CheckerView`
6767
subclass of :class:`View` that provides :class:`CheckSheet` views onto
@@ -72,32 +72,34 @@ data in the view of the sheet is not as expected:
7272
>>> from xlutils.view import CheckerView
7373
>>> sheet_view = CheckerView(join(test_files,'testall.xls'))[0]
7474
>>> sheet_view[:, Col('A'):Col('A')].compare(
75-
... ('R0C0', ),
76-
... ('R0C1', ),
75+
... (u'R0C0', ),
76+
... (u'R0C1', ),
7777
... )
7878
Traceback (most recent call last):
7979
...
8080
AssertionError: sequence not as expected:
8181
<BLANKLINE>
8282
same:
83-
(('R0C0',),)
83+
((u'R0C0',),)
8484
<BLANKLINE>
85-
first:
86-
(('R0C1',),)
85+
expected:
86+
((u'R0C1',),)
8787
<BLANKLINE>
88-
second:
88+
actual:
8989
((u'R1C0',), (u'A merged cell',), (u'',), (u'',), (u'More merged cells',))
9090
<BLANKLINE>
9191
While comparing [1]: sequence not as expected:
9292
<BLANKLINE>
9393
same:
9494
()
9595
<BLANKLINE>
96-
first:
97-
('R0C1',)
96+
expected:
97+
(u'R0C1',)
9898
<BLANKLINE>
99-
second:
99+
actual:
100100
(u'R1C0',)
101+
<BLANKLINE>
102+
While comparing [1][0]: u'R0C1' (expected) != u'R1C0' (actual)
101103

102104
Use of the :meth:`~CheckSheet.compare` method requires
103105
`testfixtures`__ to be installed.

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[wheel]
2+
universal=1

setup.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,17 @@
2020
url='http://www.python-excel.org',
2121
keywords="excel xls xlrd xlwt",
2222
classifiers=[
23-
'Development Status :: 6 - Mature',
24-
'Intended Audience :: Developers',
25-
'License :: OSI Approved :: MIT License',
26-
'Topic :: Office/Business :: Financial :: Spreadsheet',
23+
'Development Status :: 6 - Mature',
24+
'Intended Audience :: Developers',
25+
'License :: OSI Approved :: MIT License',
26+
'Topic :: Office/Business :: Financial :: Spreadsheet',
27+
'Programming Language :: Python :: 2',
28+
'Programming Language :: Python :: 2.6',
29+
'Programming Language :: Python :: 2.7',
30+
'Programming Language :: Python :: 3',
31+
'Programming Language :: Python :: 3.3',
32+
'Programming Language :: Python :: 3.4',
33+
'Programming Language :: Python :: 3.5',
2734
],
2835
packages=['xlutils','xlutils.tests'],
2936
zip_safe=False,

xlutils/compat.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import sys
2+
3+
PY3 = sys.version_info[0] >= 3
4+
5+
if PY3:
6+
unicode = str
7+
basestring = str
8+
xrange = range
9+
from io import StringIO
10+
from io import BytesIO
11+
else:
12+
unicode = unicode
13+
basestring = basestring
14+
xrange = xrange
15+
from StringIO import StringIO
16+
from StringIO import StringIO as BytesIO

xlutils/filter.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# This Software is released under the MIT License:
44
# http://www.opensource.org/licenses/mit-license.html
55
# See license.txt for more details.
6+
from __future__ import print_function
67

78
import logging
89
import os
@@ -15,6 +16,9 @@
1516
from xlutils.display import quoted_sheet_name,cell_display
1617
from xlutils.margins import cells_all_junk
1718
from xlwt.Style import default_style
19+
from .compat import xrange
20+
21+
1822
logger = logging.getLogger('xlutils.filter')
1923

2024
class BaseReader:
@@ -622,7 +626,7 @@ def get_stream(self,filename):
622626
Returns a stream for the file in the configured directory
623627
with the specified name.
624628
"""
625-
return file(os.path.join(self.dir_path,filename),'wb')
629+
return open(os.path.join(self.dir_path, filename), 'wb')
626630

627631
class StreamWriter(BaseWriter):
628632
"A writer for writing exactly one workbook to the supplied stream"
@@ -696,8 +700,8 @@ def __init__(self,name=None,methods=True):
696700

697701
def method(self,name,*args):
698702
if self.name:
699-
print repr(self.name),
700-
print "%s:%r"%(name,args)
703+
print(repr(self.name), end=' ')
704+
print("%s:%r" % (name, args))
701705

702706
try:
703707
from guppy import hpy

0 commit comments

Comments
 (0)