Repository: pandas-dev/pandas

Issue:
Pandas 0.14 broke Python 2.6 support
Since today I've the following issue when importing Pandas on Python 2.6

```
2014-05-30 15:14:29.999 |   File "gnocchi/carbonara.py", line 22, in <module>
2014-05-30 15:14:29.999 |     import pandas
2014-05-30 15:14:29.999 |   File "/home/jenkins/workspace/gate-gnocchi-python26/.tox/py26/lib/python2.6/site-packages/pandas/__init__.py", line 45, in <module>
2014-05-30 15:14:30.000 |     from pandas.io.api import *
2014-05-30 15:14:30.000 |   File "/home/jenkins/workspace/gate-gnocchi-python26/.tox/py26/lib/python2.6/site-packages/pandas/io/api.py", line 7, in <module>
2014-05-30 15:14:30.000 |     from pandas.io.excel import ExcelFile, ExcelWriter, read_excel
2014-05-30 15:14:30.000 |   File "/home/jenkins/workspace/gate-gnocchi-python26/.tox/py26/lib/python2.6/site-packages/pandas/io/excel.py", line 626, in <module>
2014-05-30 15:14:30.000 |     .format(openpyxl_compat.start_ver, openpyxl_compat.stop_ver))
2014-05-30 15:14:30.000 | ValueError: zero length field name in format
```



Discussion:
hmm, very odd, can you show `pd.show_versions()`

i got this i see the problem

gr8!

hmm..very odd as travis passes just fine with an older version of `openpyxl`

cc @neirbowj

it's because it's just a warning and maybe tests don't cover on the one it runs

actually all versions running on travis are >= 1.6.2 so the warning won't be triggered.. should i put on 2.6 req file?

yes, change that to 2.0.3 so it will trigger the warning (and fail)

cool


Resolving patch:
diff --git a/pandas/compat/openpyxl_compat.py b/pandas/compat/openpyxl_compat.py
--- 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
--- 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):


Test patch:
diff --git a/pandas/io/tests/test_excel.py b/pandas/io/tests/test_excel.py
--- 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)