Skip to content

Commit 25cf0fc

Browse files
committed
DEPS: require openpyxl >= 2.3.2
closes pandas-dev#15184
1 parent f58ae29 commit 25cf0fc

File tree

7 files changed

+25
-453
lines changed

7 files changed

+25
-453
lines changed

ci/requirements-2.7.run

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ xlwt=0.7.5
55
numexpr
66
pytables
77
matplotlib
8-
openpyxl=1.6.2
8+
openpyxl=2.4.0
99
xlrd=0.9.2
1010
sqlalchemy=0.9.6
1111
lxml

ci/requirements-2.7_LOCALE.run

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ python-dateutil
22
pytz=2013b
33
numpy=1.9.2
44
xlwt=0.7.5
5-
openpyxl=1.6.2
5+
openpyxl=2.4.0
66
xlsxwriter=0.5.2
77
xlrd=0.9.2
88
bottleneck=1.0.0

doc/source/install.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,8 @@ Optional Dependencies
244244
* For Excel I/O:
245245

246246
* `xlrd/xlwt <http://www.python-excel.org/>`__: Excel reading (xlrd) and writing (xlwt)
247-
* `openpyxl <http://packages.python.org/openpyxl/>`__: openpyxl version 1.6.1
248-
or higher (but lower than 2.0.0), or version 2.2 or higher, for writing .xlsx files (xlrd >= 0.9.0)
247+
* `openpyxl <http://https://openpyxl.readthedocs.io/en/default/>`__: openpyxl version 2.4.0
248+
for writing .xlsx files (xlrd >= 0.9.0)
249249
* `XlsxWriter <https://pypi.python.org/pypi/XlsxWriter>`__: Alternative Excel writer
250250

251251
* `Jinja2 <http://jinja.pocoo.org/>`__: Template engine for conditional HTML formatting.

doc/source/whatsnew/v0.22.0.txt

+6-1
Original file line numberDiff line numberDiff line change
@@ -82,21 +82,26 @@ Other Enhancements
8282
Backwards incompatible API changes
8383
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8484

85+
<<<<<<< 93855ed7385e8a4a3550332bb706821633b97c03
8586
- :func:`Series.fillna` now raises a ``TypeError`` instead of a ``ValueError`` when passed a list, tuple or DataFrame as a ``value`` (:issue:`18293`)
8687
- :func:`pandas.DataFrame.merge` no longer casts a ``float`` column to ``object`` when merging on ``int`` and ``float`` columns (:issue:`16572`)
8788
- The default NA value for :class:`UInt64Index` has changed from 0 to ``NaN``, which impacts methods that mask with NA, such as ``UInt64Index.where()`` (:issue:`18398`)
8889

90+
.. _whatsnew_0220.api_breaking.deps:
91+
8992
Dependencies have increased minimum versions
9093
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
9194

92-
We have updated our minimum supported versions of dependencies ().
95+
We have updated our minimum supported versions of dependencies (:issue:`15184`).
9396
If installed, we now require:
9497

9598
+-----------------+-----------------+----------+
9699
| Package | Minimum Version | Required |
97100
+=================+=================+==========+
98101
| python-dateutil | 2.5.0 | X |
99102
+-----------------+-----------------+----------+
103+
| openpyxl | 2.4.0 | |
104+
+-----------------+-----------------+----------+
100105

101106
- :func:`Series.fillna` now raises a ``TypeError`` instead of a ``ValueError`` when passed a list, tuple or DataFrame as a ``value`` (:issue:`18293`)
102107

pandas/compat/openpyxl_compat.py

-35
This file was deleted.

pandas/io/excel.py

+5-192
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
from pandas.core import config
2929
from pandas.io.formats.printing import pprint_thing
3030
import pandas.compat as compat
31-
import pandas.compat.openpyxl_compat as openpyxl_compat
3231
from warnings import warn
3332
from distutils.version import LooseVersion
3433
from pandas.util._decorators import Appender, deprecate_kwarg
@@ -185,22 +184,6 @@ def _get_default_writer(ext):
185184

186185

187186
def get_writer(engine_name):
188-
if engine_name == 'openpyxl':
189-
try:
190-
import openpyxl
191-
192-
# with version-less openpyxl engine
193-
# make sure we make the intelligent choice for the user
194-
if LooseVersion(openpyxl.__version__) < '2.0.0':
195-
return _writers['openpyxl1']
196-
elif LooseVersion(openpyxl.__version__) < '2.2.0':
197-
return _writers['openpyxl20']
198-
else:
199-
return _writers['openpyxl22']
200-
except ImportError:
201-
# fall through to normal exception handling below
202-
pass
203-
204187
try:
205188
return _writers[engine_name]
206189
except KeyError:
@@ -828,20 +811,15 @@ def close(self):
828811
return self.save()
829812

830813

831-
class _Openpyxl1Writer(ExcelWriter):
832-
engine = 'openpyxl1'
814+
class _OpenpyxlWriter(ExcelWriter):
815+
engine = 'openpyxl'
833816
supported_extensions = ('.xlsx', '.xlsm')
834-
openpyxl_majorver = 1
835817

836818
def __init__(self, path, engine=None, **engine_kwargs):
837-
if not openpyxl_compat.is_compat(major_ver=self.openpyxl_majorver):
838-
raise ValueError('Installed openpyxl is not supported at this '
839-
'time. Use {majorver}.x.y.'
840-
.format(majorver=self.openpyxl_majorver))
841819
# Use the openpyxl module as the Excel writer.
842820
from openpyxl.workbook import Workbook
843821

844-
super(_Openpyxl1Writer, self).__init__(path, **engine_kwargs)
822+
super(_OpenpyxlWriter, self).__init__(path, **engine_kwargs)
845823

846824
# Create workbook object with default optimized_write=True.
847825
self.book = Workbook()
@@ -861,72 +839,6 @@ def save(self):
861839
"""
862840
return self.book.save(self.path)
863841

864-
def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0,
865-
freeze_panes=None):
866-
# Write the frame cells using openpyxl.
867-
from openpyxl.cell import get_column_letter
868-
869-
sheet_name = self._get_sheet_name(sheet_name)
870-
871-
if sheet_name in self.sheets:
872-
wks = self.sheets[sheet_name]
873-
else:
874-
wks = self.book.create_sheet()
875-
wks.title = sheet_name
876-
self.sheets[sheet_name] = wks
877-
878-
for cell in cells:
879-
colletter = get_column_letter(startcol + cell.col + 1)
880-
xcell = wks.cell("{col}{row}".format(col=colletter,
881-
row=startrow + cell.row + 1))
882-
if (isinstance(cell.val, compat.string_types) and
883-
xcell.data_type_for_value(cell.val) != xcell.TYPE_STRING):
884-
xcell.set_value_explicit(cell.val)
885-
else:
886-
xcell.value = _conv_value(cell.val)
887-
style = None
888-
if cell.style:
889-
style = self._convert_to_style(cell.style)
890-
for field in style.__fields__:
891-
xcell.style.__setattr__(field,
892-
style.__getattribute__(field))
893-
894-
if isinstance(cell.val, datetime):
895-
xcell.style.number_format.format_code = self.datetime_format
896-
elif isinstance(cell.val, date):
897-
xcell.style.number_format.format_code = self.date_format
898-
899-
if cell.mergestart is not None and cell.mergeend is not None:
900-
cletterstart = get_column_letter(startcol + cell.col + 1)
901-
cletterend = get_column_letter(startcol + cell.mergeend + 1)
902-
903-
wks.merge_cells('{start}{row}:{end}{mergestart}'
904-
.format(start=cletterstart,
905-
row=startrow + cell.row + 1,
906-
end=cletterend,
907-
mergestart=startrow +
908-
cell.mergestart + 1))
909-
910-
# Excel requires that the format of the first cell in a merged
911-
# range is repeated in the rest of the merged range.
912-
if style:
913-
first_row = startrow + cell.row + 1
914-
last_row = startrow + cell.mergestart + 1
915-
first_col = startcol + cell.col + 1
916-
last_col = startcol + cell.mergeend + 1
917-
918-
for row in range(first_row, last_row + 1):
919-
for col in range(first_col, last_col + 1):
920-
if row == first_row and col == first_col:
921-
# Ignore first cell. It is already handled.
922-
continue
923-
colletter = get_column_letter(col)
924-
xcell = wks.cell("{col}{row}"
925-
.format(col=colletter, row=row))
926-
for field in style.__fields__:
927-
xcell.style.__setattr__(
928-
field, style.__getattribute__(field))
929-
930842
@classmethod
931843
def _convert_to_style(cls, style_dict):
932844
"""
@@ -948,88 +860,6 @@ def _convert_to_style(cls, style_dict):
948860

949861
return xls_style
950862

951-
952-
register_writer(_Openpyxl1Writer)
953-
954-
955-
class _OpenpyxlWriter(_Openpyxl1Writer):
956-
engine = 'openpyxl'
957-
958-
959-
register_writer(_OpenpyxlWriter)
960-
961-
962-
class _Openpyxl20Writer(_Openpyxl1Writer):
963-
"""
964-
Note: Support for OpenPyxl v2 is currently EXPERIMENTAL (GH7565).
965-
"""
966-
engine = 'openpyxl20'
967-
openpyxl_majorver = 2
968-
969-
def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0,
970-
freeze_panes=None):
971-
# Write the frame cells using openpyxl.
972-
from openpyxl.cell import get_column_letter
973-
974-
sheet_name = self._get_sheet_name(sheet_name)
975-
976-
if sheet_name in self.sheets:
977-
wks = self.sheets[sheet_name]
978-
else:
979-
wks = self.book.create_sheet()
980-
wks.title = sheet_name
981-
self.sheets[sheet_name] = wks
982-
983-
for cell in cells:
984-
colletter = get_column_letter(startcol + cell.col + 1)
985-
xcell = wks["{col}{row}"
986-
.format(col=colletter, row=startrow + cell.row + 1)]
987-
xcell.value = _conv_value(cell.val)
988-
style_kwargs = {}
989-
990-
# Apply format codes before cell.style to allow override
991-
if isinstance(cell.val, datetime):
992-
style_kwargs.update(self._convert_to_style_kwargs({
993-
'number_format': {'format_code': self.datetime_format}}))
994-
elif isinstance(cell.val, date):
995-
style_kwargs.update(self._convert_to_style_kwargs({
996-
'number_format': {'format_code': self.date_format}}))
997-
998-
if cell.style:
999-
style_kwargs.update(self._convert_to_style_kwargs(cell.style))
1000-
1001-
if style_kwargs:
1002-
xcell.style = xcell.style.copy(**style_kwargs)
1003-
1004-
if cell.mergestart is not None and cell.mergeend is not None:
1005-
cletterstart = get_column_letter(startcol + cell.col + 1)
1006-
cletterend = get_column_letter(startcol + cell.mergeend + 1)
1007-
1008-
wks.merge_cells('{start}{row}:{end}{mergestart}'
1009-
.format(start=cletterstart,
1010-
row=startrow + cell.row + 1,
1011-
end=cletterend,
1012-
mergestart=startrow +
1013-
cell.mergestart + 1))
1014-
1015-
# Excel requires that the format of the first cell in a merged
1016-
# range is repeated in the rest of the merged range.
1017-
if style_kwargs:
1018-
first_row = startrow + cell.row + 1
1019-
last_row = startrow + cell.mergestart + 1
1020-
first_col = startcol + cell.col + 1
1021-
last_col = startcol + cell.mergeend + 1
1022-
1023-
for row in range(first_row, last_row + 1):
1024-
for col in range(first_col, last_col + 1):
1025-
if row == first_row and col == first_col:
1026-
# Ignore first cell. It is already handled.
1027-
continue
1028-
colletter = get_column_letter(col)
1029-
xcell = wks["{col}{row}"
1030-
.format(col=colletter, row=row)]
1031-
xcell.style = xcell.style.copy(**style_kwargs)
1032-
1033863
@classmethod
1034864
def _convert_to_style_kwargs(cls, style_dict):
1035865
"""
@@ -1341,13 +1171,7 @@ def _convert_to_number_format(cls, number_format_dict):
13411171
-------
13421172
number_format : str
13431173
"""
1344-
try:
1345-
# >= 2.0.0 < 2.1.0
1346-
from openpyxl.styles import NumberFormat
1347-
return NumberFormat(**number_format_dict)
1348-
except:
1349-
# >= 2.1.0
1350-
return number_format_dict['format_code']
1174+
return number_format_dict['format_code']
13511175

13521176
@classmethod
13531177
def _convert_to_protection(cls, protection_dict):
@@ -1367,17 +1191,6 @@ def _convert_to_protection(cls, protection_dict):
13671191

13681192
return Protection(**protection_dict)
13691193

1370-
1371-
register_writer(_Openpyxl20Writer)
1372-
1373-
1374-
class _Openpyxl22Writer(_Openpyxl20Writer):
1375-
"""
1376-
Note: Support for OpenPyxl v2.2 is currently EXPERIMENTAL (GH7565).
1377-
"""
1378-
engine = 'openpyxl22'
1379-
openpyxl_majorver = 2
1380-
13811194
def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0,
13821195
freeze_panes=None):
13831196
# Write the frame cells using openpyxl.
@@ -1443,7 +1256,7 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0,
14431256
setattr(xcell, k, v)
14441257

14451258

1446-
register_writer(_Openpyxl22Writer)
1259+
register_writer(_OpenpyxlWriter)
14471260

14481261

14491262
class _XlwtWriter(ExcelWriter):

0 commit comments

Comments
 (0)