From 32bc72f00155cf60603a7984dc9a3abeb48e0f50 Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Thu, 8 Sep 2022 12:16:58 +0100 Subject: [PATCH 01/12] ENH: Improve styler typing --- pandas-stubs/io/formats/style.pyi | 306 +++++++++++++++++------ pandas-stubs/io/formats/style_render.pyi | 57 +++++ 2 files changed, 291 insertions(+), 72 deletions(-) create mode 100644 pandas-stubs/io/formats/style_render.pyi diff --git a/pandas-stubs/io/formats/style.pyi b/pandas-stubs/io/formats/style.pyi index 7ff349f02..92fc7553a 100644 --- a/pandas-stubs/io/formats/style.pyi +++ b/pandas-stubs/io/formats/style.pyi @@ -1,97 +1,259 @@ -jinja2 = ... -has_mpl: bool = ... -no_mpl_message: str = ... +from typing import ( + Any, + Callable, + Hashable, + Sequence, +) -class Styler: - loader = ... - env = ... - template = ... - ctx = ... - data = ... - index = ... - columns = ... - uuid = ... - table_styles = ... - caption = ... - precision = ... - table_attributes = ... - hidden_index: bool = ... - hidden_columns = ... - cell_ids = ... - na_rep = ... +from pandas.core.frame import DataFrame +from pandas.core.series import Series + +from pandas._typing import ( + Axis, + FilePath, + IndexLabel, + Level, + Scalar, + WriteBuffer, +) + +from pandas.io.formats.style_render import ( + CSSProperties, + CSSStyles, + ExtFormatter, + StylerRenderer, + Subset, +) + +class Styler(StylerRenderer): def __init__( self, - data, - precision=..., - table_styles=..., - uuid=..., - caption=..., - table_attributes=..., - cell_ids=..., + data: DataFrame | Series, + precision: int | None = ..., + table_styles: CSSStyles | None = ..., + uuid: str | None = ..., + caption: str | tuple | None = ..., + table_attributes: str | None = ..., + cell_ids: bool = ..., na_rep: str | None = ..., - ): ... + uuid_len: int = ..., + decimal: str | None = ..., + thousands: str | None = ..., + escape: str | None = ..., + formatter: ExtFormatter | None = ..., + ) -> None: ... + # def render(self,sparse_index: bool | None = ...,sparse_columns: bool | None = ...,**kwargs,) -> str: ... + def set_tooltips( + self, + ttips: DataFrame, + props: CSSProperties | None = ..., + css_class: str | None = ..., + ) -> Styler: ... def to_excel( self, excel_writer, sheet_name: str = ..., na_rep: str = ..., - float_format=..., - columns=..., - header: bool = ..., + float_format: str | None = ..., + columns: Sequence[Hashable] | None = ..., + header: Sequence[Hashable] | bool = ..., index: bool = ..., - index_label=..., + index_label: IndexLabel | None = ..., startrow: int = ..., startcol: int = ..., - engine=..., + engine: str | None = ..., merge_cells: bool = ..., - encoding=..., + encoding: str | None = ..., inf_rep: str = ..., verbose: bool = ..., - freeze_panes=..., + freeze_panes: tuple[int, int] | None = ..., ) -> None: ... - def format(self, formatter, subset=..., na_rep: str | None = ...): ... - def render(self, **kwargs): ... - def __copy__(self): ... - def __deepcopy__(self, memo): ... + def to_latex( + self, + buf: FilePath | WriteBuffer[str] | None = ..., + *, + column_format: str | None = ..., + position: str | None = ..., + position_float: str | None = ..., + hrules: bool | None = ..., + clines: str | None = ..., + label: str | None = ..., + caption: str | tuple | None = ..., + sparse_index: bool | None = ..., + sparse_columns: bool | None = ..., + multirow_align: str | None = ..., + multicol_align: str | None = ..., + siunitx: bool = ..., + environment: str | None = ..., + encoding: str | None = ..., + convert_css: bool = ..., + ): ... + def to_html( + self, + buf: FilePath | WriteBuffer[str] | None = ..., + *, + table_uuid: str | None = ..., + table_attributes: str | None = ..., + sparse_index: bool | None = ..., + sparse_columns: bool | None = ..., + bold_headers: bool = ..., + caption: str | None = ..., + max_rows: int | None = ..., + max_columns: int | None = ..., + encoding: str | None = ..., + doctype_html: bool = ..., + exclude_styles: bool = ..., + **kwargs, + ): ... + def set_td_classes(self, classes: DataFrame) -> Styler: ... + def __copy__(self) -> Styler: ... + def __deepcopy__(self, memo) -> Styler: ... def clear(self) -> None: ... - def apply(self, func, axis: int = ..., subset=..., **kwargs): ... - def applymap(self, func, subset=..., **kwargs): ... - def where(self, cond, value, other=..., subset=..., **kwargs): ... - def set_precision(self, precision): ... - def set_table_attributes(self, attributes): ... - def export(self): ... - def use(self, styles): ... - def set_uuid(self, uuid): ... - def set_caption(self, caption): ... - def set_table_styles(self, table_styles): ... - def set_na_rep(self, na_rep: str) -> Styler: ... - def hide_index(self): ... - def hide_columns(self, subset): ... - def highlight_null(self, null_color: str = ...): ... + def apply( + self, + func: Callable, + axis: Axis | None = ..., + subset: Subset | None = ..., + **kwargs, + ) -> Styler: ... + def apply_index( + self, + func: Callable, + axis: int | str = ..., + level: Level | list[Level] | None = ..., + **kwargs, + ) -> Styler: ... + def applymap_index( + self, + func: Callable, + axis: int | str = ..., + level: Level | list[Level] | None = ..., + **kwargs, + ) -> Styler: ... + def applymap( + self, func: Callable, subset: Subset | None = ..., **kwargs + ) -> Styler: ... + # def where(self, cond: Callable, value: str, other: str | None = ..., subset: Subset | None = ..., **kwargs) -> Styler: ... + # def set_precision(self, precision: int) -> StylerRenderer: ... + def set_table_attributes(self, attributes: str) -> Styler: ... + def export(self) -> dict[str, Any]: ... + def use(self, styles: dict[str, Any]) -> Styler: ... + uuid: Any # Incomplete + def set_uuid(self, uuid: str) -> Styler: ... + caption: Any # Incomplete + # def set_caption(self, caption: str | tuple) -> Styler: ... + def set_sticky( + self, + axis: Axis = ..., + pixel_size: int | None = ..., + levels: Level | list[Level] | None = ..., + ) -> Styler: ... + def set_table_styles( + self, + table_styles: dict[Any, CSSStyles] | CSSStyles | None = ..., + axis: int = ..., + overwrite: bool = ..., + css_class_names: dict[str, str] | None = ..., + ) -> Styler: ... + # def set_na_rep(self, na_rep: str) -> StylerRenderer: ... + # def hide_index(self, subset: Subset | None = ..., level: Level | list[Level] | None = ..., names: bool = ...,) -> Styler: ... + def hide_columns( + self, + subset: Subset | None = ..., + level: Level | list[Level] | None = ..., + names: bool = ..., + ) -> Styler: ... + def hide( + self, + subset: Subset | None = ..., + axis: Axis = ..., + level: Level | list[Level] | None = ..., + names: bool = ..., + ) -> Styler: ... def background_gradient( self, - cmap=..., - low=..., - high=..., - axis=..., - subset=..., - text_color_threshold=..., + cmap: str = ..., + low: float = ..., + high: float = ..., + axis: Axis | None = ..., + subset: Subset | None = ..., + text_color_threshold: float = ..., vmin: float | None = ..., vmax: float | None = ..., - ): ... - def set_properties(self, subset=..., **kwargs): ... + gmap: Sequence | None = ..., + ) -> Styler: ... + def text_gradient( + self, + cmap: str = ..., + low: float = ..., + high: float = ..., + axis: Axis | None = ..., + subset: Subset | None = ..., + vmin: float | None = ..., + vmax: float | None = ..., + gmap: Sequence | None = ..., + ) -> Styler: ... + def set_properties(self, subset: Subset | None = ..., **kwargs) -> Styler: ... def bar( self, - subset=..., - axis: int = ..., + subset: Subset | None = ..., + axis: Axis | None = ..., + *, + color: str | list | tuple | None = ..., + cmap: Any | None = ..., + width: float = ..., + height: float = ..., + align: str | float | Callable = ..., + vmin: float | None = ..., + vmax: float | None = ..., + props: str = ..., + ) -> Styler: ... + def highlight_null( + self, + null_color: str = ..., + subset: Subset | None = ..., + props: str | None = ..., + ) -> Styler: ... + def highlight_max( + self, + subset: Subset | None = ..., color: str = ..., - width: int = ..., - align: str = ..., - vmin=..., - vmax=..., - ): ... - def highlight_max(self, subset=..., color: str = ..., axis: int = ...): ... - def highlight_min(self, subset=..., color: str = ..., axis: int = ...): ... + axis: Axis | None = ..., + props: str | None = ..., + ) -> Styler: ... + def highlight_min( + self, + subset: Subset | None = ..., + color: str = ..., + axis: Axis | None = ..., + props: str | None = ..., + ) -> Styler: ... + def highlight_between( + self, + subset: Subset | None = ..., + color: str = ..., + axis: Axis | None = ..., + left: Scalar | Sequence | None = ..., + right: Scalar | Sequence | None = ..., + inclusive: str = ..., + props: str | None = ..., + ) -> Styler: ... + def highlight_quantile( + self, + subset: Subset | None = ..., + color: str = ..., + axis: Axis | None = ..., + q_left: float = ..., + q_right: float = ..., + interpolation: str = ..., + inclusive: str = ..., + props: str | None = ..., + ) -> Styler: ... @classmethod - def from_custom_template(cls, searchpath, name): ... - def pipe(self, func, *args, **kwargs): ... + def from_custom_template( + cls, + searchpath, + html_table: str | None = ..., + html_style: str | None = ..., + ): ... + def pipe(self, func: Callable, *args, **kwargs): ... diff --git a/pandas-stubs/io/formats/style_render.pyi b/pandas-stubs/io/formats/style_render.pyi new file mode 100644 index 000000000..581e167f6 --- /dev/null +++ b/pandas-stubs/io/formats/style_render.pyi @@ -0,0 +1,57 @@ +from typing import ( + Any, + Callable, + Optional, + Sequence, + TypedDict, + Union, +) + +import jinja2 +from pandas import Index + +from pandas._typing import Level + +BaseFormatter = Union[str, Callable] +ExtFormatter = Union[BaseFormatter, dict[Any, Optional[BaseFormatter]]] +CSSPair = tuple[str, Union[str, float]] +CSSList = list[CSSPair] +CSSProperties = Union[str, CSSList] + +class CSSDict(TypedDict): + selector: str + props: CSSProperties + +CSSStyles = list[CSSDict] +Subset = Union[slice, Sequence, Index] + +class StylerRenderer: + loader: jinja2.loaders.BaseLoader + env: jinja2.environment.Environment + template_html: jinja2.environment.Template + template_html_table: jinja2.environment.Template + template_html_style: jinja2.environment.Template + template_latex: jinja2.environment.Template + def format( + self, + formatter: ExtFormatter | None = ..., + subset: Subset | None = ..., + na_rep: str | None = ..., + precision: int | None = ..., + decimal: str = ..., + thousands: str | None = ..., + escape: str | None = ..., + hyperlinks: str | None = ..., + ) -> StylerRenderer: ... + def format_index( + self, + formatter: ExtFormatter | None = ..., + axis: int | str = ..., + level: Level | list[Level] | None = ..., + na_rep: str | None = ..., + precision: int | None = ..., + decimal: str = ..., + thousands: str | None = ..., + escape: str | None = ..., + hyperlinks: str | None = ..., + ) -> StylerRenderer: ... From 74ac5513ff9b8b8bace34db005f0acc9027b9517 Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Thu, 8 Sep 2022 12:23:11 +0100 Subject: [PATCH 02/12] TYP: Add typing for StylerRenderer --- pandas-stubs/io/formats/style_render.pyi | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pandas-stubs/io/formats/style_render.pyi b/pandas-stubs/io/formats/style_render.pyi index 581e167f6..d4661dcd9 100644 --- a/pandas-stubs/io/formats/style_render.pyi +++ b/pandas-stubs/io/formats/style_render.pyi @@ -1,6 +1,7 @@ from typing import ( Any, Callable, + Literal, Optional, Sequence, TypedDict, @@ -10,9 +11,12 @@ from typing import ( import jinja2 from pandas import Index -from pandas._typing import Level +from pandas._typing import ( + HashableT, + Level, +) -BaseFormatter = Union[str, Callable] +BaseFormatter = Union[str, Callable[[object], str]] ExtFormatter = Union[BaseFormatter, dict[Any, Optional[BaseFormatter]]] CSSPair = tuple[str, Union[str, float]] CSSList = list[CSSPair] @@ -23,7 +27,7 @@ class CSSDict(TypedDict): props: CSSProperties CSSStyles = list[CSSDict] -Subset = Union[slice, Sequence, Index] +Subset = Union[slice, list[HashableT], Index] class StylerRenderer: loader: jinja2.loaders.BaseLoader @@ -41,17 +45,17 @@ class StylerRenderer: decimal: str = ..., thousands: str | None = ..., escape: str | None = ..., - hyperlinks: str | None = ..., + hyperlinks: Literal["html", "latex"] | None = ..., ) -> StylerRenderer: ... def format_index( self, formatter: ExtFormatter | None = ..., - axis: int | str = ..., + axis: int | Literal["index", "columns"] = ..., level: Level | list[Level] | None = ..., na_rep: str | None = ..., precision: int | None = ..., decimal: str = ..., thousands: str | None = ..., escape: str | None = ..., - hyperlinks: str | None = ..., + hyperlinks: Literal["html", "latex"] | None = ..., ) -> StylerRenderer: ... From 3085bb94da90735f9f871224a1f54fd685be53a9 Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Thu, 8 Sep 2022 12:35:42 +0100 Subject: [PATCH 03/12] ENH: Improve Styler --- pandas-stubs/io/formats/style.pyi | 68 +++++++++++++++++++++---------- pyproject.toml | 2 +- 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/pandas-stubs/io/formats/style.pyi b/pandas-stubs/io/formats/style.pyi index 92fc7553a..dd0b5ca2a 100644 --- a/pandas-stubs/io/formats/style.pyi +++ b/pandas-stubs/io/formats/style.pyi @@ -2,19 +2,25 @@ from typing import ( Any, Callable, Hashable, + Literal, Sequence, + TypeVar, + overload, ) +import numpy as np from pandas.core.frame import DataFrame from pandas.core.series import Series from pandas._typing import ( Axis, FilePath, + HashableT, IndexLabel, Level, Scalar, WriteBuffer, + npt, ) from pandas.io.formats.style_render import ( @@ -25,6 +31,8 @@ from pandas.io.formats.style_render import ( Subset, ) +_StylerT = TypeVar("_StylerT", bound=Styler) + class Styler(StylerRenderer): def __init__( self, @@ -32,7 +40,7 @@ class Styler(StylerRenderer): precision: int | None = ..., table_styles: CSSStyles | None = ..., uuid: str | None = ..., - caption: str | tuple | None = ..., + caption: str | tuple[str, str] | None = ..., table_attributes: str | None = ..., cell_ids: bool = ..., na_rep: str | None = ..., @@ -103,35 +111,44 @@ class Styler(StylerRenderer): encoding: str | None = ..., doctype_html: bool = ..., exclude_styles: bool = ..., - **kwargs, + **kwargs: Any, ): ... def set_td_classes(self, classes: DataFrame) -> Styler: ... def __copy__(self) -> Styler: ... def __deepcopy__(self, memo) -> Styler: ... def clear(self) -> None: ... + @overload def apply( self, - func: Callable, - axis: Axis | None = ..., + func: Callable[[Series], list | Series], + axis: Axis = ..., + subset: Subset | None = ..., + **kwargs: Any, + ) -> Styler: ... + @overload + def apply( + self, + func: Callable[[DataFrame], npt.NDArray | DataFrame], + axis: None, subset: Subset | None = ..., - **kwargs, + **kwargs: Any, ) -> Styler: ... def apply_index( self, - func: Callable, + func: Callable[[Series], npt.NDArray[np.str_]], axis: int | str = ..., level: Level | list[Level] | None = ..., - **kwargs, + **kwargs: Any, ) -> Styler: ... def applymap_index( self, - func: Callable, + func: Callable[[object], str], axis: int | str = ..., level: Level | list[Level] | None = ..., - **kwargs, + **kwargs: Any, ) -> Styler: ... def applymap( - self, func: Callable, subset: Subset | None = ..., **kwargs + self, func: Callable[[object], str], subset: Subset | None = ..., **kwargs: Any ) -> Styler: ... # def where(self, cond: Callable, value: str, other: str | None = ..., subset: Subset | None = ..., **kwargs) -> Styler: ... # def set_precision(self, precision: int) -> StylerRenderer: ... @@ -141,7 +158,7 @@ class Styler(StylerRenderer): uuid: Any # Incomplete def set_uuid(self, uuid: str) -> Styler: ... caption: Any # Incomplete - # def set_caption(self, caption: str | tuple) -> Styler: ... + def set_caption(self, caption: str | tuple[str, str]) -> Styler: ... def set_sticky( self, axis: Axis = ..., @@ -150,7 +167,7 @@ class Styler(StylerRenderer): ) -> Styler: ... def set_table_styles( self, - table_styles: dict[Any, CSSStyles] | CSSStyles | None = ..., + table_styles: dict[HashableT, CSSStyles] | CSSStyles | None = ..., axis: int = ..., overwrite: bool = ..., css_class_names: dict[str, str] | None = ..., @@ -193,7 +210,9 @@ class Styler(StylerRenderer): vmax: float | None = ..., gmap: Sequence | None = ..., ) -> Styler: ... - def set_properties(self, subset: Subset | None = ..., **kwargs) -> Styler: ... + def set_properties( + self, subset: Subset | None = ..., **kwargs: str | int + ) -> Styler: ... def bar( self, subset: Subset | None = ..., @@ -233,9 +252,9 @@ class Styler(StylerRenderer): subset: Subset | None = ..., color: str = ..., axis: Axis | None = ..., - left: Scalar | Sequence | None = ..., - right: Scalar | Sequence | None = ..., - inclusive: str = ..., + left: Scalar | list[Scalar] | None = ..., + right: Scalar | list[Scalar] | None = ..., + inclusive: Literal["both", "neither", "left", "right"] = ..., props: str | None = ..., ) -> Styler: ... def highlight_quantile( @@ -245,15 +264,22 @@ class Styler(StylerRenderer): axis: Axis | None = ..., q_left: float = ..., q_right: float = ..., - interpolation: str = ..., - inclusive: str = ..., + interpolation: Literal[ + "linear", "lower", "higher", "midpoint", "nearest" + ] = ..., + inclusive: Literal["both", "neither", "left", "right"] = ..., props: str | None = ..., ) -> Styler: ... @classmethod def from_custom_template( cls, - searchpath, + searchpath: str | list[str], html_table: str | None = ..., html_style: str | None = ..., - ): ... - def pipe(self, func: Callable, *args, **kwargs): ... + ) -> _StylerT: ... + def pipe( + self, + func: Callable[[Styler], Styler] | tuple[Callable[[Styler], Styler], str], + *args: Any, + **kwargs: Any, + ) -> Styler: ... diff --git a/pyproject.toml b/pyproject.toml index da4ec0ebe..7af9e2b45 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ pyxlsb = ">=1.0.9" odfpy = ">=1.4.1" xarray = ">=22.6.0" tabulate = ">=0.8.10" - +jinja2 = "^3.1" [build-system] requires = ["poetry-core>=1.0.0"] From b3dec7d26042146d5feb33fc1917101880eec5d0 Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Thu, 8 Sep 2022 18:51:19 +0100 Subject: [PATCH 04/12] ENH: Improve definition os subset --- pandas-stubs/io/formats/style.pyi | 12 ------------ pandas-stubs/io/formats/style_render.pyi | 5 +++-- tests/test_styler.py | 10 ++++++++++ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/pandas-stubs/io/formats/style.pyi b/pandas-stubs/io/formats/style.pyi index 8ccfc72fd..356e94ff9 100644 --- a/pandas-stubs/io/formats/style.pyi +++ b/pandas-stubs/io/formats/style.pyi @@ -50,7 +50,6 @@ class Styler(StylerRenderer[Styler]): escape: str | None = ..., formatter: ExtFormatter | None = ..., ) -> None: ... - # def render(self,sparse_index: bool | None = ...,sparse_columns: bool | None = ...,**kwargs,) -> str: ... def set_tooltips( self, ttips: DataFrame, @@ -195,8 +194,6 @@ class Styler(StylerRenderer[Styler]): def applymap( self, func: Callable[[object], str], subset: Subset | None = ..., **kwargs: Any ) -> Styler: ... - # def where(self, cond: Callable, value: str, other: str | None = ..., subset: Subset | None = ..., **kwargs) -> Styler: ... - # def set_precision(self, precision: int) -> StylerRenderer: ... def set_table_attributes(self, attributes: str) -> Styler: ... def export(self) -> StyleExportDict: ... def use(self, styles: StyleExportDict) -> Styler: ... @@ -217,15 +214,6 @@ class Styler(StylerRenderer[Styler]): overwrite: bool = ..., css_class_names: dict[str, str] | None = ..., ) -> Styler: ... - # def set_na_rep(self, na_rep: str) -> StylerRenderer: ... - # def hide_index(self, subset: Subset | None = ..., level: Level | list[Level] | None = ..., names: bool = ...,) -> Styler: ... - # TODO: Not in docs? Should it be? - def hide_columns( - self, - subset: Subset | None = ..., - level: Level | list[Level] | None = ..., - names: bool = ..., - ) -> Styler: ... def hide( self, subset: Subset | None = ..., diff --git a/pandas-stubs/io/formats/style_render.pyi b/pandas-stubs/io/formats/style_render.pyi index 4c904da89..fa04f29bd 100644 --- a/pandas-stubs/io/formats/style_render.pyi +++ b/pandas-stubs/io/formats/style_render.pyi @@ -13,6 +13,7 @@ import jinja2 from pandas import Index from pandas._typing import ( + Axis, HashableT, Level, ) @@ -38,7 +39,7 @@ class StyleExportDict(TypedDict, total=False): css: dict[str, str | int] CSSStyles = list[CSSDict] -Subset = Union[slice, list[HashableT], Index] +Subset = Union[slice, tuple[slice, ...], list[HashableT], Index] _StylerT = TypeVar("_StylerT", bound=StylerRenderer) @@ -63,7 +64,7 @@ class StylerRenderer(Generic[_StylerT]): def format_index( self, formatter: ExtFormatter | None = ..., - axis: int | Literal["index", "columns"] = ..., + axis: Axis = ..., level: Level | list[Level] | None = ..., na_rep: str | None = ..., precision: int | None = ..., diff --git a/tests/test_styler.py b/tests/test_styler.py index 8fe44cf5b..61dd8a2fd 100644 --- a/tests/test_styler.py +++ b/tests/test_styler.py @@ -212,3 +212,13 @@ def test_export_use() -> None: exported = DF.style.export() check(assert_type(exported, StyleExportDict), dict) check(assert_type(DF.style.use(exported), Styler), Styler) + + +def test_subset() -> None: + from pandas import IndexSlice + + check(assert_type(DF.style.highlight_min(subset=slice(1, 2)), Styler), Styler) + # TODO: IndexSlice is not a valid type for mypy, but works. Should fix IndexSlice. + check(assert_type(DF.style.highlight_min(subset=IndexSlice[1:2]), Styler), Styler) # type: ignore[arg-type] + check(assert_type(DF.style.highlight_min(subset=[1]), Styler), Styler) + check(assert_type(DF.style.highlight_min(subset=DF.columns[1:]), Styler), Styler) From 8db7d1d9db84bfdd3db42d54f11d526dab514ff1 Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Thu, 8 Sep 2022 19:00:27 +0100 Subject: [PATCH 05/12] BUG: Overcome 3.8 limit --- tests/test_styler.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_styler.py b/tests/test_styler.py index 61dd8a2fd..0148f3791 100644 --- a/tests/test_styler.py +++ b/tests/test_styler.py @@ -2,7 +2,10 @@ import os import pathlib -from typing import TYPE_CHECKING +from typing import ( + TYPE_CHECKING, + Type, +) from jinja2.environment import ( Environment, @@ -105,7 +108,7 @@ def test_from_custom_template() -> None: check( assert_type( Styler.from_custom_template(str(PWD / "data" / "myhtml_table.tpl")), - type[Styler], + Type[Styler], ), type(Styler), ) From e6a6839cc360e5a2d0a2f5e6ab1b59ab45bb5582 Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Thu, 8 Sep 2022 23:20:02 +0100 Subject: [PATCH 06/12] BUG: Fix IndexSlice type --- pandas-stubs/core/indexing.pyi | 12 ++++++++++-- pandas-stubs/io/formats/style_render.pyi | 3 ++- tests/test_styler.py | 3 +-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pandas-stubs/core/indexing.pyi b/pandas-stubs/core/indexing.pyi index bd029ee48..938696292 100644 --- a/pandas-stubs/core/indexing.pyi +++ b/pandas-stubs/core/indexing.pyi @@ -1,3 +1,9 @@ +from typing import ( + Generic, + TypeVar, + Union, +) + import numpy as np from pandas.core.indexes.api import Index @@ -7,8 +13,10 @@ from pandas._typing import ( StrLike, ) -class _IndexSlice: - def __getitem__(self, arg) -> tuple[StrLike | Scalar | slice, ...]: ... +_IndexSliceT = TypeVar("_IndexSliceT", bound=Union[StrLike, Scalar, slice]) + +class _IndexSlice(Generic[_IndexSliceT]): + def __getitem__(self, arg) -> tuple[_IndexSliceT, ...]: ... IndexSlice: _IndexSlice diff --git a/pandas-stubs/io/formats/style_render.pyi b/pandas-stubs/io/formats/style_render.pyi index fa04f29bd..102a7c516 100644 --- a/pandas-stubs/io/formats/style_render.pyi +++ b/pandas-stubs/io/formats/style_render.pyi @@ -11,6 +11,7 @@ from typing import ( import jinja2 from pandas import Index +from pandas.core.indexing import _IndexSlice from pandas._typing import ( Axis, @@ -39,7 +40,7 @@ class StyleExportDict(TypedDict, total=False): css: dict[str, str | int] CSSStyles = list[CSSDict] -Subset = Union[slice, tuple[slice, ...], list[HashableT], Index] +Subset = Union[_IndexSlice, slice, tuple[slice, ...], list[HashableT], Index] _StylerT = TypeVar("_StylerT", bound=StylerRenderer) diff --git a/tests/test_styler.py b/tests/test_styler.py index 0148f3791..8e2ba8453 100644 --- a/tests/test_styler.py +++ b/tests/test_styler.py @@ -221,7 +221,6 @@ def test_subset() -> None: from pandas import IndexSlice check(assert_type(DF.style.highlight_min(subset=slice(1, 2)), Styler), Styler) - # TODO: IndexSlice is not a valid type for mypy, but works. Should fix IndexSlice. - check(assert_type(DF.style.highlight_min(subset=IndexSlice[1:2]), Styler), Styler) # type: ignore[arg-type] + check(assert_type(DF.style.highlight_min(subset=IndexSlice[1:2]), Styler), Styler) check(assert_type(DF.style.highlight_min(subset=[1]), Styler), Styler) check(assert_type(DF.style.highlight_min(subset=DF.columns[1:]), Styler), Styler) From 84e12e3bbfef88314a804b5b477c982d87ae4e76 Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Thu, 8 Sep 2022 23:29:45 +0100 Subject: [PATCH 07/12] MAINT: Set backend for MPL --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9f4175ad2..0e0979142 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,6 +2,10 @@ name: 'Test' on: [push, pull_request, workflow_dispatch] + +env: + MPLBACKEND: 'Agg' + jobs: released: runs-on: ${{ matrix.os }} From 760b24842db3793999b240ad4477d828617d4047 Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Fri, 9 Sep 2022 16:11:29 +0100 Subject: [PATCH 08/12] TYP: Simplify types --- pandas-stubs/io/formats/style.pyi | 27 ++++++++++++------------ pandas-stubs/io/formats/style_render.pyi | 4 ++-- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/pandas-stubs/io/formats/style.pyi b/pandas-stubs/io/formats/style.pyi index 356e94ff9..0008ecf2e 100644 --- a/pandas-stubs/io/formats/style.pyi +++ b/pandas-stubs/io/formats/style.pyi @@ -12,10 +12,11 @@ from pandas.core.frame import DataFrame from pandas.core.series import Series from pandas._typing import ( - Axis, + AxisType, FilePath, HashableT, IndexLabel, + IntervalClosedType, Level, Scalar, WriteBuffer, @@ -165,7 +166,7 @@ class Styler(StylerRenderer[Styler]): def apply( self, func: Callable[[Series], list | Series], - axis: Axis = ..., + axis: AxisType = ..., subset: Subset | None = ..., **kwargs: Any, ) -> Styler: ... @@ -203,7 +204,7 @@ class Styler(StylerRenderer[Styler]): def set_caption(self, caption: str | tuple[str, str]) -> Styler: ... def set_sticky( self, - axis: Axis = ..., + axis: AxisType = ..., pixel_size: int | None = ..., levels: Level | list[Level] | None = ..., ) -> Styler: ... @@ -217,7 +218,7 @@ class Styler(StylerRenderer[Styler]): def hide( self, subset: Subset | None = ..., - axis: Axis = ..., + axis: AxisType = ..., level: Level | list[Level] | None = ..., names: bool = ..., ) -> Styler: ... @@ -226,7 +227,7 @@ class Styler(StylerRenderer[Styler]): cmap: str | Colormap = ..., low: float = ..., high: float = ..., - axis: Axis | None = ..., + axis: AxisType | None = ..., subset: Subset | None = ..., text_color_threshold: float = ..., vmin: float | None = ..., @@ -243,7 +244,7 @@ class Styler(StylerRenderer[Styler]): cmap: str | Colormap = ..., low: float = ..., high: float = ..., - axis: Axis | None = ..., + axis: AxisType | None = ..., subset: Subset | None = ..., # In docs but not in function declaration # text_color_threshold: float @@ -262,7 +263,7 @@ class Styler(StylerRenderer[Styler]): def bar( self, subset: Subset | None = ..., - axis: Axis | None = ..., + axis: AxisType | None = ..., *, color: str | list[str] | tuple[str, str] | None = ..., cmap: str | Colormap = ..., @@ -285,37 +286,37 @@ class Styler(StylerRenderer[Styler]): self, subset: Subset | None = ..., color: str = ..., - axis: Axis | None = ..., + axis: AxisType | None = ..., props: str | None = ..., ) -> Styler: ... def highlight_min( self, subset: Subset | None = ..., color: str = ..., - axis: Axis | None = ..., + axis: AxisType | None = ..., props: str | None = ..., ) -> Styler: ... def highlight_between( self, subset: Subset | None = ..., color: str = ..., - axis: Axis | None = ..., + axis: AxisType | None = ..., left: Scalar | list[Scalar] | None = ..., right: Scalar | list[Scalar] | None = ..., - inclusive: Literal["both", "neither", "left", "right"] = ..., + inclusive: IntervalClosedType = ..., props: str | None = ..., ) -> Styler: ... def highlight_quantile( self, subset: Subset | None = ..., color: str = ..., - axis: Axis | None = ..., + axis: AxisType | None = ..., q_left: float = ..., q_right: float = ..., interpolation: Literal[ "linear", "lower", "higher", "midpoint", "nearest" ] = ..., - inclusive: Literal["both", "neither", "left", "right"] = ..., + inclusive: IntervalClosedType = ..., props: str | None = ..., ) -> Styler: ... @classmethod diff --git a/pandas-stubs/io/formats/style_render.pyi b/pandas-stubs/io/formats/style_render.pyi index 102a7c516..db19a4e05 100644 --- a/pandas-stubs/io/formats/style_render.pyi +++ b/pandas-stubs/io/formats/style_render.pyi @@ -14,7 +14,7 @@ from pandas import Index from pandas.core.indexing import _IndexSlice from pandas._typing import ( - Axis, + AxisType, HashableT, Level, ) @@ -65,7 +65,7 @@ class StylerRenderer(Generic[_StylerT]): def format_index( self, formatter: ExtFormatter | None = ..., - axis: Axis = ..., + axis: AxisType = ..., level: Level | list[Level] | None = ..., na_rep: str | None = ..., precision: int | None = ..., From e3b5f8132859006841cb245d72825ce180627a92 Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Fri, 9 Sep 2022 17:38:38 +0100 Subject: [PATCH 09/12] TYP: Improve axis --- pandas-stubs/io/formats/style.pyi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas-stubs/io/formats/style.pyi b/pandas-stubs/io/formats/style.pyi index 0008ecf2e..b7899b087 100644 --- a/pandas-stubs/io/formats/style.pyi +++ b/pandas-stubs/io/formats/style.pyi @@ -181,14 +181,14 @@ class Styler(StylerRenderer[Styler]): def apply_index( self, func: Callable[[Series], npt.NDArray[np.str_] | list[str] | Series[str]], - axis: int | str = ..., + axis: AxisType = ..., level: Level | list[Level] | None = ..., **kwargs: Any, ) -> Styler: ... def applymap_index( self, func: Callable[[object], str], - axis: int | str = ..., + axis: AxisType = ..., level: Level | list[Level] | None = ..., **kwargs: Any, ) -> Styler: ... @@ -211,7 +211,7 @@ class Styler(StylerRenderer[Styler]): def set_table_styles( self, table_styles: dict[HashableT, CSSStyles] | CSSStyles | None = ..., - axis: int = ..., + axis: AxisType = ..., overwrite: bool = ..., css_class_names: dict[str, str] | None = ..., ) -> Styler: ... From 00108c325263d2b871b3e47fcb59be617ae8eb3a Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Fri, 9 Sep 2022 17:39:50 +0100 Subject: [PATCH 10/12] TYP: Remove attributes --- pandas-stubs/io/formats/style.pyi | 2 -- 1 file changed, 2 deletions(-) diff --git a/pandas-stubs/io/formats/style.pyi b/pandas-stubs/io/formats/style.pyi index b7899b087..d551ff072 100644 --- a/pandas-stubs/io/formats/style.pyi +++ b/pandas-stubs/io/formats/style.pyi @@ -198,9 +198,7 @@ class Styler(StylerRenderer[Styler]): def set_table_attributes(self, attributes: str) -> Styler: ... def export(self) -> StyleExportDict: ... def use(self, styles: StyleExportDict) -> Styler: ... - uuid: Any # Incomplete def set_uuid(self, uuid: str) -> Styler: ... - caption: Any # Incomplete def set_caption(self, caption: str | tuple[str, str]) -> Styler: ... def set_sticky( self, From 880e1c791011d36e5a390775ac756d621bb784be Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Fri, 9 Sep 2022 17:43:55 +0100 Subject: [PATCH 11/12] TYP: Final small fixes --- pandas-stubs/io/formats/style.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas-stubs/io/formats/style.pyi b/pandas-stubs/io/formats/style.pyi index d551ff072..3be9bc171 100644 --- a/pandas-stubs/io/formats/style.pyi +++ b/pandas-stubs/io/formats/style.pyi @@ -187,13 +187,13 @@ class Styler(StylerRenderer[Styler]): ) -> Styler: ... def applymap_index( self, - func: Callable[[object], str], + func: Callable[[Scalar], str], axis: AxisType = ..., level: Level | list[Level] | None = ..., **kwargs: Any, ) -> Styler: ... def applymap( - self, func: Callable[[object], str], subset: Subset | None = ..., **kwargs: Any + self, func: Callable[[Scalar], str], subset: Subset | None = ..., **kwargs: Any ) -> Styler: ... def set_table_attributes(self, attributes: str) -> Styler: ... def export(self) -> StyleExportDict: ... From 71083dffa362fd97b17c8b345a272c2f0d9439c5 Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Mon, 12 Sep 2022 10:56:43 +0100 Subject: [PATCH 12/12] TYP: Small fixes for Styler pipe --- pandas-stubs/io/formats/style.pyi | 5 +++-- tests/test_frame.py | 25 +++++++++---------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/pandas-stubs/io/formats/style.pyi b/pandas-stubs/io/formats/style.pyi index 3be9bc171..df8f9d4f6 100644 --- a/pandas-stubs/io/formats/style.pyi +++ b/pandas-stubs/io/formats/style.pyi @@ -19,6 +19,7 @@ from pandas._typing import ( IntervalClosedType, Level, Scalar, + T, WriteBuffer, WriteExcelBuffer, npt, @@ -326,7 +327,7 @@ class Styler(StylerRenderer[Styler]): ) -> type[Styler]: ... def pipe( self, - func: Callable | tuple[Callable, str], + func: Callable[..., T] | tuple[Callable[..., T], str], *args: Any, **kwargs: Any, - ) -> Styler: ... + ) -> T: ... diff --git a/tests/test_frame.py b/tests/test_frame.py index 18f85cc1e..7fcd016ec 100644 --- a/tests/test_frame.py +++ b/tests/test_frame.py @@ -31,10 +31,7 @@ from typing_extensions import assert_type import xarray as xr -from pandas._typing import ( - Scalar, - T, -) +from pandas._typing import Scalar from tests import ( TYPE_CHECKING_INVALID_USAGE, @@ -1019,23 +1016,19 @@ def foo(df: pd.DataFrame) -> pd.DataFrame: check(assert_type(val, pd.DataFrame), pd.DataFrame) - check( - assert_type(pd.DataFrame({"a": [1], "b": [1]}).style.pipe(foo), Styler), - Styler, - ) - check(assert_type(pd.DataFrame({"a": [1]}).pipe(foo), pd.DataFrame), pd.DataFrame) - # TODO: Needs work to get type for DataFrameGroupBy.pipe + + def bar(val: Styler) -> Styler: + return val + check( - assert_type(pd.DataFrame({"a": [1], "b": [1]}).groupby("a").pipe(foo), Any), - pd.DataFrame, + assert_type(pd.DataFrame({"a": [1], "b": [1]}).style.pipe(bar), Styler), Styler ) - def bar(val: T) -> T: - return val + def baz(val: Styler) -> str: + return val.to_latex() - # TODO: Needs work to get type for Styler.pipe - check(assert_type(pd.DataFrame({"a": [1], "b": [1]}).style.pipe(bar), Any), Styler) + check(assert_type(pd.DataFrame({"a": [1], "b": [1]}).style.pipe(baz), str), str) # set_flags() method added in 1.2.0 https://pandas.pydata.org/docs/whatsnew/v1.2.0.html