diff --git a/ci/environment-dev.yaml b/ci/environment-dev.yaml index c3d3d59f895c6..57748fef1a2e5 100644 --- a/ci/environment-dev.yaml +++ b/ci/environment-dev.yaml @@ -6,8 +6,8 @@ dependencies: - Cython - NumPy - moto - - pytest - - python-dateutil + - pytest>=3.1 + - python-dateutil>=2.5.0 - python=3 - pytz - setuptools diff --git a/ci/requirements-2.7.build b/ci/requirements-2.7.build index 415df13179fcf..d1cc61df0a77c 100644 --- a/ci/requirements-2.7.build +++ b/ci/requirements-2.7.build @@ -1,5 +1,5 @@ python=2.7* -python-dateutil=2.4.1 +python-dateutil=2.5.0 pytz=2013b nomkl numpy diff --git a/ci/requirements-2.7.run b/ci/requirements-2.7.run index a68e1d256058d..7c10b98fb6e14 100644 --- a/ci/requirements-2.7.run +++ b/ci/requirements-2.7.run @@ -1,11 +1,11 @@ -python-dateutil=2.4.1 +python-dateutil=2.5.0 pytz=2013b numpy xlwt=0.7.5 numexpr pytables matplotlib -openpyxl=1.6.2 +openpyxl=2.4.0 xlrd=0.9.2 sqlalchemy=0.9.6 lxml diff --git a/ci/requirements-2.7_COMPAT.build b/ci/requirements-2.7_COMPAT.build index d9c932daa110b..aa767c1001196 100644 --- a/ci/requirements-2.7_COMPAT.build +++ b/ci/requirements-2.7_COMPAT.build @@ -1,5 +1,5 @@ python=2.7* numpy=1.9.2 cython=0.23 -dateutil=1.5 +python-dateutil=2.5.0 pytz=2013b diff --git a/ci/requirements-2.7_COMPAT.run b/ci/requirements-2.7_COMPAT.run index 39bf720140733..c3daed6e6e1da 100644 --- a/ci/requirements-2.7_COMPAT.run +++ b/ci/requirements-2.7_COMPAT.run @@ -1,5 +1,5 @@ numpy=1.9.2 -dateutil=1.5 +python-dateutil=2.5.0 pytz=2013b scipy=0.14.0 xlwt=0.7.5 diff --git a/ci/requirements-2.7_LOCALE.run b/ci/requirements-2.7_LOCALE.run index 978bbf6a051c5..0a809a7dd6e5d 100644 --- a/ci/requirements-2.7_LOCALE.run +++ b/ci/requirements-2.7_LOCALE.run @@ -1,8 +1,8 @@ python-dateutil -pytz=2013b +pytz numpy=1.9.2 xlwt=0.7.5 -openpyxl=1.6.2 +openpyxl=2.4.0 xlsxwriter=0.5.2 xlrd=0.9.2 bottleneck=1.0.0 diff --git a/ci/requirements-optional-pip.txt b/ci/requirements-optional-pip.txt index 06b22bd8f2c63..8d4421ba2b681 100644 --- a/ci/requirements-optional-pip.txt +++ b/ci/requirements-optional-pip.txt @@ -1,11 +1,13 @@ # This file was autogenerated by scripts/convert_deps.py -# Do not modify directlybeautifulsoup4 +# Do not modify directly +beautifulsoup4 blosc bottleneck fastparquet feather-format html5lib ipython +ipykernel jinja2 lxml matplotlib diff --git a/ci/requirements_dev.txt b/ci/requirements_dev.txt index 2fb36b7cd70d8..e9840388203b1 100644 --- a/ci/requirements_dev.txt +++ b/ci/requirements_dev.txt @@ -3,8 +3,8 @@ Cython NumPy moto -pytest -python-dateutil +pytest>=3.1 +python-dateutil>=2.5.0 pytz setuptools sphinx \ No newline at end of file diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index 2aee11772896f..8152af84228b8 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -16,13 +16,11 @@ requirements: - cython - numpy x.x - setuptools - - pytz - - python-dateutil run: - python - numpy x.x - - python-dateutil + - python-dateutil >=2.5.0 - pytz test: diff --git a/doc/source/install.rst b/doc/source/install.rst index 7c1fde119ceaa..ae89c64b6e91e 100644 --- a/doc/source/install.rst +++ b/doc/source/install.rst @@ -200,8 +200,8 @@ Dependencies * `setuptools `__ * `NumPy `__: 1.9.0 or higher -* `python-dateutil `__: 1.5 or higher -* `pytz `__: Needed for time zone support +* `python-dateutil `__: 2.5.0 or higher +* `pytz `__ .. _install.recommended_dependencies: @@ -244,8 +244,8 @@ Optional Dependencies * For Excel I/O: * `xlrd/xlwt `__: Excel reading (xlrd) and writing (xlwt) - * `openpyxl `__: openpyxl version 1.6.1 - or higher (but lower than 2.0.0), or version 2.2 or higher, for writing .xlsx files (xlrd >= 0.9.0) + * `openpyxl `__: openpyxl version 2.4.0 + for writing .xlsx files (xlrd >= 0.9.0) * `XlsxWriter `__: Alternative Excel writer * `Jinja2 `__: Template engine for conditional HTML formatting. diff --git a/doc/source/io.rst b/doc/source/io.rst index 2aeafd99f6e72..f96e33dbf9882 100644 --- a/doc/source/io.rst +++ b/doc/source/io.rst @@ -2935,7 +2935,7 @@ Writing Excel Files to Memory +++++++++++++++++++++++++++++ Pandas supports writing Excel files to buffer-like objects such as ``StringIO`` or -``BytesIO`` using :class:`~pandas.io.excel.ExcelWriter`. Pandas also supports Openpyxl >= 2.2. +``BytesIO`` using :class:`~pandas.io.excel.ExcelWriter`. .. code-block:: python @@ -2991,9 +2991,7 @@ files if `Xlsxwriter`_ is not available. To specify which writer you want to use, you can pass an engine keyword argument to ``to_excel`` and to ``ExcelWriter``. The built-in engines are: -- ``openpyxl``: This includes stable support for Openpyxl from 1.6.1. However, - it is advised to use version 2.2 and higher, especially when working with - styles. +- ``openpyxl``: version 2.4 or higher is required - ``xlsxwriter`` - ``xlwt`` diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index e8f2823f32edd..5e605ecb7d8d5 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -86,9 +86,22 @@ Backwards incompatible API changes - :func:`Series.fillna` now raises a ``TypeError`` instead of a ``ValueError`` when passed a list, tuple or DataFrame as a ``value`` (:issue:`18293`) - :func:`pandas.DataFrame.merge` no longer casts a ``float`` column to ``object`` when merging on ``int`` and ``float`` columns (:issue:`16572`) - 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`) -- +.. _whatsnew_0220.api_breaking.deps: + +Dependencies have increased minimum versions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We have updated our minimum supported versions of dependencies (:issue:`15184`). +If installed, we now require: + +-----------------+-----------------+----------+ + | Package | Minimum Version | Required | + +=================+=================+==========+ + | python-dateutil | 2.5.0 | X | + +-----------------+-----------------+----------+ + | openpyxl | 2.4.0 | | + +-----------------+-----------------+----------+ diff --git a/pandas/compat/__init__.py b/pandas/compat/__init__.py index a615e098135a9..2deb29dabe764 100644 --- a/pandas/compat/__init__.py +++ b/pandas/compat/__init__.py @@ -396,25 +396,13 @@ def raise_with_traceback(exc, traceback=Ellipsis): If traceback is not passed, uses sys.exc_info() to get traceback.""" -# http://stackoverflow.com/questions/4126348 -# Thanks to @martineau at SO - +# dateutil minimum version import dateutil -if PY2 and LooseVersion(dateutil.__version__) == '2.0': - # dateutil brokenness - raise Exception('dateutil 2.0 incompatible with Python 2.x, you must ' - 'install version 1.5 or 2.1+!') - +if LooseVersion(dateutil.__version__) < '2.5': + raise ImportError('dateutil 2.5.0 is the minimum required version') from dateutil import parser as _date_parser -if LooseVersion(dateutil.__version__) < '2.0': - - @functools.wraps(_date_parser.parse) - def parse_date(timestr, *args, **kwargs): - timestr = bytes(timestr) - return _date_parser.parse(timestr, *args, **kwargs) -else: - parse_date = _date_parser.parse +parse_date = _date_parser.parse # https://github.com/pandas-dev/pandas/pull/9123 diff --git a/pandas/compat/openpyxl_compat.py b/pandas/compat/openpyxl_compat.py deleted file mode 100644 index 87cf52cf00fef..0000000000000 --- a/pandas/compat/openpyxl_compat.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -Detect incompatible version of OpenPyXL - -GH7169 -""" - -from distutils.version import LooseVersion - -start_ver = '1.6.1' -stop_ver = '2.0.0' - - -def is_compat(major_ver=1): - """Detect whether the installed version of openpyxl is supported - - Parameters - ---------- - ver : int - 1 requests compatibility status among the 1.x.y series - 2 requests compatibility status of 2.0.0 and later - Returns - ------- - compat : bool - ``True`` if openpyxl is installed and is a compatible version. - ``False`` otherwise. - """ - import openpyxl - ver = LooseVersion(openpyxl.__version__) - if major_ver == 1: - return LooseVersion(start_ver) <= ver < LooseVersion(stop_ver) - elif major_ver == 2: - return LooseVersion(stop_ver) <= ver - else: - raise ValueError('cannot test for openpyxl compatibility with ver {0}' - .format(major_ver)) diff --git a/pandas/io/excel.py b/pandas/io/excel.py index fec916dc52d20..882130bedcbf0 100644 --- a/pandas/io/excel.py +++ b/pandas/io/excel.py @@ -28,7 +28,6 @@ from pandas.core import config from pandas.io.formats.printing import pprint_thing import pandas.compat as compat -import pandas.compat.openpyxl_compat as openpyxl_compat from warnings import warn from distutils.version import LooseVersion from pandas.util._decorators import Appender, deprecate_kwarg @@ -185,22 +184,6 @@ def _get_default_writer(ext): def get_writer(engine_name): - if engine_name == 'openpyxl': - try: - import openpyxl - - # with version-less openpyxl engine - # make sure we make the intelligent choice for the user - if LooseVersion(openpyxl.__version__) < '2.0.0': - return _writers['openpyxl1'] - elif LooseVersion(openpyxl.__version__) < '2.2.0': - return _writers['openpyxl20'] - else: - return _writers['openpyxl22'] - except ImportError: - # fall through to normal exception handling below - pass - try: return _writers[engine_name] except KeyError: @@ -828,20 +811,15 @@ def close(self): return self.save() -class _Openpyxl1Writer(ExcelWriter): - engine = 'openpyxl1' +class _OpenpyxlWriter(ExcelWriter): + engine = 'openpyxl' supported_extensions = ('.xlsx', '.xlsm') - openpyxl_majorver = 1 def __init__(self, path, engine=None, **engine_kwargs): - if not openpyxl_compat.is_compat(major_ver=self.openpyxl_majorver): - raise ValueError('Installed openpyxl is not supported at this ' - 'time. Use {majorver}.x.y.' - .format(majorver=self.openpyxl_majorver)) # Use the openpyxl module as the Excel writer. from openpyxl.workbook import Workbook - super(_Openpyxl1Writer, self).__init__(path, **engine_kwargs) + super(_OpenpyxlWriter, self).__init__(path, **engine_kwargs) # Create workbook object with default optimized_write=True. self.book = Workbook() @@ -861,72 +839,6 @@ def save(self): """ return self.book.save(self.path) - def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0, - freeze_panes=None): - # Write the frame cells using openpyxl. - from openpyxl.cell import get_column_letter - - sheet_name = self._get_sheet_name(sheet_name) - - if sheet_name in self.sheets: - wks = self.sheets[sheet_name] - else: - wks = self.book.create_sheet() - wks.title = sheet_name - self.sheets[sheet_name] = wks - - for cell in cells: - colletter = get_column_letter(startcol + cell.col + 1) - xcell = wks.cell("{col}{row}".format(col=colletter, - row=startrow + cell.row + 1)) - if (isinstance(cell.val, compat.string_types) and - xcell.data_type_for_value(cell.val) != xcell.TYPE_STRING): - xcell.set_value_explicit(cell.val) - else: - xcell.value = _conv_value(cell.val) - style = None - if cell.style: - style = self._convert_to_style(cell.style) - for field in style.__fields__: - xcell.style.__setattr__(field, - style.__getattribute__(field)) - - if isinstance(cell.val, datetime): - xcell.style.number_format.format_code = self.datetime_format - elif isinstance(cell.val, date): - xcell.style.number_format.format_code = self.date_format - - if cell.mergestart is not None and cell.mergeend is not None: - cletterstart = get_column_letter(startcol + cell.col + 1) - cletterend = get_column_letter(startcol + cell.mergeend + 1) - - wks.merge_cells('{start}{row}:{end}{mergestart}' - .format(start=cletterstart, - row=startrow + cell.row + 1, - end=cletterend, - mergestart=startrow + - cell.mergestart + 1)) - - # Excel requires that the format of the first cell in a merged - # range is repeated in the rest of the merged range. - if style: - first_row = startrow + cell.row + 1 - last_row = startrow + cell.mergestart + 1 - first_col = startcol + cell.col + 1 - last_col = startcol + cell.mergeend + 1 - - for row in range(first_row, last_row + 1): - for col in range(first_col, last_col + 1): - if row == first_row and col == first_col: - # Ignore first cell. It is already handled. - continue - colletter = get_column_letter(col) - xcell = wks.cell("{col}{row}" - .format(col=colletter, row=row)) - for field in style.__fields__: - xcell.style.__setattr__( - field, style.__getattribute__(field)) - @classmethod def _convert_to_style(cls, style_dict): """ @@ -948,88 +860,6 @@ def _convert_to_style(cls, style_dict): return xls_style - -register_writer(_Openpyxl1Writer) - - -class _OpenpyxlWriter(_Openpyxl1Writer): - engine = 'openpyxl' - - -register_writer(_OpenpyxlWriter) - - -class _Openpyxl20Writer(_Openpyxl1Writer): - """ - Note: Support for OpenPyxl v2 is currently EXPERIMENTAL (GH7565). - """ - engine = 'openpyxl20' - openpyxl_majorver = 2 - - def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0, - freeze_panes=None): - # Write the frame cells using openpyxl. - from openpyxl.cell import get_column_letter - - sheet_name = self._get_sheet_name(sheet_name) - - if sheet_name in self.sheets: - wks = self.sheets[sheet_name] - else: - wks = self.book.create_sheet() - wks.title = sheet_name - self.sheets[sheet_name] = wks - - for cell in cells: - colletter = get_column_letter(startcol + cell.col + 1) - xcell = wks["{col}{row}" - .format(col=colletter, row=startrow + cell.row + 1)] - xcell.value = _conv_value(cell.val) - style_kwargs = {} - - # Apply format codes before cell.style to allow override - if isinstance(cell.val, datetime): - style_kwargs.update(self._convert_to_style_kwargs({ - 'number_format': {'format_code': self.datetime_format}})) - elif isinstance(cell.val, date): - style_kwargs.update(self._convert_to_style_kwargs({ - 'number_format': {'format_code': self.date_format}})) - - if cell.style: - style_kwargs.update(self._convert_to_style_kwargs(cell.style)) - - if style_kwargs: - xcell.style = xcell.style.copy(**style_kwargs) - - if cell.mergestart is not None and cell.mergeend is not None: - cletterstart = get_column_letter(startcol + cell.col + 1) - cletterend = get_column_letter(startcol + cell.mergeend + 1) - - wks.merge_cells('{start}{row}:{end}{mergestart}' - .format(start=cletterstart, - row=startrow + cell.row + 1, - end=cletterend, - mergestart=startrow + - cell.mergestart + 1)) - - # Excel requires that the format of the first cell in a merged - # range is repeated in the rest of the merged range. - if style_kwargs: - first_row = startrow + cell.row + 1 - last_row = startrow + cell.mergestart + 1 - first_col = startcol + cell.col + 1 - last_col = startcol + cell.mergeend + 1 - - for row in range(first_row, last_row + 1): - for col in range(first_col, last_col + 1): - if row == first_row and col == first_col: - # Ignore first cell. It is already handled. - continue - colletter = get_column_letter(col) - xcell = wks["{col}{row}" - .format(col=colletter, row=row)] - xcell.style = xcell.style.copy(**style_kwargs) - @classmethod def _convert_to_style_kwargs(cls, style_dict): """ @@ -1341,13 +1171,7 @@ def _convert_to_number_format(cls, number_format_dict): ------- number_format : str """ - try: - # >= 2.0.0 < 2.1.0 - from openpyxl.styles import NumberFormat - return NumberFormat(**number_format_dict) - except: - # >= 2.1.0 - return number_format_dict['format_code'] + return number_format_dict['format_code'] @classmethod def _convert_to_protection(cls, protection_dict): @@ -1367,17 +1191,6 @@ def _convert_to_protection(cls, protection_dict): return Protection(**protection_dict) - -register_writer(_Openpyxl20Writer) - - -class _Openpyxl22Writer(_Openpyxl20Writer): - """ - Note: Support for OpenPyxl v2.2 is currently EXPERIMENTAL (GH7565). - """ - engine = 'openpyxl22' - openpyxl_majorver = 2 - def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0, freeze_panes=None): # Write the frame cells using openpyxl. @@ -1443,7 +1256,7 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0, setattr(xcell, k, v) -register_writer(_Openpyxl22Writer) +register_writer(_OpenpyxlWriter) class _XlwtWriter(ExcelWriter): diff --git a/pandas/tests/indexes/datetimes/test_tools.py b/pandas/tests/indexes/datetimes/test_tools.py index a1287c3102b77..6c72e65b1021c 100644 --- a/pandas/tests/indexes/datetimes/test_tools.py +++ b/pandas/tests/indexes/datetimes/test_tools.py @@ -1160,9 +1160,9 @@ class TestDatetimeParsingWrappers(object): @pytest.mark.parametrize('cache', [True, False]) def test_parsers(self, cache): + # dateutil >= 2.5.0 defaults to yearfirst=True # https://github.com/dateutil/dateutil/issues/217 - import dateutil - yearfirst = dateutil.__version__ >= LooseVersion('2.5.0') + yearfirst = True cases = {'2011-01-01': datetime(2011, 1, 1), '2Q2005': datetime(2005, 4, 1), diff --git a/pandas/tests/io/test_excel.py b/pandas/tests/io/test_excel.py index d33136a86faad..96117b3c21a9b 100644 --- a/pandas/tests/io/test_excel.py +++ b/pandas/tests/io/test_excel.py @@ -1,6 +1,4 @@ # pylint: disable=E1101 -import functools -import operator import os import sys import warnings @@ -17,12 +15,12 @@ import pandas as pd import pandas.util.testing as tm from pandas import DataFrame, Index, MultiIndex -from pandas.compat import u, range, map, openpyxl_compat, BytesIO, iteritems +from pandas.compat import u, range, map, BytesIO, iteritems from pandas.core.config import set_option, get_option from pandas.io.common import URLError from pandas.io.excel import ( - ExcelFile, ExcelWriter, read_excel, _XlwtWriter, _Openpyxl1Writer, - _Openpyxl20Writer, _Openpyxl22Writer, register_writer, _XlsxWriter + ExcelFile, ExcelWriter, read_excel, _XlwtWriter, _OpenpyxlWriter, + register_writer, _XlsxWriter ) from pandas.io.formats.excel import ExcelFormatter from pandas.io.parsers import read_csv @@ -1926,207 +1924,10 @@ def test_path_localpath(self): tm.assert_frame_equal(df, result) -def raise_wrapper(major_ver): - def versioned_raise_wrapper(orig_method): - @functools.wraps(orig_method) - def wrapped(self, *args, **kwargs): - _skip_if_no_openpyxl() - if openpyxl_compat.is_compat(major_ver=major_ver): - orig_method(self, *args, **kwargs) - else: - msg = (r'Installed openpyxl is not supported at this ' - r'time\. Use.+') - with tm.assert_raises_regex(ValueError, msg): - orig_method(self, *args, **kwargs) - return wrapped - return versioned_raise_wrapper - - -def raise_on_incompat_version(major_ver): - def versioned_raise_on_incompat_version(cls): - methods = filter(operator.methodcaller( - 'startswith', 'test_'), dir(cls)) - for method in methods: - setattr(cls, method, raise_wrapper( - major_ver)(getattr(cls, method))) - return cls - return versioned_raise_on_incompat_version - - -@raise_on_incompat_version(1) class TestOpenpyxlTests(ExcelWriterBase): + engine_name = 'openpyxl' ext = '.xlsx' - engine_name = 'openpyxl1' - check_skip = staticmethod(lambda *args, **kwargs: None) - - def test_to_excel_styleconverter(self): - _skip_if_no_openpyxl() - if not openpyxl_compat.is_compat(major_ver=1): - pytest.skip('incompatible openpyxl version') - - import openpyxl - - hstyle = {"font": {"bold": True}, - "borders": {"top": "thin", - "right": "thin", - "bottom": "thin", - "left": "thin"}, - "alignment": {"horizontal": "center", "vertical": "top"}} - - xlsx_style = _Openpyxl1Writer._convert_to_style(hstyle) - assert xlsx_style.font.bold - assert (openpyxl.style.Border.BORDER_THIN == - xlsx_style.borders.top.border_style) - assert (openpyxl.style.Border.BORDER_THIN == - xlsx_style.borders.right.border_style) - assert (openpyxl.style.Border.BORDER_THIN == - xlsx_style.borders.bottom.border_style) - assert (openpyxl.style.Border.BORDER_THIN == - xlsx_style.borders.left.border_style) - assert (openpyxl.style.Alignment.HORIZONTAL_CENTER == - xlsx_style.alignment.horizontal) - assert (openpyxl.style.Alignment.VERTICAL_TOP == - xlsx_style.alignment.vertical) - - -def skip_openpyxl_gt21(cls): - """Skip test case if openpyxl >= 2.2""" - - @classmethod - def setup_class(cls): - _skip_if_no_openpyxl() - import openpyxl - ver = openpyxl.__version__ - if (not (LooseVersion(ver) >= LooseVersion('2.0.0') and - LooseVersion(ver) < LooseVersion('2.2.0'))): - pytest.skip("openpyxl %s >= 2.2" % str(ver)) - - cls.setup_class = setup_class - return cls - - -@raise_on_incompat_version(2) -@skip_openpyxl_gt21 -class TestOpenpyxl20Tests(ExcelWriterBase): - ext = '.xlsx' - engine_name = 'openpyxl20' - check_skip = staticmethod(lambda *args, **kwargs: None) - - def test_to_excel_styleconverter(self): - import openpyxl - from openpyxl import styles - - hstyle = { - "font": { - "color": '00FF0000', - "bold": True, - }, - "borders": { - "top": "thin", - "right": "thin", - "bottom": "thin", - "left": "thin", - }, - "alignment": { - "horizontal": "center", - "vertical": "top", - }, - "fill": { - "patternType": 'solid', - 'fgColor': { - 'rgb': '006666FF', - 'tint': 0.3, - }, - }, - "number_format": { - "format_code": "0.00" - }, - "protection": { - "locked": True, - "hidden": False, - }, - } - - font_color = styles.Color('00FF0000') - font = styles.Font(bold=True, color=font_color) - side = styles.Side(style=styles.borders.BORDER_THIN) - border = styles.Border(top=side, right=side, bottom=side, left=side) - alignment = styles.Alignment(horizontal='center', vertical='top') - fill_color = styles.Color(rgb='006666FF', tint=0.3) - fill = styles.PatternFill(patternType='solid', fgColor=fill_color) - - # ahh openpyxl API changes - ver = openpyxl.__version__ - if ver >= LooseVersion('2.0.0') and ver < LooseVersion('2.1.0'): - number_format = styles.NumberFormat(format_code='0.00') - else: - number_format = '0.00' # XXX: Only works with openpyxl-2.1.0 - - protection = styles.Protection(locked=True, hidden=False) - - kw = _Openpyxl20Writer._convert_to_style_kwargs(hstyle) - assert kw['font'] == font - assert kw['border'] == border - assert kw['alignment'] == alignment - assert kw['fill'] == fill - assert kw['number_format'] == number_format - assert kw['protection'] == protection - - def test_write_cells_merge_styled(self): - from pandas.io.formats.excel import ExcelCell - from openpyxl import styles - - sheet_name = 'merge_styled' - - sty_b1 = {'font': {'color': '00FF0000'}} - sty_a2 = {'font': {'color': '0000FF00'}} - - initial_cells = [ - ExcelCell(col=1, row=0, val=42, style=sty_b1), - ExcelCell(col=0, row=1, val=99, style=sty_a2), - ] - - sty_merged = {'font': {'color': '000000FF', 'bold': True}} - sty_kwargs = _Openpyxl20Writer._convert_to_style_kwargs(sty_merged) - openpyxl_sty_merged = styles.Style(**sty_kwargs) - merge_cells = [ - ExcelCell(col=0, row=0, val='pandas', - mergestart=1, mergeend=1, style=sty_merged), - ] - - with ensure_clean('.xlsx') as path: - writer = _Openpyxl20Writer(path) - writer.write_cells(initial_cells, sheet_name=sheet_name) - writer.write_cells(merge_cells, sheet_name=sheet_name) - - wks = writer.sheets[sheet_name] - xcell_b1 = wks['B1'] - xcell_a2 = wks['A2'] - assert xcell_b1.style == openpyxl_sty_merged - assert xcell_a2.style == openpyxl_sty_merged - - -def skip_openpyxl_lt22(cls): - """Skip test case if openpyxl < 2.2""" - - @classmethod - def setup_class(cls): - _skip_if_no_openpyxl() - import openpyxl - ver = openpyxl.__version__ - if LooseVersion(ver) < LooseVersion('2.2.0'): - pytest.skip("openpyxl %s < 2.2" % str(ver)) - - cls.setup_class = setup_class - return cls - - -@raise_on_incompat_version(2) -@skip_openpyxl_lt22 -class TestOpenpyxl22Tests(ExcelWriterBase): - ext = '.xlsx' - engine_name = 'openpyxl22' - check_skip = staticmethod(lambda *args, **kwargs: None) + check_skip = staticmethod(_skip_if_no_openpyxl) def test_to_excel_styleconverter(self): from openpyxl import styles @@ -2174,7 +1975,7 @@ def test_to_excel_styleconverter(self): protection = styles.Protection(locked=True, hidden=False) - kw = _Openpyxl22Writer._convert_to_style_kwargs(hstyle) + kw = _OpenpyxlWriter._convert_to_style_kwargs(hstyle) assert kw['font'] == font assert kw['border'] == border assert kw['alignment'] == alignment @@ -2183,9 +1984,6 @@ def test_to_excel_styleconverter(self): assert kw['protection'] == protection def test_write_cells_merge_styled(self): - if not openpyxl_compat.is_compat(major_ver=2): - pytest.skip('incompatible openpyxl version') - from pandas.io.formats.excel import ExcelCell sheet_name = 'merge_styled' @@ -2199,7 +1997,7 @@ def test_write_cells_merge_styled(self): ] sty_merged = {'font': {'color': '000000FF', 'bold': True}} - sty_kwargs = _Openpyxl22Writer._convert_to_style_kwargs(sty_merged) + sty_kwargs = _OpenpyxlWriter._convert_to_style_kwargs(sty_merged) openpyxl_sty_merged = sty_kwargs['font'] merge_cells = [ ExcelCell(col=0, row=0, val='pandas', @@ -2207,7 +2005,7 @@ def test_write_cells_merge_styled(self): ] with ensure_clean('.xlsx') as path: - writer = _Openpyxl22Writer(path) + writer = _OpenpyxlWriter(path) writer.write_cells(initial_cells, sheet_name=sheet_name) writer.write_cells(merge_cells, sheet_name=sheet_name) @@ -2322,7 +2120,7 @@ def test_column_format(self): try: read_num_format = cell.number_format - except: + except Exception: read_num_format = cell.style.number_format._format_code assert read_num_format == num_format @@ -2366,9 +2164,7 @@ def test_ExcelWriter_dispatch(self): writer_klass = _XlsxWriter except ImportError: _skip_if_no_openpyxl() - if not openpyxl_compat.is_compat(major_ver=1): - pytest.skip('incompatible openpyxl version') - writer_klass = _Openpyxl1Writer + writer_klass = _OpenpyxlWriter with ensure_clean('.xlsx') as path: writer = ExcelWriter(path) @@ -2461,10 +2257,6 @@ def custom_converter(css): pytest.importorskip('jinja2') pytest.importorskip(engine) - if engine == 'openpyxl' and openpyxl_compat.is_compat(major_ver=1): - pytest.xfail('openpyxl1 does not support some openpyxl2-compatible ' - 'style dicts') - # Prepare spreadsheets df = DataFrame(np.random.randn(10, 3)) @@ -2482,9 +2274,6 @@ def custom_converter(css): # For other engines, we only smoke test return openpyxl = pytest.importorskip('openpyxl') - if not openpyxl_compat.is_compat(major_ver=2): - pytest.skip('incompatible openpyxl version') - wb = openpyxl.load_workbook(path) # (1) compare DataFrame.to_excel and Styler.to_excel when unstyled diff --git a/pandas/tests/scalar/test_timestamp.py b/pandas/tests/scalar/test_timestamp.py index dab508de335c4..e23911e8d2003 100644 --- a/pandas/tests/scalar/test_timestamp.py +++ b/pandas/tests/scalar/test_timestamp.py @@ -16,7 +16,7 @@ import pandas.util.testing as tm from pandas.tseries import offsets, frequencies -from pandas._libs.tslibs.timezones import get_timezone +from pandas._libs.tslibs.timezones import get_timezone, dateutil_gettz as gettz from pandas._libs.tslibs import conversion, period from pandas.compat import long, PY3 @@ -359,9 +359,7 @@ def test_conversion(self): '2014-01-01 00:00:00.000000001']) def test_repr(self, date, freq): # dateutil zone change (only matters for repr) - if (dateutil.__version__ >= LooseVersion('2.3') and - (dateutil.__version__ <= LooseVersion('2.4.0') or - dateutil.__version__ >= LooseVersion('2.6.0'))): + if dateutil.__version__ >= LooseVersion('2.6.0'): timezones = ['UTC', 'Asia/Tokyo', 'US/Eastern', 'dateutil/US/Pacific'] else: @@ -1381,7 +1379,6 @@ def test_timestamp_to_datetime_explicit_pytz(self): def test_timestamp_to_datetime_explicit_dateutil(self): tm._skip_if_windows_python_3() - from pandas._libs.tslibs.timezones import dateutil_gettz as gettz stamp = Timestamp('20090415', tz=gettz('US/Eastern'), freq='D') dtval = stamp.to_pydatetime() assert stamp == dtval diff --git a/setup.py b/setup.py index ba948abf4302b..57131255884de 100755 --- a/setup.py +++ b/setup.py @@ -19,8 +19,6 @@ import versioneer cmdclass = versioneer.get_cmdclass() -PY3 = sys.version_info[0] >= 3 - def is_platform_windows(): return sys.platform == 'win32' or sys.platform == 'cygwin' @@ -46,7 +44,7 @@ def is_platform_mac(): min_numpy_ver = '1.9.0' setuptools_kwargs = { 'install_requires': [ - 'python-dateutil >= 2' if PY3 else 'python-dateutil', + 'python-dateutil >= 2.5.0', 'pytz >= 2011k', 'numpy >= {numpy_ver}'.format(numpy_ver=min_numpy_ver), ],