From 488cc45548c29f6d0ee6c73f36a783d3a380bd0e Mon Sep 17 00:00:00 2001 From: DeepSpace2 Date: Wed, 15 Jun 2022 16:57:39 +0300 Subject: [PATCH 01/19] Added tests for creation of StyleFrame from a numpy array --- styleframe/tests/style_frame_tests.py | 28 +++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/styleframe/tests/style_frame_tests.py b/styleframe/tests/style_frame_tests.py index 8531cb7..c81293b 100644 --- a/styleframe/tests/style_frame_tests.py +++ b/styleframe/tests/style_frame_tests.py @@ -1,9 +1,13 @@ +import os import unittest + +from functools import partial + +import numpy as np import pandas as pd + from pandas.testing import assert_frame_equal -import os -from functools import partial from styleframe import Container, StyleFrame, Styler, utils from styleframe.tests import TEST_FILENAME @@ -76,6 +80,26 @@ def test_init_styleframe(self): with self.assertRaises(TypeError): StyleFrame({}, styler_obj=1) + def test_init_np_array(self): + for (np_array, expected_columns), sf in zip( + [ + (np.array([1, 2]), [0]), + (np.array([1, 2]), ['a']), + (np.array([[1, 2], [3, 4]]), [0, 1]), + (np.array([[1, 2], [3, 4]]), ['a', 1]), + (np.array([[1, 2], [3, 4]]), ['a', 'b']) + ], + [ + StyleFrame(np.array([1, 2])), + StyleFrame(np.array([1, 2]), columns=['a']), + StyleFrame(np.array([[1, 2], [3, 4]])), + StyleFrame(np.array([[1, 2], [3, 4]]), columns=['a']), + StyleFrame(np.array([[1, 2], [3, 4]]), columns=['a', 'b']) + ] + ): + self.assertIsInstance(sf, StyleFrame) + assert np.all(expected_columns == sf.columns) + def test_len(self): self.assertEqual(len(self.sf), len(self.sf.data_df)) self.assertEqual(len(self.sf), 3) From db0f6f2e4d2ebe6d657d477312bfc797c801088b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jun 2022 04:01:40 +0000 Subject: [PATCH 02/19] depen update(deps): bump sphinx from 5.0.1 to 5.0.2 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 5.0.1 to 5.0.2. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/5.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v5.0.1...v5.0.2) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 5fbc1b1..bcc98b1 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,5 @@ colour jsonschema openpyxl pandas -sphinx==5.0.1 +sphinx==5.0.2 xlrd \ No newline at end of file From c70243dc2add192b81d5fd0581e7566cbe135efd Mon Sep 17 00:00:00 2001 From: DeepSpace2 Date: Fri, 24 Jun 2022 14:25:38 +0300 Subject: [PATCH 03/19] Fixed and updated badges --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3fd49bb..60be328 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -[![Codecov branch](https://img.shields.io/codecov/c/github/DeepSpace2/StyleFrame/master.svg?style=plastic)]() -[![Travis branch](https://img.shields.io/travis/DeepSpace2/StyleFrame/master.svg?style=plastic)]() -[![PyPI](https://img.shields.io/pypi/v/styleframe.svg?style=plastic)]() -[![PyPI](https://img.shields.io/pypi/pyversions/StyleFrame.svg?style=plastic)]() -[![Downloads](http://pepy.tech/badge/styleframe)](http://pepy.tech/count/styleframe) +[![codecov](https://codecov.io/gh/DeepSpace2/StyleFrame/branch/master/graph/badge.svg?token=f4Q048ZOwC)](https://codecov.io/gh/DeepSpace2/StyleFrame) +[![GitHub Actions](https://img.shields.io/github/checks-status/deepspace2/styleframe/master?label=Tests&logo=github&style=plastic)](https://github.com/DeepSpace2/StyleFrame/actions/workflows/test.yml?query=branch%3Amaster) +[![PyPI](https://img.shields.io/pypi/v/styleframe.svg?style=plastic)](https://pypi.org/project/styleframe/) +![PyPI](https://img.shields.io/pypi/pyversions/StyleFrame.svg?style=plastic) +[![Downloads](http://pepy.tech/badge/styleframe)](https://pepy.tech/project/styleframe) [![Documentation Status](https://readthedocs.org/projects/styleframe/badge/?version=latest&style=plastic)](https://styleframe.readthedocs.io/en/latest/?badge=latest) # StyleFrame From d70150a0eacff154e9bcf7e842a7d639e2d1bb57 Mon Sep 17 00:00:00 2001 From: Adi Vaknin Date: Tue, 12 Jul 2022 21:22:10 +0300 Subject: [PATCH 04/19] Individual borders control (#137) Added support for controlling individual borders styles --- docs/utils.rst | 3 ++ styleframe/styler.py | 69 ++++++++++++++++++++------- styleframe/tests/style_frame_tests.py | 16 +++---- styleframe/utils.py | 16 +++++++ 4 files changed, 79 insertions(+), 25 deletions(-) diff --git a/docs/utils.rst b/docs/utils.rst index 0836d90..68fdc18 100644 --- a/docs/utils.rst +++ b/docs/utils.rst @@ -16,6 +16,9 @@ It is possible to directly use a value that is not present in the utils module a .. autoclass:: styleframe.utils.borders :members: +.. autoclass:: styleframe.utils.border_locations + :members: + .. autoclass:: styleframe.utils.horizontal_alignments :members: diff --git a/styleframe/styler.py b/styleframe/styler.py index 35c1dd4..00f919e 100644 --- a/styleframe/styler.py +++ b/styleframe/styler.py @@ -7,7 +7,7 @@ from openpyxl.comments import Comment from pprint import pformat -from typing import Dict, List, Optional, Union +from typing import Dict, List, Optional, Union, Set class Styler: @@ -27,8 +27,20 @@ class Styler: :param bool protection: If ``True``, the cell/column will be write-protected :param underline: The underline type :type underline: str: one of :class:`.utils.underline` or any other underline Excel supports - :param border_type: The border type - :type border_type: str: one of :class:`.utils.borders` or any other border type Excel supports + + .. versionchanged:: 4.2 + + :param border_type: + - If provided a string (one of :class:`.utils.borders` or any other border type Excel supports): all borders + will be set to that type. + - If provided a set of strings (:class:`.utils.border_locations` or any other border location Excel supports): + each provided border will be set to the default border type. + - If provided a dict (from location, + one of :class:`.utils.border_locations` or any other border location Excel supports) to border type + (one of :class:`.utils.borders` or any other border type Excel supports): each provided border will be set to + the provided border type. + + :type border_type: str or set[str] or dict[str, str] .. versionadded:: 1.2 @@ -77,7 +89,7 @@ def __init__(self, number_format: str = utils.number_formats.general, protection: bool = False, underline: Optional[str] = None, - border_type: str = utils.borders.thin, + border_type: Union[str, Set[str], Dict[str, str]] = utils.borders.thin, horizontal_alignment: str = utils.horizontal_alignments.center, vertical_alignment: str = utils.vertical_alignments.center, wrap_text: bool = True, @@ -100,15 +112,6 @@ def get_color_from_string(color_str: str, default_color: Optional[str] = None) - color_str = utils.colors.get(color_str, default_color) return color_str - if border_type == utils.borders.default_grid: - if bg_color is not None or fill_pattern_type != utils.fill_pattern_types.solid: - raise ValueError('`bg_color`or `fill_pattern_type` conflict with border_type={}'.format(utils.borders.default_grid)) - self.border_type = None - self.fill_pattern_type = None - else: - self.border_type = border_type - self.fill_pattern_type = fill_pattern_type - self.bold = bold self.font = font self.font_size = font_size @@ -130,12 +133,32 @@ def get_color_from_string(color_str: str, default_color: Optional[str] = None) - self.date_time_format = date_time_format self.strikethrough = strikethrough self.italic = italic + self.fill_pattern_type = fill_pattern_type + + if isinstance(border_type, set): + self.border_type = {border_location: utils.borders.thin for border_location in border_type} + elif isinstance(border_type, dict): + self.border_type = border_type + else: + self.border_type = { + utils.border_locations.top: border_type, + utils.border_locations.right: border_type, + utils.border_locations.bottom: border_type, + utils.border_locations.left: border_type + } + + if border_type == utils.borders.default_grid: + if bg_color is not None or fill_pattern_type != utils.fill_pattern_types.solid: + raise ValueError(f'`bg_color`or `fill_pattern_type` conflict with border_type={utils.borders.default_grid}') + self.border_type = None + self.fill_pattern_type = None def __eq__(self, other): return isinstance(other, self.__class__) and self.__dict__ == other.__dict__ def __hash__(self): - return hash(tuple((k, v) for k, v in sorted(self.__dict__.items()))) + return hash(tuple((k, v) if not isinstance(v, (dict, set)) else hash(tuple(v.items())) + for k, v in sorted(self.__dict__.items()))) def __add__(self, other): default = Styler().__dict__ @@ -161,8 +184,13 @@ def to_openpyxl_style(self): try: openpyxl_style = self.cache[self] except KeyError: - side = Side(border_style=self.border_type, color=utils.colors.black) - border = Border(left=side, right=side, top=side, bottom=side) + if isinstance(self.border_type, str): + side = Side(border_style=self.border_type, color=utils.colors.black) + border = Border(left=side, right=side, top=side, bottom=side) + else: + border = Border(**{border_location: Side(border_style=border_type, color=utils.colors.black) + for border_location, border_type in self.border_type.items()}) + openpyxl_style = self.cache[self] = NamedStyle( name=str(hash(self)), font=Font(name=self.font, size=self.font_size, color=OpenPyColor(self.font_color), @@ -181,6 +209,7 @@ def to_openpyxl_style(self): @classmethod def from_openpyxl_style(cls, openpyxl_style: Cell, theme_colors: List[str], openpyxl_comment: Optional[Comment] = None): + def _calc_new_hex_from_theme_hex_and_tint(theme_hex, color_tint): if not theme_hex.startswith('#'): theme_hex = '#' + theme_hex @@ -235,7 +264,6 @@ def _calc_lum_from_tint(color_tint: Optional[float], current_lum: float) -> floa number_format = openpyxl_style.number_format protection = openpyxl_style.protection.locked underline = openpyxl_style.font.underline - border_type = openpyxl_style.border.bottom.border_style horizontal_alignment = openpyxl_style.alignment.horizontal vertical_alignment = openpyxl_style.alignment.vertical wrap_text = openpyxl_style.alignment.wrap_text or False @@ -244,6 +272,13 @@ def _calc_lum_from_tint(color_tint: Optional[float], current_lum: float) -> floa indent = openpyxl_style.alignment.indent text_rotation = openpyxl_style.alignment.text_rotation + border_type = { + utils.border_locations.top: getattr(openpyxl_style.border.top, 'border_style', None), + utils.border_locations.right: getattr(openpyxl_style.border.right, 'border_style', None), + utils.border_locations.bottom: getattr(openpyxl_style.border.bottom, 'border_style', None), + utils.border_locations.left: getattr(openpyxl_style.border.left, 'border_style', None) + } + if openpyxl_comment: comment_author = openpyxl_comment.author comment_text = openpyxl_comment.text diff --git a/styleframe/tests/style_frame_tests.py b/styleframe/tests/style_frame_tests.py index c81293b..9a4b293 100644 --- a/styleframe/tests/style_frame_tests.py +++ b/styleframe/tests/style_frame_tests.py @@ -357,10 +357,9 @@ def test_read_excel_with_string_sheet_name(self): rows_in_excel = sf_from_excel.data_df.itertuples() rows_in_self = self.sf.data_df.itertuples() - # making sure styles are the same - self.assertTrue(all(self_cell.style == Styler.from_openpyxl_style(excel_cell.style, []) - for row_in_excel, row_in_self in zip(rows_in_excel, rows_in_self) - for excel_cell, self_cell in zip(row_in_excel[1:], row_in_self[1:]))) + for row_in_excel, row_in_self in zip(rows_in_excel, rows_in_self): + for excel_cell, self_cell in zip(row_in_excel[1:], row_in_self[1:]): + self.assertEqual(self_cell.style, Styler.from_openpyxl_style(excel_cell.style, [])) def test_read_excel_with_style_openpyxl_objects(self): self.export_and_get_default_sheet(save=True) @@ -396,15 +395,16 @@ def test_read_excel_with_style_styler_objects(self): self.export_and_get_default_sheet(save=True) sf_from_excel = StyleFrame.read_excel(TEST_FILENAME, read_style=True) # making sure content is the same - self.assertTrue(all(list(self.sf[col]) == list(sf_from_excel[col]) for col in self.sf.columns)) + for col in self.sf.columns: + self.assertEqual(list(self.sf[col]), list(sf_from_excel[col])) rows_in_excel = sf_from_excel.data_df.itertuples() rows_in_self = self.sf.data_df.itertuples() # making sure styles are the same - self.assertTrue(all(excel_cell.style == self_cell.style - for row_in_excel, row_in_self in zip(rows_in_excel, rows_in_self) - for excel_cell, self_cell in zip(row_in_excel[1:], row_in_self[1:]))) + for row_in_excel, row_in_self in zip(rows_in_excel, rows_in_self): + for excel_cell, self_cell in zip(row_in_excel[1:], row_in_self[1:]): + self.assertEqual(excel_cell.style, self_cell.style) def test_read_excel_with_style_comments_openpyxl_objects(self): self.export_and_get_default_sheet(save=True) diff --git a/styleframe/utils.py b/styleframe/utils.py index 3475cf2..14e31a9 100644 --- a/styleframe/utils.py +++ b/styleframe/utils.py @@ -159,6 +159,22 @@ class borders(BaseDefClass): thin = 'thin' +class border_locations(BaseDefClass): + """ + .. versionadded:: 4.2 + + :cvar str top: 'top' + :cvar str bottom: 'bottom' + :cvar str left: 'left' + :cvar str right: 'right' + """ + + top = 'top' + bottom = 'bottom' + left = 'left' + right = 'right' + + class horizontal_alignments(BaseDefClass): """ :cvar str general: 'general' From a1058d1f99b2e0033fb9281db05d4bca8fff549e Mon Sep 17 00:00:00 2001 From: buhtz Date: Wed, 13 Jul 2022 10:00:45 +0200 Subject: [PATCH 05/19] explicte save() --- README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 60be328..d619b3e 100644 --- a/README.md +++ b/README.md @@ -141,11 +141,14 @@ sf.apply_style_by_indexes(indexes_to_style=sf[sf['Pass/Fail'] == 'Failed'], sf.set_column_width(columns=sf.columns, width=20) sf.set_row_height(rows=sf.row_indexes, height=25) -sf.to_excel('output.xlsx', - # Add filters in row 0 to each column. - row_to_add_filters=0, - # Freeze the columns before column 'A' (=None) and rows above '2' (=1). - columns_and_rows_to_freeze='A2').save() +writer = sf.to_excel('output.xlsx', + # Add filters in row 0 to each column. + row_to_add_filters=0, + # Freeze the columns before column 'A' (=None) + # and rows above '2' (=1). + columns_and_rows_to_freeze='A2') +# Don't forget to save. +writer.save() ``` The final output saved under output.xlsx: ![Example 1](readme-images/example1.PNG?raw=true) From f65c45186a45829375c73abbf65b21f11cad271f Mon Sep 17 00:00:00 2001 From: Codeberg-AsGithubAlternative-buhtz Date: Wed, 13 Jul 2022 18:17:19 +0200 Subject: [PATCH 06/19] Improve contributing.md (#136) * Improved contributing.md * Linked CONTRIBUTING.md in README.md --- .github/CONTRIBUTING.md | 41 +++++++++++++++++++++++++++++++++++++++-- README.md | 5 +++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index ae682f5..eff512a 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,2 +1,39 @@ -Thanks for contributing. If creating a pull request please make sure to submit it to the `devel` branch and not to `master`. -Happy coding! +### Thanks for contributing! + +# Branch +Create the pull request against the `devel` branch instead of the `master`. + +# Quality +All pull requests are welcome regardless of quality. We will work together to review and improve the pull request. + +Of course there are some steps that could be considered to improve the quality of the pull request and to support the process of review and development. + +- Make sure your code follows [PEP 8 — Style Guide for Python Code](https://pep8.org/). You can use some [linting tools](https://en.wikipedia.org/wiki/Lint_(software)) to automate the process (e.g. [`pycodestyle`](https://pycodestyle.pycqa.org) or [`flake8`](https://github.com/pycqa/flake8)). +- Test your code using the existing test cases. For details read further. +- It would be great if you could create [`unittest`](https://docs.python.org/3/library/unittest.html)'s for your code. + +# Unittesting + +This project uses Python's default [`unittest`](https://docs.python.org/3/library/unittest.html) package. You can run all test cases via the *discover* feature when you run this in the projects root folder. + +```sh +python3 -m unittest discover +``` + +The output should look like this +```sh +---------------------------------------------------------------------- +Ran 74 tests in 0.823s + +OK +``` + +If you want to run a specific test case or method you need to *install* the package first. It is recommended to use a virtual environment and the `--editable` flag of `pip`. Run this in the projects root folder: +```sh +python3 -m pip install --editable . +``` +Please read further to understand the consequences of `--editable`. +- ["pip documentation - Local project installs - Editable installs"](https://pip.pypa.io/en/stable/topics/local-project-installs/#editable-installs) +- ["When would the -e, --editable option be useful with pip install?"](https://stackoverflow.com/q/35064426/4865723) + +### Happy coding! diff --git a/README.md b/README.md index 60be328..9b77eec 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,11 @@ # StyleFrame _Exporting DataFrames to a styled Excel file has never been so easy_ - A library that wraps pandas and openpyxl and allows easy styling of dataframes in Excel. -[Documentation](http://styleframe.readthedocs.org/en/latest/) and [Changelog](CHANGELOG.md) +- [Documentation](http://styleframe.readthedocs.org/en/latest/) +- [Changelog](CHANGELOG.md) +- [How to contribute](.github/CONTRIBUTING.md) --- From 172c384e01086b143b2908fd559be3ab85b4f064 Mon Sep 17 00:00:00 2001 From: DeepSpace2 Date: Sat, 23 Jul 2022 18:47:16 +0300 Subject: [PATCH 07/19] Added Python 3.10 support --- .github/workflows/test.yml | 6 +----- setup.py | 3 ++- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e8d8c5b..bea29d2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,12 +12,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] experimental: [false] - include: - - python-version: 3.10-dev - experimental: true - steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/setup.py b/setup.py index 60d91cc..5b4248b 100644 --- a/setup.py +++ b/setup.py @@ -67,7 +67,8 @@ def find_version(*file_paths): 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9' + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10' ], # What does your project relate to? From 979a2d8373429d96789ae3ede8c6f44e9f800239 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Sep 2022 04:01:31 +0000 Subject: [PATCH 08/19] depen update(deps): bump sphinx from 5.0.2 to 5.2.2 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 5.0.2 to 5.2.2. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/5.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v5.0.2...v5.2.2) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index bcc98b1..5526e8b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,5 @@ colour jsonschema openpyxl pandas -sphinx==5.0.2 +sphinx==5.2.2 xlrd \ No newline at end of file From 1b846feae284d19d7b7269eef144c9a111a8c3a9 Mon Sep 17 00:00:00 2001 From: buhtzz Date: Fri, 30 Sep 2022 14:55:20 +0200 Subject: [PATCH 09/19] Refactored StylelFrame.to_excel --- styleframe/style_frame.py | 26 +++++++++++++++++++------- styleframe/tests/style_frame_tests.py | 14 ++++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/styleframe/style_frame.py b/styleframe/style_frame.py index 53a4961..2b30491 100644 --- a/styleframe/style_frame.py +++ b/styleframe/style_frame.py @@ -5,7 +5,7 @@ from collections.abc import Iterable from copy import deepcopy from functools import partial -from typing import Union, Optional, List, Dict, Tuple, Set +from typing import Any, Dict, List, Optional, Set, Tuple, Union import numpy as np import pandas as pd @@ -335,10 +335,23 @@ def row_indexes(self): return tuple(range(1, len(self) + 2)) + @staticmethod + def _to_excel_pandas_defaults(kwargs: Dict[str, Any]) -> Tuple[bool, int, int, str]: + """Returns the provided or default values for specific arguments as set by :meth:`pandas.DataFrame.to_excel` + """ + + header = kwargs.pop('header', True) + startcol = kwargs.pop('startcol', 0) + startrow = kwargs.pop('startrow', 0) + na_rep = kwargs.pop('na_rep', '') + + return header, startcol, startrow, na_rep + def to_excel(self, excel_writer: Union[str, pd.ExcelWriter, pathlib.Path] = 'output.xlsx', sheet_name: str = 'Sheet1', allow_protection: bool = False, right_to_left: bool = False, columns_to_hide: Union[None, str, list, tuple, set] = None, row_to_add_filters: Optional[int] = None, columns_and_rows_to_freeze: Optional[str] = None, best_fit: Union[None, str, list, tuple, set] = None, + index: bool = False, **kwargs) -> pd.ExcelWriter: """Saves the dataframe to excel and applies the styles. @@ -374,6 +387,10 @@ def to_excel(self, excel_writer: Union[str, pd.ExcelWriter, pathlib.Path] = 'out calling ``StyleFrame.to_excel`` by directly modifying ``StyleFrame.A_FACTOR`` and ``StyleFrame.P_FACTOR`` :type best_fit: None or str or list or tuple or set + + .. versionadded:: 4.2 + + :param bool index: Write row names. :rtype: :class:`pandas.ExcelWriter` """ @@ -382,12 +399,7 @@ def to_excel(self, excel_writer: Union[str, pd.ExcelWriter, pathlib.Path] = 'out if excel_writer.engine != 'openpyxl': raise TypeError('styleframe supports only openpyxl, attempted to use {}'.format(excel_writer.engine)) - # dealing with needed pandas.to_excel defaults - header = kwargs.pop('header', True) - index = kwargs.pop('index', False) - startcol = kwargs.pop('startcol', 0) - startrow = kwargs.pop('startrow', 0) - na_rep = kwargs.pop('na_rep', '') + header, startcol, startrow, na_rep = self._to_excel_pandas_defaults(kwargs) def get_values(x): if isinstance(x, Container): diff --git a/styleframe/tests/style_frame_tests.py b/styleframe/tests/style_frame_tests.py index 9a4b293..b384cf9 100644 --- a/styleframe/tests/style_frame_tests.py +++ b/styleframe/tests/style_frame_tests.py @@ -1,3 +1,4 @@ +import inspect import os import unittest @@ -674,3 +675,16 @@ def test_columns_setter(self): self.sf.columns = ['c', 'd'] self.assertTrue(all(isinstance(col, Container) for col in self.sf.columns)) self.assertEqual([col.value for col in self.sf.columns], ['c', 'd']) + def test_to_excel_pandas_defaults(self): + # values that StyleFrame assume as pandas defaults + header, startcol, startrow, na_rep = StyleFrame._to_excel_pandas_defaults({}) + + # the "real" default values pandas use + sig = inspect.signature(pd.DataFrame.to_excel) + + # compare them + self.assertEqual(header, sig.parameters['header'].default) + self.assertEqual(startcol, sig.parameters['startcol'].default) + self.assertEqual(startrow, sig.parameters['startrow'].default) + self.assertEqual(na_rep, sig.parameters['na_rep'].default) + From 8304dcfe2897bb55983f7218ab6081786b73c5b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Oct 2022 04:01:46 +0000 Subject: [PATCH 10/19] depen update(deps): bump sphinx from 5.2.2 to 5.2.3 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 5.2.2 to 5.2.3. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/5.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v5.2.2...v5.2.3) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 5526e8b..1f44793 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,5 @@ colour jsonschema openpyxl pandas -sphinx==5.2.2 +sphinx==5.2.3 xlrd \ No newline at end of file From 9108d4d6ee7181297b0e575601f2716460c40a04 Mon Sep 17 00:00:00 2001 From: DeepSpace2 Date: Tue, 4 Oct 2022 22:48:54 +0300 Subject: [PATCH 11/19] Changed usages of `ExcelWriter.save` to `ExcelWriter.close` and `number_format` to `date_format` (where applicable). Closes #148 --- README.md | 51 +++++++-------------------- docs/usage_examples.rst | 2 +- styleframe/tests/style_frame_tests.py | 12 +++---- 3 files changed, 19 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 49c052f..5f5f331 100644 --- a/README.md +++ b/README.md @@ -49,45 +49,18 @@ $ pip install styleframe ## Basics * ***Styler***: -```python -__init__(self, bg_color=None, bold=False, font=utils.fonts.arial, font_size=12, font_color=None, - number_format=utils.number_formats.general, protection=False, underline=None, - border_type=utils.borders.thin, horizontal_alignment=utils.horizontal_alignments.center, - vertical_alignment=utils.vertical_alignments.center, wrap_text=True, shrink_to_fit=True, - fill_pattern_type=utils.fill_pattern_types.solid, indent=0, - comment_author=None, comment_text=None, text_rotation=0) -``` -Object that represents the style of a cell in our excel file. -Styler is responsible of storing the style of single cell. -Once the style is ready, ```.to_openpyxl_style()``` method is called. +The `Styler` class represents a style of a cell. * ***utils***: -```python -from styleframe import utils -``` -Before you start to style your StyleFrame, take a look in the utils module. -You may find there very useful things such as number formats, colors, borders and more! - +The `utils` module contains helper classes for frequently used styling elements, +such as number and date formats, colors and border types. * ***Container***: -```python -__init__(self, value, styler=None) -``` -Object that represents cell in our excel file. - it contains two variables: -    - value which may be anything you wish to put in the cell as long as excel file support its format. -    - style which is the style of the cell- created by ```Styler(...).to_openpyxl_style()``` - -And finally: +The `Container` class represents a cell, a value/style pair. * ***StyleFrame***: -```python -__init__(self, obj, styler_obj=None): -``` -StyleFrame is the main object we will be dealing with. -It contains self DataFrame which is based on the given obj. -Each item of the self DataFrame is wrapped by a Container object to store the given data and its` style. -StyleFrame (usually referred as sf) reveals a very easy api for styling. +The `StyleFrame` is the main interaction point you will have. +It wraps the `DataFrame` object you will be styling. ## Usage Examples @@ -148,8 +121,8 @@ writer = sf.to_excel('output.xlsx', # Freeze the columns before column 'A' (=None) # and rows above '2' (=1). columns_and_rows_to_freeze='A2') -# Don't forget to save. -writer.save() + +writer.close() ``` The final output saved under output.xlsx: ![Example 1](readme-images/example1.PNG?raw=true) @@ -228,7 +201,7 @@ from styleframe import Styler, utils sf.apply_column_style(cols_to_style='Date', - styler_obj=Styler(number_format=utils.number_formats.date, + styler_obj=Styler(date_format=utils.number_formats.date, font=utils.fonts.calibri, bold=True)) @@ -267,7 +240,7 @@ Change the background of all rows where the date is after 14/1/2000 to green sf.apply_style_by_indexes(indexes_to_style=sf[sf['Date'] > date(2000, 1, 14)], cols_to_style='Date', styler_obj=Styler(bg_color=utils.colors.green, - number_format=utils.number_formats.date, + date_format=utils.number_formats.date, bold=True)) ``` @@ -289,14 +262,14 @@ sf.to_excel(excel_writer=ew, Adding another excel sheet ```python other_sheet_sf = StyleFrame({'Dates': [date(2016, 10, 20), date(2016, 10, 21), date(2016, 10, 22)]}, - styler_obj=Styler(number_format=utils.number_formats.date)) + styler_obj=Styler(date_format=utils.number_formats.date)) other_sheet_sf.to_excel(excel_writer=ew, sheet_name='2') ``` Don't forget to save ```python -ew.save() +ew.close() ``` **_the result:_** diff --git a/docs/usage_examples.rst b/docs/usage_examples.rst index b1ceff8..3b41446 100644 --- a/docs/usage_examples.rst +++ b/docs/usage_examples.rst @@ -24,7 +24,7 @@ Creating ExcelWriter object: ew = StyleFrame.ExcelWriter(r'C:\my_excel.xlsx') sf.to_excel(ew) - ew.save() + ew.close() It is also possible to style a whole column or columns, and decide whether to style the headers or not: :: diff --git a/styleframe/tests/style_frame_tests.py b/styleframe/tests/style_frame_tests.py index b384cf9..f1456f5 100644 --- a/styleframe/tests/style_frame_tests.py +++ b/styleframe/tests/style_frame_tests.py @@ -46,7 +46,7 @@ def export_and_get_default_sheet(self, save=False): self.sf.to_excel(excel_writer=self.ew, right_to_left=True, columns_to_hide=self.sf.columns[0], row_to_add_filters=0, columns_and_rows_to_freeze='A2', allow_protection=True) if save: - self.ew.save() + self.ew.close() return self.ew.sheets['Sheet1'] def get_cf_rules(self, sheet): @@ -390,7 +390,7 @@ def test_read_excel_with_style_openpyxl_objects_and_save(self): for row_in_excel, row_in_self in zip(rows_in_excel, rows_in_self) for excel_cell, self_cell in zip(row_in_excel[1:], row_in_self[1:]))) - sf_from_excel.to_excel(TEST_FILENAME).save() + sf_from_excel.to_excel(TEST_FILENAME).close() def test_read_excel_with_style_styler_objects(self): self.export_and_get_default_sheet(save=True) @@ -485,7 +485,7 @@ def test_read_excel_template_equal_boundaries(self): styler_obj=self.styler_obj_1 ) template_sf.index[0].style = self.styler_obj_2 - template_sf.to_excel(TEST_FILENAME, index=True).save() + template_sf.to_excel(TEST_FILENAME, index=True).close() df = pd.DataFrame( data={ @@ -519,7 +519,7 @@ def test_read_excel_template_boundaries_with_more_rows_and_columns_than_df(self) }, styler_obj=self.styler_obj_1 ) - template_sf.to_excel(TEST_FILENAME).save() + template_sf.to_excel(TEST_FILENAME).close() df = pd.DataFrame( data={ @@ -556,7 +556,7 @@ def test_read_excel_template_boundaries_with_less_rows_and_columns_than_df(self) styler_obj=self.styler_obj_1 ) template_sf.index[0].style = self.styler_obj_2 - template_sf.to_excel(TEST_FILENAME, index=True).save() + template_sf.to_excel(TEST_FILENAME, index=True).close() df = pd.DataFrame( data={ @@ -591,7 +591,7 @@ def test_read_excel_template_with_use_df_boundaries(self): }, styler_obj=self.styler_obj_1 ) - template_sf.to_excel(TEST_FILENAME).save() + template_sf.to_excel(TEST_FILENAME).close() df = pd.DataFrame( data={ From 841ce5dc1e9755b72c1b5fd2be907351dd8566cb Mon Sep 17 00:00:00 2001 From: DeepSpace2 Date: Thu, 6 Oct 2022 20:10:17 +0300 Subject: [PATCH 12/19] Fixed a bug when the dataframe has a column called "index". Closes #108 --- styleframe/style_frame.py | 18 ++++++------- styleframe/tests/style_frame_tests.py | 37 +++++++++++++++------------ 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/styleframe/style_frame.py b/styleframe/style_frame.py index 2b30491..11685e0 100644 --- a/styleframe/style_frame.py +++ b/styleframe/style_frame.py @@ -222,12 +222,12 @@ def _read_style(): for col_index, col_name in enumerate(sf.columns): col_index_in_excel = col_index + 1 if col_index_in_excel == excel_index_col: - for row_index, sf_index in enumerate(sf.index, start=2): + for row_index, sf_index in enumerate(sf.data_df.index, start=2): sf_index.style = get_style_object(row=row_index, column=col_index_in_excel) col_index_in_excel += 1 # Move next to excel indices column sf.columns[col_index].style = get_style_object(row=1, column=col_index_in_excel) - for row_index, sf_index in enumerate(sf.index, start=start_row_index): + for row_index, sf_index in enumerate(sf.data_df.index, start=start_row_index): sf.at[sf_index, col_name].style = get_style_object(row=row_index, column=col_index_in_excel) sf._rows_height[row_index] = sheet.row_dimensions[row_index].height @@ -273,7 +273,7 @@ def read_excel_as_template(cls, path: str, df: pd.DataFrame, use_df_boundaries: sf = cls.read_excel(path=path, read_style=True, **kwargs) num_of_rows, num_of_cols = len(df.index), len(df.columns) - template_num_of_rows, template_num_of_cols = len(sf.index), len(sf.columns) + template_num_of_rows, template_num_of_cols = len(sf.data_df.index), len(sf.columns) num_of_cols_to_copy_with_style = min(num_of_cols, template_num_of_cols) num_of_rows_to_copy_with_style = min(num_of_rows, template_num_of_rows) @@ -643,7 +643,7 @@ def apply_style_by_indexes(self, raise TypeError('styler_obj must be {}, got {} instead.'.format(Styler.__name__, type(styler_obj).__name__)) if isinstance(indexes_to_style, (list, tuple, int)): - indexes_to_style = self.index[indexes_to_style] + indexes_to_style = self.data_df.index[indexes_to_style] elif isinstance(indexes_to_style, Container): indexes_to_style = pd.Index([indexes_to_style]) @@ -661,15 +661,15 @@ def apply_style_by_indexes(self, for index in indexes_to_style: index.style = style_to_apply for col in cols_to_style: - self.iloc[self.index.get_loc(index), self.columns.get_loc(col)].style = style_to_apply + self.iloc[self.data_df.index.get_loc(index), self.columns.get_loc(col)].style = style_to_apply if height: # Add offset 2 since rows do not include the headers and they starts from 1 (not 0). - rows_indexes_for_height_change = [self.index.get_loc(idx) + 2 for idx in indexes_to_style] + rows_indexes_for_height_change = [self.data_df.index.get_loc(idx) + 2 for idx in indexes_to_style] self.set_row_height(rows=rows_indexes_for_height_change, height=height) if complement_style: - self.apply_style_by_indexes(self.index.difference(indexes_to_style), complement_style, cols_to_style, + self.apply_style_by_indexes(self.data_df.index.difference(indexes_to_style), complement_style, cols_to_style, complement_height if complement_height else height) return self @@ -715,7 +715,7 @@ def apply_column_style(self, if style_header: self.columns[self.columns.get_loc(col_name)].style = style_to_apply self._has_custom_headers_style = True - for index in self.index: + for index in self.data_df.index: if use_default_formats: if isinstance(self.at[index, col_name].value, pd_timestamp): style_to_apply.number_format = utils.number_formats.date_time @@ -899,7 +899,7 @@ def style_alternate_rows(self, styles: Union[List[Styler], Tuple[Styler]], **kwa """ num_of_styles = len(styles) - split_indexes = (self.index[i::num_of_styles] for i in range(num_of_styles)) + split_indexes = (self.data_df.index[i::num_of_styles] for i in range(num_of_styles)) for i, indexes in enumerate(split_indexes): self.apply_style_by_indexes(indexes, styles[i], **kwargs) return self diff --git a/styleframe/tests/style_frame_tests.py b/styleframe/tests/style_frame_tests.py index f1456f5..81587b3 100644 --- a/styleframe/tests/style_frame_tests.py +++ b/styleframe/tests/style_frame_tests.py @@ -30,7 +30,9 @@ def setUpClass(cls): def setUp(self): self.ew = StyleFrame.ExcelWriter(TEST_FILENAME) self.sf = StyleFrame({'a': ['col_a_row_1', 'col_a_row_2', 'col_a_row_3'], - 'b': ['col_b_row_1', 'col_b_row_2', 'col_b_row_3']}, self.default_styler_obj) + 'b': ['col_b_row_1', 'col_b_row_2', 'col_b_row_3'], + 'index': ['col_c_row_1', 'col_c_row_2', 'col_c_row_3']}, + self.default_styler_obj) self.apply_column_style = partial(self.sf.apply_column_style, styler_obj=self.styler_obj_1, width=10) self.apply_style_by_indexes = partial(self.sf.apply_style_by_indexes, styler_obj=self.styler_obj_1, height=10) self.apply_headers_style = partial(self.sf.apply_headers_style, styler_obj=self.styler_obj_1) @@ -60,7 +62,7 @@ def test_init_styler_obj(self): self.sf = StyleFrame({'a': [1, 2, 3], 'b': [1, 2, 3]}, styler_obj=self.styler_obj_1) self.assertTrue(all(self.sf.at[index, 'a'].style.to_openpyxl_style()._style == self.openpy_style_obj_1 - for index in self.sf.index)) + for index in self.sf.data_df.index)) sheet = self.export_and_get_default_sheet() @@ -141,7 +143,7 @@ def test_apply_column_style(self): self.apply_column_style(cols_to_style=['a']) self.assertTrue(all([self.sf.at[index, 'a'].style.to_openpyxl_style()._style == self.openpy_style_obj_1 and self.sf.at[index, 'b'].style.to_openpyxl_style()._style != self.openpy_style_obj_1 - for index in self.sf.index])) + for index in self.sf.data_df.index])) sheet = self.export_and_get_default_sheet() @@ -164,7 +166,7 @@ def test_apply_column_style_no_override_default_style(self): self.apply_column_style(cols_to_style=['a'], overwrite_default_style=False) self.assertTrue(all([self.sf.at[index, 'a'].style == Styler.combine(self.default_styler_obj, self.styler_obj_1) and self.sf.at[index, 'b'].style == self.default_styler_obj - for index in self.sf.index])) + for index in self.sf.data_df.index])) sheet = self.export_and_get_default_sheet() @@ -182,7 +184,7 @@ def test_apply_style_by_indexes_single_col(self): self.apply_style_by_indexes(self.sf[self.sf['a'] == 'col_a_row_2'], cols_to_style=['a']) self.assertTrue(all(self.sf.at[index, 'a'].style.to_openpyxl_style()._style == self.openpy_style_obj_1 - for index in self.sf.index if self.sf.at[index, 'a'] == 'col_a_row_2')) + for index in self.sf.data_df.index if self.sf.at[index, 'a'] == 'col_a_row_2')) sheet = self.export_and_get_default_sheet() @@ -195,7 +197,7 @@ def test_apply_style_by_indexes_all_cols(self): self.apply_style_by_indexes(self.sf[self.sf['a'] == 2]) self.assertTrue(all(self.sf.at[index, 'a'].style.to_openpyxl_style()._style == self.openpy_style_obj_1 - for index in self.sf.index if self.sf.at[index, 'a'] == 2)) + for index in self.sf.data_df.index if self.sf.at[index, 'a'] == 2)) sheet = self.export_and_get_default_sheet() @@ -208,13 +210,13 @@ def test_apply_style_by_indexes_complement_style(self): self.apply_style_by_indexes(self.sf[self.sf['a'] == 'col_a_row_1'], complement_style=self.styler_obj_2) self.assertTrue(all(self.sf.at[index, 'a'].style.to_openpyxl_style()._style == self.openpy_style_obj_1 - for index in self.sf.index if self.sf.at[index, 'a'] == 'col_a_row_1')) + for index in self.sf.data_df.index if self.sf.at[index, 'a'] == 'col_a_row_1')) self.assertTrue(all(self.sf.at[index, 'a'].style.to_openpyxl_style()._style == self.openpy_style_obj_2 - for index in self.sf.index if self.sf.at[index, 'a'] != 'col_a_row_1')) + for index in self.sf.data_df.index if self.sf.at[index, 'a'] != 'col_a_row_1')) def test_apply_style_by_indexes_with_single_index(self): - self.apply_style_by_indexes(self.sf.index[0]) + self.apply_style_by_indexes(self.sf.data_df.index[0]) self.assertTrue(all(self.sf.iloc[0, self.sf.columns.get_loc(col)].style.to_openpyxl_style()._style == self.openpy_style_obj_1 for col in self.sf.columns)) @@ -484,7 +486,7 @@ def test_read_excel_template_equal_boundaries(self): }, styler_obj=self.styler_obj_1 ) - template_sf.index[0].style = self.styler_obj_2 + template_sf.data_df.index[0].style = self.styler_obj_2 template_sf.to_excel(TEST_FILENAME, index=True).close() df = pd.DataFrame( @@ -555,7 +557,7 @@ def test_read_excel_template_boundaries_with_less_rows_and_columns_than_df(self) }, styler_obj=self.styler_obj_1 ) - template_sf.index[0].style = self.styler_obj_2 + template_sf.data_df.index[0].style = self.styler_obj_2 template_sf.to_excel(TEST_FILENAME, index=True).close() df = pd.DataFrame( @@ -624,13 +626,13 @@ def test_style_alternate_rows(self): self.sf.style_alternate_rows(styles) self.assertTrue(all(self.sf.iloc[index.value, 0].style.to_openpyxl_style() == styles[index.value % len(styles)].to_openpyxl_style() - for index in self.sf.index)) + for index in self.sf.data_df.index)) sheet = self.export_and_get_default_sheet() # sheet start from row 1 and headers are row 1, so need to add 2 when iterating self.assertTrue(all(sheet.cell(row=i.value + 2, column=1)._style == openpy_styles[i.value % len(styles)] - for i in self.sf.index)) + for i in self.sf.data_df.index)) def test_add_color_scale_conditional_formatting_start_end(self): self.sf.add_color_scale_conditional_formatting(start_type=utils.conditional_formatting_types.percentile, @@ -639,7 +641,7 @@ def test_add_color_scale_conditional_formatting_start_end(self): end_value=100, end_color=utils.colors.green) sheet = self.export_and_get_default_sheet(save=True) cf_rules = self.get_cf_rules(sheet=sheet) - rules_dict = cf_rules['A1:B4'] + rules_dict = cf_rules['A1:C4'] self.assertEqual(rules_dict[0].type, 'colorScale') self.assertEqual(rules_dict[0].colorScale.color[0].rgb, utils.colors.red) @@ -658,7 +660,7 @@ def test_add_color_scale_conditional_formatting_start_mid_end(self): end_value=100, end_color=utils.colors.green) sheet = self.export_and_get_default_sheet(save=True) cf_rules = self.get_cf_rules(sheet=sheet) - rules_dict = cf_rules['A1:B4'] + rules_dict = cf_rules['A1:C4'] self.assertEqual(rules_dict[0].type, 'colorScale') self.assertEqual(rules_dict[0].colorScale.color[0].rgb, utils.colors.red) @@ -672,9 +674,10 @@ def test_add_color_scale_conditional_formatting_start_mid_end(self): self.assertEqual(rules_dict[0].colorScale.cfvo[2].val, 100.0) def test_columns_setter(self): - self.sf.columns = ['c', 'd'] + self.sf.columns = ['d', 'e', 'f'] self.assertTrue(all(isinstance(col, Container) for col in self.sf.columns)) - self.assertEqual([col.value for col in self.sf.columns], ['c', 'd']) + self.assertEqual([col.value for col in self.sf.columns], ['d', 'e', 'f']) + def test_to_excel_pandas_defaults(self): # values that StyleFrame assume as pandas defaults header, startcol, startrow, na_rep = StyleFrame._to_excel_pandas_defaults({}) From 02566c969087b6fc501393cebfcb842940ee7af4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 04:01:29 +0000 Subject: [PATCH 13/19] depen update(deps): bump sphinx from 5.2.3 to 5.3.0 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 5.2.3 to 5.3.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v5.2.3...v5.3.0) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 1f44793..6e61b73 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,5 @@ colour jsonschema openpyxl pandas -sphinx==5.2.3 +sphinx==5.3.0 xlrd \ No newline at end of file From b45ccc5bcb3a07e7caed7daf2e6b07e1a486872b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 Nov 2023 21:32:19 +0200 Subject: [PATCH 14/19] depen update(deps): bump sphinx from 5.3.0 to 7.2.6 (#158) * depen update(deps): bump sphinx from 5.3.0 to 7.2.6 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 5.3.0 to 7.2.6. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES.rst) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v5.3.0...v7.2.6) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Added same versions pin to docs/requirements.txt to match setup.py * Revert "Added same versions pin to docs/requirements.txt to match setup.py" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: DeepSpace2 <6841988+DeepSpace2@users.noreply.github.com> --- docs/requirements.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 6e61b73..1d37a7f 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,6 @@ -colour +openpyxl>=2.5,<4 +colour>=0.1.5,<0.2 jsonschema -openpyxl -pandas -sphinx==5.3.0 -xlrd \ No newline at end of file +pandas<2 +xlrd>=1.0.0,<1.3.0 ; python_version<='3.6' +sphinx==7.2.6 From 1e54372cc819c02accadb79e4553cdba3e50a08f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 19:29:37 +0200 Subject: [PATCH 15/19] depen update(deps): update pandas requirement from <2 to <3 (#159) * depen update(deps): update pandas requirement from <2 to <3 Updates the requirements on [pandas](https://github.com/pandas-dev/pandas) to permit the latest version. - [Release notes](https://github.com/pandas-dev/pandas/releases) - [Commits](https://github.com/pandas-dev/pandas/compare/0.3.0...v2.1.2) --- updated-dependencies: - dependency-name: pandas dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Fixed ExcelWriter.save to .close aliasing DataFrame applymap to DataFrame.map * Using .iloc in tests to access series by position due to deprecation in pandas > 2 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: DeepSpace2 <6841988+DeepSpace2@users.noreply.github.com> --- setup.py | 2 +- styleframe/__init__.py | 6 ++++++ styleframe/command_line/commandline.py | 2 +- styleframe/style_frame.py | 14 +++++++++----- styleframe/tests/style_frame_tests.py | 15 +++++++-------- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/setup.py b/setup.py index 5b4248b..4369691 100644 --- a/setup.py +++ b/setup.py @@ -87,7 +87,7 @@ def find_version(*file_paths): 'openpyxl>=2.5,<4', 'colour>=0.1.5,<0.2', 'jsonschema', - 'pandas<2', + 'pandas<3', "xlrd>=1.0.0,<1.3.0 ; python_version<='3.6'" ] ) diff --git a/styleframe/__init__.py b/styleframe/__init__.py index cbac45c..b77f7e4 100644 --- a/styleframe/__init__.py +++ b/styleframe/__init__.py @@ -1,5 +1,7 @@ import sys +from pandas import DataFrame + from .container import Container from .series import Series from .style_frame import StyleFrame @@ -14,3 +16,7 @@ ExcelWriter = StyleFrame.ExcelWriter read_excel = StyleFrame.read_excel + +# applymap is deprecated in pandas > 2, nasty hack to support < 2 and > 2 versions at the same time +if not hasattr(DataFrame, 'map'): + DataFrame.map = DataFrame.applymap diff --git a/styleframe/command_line/commandline.py b/styleframe/command_line/commandline.py index 7ef2c50..f3ec382 100644 --- a/styleframe/command_line/commandline.py +++ b/styleframe/command_line/commandline.py @@ -82,7 +82,7 @@ def _apply_cols_and_rows_dimensions(self, sf, sheet): sf.set_row_height_dict(row_heights) def _save(self): - self.excel_writer.save() + self.excel_writer.close() def get_cli_args(): diff --git a/styleframe/style_frame.py b/styleframe/style_frame.py index 11685e0..5822069 100644 --- a/styleframe/style_frame.py +++ b/styleframe/style_frame.py @@ -56,11 +56,11 @@ def __init__(self, obj, styler_obj: Optional[Styler] = None, columns: Optional[L if obj.empty: self.data_df = deepcopy(obj) else: - self.data_df = obj.applymap(lambda x: Container(x, deepcopy(styler_obj)) if not isinstance(x, Container) else x) + self.data_df = obj.map(lambda x: Container(x, deepcopy(styler_obj)) if not isinstance(x, Container) else x) elif isinstance(obj, pd.Series): self.data_df = obj.apply(lambda x: Container(x, deepcopy(styler_obj)) if not isinstance(x, Container) else x) elif isinstance(obj, (dict, list)): - self.data_df = pd.DataFrame(obj).applymap(lambda x: Container(x, deepcopy(styler_obj)) if not isinstance(x, Container) else x) + self.data_df = pd.DataFrame(obj).map(lambda x: Container(x, deepcopy(styler_obj)) if not isinstance(x, Container) else x) elif isinstance(obj, StyleFrame): self.data_df = deepcopy(obj.data_df) from_another_styleframe = True @@ -84,10 +84,14 @@ def __init__(self, obj, styler_obj: Optional[Styler] = None, columns: Optional[L self._known_attrs = {'at': self.data_df.at, 'loc': self.data_df.loc, 'iloc': self.data_df.iloc, - 'applymap': self.data_df.applymap, 'groupby': self.data_df.groupby, 'index': self.data_df.index, - 'fillna': self.data_df.fillna} + 'fillna': self.data_df.fillna, + + # applymap is deprecated in pandas > 2 + 'applymap': self.data_df.map, + 'map': self.data_df.map + } def __str__(self): return str(self.data_df) @@ -437,7 +441,7 @@ def get_range_of_cells(row_index=None, columns=None): end_index=end_index) if len(self.data_df) > 0: - export_df = self.data_df.applymap(get_values) + export_df = self.data_df.map(get_values) else: export_df = deepcopy(self.data_df) diff --git a/styleframe/tests/style_frame_tests.py b/styleframe/tests/style_frame_tests.py index 81587b3..875234e 100644 --- a/styleframe/tests/style_frame_tests.py +++ b/styleframe/tests/style_frame_tests.py @@ -121,7 +121,7 @@ def test__setitem__(self): self.sf['d'] = self.sf['a'] + self.sf['b'] self.sf['e'] = self.sf['a'] + 5 - self.assertTrue(all(self.sf.applymap(lambda x: isinstance(x, Container)).all())) + self.assertTrue(all(self.sf.map(lambda x: isinstance(x, Container)).all())) def test__getattr__(self): self.assertEqual(self.sf.fillna, self.sf.data_df.fillna) @@ -536,12 +536,12 @@ def test_read_excel_template_boundaries_with_more_rows_and_columns_than_df(self) # and be left from the original template self.assertListEqual([col.value for col in sf_from_template.columns], ['A', 'b']) - self.assertEqual(template_sf['a'][0].style, sf_from_template['A'][0].style, + self.assertEqual(template_sf['a'].iloc[0].style, sf_from_template['A'].iloc[0].style, 'Different styles in template cell with style {template_style}' '\nand actual cell with style {actual_cell_style}'.format( - template_style=template_sf['a'][0].style, actual_cell_style=sf_from_template['A'][0].style) + template_style=template_sf['a'].iloc[0].style, actual_cell_style=sf_from_template['A'].iloc[0].style) ) - self.assertEqual(sf_from_template['A'][0].value, 1) + self.assertEqual(sf_from_template['A'].iloc[0].value, 1) # Assert extra column equals self.assertListEqual(list(sf_from_template['b']), list(template_sf['b'])) @@ -607,15 +607,15 @@ def test_read_excel_template_with_use_df_boundaries(self): self.assertListEqual([col.value for col in sf_from_template.columns], ['A']) self.assertEqual(len(df), len(sf_from_template)) - expected_cell_style = template_sf['a'][0].style - actual_cell_style = sf_from_template['A'][0].style + expected_cell_style = template_sf['a'].iloc[0].style + actual_cell_style = sf_from_template['A'].iloc[0].style self.assertEqual(actual_cell_style, expected_cell_style, 'Different styles in template cell with style {template_style}' '\nand actual cell with style {actual_cell_style}'.format( template_style=expected_cell_style, actual_cell_style=actual_cell_style) ) - self.assertEqual(sf_from_template['A'][0].value, 1) + self.assertEqual(sf_from_template['A'].iloc[0].value, 1) def test_row_indexes(self): self.assertEqual(self.sf.row_indexes, (1, 2, 3, 4)) @@ -690,4 +690,3 @@ def test_to_excel_pandas_defaults(self): self.assertEqual(startcol, sig.parameters['startcol'].default) self.assertEqual(startrow, sig.parameters['startrow'].default) self.assertEqual(na_rep, sig.parameters['na_rep'].default) - From d937c3b40ad989b0f5ea3ae1afe5a51bc3ed8dd2 Mon Sep 17 00:00:00 2001 From: Adi Vaknin <6841988+DeepSpace2@users.noreply.github.com> Date: Mon, 13 Nov 2023 20:23:44 +0200 Subject: [PATCH 16/19] .readthedocs.yaml (#160) --- .readthedocs.yaml | 13 +++++++++++++ docs/requirements.txt | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..e88e6c7 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,13 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +sphinx: + configuration: docs/conf.py + +python: + install: + - requirements: docs/requirements.txt diff --git a/docs/requirements.txt b/docs/requirements.txt index 1d37a7f..9ae2f24 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,6 @@ openpyxl>=2.5,<4 colour>=0.1.5,<0.2 jsonschema -pandas<2 +pandas<3 xlrd>=1.0.0,<1.3.0 ; python_version<='3.6' sphinx==7.2.6 From 4b76b8201314fecb5e0c0ab35fc36c5cbc3582fa Mon Sep 17 00:00:00 2001 From: Adi Vaknin <6841988+DeepSpace2@users.noreply.github.com> Date: Mon, 13 Nov 2023 20:27:10 +0200 Subject: [PATCH 17/19] Add sphinx-rtd-theme to docs requirements (#161) --- docs/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/requirements.txt b/docs/requirements.txt index 9ae2f24..b9ff4f6 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -4,3 +4,4 @@ jsonschema pandas<3 xlrd>=1.0.0,<1.3.0 ; python_version<='3.6' sphinx==7.2.6 +sphinx-rtd-theme From 284b47bbbfa2025b7955571cfc5c10ebc39f587f Mon Sep 17 00:00:00 2001 From: Roman Konnov <41418703+Exzentttt@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:45:29 +0300 Subject: [PATCH 18/19] fix: change best fit formula if dataframe is empty (#157) Co-authored-by: Adi Vaknin --- styleframe/style_frame.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/styleframe/style_frame.py b/styleframe/style_frame.py index 5822069..0a07f31 100644 --- a/styleframe/style_frame.py +++ b/styleframe/style_frame.py @@ -549,8 +549,12 @@ def get_range_of_cells(row_index=None, columns=None): if best_fit: if not isinstance(best_fit, (list, set, tuple)): best_fit = [best_fit] - self.set_column_width_dict({column: (max(self.data_df[column].astype(str).str.len()) + self.A_FACTOR) * self.P_FACTOR - for column in best_fit}) + self.set_column_width_dict( + { + column: (max(self.data_df[column].astype(str).str.len(), default=0) + self.A_FACTOR) * self.P_FACTOR + for column in best_fit + } + ) for column in self._columns_width: column_letter = self._get_column_as_letter(sheet, column, startcol) From f5aafe9041c8f4aecdf1eae076f627cc98e1ef2d Mon Sep 17 00:00:00 2001 From: DeepSpace2 <6841988+DeepSpace2@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:11:20 +0200 Subject: [PATCH 19/19] version bump to 4.2 --- CHANGELOG.md | 9 +++++++++ styleframe/version.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee24ac9..4698c9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +#### 4.2 +* **Added Python 3.10 support** +* Added ability to set individual borders' type via the `border_type` argument + when creating a `Styler` object +* Fixes [GitHub issue #108](https://github.com/DeepSpace2/StyleFrame/issues/108) - Styling and exporting a dataframe that + contains a column called "index" +* Fixes error when attempting to use `best_fit` argument in `StyleFrame.to_excel` + on an empty dataframe [GitHub PR #157](https://github.com/DeepSpace2/StyleFrame/pull/157) + #### 4.1 * Added `strikethrough` and `italic` to `Styler` * Raising `TypeError` in `to_excel` if trying to use a non-openpyxl engine diff --git a/styleframe/version.py b/styleframe/version.py index 2b82bda..e534871 100644 --- a/styleframe/version.py +++ b/styleframe/version.py @@ -17,7 +17,7 @@ def get_all_versions() -> str: return _versions_ -_version_ = '4.1' +_version_ = '4.2' _python_version_ = get_python_version() _pandas_version_ = get_pandas_version() _openpyxl_version_ = get_openpyxl_version()