Skip to content

Commit c8a1117

Browse files
authored
Merge pull request #125 from DeepSpace2/devel
Version 4.1
2 parents bb05187 + 7f1cbf2 commit c8a1117

File tree

9 files changed

+168
-76
lines changed

9 files changed

+168
-76
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ coverage.xml
5454
# Sphinx documentation
5555
docs/*
5656
!docs/*.rst
57+
!docs/requirements.txt
5758

5859
# PyBuilder
5960
target/

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
#### 4.1
2+
* Added `strikethrough` and `italic` to `Styler`
3+
* Raising `TypeError` in `to_excel` if trying to use a non-openpyxl engine
4+
* Exposing `Exposing StyleFrame.ExcelWriter` and `StyleFrame.read_excel` to the module-level (`styleframe.ExcelWriter`
5+
and `styleframe.read_excel`) to more closely mimic pandas' API
6+
* Fixes [GitHub issue #124](https://github.com/DeepSpace2/StyleFrame/issues/124) - Improved calculation of color luminosity from tint
7+
18
#### 4.0
29
* **Removed Python 3.4 support**
310
* **Removed Python 3.5 support**

docs/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ colour
22
jsonschema
33
openpyxl
44
pandas
5-
sphinx==4.4.0
5+
sphinx==5.0.1
66
xlrd

setup.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,11 @@ def find_version(*file_paths):
8282
# your project is installed. For an analysis of "install_requires" vs pip's
8383
# requirements files see:
8484
# https://packaging.python.org/en/latest/requirements.html
85-
install_requires=['openpyxl>=2.5,<4', 'colour>=0.1.5,<0.2', 'jsonschema', 'xlrd>=1.0.0,<1.3.0', 'pandas<2']
85+
install_requires=[
86+
'openpyxl>=2.5,<4',
87+
'colour>=0.1.5,<0.2',
88+
'jsonschema',
89+
'pandas<2',
90+
"xlrd>=1.0.0,<1.3.0 ; python_version<='3.6'"
91+
]
8692
)

styleframe/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@
1111

1212
if 'utrunner' not in sys.argv[0]:
1313
from styleframe.tests import tests
14+
15+
ExcelWriter = StyleFrame.ExcelWriter
16+
read_excel = StyleFrame.read_excel

styleframe/style_frame.py

Lines changed: 81 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
import datetime as dt
2-
import numpy as np
32
import pathlib
4-
import pandas as pd
53

6-
from functools import partial
7-
8-
from .deprecations import deprecated_kwargs
9-
from . import utils
10-
from copy import deepcopy
114
from collections import OrderedDict
125
from collections.abc import Iterable
13-
from openpyxl import load_workbook
14-
from openpyxl.cell.cell import get_column_letter
15-
from openpyxl.xml.functions import fromstring, QName
6+
from copy import deepcopy
7+
from functools import partial
8+
from typing import Union, Optional, List, Dict, Tuple, Set
9+
10+
import numpy as np
11+
import pandas as pd
12+
13+
from openpyxl import load_workbook, Workbook
14+
from openpyxl.cell.cell import get_column_letter, Cell
1615
from openpyxl.utils import cell
16+
from openpyxl.worksheet.worksheet import Worksheet
17+
from openpyxl.xml.functions import fromstring, QName
1718

1819
from styleframe.container import Container
1920
from styleframe.series import Series
2021
from styleframe.styler import Styler, ColorScaleConditionalFormatRule
22+
from . import utils
2123

2224
try:
2325
pd_timestamp = pd.Timestamp
@@ -34,17 +36,23 @@ class StyleFrame:
3436
a list of dictionaries or another StyleFrame.
3537
:param styler_obj: Will be used as the default style of all cells.
3638
:type styler_obj: :class:`.Styler`
39+
:param columns: Names of columns to use. Only applicable if ``obj`` is :class:`numpy.ndarray`
40+
:type columns: None or list[str]
3741
"""
38-
P_FACTOR = 1.3
39-
A_FACTOR = 13
42+
P_FACTOR: Union[int, float] = 1.3
43+
A_FACTOR: Union[int, float] = 13
4044

41-
def __init__(self, obj, styler_obj=None):
45+
def __init__(self, obj, styler_obj: Optional[Styler] = None, columns: Optional[List[str]] = None):
4246
from_another_styleframe = False
4347
from_pandas_dataframe = False
4448
if styler_obj and not isinstance(styler_obj, Styler):
4549
raise TypeError('styler_obj must be {}, got {} instead.'.format(Styler.__name__, type(styler_obj).__name__))
46-
if isinstance(obj, pd.DataFrame):
50+
if isinstance(obj, (pd.DataFrame, np.ndarray)):
4751
from_pandas_dataframe = True
52+
if isinstance(obj, np.ndarray):
53+
obj = pd.DataFrame(obj)
54+
if columns:
55+
obj = obj.rename(columns=dict(zip(obj.columns, columns)))
4856
if obj.empty:
4957
self.data_df = deepcopy(obj)
5058
else:
@@ -69,7 +77,7 @@ def __init__(self, obj, styler_obj=None):
6977
self._columns_width = obj._columns_width if from_another_styleframe else OrderedDict()
7078
self._rows_height = obj._rows_height if from_another_styleframe else OrderedDict()
7179
self._has_custom_headers_style = obj._has_custom_headers_style if from_another_styleframe else False
72-
self._cond_formatting = []
80+
self._cond_formatting: List[ColorScaleConditionalFormatRule] = []
7381
self._default_style = styler_obj or Styler()
7482
self._index_header_style = obj._index_header_style if from_another_styleframe else self._default_style
7583

@@ -112,15 +120,15 @@ def __getattr__(self, attr):
112120
raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, attr))
113121

114122
@property
115-
def columns(self):
123+
def columns(self) -> List[Container]:
116124
return self.data_df.columns
117125

118126
@columns.setter
119-
def columns(self, columns):
127+
def columns(self, columns: Iterable) -> None:
120128
self.data_df.columns = [col if isinstance(col, Container) else Container(value=col)
121129
for col in columns]
122130

123-
def _get_column_as_letter(self, sheet, column_to_convert, startcol=0):
131+
def _get_column_as_letter(self, sheet: Worksheet, column_to_convert, startcol: int = 0) -> str:
124132
col = column_to_convert.value if isinstance(column_to_convert, Container) else column_to_convert
125133
if not isinstance(col, (int, str)):
126134
raise TypeError("column must be an index, column letter or column name")
@@ -142,19 +150,14 @@ def _get_column_as_letter(self, sheet, column_to_convert, startcol=0):
142150
return column_as_letter
143151

144152
@classmethod
145-
def read_excel(cls, path, sheet_name=0, read_style=False, use_openpyxl_styles=False,
146-
read_comments=False, **kwargs):
153+
def read_excel(cls, path: str, sheet_name: Union[str, int] = 0, read_style: bool = False,
154+
use_openpyxl_styles: bool = False, read_comments: bool = False, **kwargs) -> 'StyleFrame':
147155
"""
148156
Creates a StyleFrame object from an existing Excel.
149157
150158
.. note:: :meth:`read_excel` also accepts all arguments that :func:`pandas.read_excel` accepts as kwargs.
151159
152160
:param str path: The path to the Excel file to read.
153-
:param sheetname:
154-
.. deprecated:: 1.6
155-
Use ``sheet_name`` instead.
156-
.. versionchanged:: 4.0
157-
Removed
158161
:param sheet_name: The sheet name to read. If an integer is provided then it be used as a zero-based
159162
sheet index. Default is 0.
160163
:type sheet_name: str or int
@@ -175,7 +178,7 @@ def read_excel(cls, path, sheet_name=0, read_style=False, use_openpyxl_styles=Fa
175178
:rtype: :class:`StyleFrame`
176179
"""
177180

178-
def _get_scheme_colors_from_excel(wb):
181+
def _get_scheme_colors_from_excel(wb: Workbook) -> List[str]:
179182
xlmns = 'http://schemas.openxmlformats.org/drawingml/2006/main'
180183
if wb.loaded_theme is None:
181184
return []
@@ -192,7 +195,7 @@ def _get_scheme_colors_from_excel(wb):
192195
colors.append(accent.attrib['val'])
193196
return colors
194197

195-
def _get_style_object(sheet, theme_colors, row, column):
198+
def _get_style_object(sheet: Worksheet, theme_colors: List[str], row: int, column: int) -> Union[Cell, Styler]:
196199
cell = sheet.cell(row=row, column=column)
197200
if use_openpyxl_styles:
198201
return cell
@@ -250,7 +253,7 @@ def _read_style():
250253
return sf
251254

252255
@classmethod
253-
def read_excel_as_template(cls, path, df, use_df_boundaries=False, **kwargs):
256+
def read_excel_as_template(cls, path: str, df: pd.DataFrame, use_df_boundaries: bool = False, **kwargs) -> 'StyleFrame':
254257
"""
255258
.. versionadded:: 3.0.1
256259
@@ -332,9 +335,11 @@ def row_indexes(self):
332335

333336
return tuple(range(1, len(self) + 2))
334337

335-
def to_excel(self, excel_writer='output.xlsx', sheet_name='Sheet1',
336-
allow_protection=False, right_to_left=False, columns_to_hide=None, row_to_add_filters=None,
337-
columns_and_rows_to_freeze=None, best_fit=None, **kwargs):
338+
def to_excel(self, excel_writer: Union[str, pd.ExcelWriter, pathlib.Path] = 'output.xlsx',
339+
sheet_name: str = 'Sheet1', allow_protection: bool = False, right_to_left: bool = False,
340+
columns_to_hide: Union[None, str, list, tuple, set] = None, row_to_add_filters: Optional[int] = None,
341+
columns_and_rows_to_freeze: Optional[str] = None, best_fit: Union[None, str, list, tuple, set] = None,
342+
**kwargs) -> pd.ExcelWriter:
338343
"""Saves the dataframe to excel and applies the styles.
339344
340345
.. note:: :meth:`to_excel` also accepts all arguments that :meth:`pandas.DataFrame.to_excel` accepts as kwargs.
@@ -369,11 +374,14 @@ def to_excel(self, excel_writer='output.xlsx', sheet_name='Sheet1',
369374
calling ``StyleFrame.to_excel`` by directly modifying ``StyleFrame.A_FACTOR`` and ``StyleFrame.P_FACTOR``
370375
371376
:type best_fit: None or str or list or tuple or set
372-
:return: self
373-
:rtype: :class:`StyleFrame`
377+
:rtype: :class:`pandas.ExcelWriter`
374378
375379
"""
376380

381+
if isinstance(excel_writer, pd.ExcelWriter):
382+
if excel_writer.engine != 'openpyxl':
383+
raise TypeError('styleframe supports only openpyxl, attempted to use {}'.format(excel_writer.engine))
384+
377385
# dealing with needed pandas.to_excel defaults
378386
header = kwargs.pop('header', True)
379387
index = kwargs.pop('index', False)
@@ -393,7 +401,7 @@ def get_values(x):
393401
except TypeError:
394402
return x
395403

396-
def within_sheet_boundaries(row=1, column='A'):
404+
def within_sheet_boundaries(row: Union[int, str] = 1, column: str = 'A'):
397405
return (1 <= int(row) <= sheet.max_row
398406
and
399407
1 <= cell.column_index_from_string(column) <= sheet.max_column)
@@ -575,8 +583,14 @@ def get_range_of_cells(row_index=None, columns=None):
575583

576584
return excel_writer
577585

578-
def apply_style_by_indexes(self, indexes_to_style, styler_obj, cols_to_style=None, height=None,
579-
complement_style=None, complement_height=None, overwrite_default_style=True):
586+
def apply_style_by_indexes(self,
587+
indexes_to_style: Union[list, tuple, int, Container],
588+
styler_obj: Styler,
589+
cols_to_style: Optional[Union[str, Union[List[str], Tuple[str], Set[str]]]] = None,
590+
height: Optional[Union[int, float]] = None,
591+
complement_style: Optional[Styler] = None,
592+
complement_height: Optional[Union[int, float]] = None,
593+
overwrite_default_style: bool = True):
580594
"""
581595
Applies a certain style to the provided indexes in the dataframe in the provided columns
582596
@@ -585,7 +599,7 @@ def apply_style_by_indexes(self, indexes_to_style, styler_obj, cols_to_style=Non
585599
586600
::
587601
588-
sf[sf['some_col'] = 20]
602+
sf[sf['some_col'] == 20]
589603
590604
:type indexes_to_style: list or tuple or int or Container
591605
:param styler_obj: `Styler` object that contains the style that will be applied to indexes in `indexes_to_style`
@@ -648,8 +662,13 @@ def apply_style_by_indexes(self, indexes_to_style, styler_obj, cols_to_style=Non
648662

649663
return self
650664

651-
def apply_column_style(self, cols_to_style, styler_obj, style_header=False, use_default_formats=True, width=None,
652-
overwrite_default_style=True):
665+
def apply_column_style(self,
666+
cols_to_style: Union[str, List[str], Tuple[str], Set[str]],
667+
styler_obj: Styler,
668+
style_header: bool = False,
669+
use_default_formats: bool = True,
670+
width: Optional[Union[int, float]] = None,
671+
overwrite_default_style: bool = True):
653672
"""Apply style to a whole column
654673
655674
:param cols_to_style: The column names to style.
@@ -700,7 +719,10 @@ def apply_column_style(self, cols_to_style, styler_obj, style_header=False, use_
700719

701720
return self
702721

703-
def apply_headers_style(self, styler_obj, style_index_header=True, cols_to_style=None):
722+
def apply_headers_style(self,
723+
styler_obj: Styler,
724+
style_index_header: bool = True,
725+
cols_to_style: Optional[Union[str, List[str], Tuple[str], Set[str]]] = None):
704726
"""Apply style to the headers only
705727
706728
:param styler_obj: The style to apply
@@ -737,7 +759,8 @@ def apply_headers_style(self, styler_obj, style_index_header=True, cols_to_style
737759
self._has_custom_headers_style = True
738760
return self
739761

740-
def set_column_width(self, columns, width):
762+
def set_column_width(self, columns: Union[str, Union[str, List[str], Tuple[str], List[int], Tuple[int]]],
763+
width: Union[int, float]) -> 'StyleFrame':
741764
"""Set the width of the given columns
742765
743766
:param columns: Column name(s) or index(es).
@@ -765,7 +788,7 @@ def set_column_width(self, columns, width):
765788

766789
return self
767790

768-
def set_column_width_dict(self, col_width_dict):
791+
def set_column_width_dict(self, col_width_dict: Dict[str, Union[int, float]]) -> 'StyleFrame':
769792
"""
770793
:param col_width_dict: A dictionary from column names to width.
771794
:type col_width_dict: dict[str, int or float]
@@ -780,7 +803,7 @@ def set_column_width_dict(self, col_width_dict):
780803

781804
return self
782805

783-
def set_row_height(self, rows, height):
806+
def set_row_height(self, rows: Union[int, List[int], Tuple[int], Set[int]], height: Union[int, float]) -> 'StyleFrame':
784807
""" Set the height of the given rows
785808
786809
:param rows: Row(s) index.
@@ -810,7 +833,7 @@ def set_row_height(self, rows, height):
810833

811834
return self
812835

813-
def set_row_height_dict(self, row_height_dict):
836+
def set_row_height_dict(self, row_height_dict: Dict[int, Union[int, float]]) -> 'StyleFrame':
814837
"""
815838
:param row_height_dict: A dictionary from row indexes to height.
816839
:type row_height_dict: dict[int, int or float]
@@ -848,16 +871,16 @@ def rename(self, columns=None, inplace=False):
848871
if old_col_name in sf._columns_width})
849872
return sf
850873

851-
def style_alternate_rows(self, styles, **kwargs):
874+
def style_alternate_rows(self, styles: Union[List[Styler], Tuple[Styler]], **kwargs) -> 'StyleFrame':
852875
"""
853876
.. versionadded:: 1.2
854877
855878
Applies the provided styles to rows in an alternating manner.
856879
857880
.. note:: :meth:`style_alternate_rows` also accepts all arguments that :meth:`apply_style_by_indexes` accepts as kwargs.
858881
859-
:param styles: List, tuple or set of :class:`.Styler` objects to be applied to rows in an alternating manner
860-
:type styles: list[:class:`.Styler`] or tuple[:class:`.Styler`] or set[:class:`.Styler`]
882+
:param styles: List or tuple of :class:`.Styler` objects to be applied to rows in an alternating manner
883+
:type styles: list[:class:`.Styler`] or tuple[:class:`.Styler`]
861884
:return: self
862885
:rtype: :class:`StyleFrame`
863886
@@ -869,8 +892,17 @@ def style_alternate_rows(self, styles, **kwargs):
869892
self.apply_style_by_indexes(indexes, styles[i], **kwargs)
870893
return self
871894

872-
def add_color_scale_conditional_formatting(self, start_type, start_value, start_color, end_type, end_value, end_color,
873-
mid_type=None, mid_value=None, mid_color=None, columns_range=None):
895+
def add_color_scale_conditional_formatting(self,
896+
start_type: str,
897+
start_value: Union[int, float],
898+
start_color: str,
899+
end_type: str,
900+
end_value: Union[int, float],
901+
end_color: str,
902+
mid_type: Optional[str] = None,
903+
mid_value: Optional[Union[int, float]] = None,
904+
mid_color: Optional[str] = None,
905+
columns_range=None):
874906
"""
875907
:param start_type: The type for the minimum bound
876908
:type start_type: str: one of :class:`.utils.conditional_formatting_types` or any other type Excel supports

0 commit comments

Comments
 (0)