Skip to content

TYP: type excel util module #45014

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Dec 28, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pandas/io/excel/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -943,7 +943,7 @@ def supported_extensions(self):

@property
@abc.abstractmethod
def engine(self):
def engine(self) -> str:
"""Name of engine."""
pass

Expand Down
74 changes: 65 additions & 9 deletions pandas/io/excel/_util.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
from __future__ import annotations

from typing import (
TYPE_CHECKING,
Any,
Callable,
Hashable,
Iterable,
Literal,
MutableMapping,
TypeVar,
overload,
)

from pandas.compat._optional import import_optional_dependency
Expand All @@ -12,10 +19,16 @@
is_list_like,
)

_writers: MutableMapping[str, str] = {}
if TYPE_CHECKING:
from pandas.io.excel._base import ExcelWriter

ExcelWriter_t = type[ExcelWriter]
usecols_func = TypeVar("usecols_func", bound=Callable[[Hashable], object])

def register_writer(klass):
_writers: MutableMapping[str, ExcelWriter_t] = {}


def register_writer(klass: ExcelWriter_t) -> None:
"""
Add engine to the excel writer registry.io.excel.

Expand All @@ -28,10 +41,12 @@ def register_writer(klass):
if not callable(klass):
raise ValueError("Can only register callables as engines")
engine_name = klass.engine
# for mypy
assert isinstance(engine_name, str)
_writers[engine_name] = klass


def get_default_engine(ext, mode="reader"):
def get_default_engine(ext: str, mode: Literal["reader", "writer"] = "reader") -> str:
"""
Return the default reader/writer for the given extension.

Expand Down Expand Up @@ -73,7 +88,7 @@ def get_default_engine(ext, mode="reader"):
return _default_readers[ext]


def get_writer(engine_name):
def get_writer(engine_name: str) -> ExcelWriter_t:
try:
return _writers[engine_name]
except KeyError as err:
Expand Down Expand Up @@ -145,7 +160,29 @@ def _range2cols(areas: str) -> list[int]:
return cols


def maybe_convert_usecols(usecols):
@overload
def maybe_convert_usecols(usecols: str | list[int]) -> list[int]:
...


@overload
def maybe_convert_usecols(usecols: list[str]) -> list[str]:
...


@overload
def maybe_convert_usecols(usecols: usecols_func) -> usecols_func:
...


@overload
def maybe_convert_usecols(usecols: None) -> None:
...


def maybe_convert_usecols(
usecols: str | list[int] | list[str] | usecols_func | None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prob can share this with the parsers (followon, maybe put in _typing.py)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The configs there are a bit different unfortunately. Don't think we can share efficiently

) -> None | list[int] | list[str] | usecols_func:
"""
Convert `usecols` into a compatible format for parsing in `parsers.py`.

Expand Down Expand Up @@ -174,7 +211,17 @@ def maybe_convert_usecols(usecols):
return usecols


def validate_freeze_panes(freeze_panes):
@overload
def validate_freeze_panes(freeze_panes: tuple[int, int]) -> Literal[True]:
...


@overload
def validate_freeze_panes(freeze_panes: None) -> Literal[False]:
...


def validate_freeze_panes(freeze_panes: tuple[int, int] | None) -> bool:
if freeze_panes is not None:
if len(freeze_panes) == 2 and all(
isinstance(item, int) for item in freeze_panes
Expand All @@ -191,7 +238,9 @@ def validate_freeze_panes(freeze_panes):
return False


def fill_mi_header(row, control_row):
def fill_mi_header(
row: list[Hashable], control_row: list[bool]
) -> tuple[list[Hashable], list[bool]]:
"""
Forward fill blank entries in row but only inside the same parent index.

Expand Down Expand Up @@ -224,7 +273,9 @@ def fill_mi_header(row, control_row):
return row, control_row


def pop_header_name(row, index_col):
def pop_header_name(
row: list[Hashable], index_col: int | list[int]
) -> tuple[Hashable | None, list[Hashable]]:
"""
Pop the header name for MultiIndex parsing.

Expand All @@ -243,7 +294,12 @@ def pop_header_name(row, index_col):
The original data row with the header name removed.
"""
# Pop out header name and fill w/blank.
i = index_col if not is_list_like(index_col) else max(index_col)
if is_list_like(index_col):
assert isinstance(index_col, Iterable)
i = max(index_col)
else:
assert not isinstance(index_col, Iterable)
i = index_col

header_name = row[i]
header_name = None if header_name == "" else header_name
Expand Down