diff --git a/ci/requirements-2.6.txt b/ci/requirements-2.6.txt index d90774e159ccc..4ed488795fb07 100644 --- a/ci/requirements-2.6.txt +++ b/ci/requirements-2.6.txt @@ -10,6 +10,6 @@ sqlalchemy==0.8.1 scipy==0.11.0 statsmodels==0.4.3 xlwt==0.7.5 -openpyxl==1.6.2 +openpyxl==2.0.3 xlsxwriter==0.4.6 xlrd==0.9.2 diff --git a/ci/requirements-3.2.txt b/ci/requirements-3.2.txt index c15871730fa16..40a5310af0c6f 100644 --- a/ci/requirements-3.2.txt +++ b/ci/requirements-3.2.txt @@ -1,6 +1,5 @@ python-dateutil==2.1 pytz==2013b -openpyxl==1.6.2 xlsxwriter==0.4.6 xlrd==0.9.2 numpy==1.7.1 diff --git a/doc/source/v0.14.1.txt b/doc/source/v0.14.1.txt index 0d4dbae07413a..b430d2eed5a10 100644 --- a/doc/source/v0.14.1.txt +++ b/doc/source/v0.14.1.txt @@ -27,6 +27,8 @@ users upgrade to this version. API changes ~~~~~~~~~~~ +- Openpyxl now raises a ValueError on construction of the openpyxl writer + instead of warning on pandas import (:issue:`7284`). .. _whatsnew_0141.prior_deprecations: diff --git a/pandas/compat/openpyxl_compat.py b/pandas/compat/openpyxl_compat.py index 4a48cdac98dd2..25ba83d58aaed 100644 --- a/pandas/compat/openpyxl_compat.py +++ b/pandas/compat/openpyxl_compat.py @@ -9,18 +9,16 @@ start_ver = '1.6.1' stop_ver = '2.0.0' + def is_compat(): - """ - Detect whether the installed version of openpyxl is supported - Returns True/False if openpyxl is installed, None otherwise - """ - try: - import openpyxl - except ImportError: - return None + """Detect whether the installed version of openpyxl is supported. + Returns + ------- + compat : bool + ``True`` if openpyxl is installed and is between versions 1.6.1 and + 2.0.0, ``False`` otherwise. + """ + import openpyxl ver = LooseVersion(openpyxl.__version__) - if ver < LooseVersion(start_ver) or LooseVersion(stop_ver) <= ver: - return False - - return True + return LooseVersion(start_ver) < ver <= LooseVersion(stop_ver) diff --git a/pandas/io/excel.py b/pandas/io/excel.py index 36af8b1da0e30..6372d83f50051 100644 --- a/pandas/io/excel.py +++ b/pandas/io/excel.py @@ -523,6 +523,11 @@ class _OpenpyxlWriter(ExcelWriter): supported_extensions = ('.xlsx', '.xlsm') def __init__(self, path, engine=None, **engine_kwargs): + if not openpyxl_compat.is_compat(): + raise ValueError('Installed openpyxl is not supported at this ' + 'time. Use >={0} and ' + '<{1}.'.format(openpyxl_compat.start_ver, + openpyxl_compat.stop_ver)) # Use the openpyxl module as the Excel writer. from openpyxl.workbook import Workbook @@ -618,12 +623,7 @@ def _convert_to_style(cls, style_dict): return xls_style - -if openpyxl_compat.is_compat(): - register_writer(_OpenpyxlWriter) -else: - warn('Installed openpyxl is not supported at this time. Use >={} and <{}.' - .format(openpyxl_compat.start_ver, openpyxl_compat.stop_ver)) +register_writer(_OpenpyxlWriter) class _XlwtWriter(ExcelWriter): diff --git a/pandas/io/tests/test_excel.py b/pandas/io/tests/test_excel.py index ef7e57076c908..5928e99ed055c 100644 --- a/pandas/io/tests/test_excel.py +++ b/pandas/io/tests/test_excel.py @@ -5,6 +5,8 @@ import os from distutils.version import LooseVersion +import operator +import functools import nose from numpy import nan @@ -45,10 +47,6 @@ def _skip_if_no_openpyxl(): except ImportError: raise nose.SkipTest('openpyxl not installed, skipping') - if not openpyxl_compat.is_compat(): - raise nose.SkipTest('need %s <= openpyxl < %s, skipping' % - (openpyxl_compat.start_ver, openpyxl_compat.stop_ver)) - def _skip_if_no_xlsxwriter(): try: @@ -884,7 +882,6 @@ def test_to_excel_output_encoding(self): result = read_excel(filename, 'TestSheet', encoding='utf8') tm.assert_frame_equal(result, df) - def test_to_excel_unicode_filename(self): _skip_if_no_xlrd() with ensure_clean(u('\u0192u.') + self.ext) as filename: @@ -1094,13 +1091,36 @@ def test_swapped_columns(self): tm.assert_series_equal(write_frame['B'], read_frame['B']) +def raise_wrapper(orig_method): + @functools.wraps(orig_method) + def wrapped(self, *args, **kwargs): + _skip_if_no_openpyxl() + if openpyxl_compat.is_compat(): + orig_method(self, *args, **kwargs) + else: + msg = 'Installed openpyxl is not supported at this time\. Use.+' + with tm.assertRaisesRegexp(ValueError, msg): + orig_method(self, *args, **kwargs) + return wrapped + + +def raise_on_incompat_version(cls): + methods = filter(operator.methodcaller('startswith', 'test_'), dir(cls)) + for method in methods: + setattr(cls, method, raise_wrapper(getattr(cls, method))) + return cls + + +@raise_on_incompat_version class OpenpyxlTests(ExcelWriterBase, tm.TestCase): ext = '.xlsx' engine_name = 'openpyxl' - check_skip = staticmethod(_skip_if_no_openpyxl) + check_skip = staticmethod(lambda *args, **kwargs: None) def test_to_excel_styleconverter(self): _skip_if_no_openpyxl() + if not openpyxl_compat.is_compat(): + raise nose.SkipTest('incompatiable openpyxl version') import openpyxl @@ -1114,17 +1134,17 @@ def test_to_excel_styleconverter(self): xlsx_style = _OpenpyxlWriter._convert_to_style(hstyle) self.assertTrue(xlsx_style.font.bold) self.assertEqual(openpyxl.style.Border.BORDER_THIN, - xlsx_style.borders.top.border_style) + xlsx_style.borders.top.border_style) self.assertEqual(openpyxl.style.Border.BORDER_THIN, - xlsx_style.borders.right.border_style) + xlsx_style.borders.right.border_style) self.assertEqual(openpyxl.style.Border.BORDER_THIN, - xlsx_style.borders.bottom.border_style) + xlsx_style.borders.bottom.border_style) self.assertEqual(openpyxl.style.Border.BORDER_THIN, - xlsx_style.borders.left.border_style) + xlsx_style.borders.left.border_style) self.assertEqual(openpyxl.style.Alignment.HORIZONTAL_CENTER, - xlsx_style.alignment.horizontal) + xlsx_style.alignment.horizontal) self.assertEqual(openpyxl.style.Alignment.VERTICAL_TOP, - xlsx_style.alignment.vertical) + xlsx_style.alignment.vertical) class XlwtTests(ExcelWriterBase, tm.TestCase): @@ -1160,6 +1180,7 @@ class XlsxWriterTests(ExcelWriterBase, tm.TestCase): check_skip = staticmethod(_skip_if_no_xlsxwriter) +@raise_on_incompat_version class OpenpyxlTests_NoMerge(ExcelWriterBase, tm.TestCase): ext = '.xlsx' engine_name = 'openpyxl' @@ -1196,6 +1217,8 @@ def test_ExcelWriter_dispatch(self): import xlsxwriter writer_klass = _XlsxWriter except ImportError: + if not openpyxl_compat.is_compat(): + raise nose.SkipTest('incompatible openpyxl version') _skip_if_no_openpyxl() writer_klass = _OpenpyxlWriter @@ -1246,6 +1269,7 @@ def check_called(func): check_called(lambda: df.to_excel('something.xls', engine='dummy')) set_option('io.excel.xlsx.writer', val) + if __name__ == '__main__': nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'], exit=False)