From bdf75023dfe3930deac1c6b4e269770427b106c4 Mon Sep 17 00:00:00 2001 From: Avasam Date: Sat, 27 Apr 2024 09:16:22 -0400 Subject: [PATCH] `openpyxl`: Use duck typing for workbook sheets (#11718) --- stubs/openpyxl/openpyxl/workbook/workbook.pyi | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/stubs/openpyxl/openpyxl/workbook/workbook.pyi b/stubs/openpyxl/openpyxl/workbook/workbook.pyi index 5687dbc7a..9756e5f7a 100644 --- a/stubs/openpyxl/openpyxl/workbook/workbook.pyi +++ b/stubs/openpyxl/openpyxl/workbook/workbook.pyi @@ -1,7 +1,7 @@ from _typeshed import Incomplete, Unused from collections.abc import Iterator from datetime import datetime -from typing import Any, Final +from typing import Any, Final, type_check_only from typing_extensions import TypeAlias, deprecated from zipfile import ZipFile @@ -17,6 +17,23 @@ from openpyxl.worksheet.worksheet import Worksheet _WorkbookWorksheet: TypeAlias = Worksheet | WriteOnlyWorksheet | ReadOnlyWorksheet _WorkbookSheet: TypeAlias = _WorkbookWorksheet | Chartsheet +# The type of worksheets in a workbook are the same as the aliases above. +# However, because Worksheet adds a lots of attributes that other _WorkbookChild subclasses +# don't have (ReadOnlyWorksheet doesn't even inherit from it), this ends up being too +# disruptive to the typical usage of openpyxl where sheets are just Worksheets. +# Using Any may just lose too much type information and duck-typing +# from Worksheet works great here. Allowing instance type check, even if direct +# type comparison might be wrong. +@type_check_only +class _WorksheetLike( # type: ignore[misc] # Incompatible definitions, favor Worksheet + Worksheet, WriteOnlyWorksheet, ReadOnlyWorksheet +): ... + +@type_check_only +class _WorksheetOrChartsheetLike( # type: ignore[misc] # Incompatible definitions, favor Worksheet + Chartsheet, _WorksheetLike +): ... + INTEGER_TYPES: Final[tuple[type[int]]] class Workbook: @@ -37,7 +54,7 @@ class Workbook: views: Incomplete # Useful as a reference of what "sheets" can be for other types # ExcelReader can add ReadOnlyWorksheet in read_only mode. - # _sheets: list[_WorkbookSheet] + # _sheets: list[_WorksheetOrChartsheetLike] def __init__(self, write_only: bool = False, iso_dates: bool = False) -> None: ... @property def epoch(self) -> datetime: ... @@ -52,7 +69,7 @@ class Workbook: @property def excel_base_date(self) -> datetime: ... @property - def active(self) -> _WorkbookSheet | None: ... + def active(self) -> _WorksheetOrChartsheetLike | None: ... @active.setter def active(self, value: Worksheet | Chartsheet | int) -> None: ... # read_only workbook cannot call this method @@ -66,18 +83,18 @@ class Workbook: def remove_sheet(self, worksheet: _WorkbookSheet) -> None: ... def create_chartsheet(self, title: str | _Decodable | None = None, index: int | None = None) -> Chartsheet: ... @deprecated("Use wb[sheetname]") - def get_sheet_by_name(self, name: str) -> _WorkbookSheet: ... + def get_sheet_by_name(self, name: str) -> _WorksheetOrChartsheetLike: ... def __contains__(self, key: str) -> bool: ... def index(self, worksheet: _WorkbookWorksheet) -> int: ... @deprecated("Use wb.index(worksheet)") def get_index(self, worksheet: _WorkbookWorksheet) -> int: ... - def __getitem__(self, key: str) -> _WorkbookSheet: ... + def __getitem__(self, key: str) -> _WorksheetOrChartsheetLike: ... def __delitem__(self, key: str) -> None: ... - def __iter__(self) -> Iterator[_WorkbookWorksheet]: ... + def __iter__(self) -> Iterator[_WorksheetLike]: ... @deprecated("Use wb.sheetnames") def get_sheet_names(self) -> list[str]: ... @property - def worksheets(self) -> list[_WorkbookWorksheet]: ... + def worksheets(self) -> list[_WorksheetLike]: ... @property def chartsheets(self) -> list[Chartsheet]: ... @property