Openpyxl: Various improvements (#11092)

This commit is contained in:
Avasam
2023-12-04 15:54:45 -05:00
committed by GitHub
parent 0f7241844e
commit 5521da8e92
20 changed files with 162 additions and 104 deletions

View File

@@ -44,6 +44,8 @@ openpyxl\.descriptors\.(base\.)?Typed\.allow_none
# Inconsistent methods because
# - using the default value results in an error because of the runtime type-guards
# - or, keyword arguments are explicitly specified
openpyxl.cell.Cell.__init__
openpyxl.cell.cell.Cell.__init__
openpyxl.cell.text.PhoneticProperties.__init__
openpyxl.cell.text.PhoneticText.__init__
openpyxl.chart.axis._BaseAxis.__init__

View File

@@ -39,11 +39,12 @@ class Cell(StyleableObject):
row: int
column: int
data_type: str
# row and column are never meant to be None and would lead to errors
def __init__(
self,
worksheet: Worksheet,
row: int | None = None,
column: int | None = None,
row: int,
column: int,
value: str | float | datetime | None = None,
style_array: StyleArray | None = None,
) -> None: ...

View File

@@ -34,7 +34,7 @@ class Chartsheet(_WorkbookChild, Serialisable):
extLst: Typed[ExtensionList, Literal[True]]
sheet_state: Set[_VisibilityType]
headerFooter: Typed[_HeaderFooter, Literal[False]]
HeaderFooter: Alias
HeaderFooter: Alias # type: ignore[assignment] # Different from parent class
__elements__: ClassVar[tuple[str, ...]]
__attrs__: ClassVar[tuple[str, ...]]
def __init__(

View File

@@ -2,22 +2,24 @@ from _typeshed import Incomplete
from collections.abc import Generator
from zipfile import ZipFile
from openpyxl.packaging.relationship import RelationshipList
from openpyxl.packaging.relationship import Relationship, RelationshipList
from openpyxl.packaging.workbook import PivotCache
from openpyxl.pivot.cache import CacheDefinition
from openpyxl.workbook import Workbook
class WorkbookParser:
archive: ZipFile
workbook_part_name: Incomplete
workbook_part_name: str
wb: Workbook
keep_links: Incomplete
sheets: Incomplete
def __init__(self, archive: ZipFile, workbook_part_name, keep_links: bool = True) -> None: ...
keep_links: bool
sheets: list[Incomplete]
def __init__(self, archive: ZipFile, workbook_part_name: str, keep_links: bool = True) -> None: ...
@property
def rels(self) -> RelationshipList: ...
caches: Incomplete
# Errors if "parse" is never called.
caches: list[PivotCache]
def parse(self) -> None: ...
def find_sheets(self) -> Generator[Incomplete, None, None]: ...
def find_sheets(self) -> Generator[tuple[Incomplete, Relationship], None, None]: ...
def assign_names(self) -> None: ...
@property
def pivot_caches(self) -> dict[int, CacheDefinition]: ...

View File

@@ -1,6 +1,6 @@
from _typeshed import Incomplete
from _typeshed import Incomplete, Unused
from typing import ClassVar
from typing_extensions import Literal
from typing_extensions import Literal, SupportsIndex
from openpyxl.descriptors.base import Alias, Typed
from openpyxl.descriptors.excel import ExtensionList
@@ -34,10 +34,10 @@ class DifferentialStyleList(Serialisable):
dxf: Incomplete
styles: Alias
__attrs__: ClassVar[tuple[str, ...]]
def __init__(self, dxf=(), count: Incomplete | None = None) -> None: ...
def append(self, dxf) -> None: ...
def add(self, dxf): ...
def __init__(self, dxf=(), count: Unused = None) -> None: ...
def append(self, dxf: DifferentialStyle) -> None: ...
def add(self, dxf: DifferentialStyle) -> int: ...
def __bool__(self) -> bool: ...
def __getitem__(self, idx): ...
def __getitem__(self, idx: SupportsIndex) -> DifferentialStyle: ...
@property
def count(self) -> int: ...

View File

@@ -113,4 +113,6 @@ class GradientFill(Fill):
stop=(),
) -> None: ...
def __iter__(self) -> Iterator[tuple[str, str]]: ...
def to_tree(self, tagname: Unused = None, namespace: Unused = None, idx: Unused = None): ... # type: ignore[override]
def to_tree( # type: ignore[override]
self, tagname: Unused = None, namespace: Unused = None, idx: Unused = None
) -> Element: ...

View File

@@ -8,9 +8,11 @@ from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.styles.alignment import Alignment
from openpyxl.styles.borders import Border
from openpyxl.styles.cell_style import CellStyle, StyleArray
from openpyxl.styles.fills import Fill
from openpyxl.styles.fonts import Font
from openpyxl.styles.protection import Protection
from openpyxl.workbook.workbook import Workbook
class NamedStyle(Serialisable):
font: Typed[Font, Literal[False]]
@@ -41,16 +43,16 @@ class NamedStyle(Serialisable):
def __iter__(self) -> Iterator[tuple[str, str]]: ...
@property
def xfId(self) -> int | None: ...
def bind(self, wb) -> None: ...
def as_tuple(self): ...
def as_xf(self): ...
def as_name(self): ...
def bind(self, wb: Workbook) -> None: ...
def as_tuple(self) -> StyleArray: ...
def as_xf(self) -> CellStyle: ...
def as_name(self) -> _NamedCellStyle: ...
class NamedStyleList(list[Incomplete]):
class NamedStyleList(list[NamedStyle]):
@property
def names(self) -> list[str]: ...
def __getitem__(self, key): ...
def append(self, style) -> None: ...
def __getitem__(self, key: int | str) -> NamedStyle: ... # type: ignore[override]
def append(self, style: NamedStyle) -> None: ...
class _NamedCellStyle(Serialisable):
tagname: ClassVar[str]

View File

@@ -1,7 +1,7 @@
from _typeshed import ConvertibleToInt, Incomplete, Unused
from re import Pattern
from typing import ClassVar, overload
from typing_extensions import Final, Literal, TypeGuard
from typing_extensions import Final, Literal, SupportsIndex, TypeGuard
from openpyxl.descriptors import Strict, String
from openpyxl.descriptors.base import Integer
@@ -58,7 +58,7 @@ def is_datetime(fmt: None) -> None: ...
@overload
def is_datetime(fmt: str) -> Literal["datetime", "date", "time"] | None: ...
def is_builtin(fmt: str | None) -> TypeGuard[str]: ...
def builtin_format_code(index): ...
def builtin_format_code(index: int) -> str | None: ...
@overload
def builtin_format_id(fmt: None) -> None: ...
@overload
@@ -81,4 +81,4 @@ class NumberFormatList(Serialisable):
def __init__(self, count: Unused = None, numFmt=()) -> None: ...
@property
def count(self) -> int: ...
def __getitem__(self, idx): ...
def __getitem__(self, idx: SupportsIndex) -> NumberFormat: ...

View File

@@ -1,11 +1,13 @@
from _typeshed import Incomplete
from collections.abc import Generator
from re import Pattern
from typing_extensions import Final
from typing_extensions import Final, TypeAlias
# "1:1" | "A1:A1" | "A:A"
_RangeBoundariesTuple: TypeAlias = tuple[None, int, None, int] | tuple[int, int, int, int] | tuple[int, None, int, None]
COORD_RE: Final[Pattern[str]]
COL_RANGE: Final = """[A-Z]{1,3}:[A-Z]{1,3}:"""
ROW_RANGE: Final = r"""\d+:\d+:"""
COL_RANGE: Final = "[A-Z]{1,3}:[A-Z]{1,3}:"
ROW_RANGE: Final = r"\d+:\d+:"
RANGE_EXPR: Final[str]
ABSOLUTE_RE: Final[Pattern[str]]
SHEET_TITLE: Final[str]
@@ -16,9 +18,9 @@ def coordinate_from_string(coord_string: str) -> tuple[str, int]: ...
def absolute_coordinate(coord_string: str) -> str: ...
def get_column_letter(idx: int) -> str: ...
def column_index_from_string(str_col: str) -> int: ...
def range_boundaries(range_string: str) -> tuple[int, int, int, int]: ...
def rows_from_range(range_string) -> Generator[Incomplete, None, None]: ...
def cols_from_range(range_string) -> Generator[Incomplete, None, None]: ...
def range_boundaries(range_string: str) -> _RangeBoundariesTuple: ...
def rows_from_range(range_string: str) -> Generator[tuple[str, ...], None, None]: ...
def cols_from_range(range_string: str) -> Generator[tuple[str, ...], None, None]: ...
def coordinate_to_tuple(coordinate: str) -> tuple[int, int]: ...
def range_to_tuple(range_string: str) -> tuple[str, tuple[int, int, int, int]]: ...
def range_to_tuple(range_string: str) -> tuple[str, _RangeBoundariesTuple]: ...
def quote_sheetname(sheetname: str) -> str: ...

View File

@@ -1,2 +1,2 @@
def escape(value): ...
def unescape(value): ...
def escape(value: str) -> str: ...
def unescape(value: str) -> str: ...

View File

@@ -1,17 +1,17 @@
from _typeshed import Incomplete
from collections.abc import Iterable
from re import Pattern
from typing_extensions import Final
from openpyxl import _Decodable
from openpyxl.workbook.workbook import Workbook
from openpyxl.worksheet.header_footer import HeaderFooterItem
from openpyxl.worksheet.header_footer import HeaderFooter, HeaderFooterItem
INVALID_TITLE_REGEX: Final[Pattern[str]]
def avoid_duplicate_name(names, value): ...
def avoid_duplicate_name(names: Iterable[str], value: str) -> str: ...
class _WorkbookChild:
HeaderFooter: Incomplete
HeaderFooter: HeaderFooter
def __init__(self, parent: Workbook | None = None, title: str | _Decodable | None = None) -> None: ...
@property
def parent(self) -> Workbook | None: ...

View File

@@ -1,6 +1,7 @@
from _typeshed import Incomplete
from _typeshed import Incomplete, Unused
from collections.abc import Iterator
from datetime import datetime
from typing import Any
from typing_extensions import Final
from zipfile import ZipFile
@@ -46,7 +47,10 @@ class Workbook:
def active(self) -> _WorkbookChild | None: ...
@active.setter
def active(self, value: _WorkbookChild | int) -> None: ...
def create_sheet(self, title: str | _Decodable | None = None, index: int | None = None): ...
# Could be generic based on write_only
def create_sheet(
self, title: str | _Decodable | None = None, index: int | None = None
) -> Any: ... # AnyOf[WriteOnlyWorksheet, Worksheet]
def move_sheet(self, sheet: Worksheet | str, offset: int = 0) -> None: ...
def remove(self, worksheet: Worksheet) -> None: ...
def remove_sheet(self, worksheet: Worksheet) -> None: ...
@@ -66,11 +70,7 @@ class Workbook:
@property
def sheetnames(self) -> list[str]: ...
def create_named_range(
self,
name: str,
worksheet: Worksheet | None = None,
value: str | Incomplete | None = None,
scope: Incomplete | None = None,
self, name: str, worksheet: Worksheet | None = None, value: str | Incomplete | None = None, scope: Unused = None
) -> None: ...
def add_named_style(self, style: NamedStyle) -> None: ...
@property

View File

@@ -4,9 +4,10 @@ from collections.abc import Generator
from openpyxl import _VisibilityType
from openpyxl.cell import _CellValue
from openpyxl.cell.cell import Cell
from openpyxl.utils.cell import _RangeBoundariesTuple
from openpyxl.worksheet.worksheet import Worksheet
def read_dimension(source): ...
def read_dimension(source) -> _RangeBoundariesTuple | None: ...
class ReadOnlyWorksheet:
cell = Worksheet.cell

View File

@@ -1,11 +1,14 @@
from _typeshed import Incomplete
from _typeshed import Incomplete, SupportsGetItem, Unused
from collections.abc import Container, Generator
from datetime import datetime
from typing_extensions import Final
from xml.etree.ElementTree import _FileRead
from openpyxl.cell.rich_text import CellRichText
from openpyxl.descriptors.serialisable import _ChildSerialisableTreeElement
from openpyxl.descriptors.serialisable import _ChildSerialisableTreeElement, _SerialisableTreeElement
from openpyxl.utils.cell import _RangeBoundariesTuple
from ..xml._functions_overloads import _HasAttrib, _SupportsIterAndAttrib
from .hyperlink import HyperlinkList
from .pagebreak import ColBreak, RowBreak
from .protection import SheetProtection
@@ -46,15 +49,15 @@ class WorkSheetParser:
min_row: Incomplete | None
min_col: Incomplete | None
epoch: datetime
source: Incomplete
shared_strings: Incomplete
source: _FileRead
shared_strings: SupportsGetItem[int, Incomplete]
data_only: bool
shared_formulae: dict[Incomplete, Incomplete]
row_counter: int
col_counter: int
tables: TablePartList
date_formats: Container[Incomplete]
timedelta_formats: Container[Incomplete]
date_formats: Container[int]
timedelta_formats: Container[int]
row_dimensions: dict[Incomplete, Incomplete]
column_dimensions: dict[Incomplete, Incomplete]
number_formats: list[Incomplete]
@@ -70,39 +73,41 @@ class WorkSheetParser:
def __init__(
self,
src,
shared_strings,
src: _FileRead,
shared_strings: SupportsGetItem[int, Incomplete],
data_only: bool = False,
epoch: datetime = ...,
date_formats: Container[Incomplete] = ...,
timedelta_formats: Container[Incomplete] = ...,
date_formats: Container[int] = ...,
timedelta_formats: Container[int] = ...,
rich_text: bool = False,
) -> None: ...
def parse(self) -> Generator[Incomplete, None, None]: ...
def parse_dimensions(self): ...
def parse_cell(self, element): ...
def parse_dimensions(self) -> _RangeBoundariesTuple | None: ...
def parse_cell(self, element) -> dict[str, Incomplete]: ...
def parse_formula(self, element): ...
def parse_column_dimensions(self, col) -> None: ...
def parse_row(self, row): ...
def parse_formatting(self, element) -> None: ...
def parse_sheet_protection(self, element) -> None: ...
def parse_extensions(self, element) -> None: ...
def parse_legacy(self, element) -> None: ...
def parse_row_breaks(self, element) -> None: ...
def parse_col_breaks(self, element) -> None: ...
def parse_custom_views(self, element) -> None: ...
def parse_column_dimensions(self, col: _HasAttrib) -> None: ...
def parse_row(self, row: _SupportsIterAndAttrib) -> tuple[int, list[dict[str, Incomplete]]]: ...
def parse_formatting(self, element: _ChildSerialisableTreeElement) -> None: ...
def parse_sheet_protection(self, element: _SerialisableTreeElement) -> None: ...
def parse_extensions(self, element: _ChildSerialisableTreeElement) -> None: ...
def parse_legacy(self, element: _ChildSerialisableTreeElement) -> None: ...
def parse_row_breaks(self, element: _ChildSerialisableTreeElement) -> None: ...
def parse_col_breaks(self, element: _ChildSerialisableTreeElement) -> None: ...
def parse_custom_views(self, element: Unused) -> None: ...
class WorksheetReader:
ws: Incomplete
parser: Incomplete
tables: Incomplete
def __init__(self, ws, xml_source, shared_strings, data_only, rich_text: bool) -> None: ...
parser: WorkSheetParser
tables: list[Incomplete]
def __init__(
self, ws, xml_source: _FileRead, shared_strings: SupportsGetItem[int, Incomplete], data_only: bool, rich_text: bool
) -> None: ...
def bind_cells(self) -> None: ...
def bind_formatting(self) -> None: ...
def bind_tables(self) -> None: ...
def bind_merged_cells(self) -> None: ...
def bind_hyperlinks(self) -> None: ...
def normalize_merged_cell_link(self, coord): ...
def normalize_merged_cell_link(self, coord: str) -> Incomplete | None: ...
def bind_col_dimensions(self) -> None: ...
def bind_row_dimensions(self) -> None: ...
def bind_properties(self) -> None: ...

View File

@@ -14,12 +14,12 @@ _OutType: TypeAlias = _SupportsCloseAndWrite | StrPath
ALL_TEMP_FILES: list[str]
def create_temporary_file(suffix: str = ""): ...
def create_temporary_file(suffix: str = "") -> str: ...
class WorksheetWriter:
ws: Incomplete
out: _OutType
xf: Incomplete
xf: Generator[Incomplete | None, Incomplete, None]
def __init__(self, ws, out: _OutType | None = None) -> None: ...
def write_properties(self) -> None: ...
def write_dimensions(self) -> None: ...
@@ -27,7 +27,7 @@ class WorksheetWriter:
def write_views(self) -> None: ...
def write_cols(self) -> None: ...
def write_top(self) -> None: ...
def rows(self): ...
def rows(self) -> list[tuple[int, list[Incomplete]]]: ...
def write_rows(self) -> None: ...
def write_row(self, xf, row, row_idx) -> None: ...
def write_protection(self) -> None: ...
@@ -50,5 +50,5 @@ class WorksheetWriter:
def write_tail(self) -> None: ...
def write(self) -> None: ...
def close(self) -> None: ...
def read(self): ...
def read(self) -> bytes: ...
def cleanup(self) -> None: ...

View File

@@ -1,4 +1,4 @@
from _typeshed import ConvertibleToInt, Incomplete, Unused
from _typeshed import ConvertibleToInt, Incomplete
from collections.abc import Generator, Iterator
from itertools import product
from typing import Any, overload
@@ -15,14 +15,17 @@ class CellRange(Serialisable):
max_row: MinMax[int, Literal[False]]
title: str | None
# With `range_string`, min/max parameters go unused.
# Enforcing `None` to avoid confusion upon which params get used
# if the user still tries to pass a `ConvertibleToInt`.
@overload
def __init__(
self,
range_string: str,
min_col: Unused = None,
min_row: Unused = None,
max_col: Unused = None,
max_row: Unused = None,
min_col: None = None,
min_row: None = None,
max_col: None = None,
max_row: None = None,
title: str | None = None,
) -> None: ...
@overload
@@ -36,6 +39,16 @@ class CellRange(Serialisable):
max_row: ConvertibleToInt,
title: str | None = None,
) -> None: ...
@overload
def __init__(
self,
range_string: None,
min_col: ConvertibleToInt,
min_row: ConvertibleToInt,
max_col: ConvertibleToInt,
max_row: ConvertibleToInt,
title: str | None = None,
) -> None: ...
@property
def bounds(self) -> tuple[int, int, int, int]: ...
@property

View File

@@ -1,7 +1,7 @@
from _typeshed import Incomplete
from openpyxl.worksheet.worksheet import Worksheet
class WorksheetCopy:
source: Incomplete
target: Incomplete
def __init__(self, source_worksheet, target_worksheet) -> None: ...
source: Worksheet
target: Worksheet
def __init__(self, source_worksheet: Worksheet, target_worksheet: Worksheet) -> None: ...
def copy_worksheet(self) -> None: ...

View File

@@ -8,6 +8,7 @@ from openpyxl.descriptors.base import Alias, Bool, Float, Integer, String, _Conv
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.styles.styleable import StyleableObject
from openpyxl.utils.bound_dictionary import BoundDictionary
from openpyxl.utils.cell import _RangeBoundariesTuple
from openpyxl.worksheet.worksheet import Worksheet
from openpyxl.xml.functions import Element
@@ -21,7 +22,7 @@ class Dimension(Strict, StyleableObject):
outlineLevel: Integer[Literal[True]]
outline_level: Alias
collapsed: Bool[Literal[False]]
style: Alias
style: Alias # type: ignore[assignment]
def __init__(
self,
@@ -139,4 +140,4 @@ class SheetDimension(Serialisable):
ref: String[Literal[False]]
def __init__(self, ref: str) -> None: ...
@property
def boundaries(self) -> tuple[int, int, int, int]: ...
def boundaries(self) -> _RangeBoundariesTuple: ...

View File

@@ -1,12 +1,15 @@
from _typeshed import Incomplete
from _typeshed import ConvertibleToInt, Incomplete
from collections.abc import Generator, Iterable, Iterator
from datetime import datetime
from types import GeneratorType
from typing import Any, NoReturn, overload
from typing_extensions import Final, Literal
from openpyxl import _Decodable, _VisibilityType
from openpyxl.cell import _CellValue
from openpyxl.cell.cell import Cell
from openpyxl.chart._chart import ChartBase
from openpyxl.drawing.image import Image
from openpyxl.formatting.formatting import ConditionalFormattingList
from openpyxl.workbook.child import _WorkbookChild
from openpyxl.workbook.defined_name import DefinedNameDict
@@ -68,7 +71,7 @@ class Worksheet(_WorkbookChild):
sheet_format: SheetFormatProperties
scenarios: ScenarioList
def __init__(self, parent: Workbook, title: str | _Decodable | None = None) -> None: ...
def __init__(self, parent: Workbook | None, title: str | _Decodable | None = None) -> None: ...
@property
def sheet_view(self) -> SheetView: ...
@property
@@ -183,22 +186,38 @@ class Worksheet(_WorkbookChild):
@property
def columns(self) -> Generator[tuple[Cell, ...], None, None]: ...
def set_printer_settings(
self, paper_size: int | None, orientation: None | Literal["default", "portrait", "landscape"]
self, paper_size: int | None, orientation: Literal["default", "portrait", "landscape"] | None
) -> None: ...
def add_data_validation(self, data_validation: DataValidation) -> None: ...
def add_chart(self, chart, anchor: Incomplete | None = None) -> None: ...
def add_image(self, img, anchor: Incomplete | None = None) -> None: ...
def add_chart(self, chart: ChartBase, anchor: str | None = None) -> None: ...
def add_image(self, img: Image, anchor: str | None = None) -> None: ...
def add_table(self, table: Table) -> None: ...
@property
def tables(self) -> TableList: ...
def add_pivot(self, pivot) -> None: ...
# Same overload as CellRange.__init__
@overload
def merge_cells(
self, range_string: str, start_row: None = None, start_column: None = None, end_row: None = None, end_column: None = None
) -> None: ...
@overload
def merge_cells(
self,
range_string: str | None = None,
start_row: int | None = None,
start_column: int | None = None,
end_row: int | None = None,
end_column: int | None = None,
range_string: None = None,
*,
start_row: ConvertibleToInt,
start_column: ConvertibleToInt,
end_row: ConvertibleToInt,
end_column: ConvertibleToInt,
) -> None: ...
@overload
def merge_cells(
self,
range_string: None,
start_row: ConvertibleToInt,
start_column: ConvertibleToInt,
end_row: ConvertibleToInt,
end_column: ConvertibleToInt,
) -> None: ...
# deprecated: Will always raise: TypeError: 'set' object is not subscriptable
@property
@@ -211,7 +230,14 @@ class Worksheet(_WorkbookChild):
end_row: int | None = None,
end_column: int | None = None,
) -> None: ...
def append(self, iterable: Iterable[Incomplete]) -> None: ...
def append(
self,
iterable: list[Incomplete]
| tuple[Incomplete, ...]
| range
| GeneratorType[Incomplete, object, object]
| dict[int | str, Incomplete],
) -> None: ...
def insert_rows(self, idx: int, amount: int = 1) -> None: ...
def insert_cols(self, idx: int, amount: int = 1) -> None: ...
def delete_rows(self, idx: int, amount: int = 1) -> None: ...

View File

@@ -1,7 +1,7 @@
# This file does not exist at runtime. It is a helper file to overload imported functions in openpyxl.xml.functions
import sys
from _typeshed import Incomplete, ReadableBuffer, SupportsIter
from _typeshed import Incomplete, ReadableBuffer
from collections.abc import Iterable, Iterator, Mapping, Sequence
from typing import Any, Protocol, TypeVar, overload
from typing_extensions import TypeAlias
@@ -38,11 +38,12 @@ class _SupportsFindChartLines(Protocol):
def find(self, __path: str) -> ChartLines | None: ...
class _SupportsFindAndIterAndAttribAndText( # noqa: Y046
_SupportsFindChartLines, SupportsIter[Incomplete], _HasAttrib, _HasText, Protocol
_SupportsFindChartLines, Iterable[Incomplete], _HasAttrib, _HasText, Protocol
): ...
class _SupportsIterAndAttribAndTextAndTag(SupportsIter[Incomplete], _HasAttrib, _HasText, _HasTag, Protocol): ... # noqa: Y046
class _SupportsIterAndAttrib(Iterable[Incomplete], _HasAttrib, Protocol): ... # noqa: Y046
class _SupportsIterAndAttribAndTextAndTag(Iterable[Incomplete], _HasAttrib, _HasText, _HasTag, Protocol): ... # noqa: Y046
class _SupportsIterAndAttribAndTextAndGet( # noqa: Y046
SupportsIter[Incomplete], _HasAttrib, _HasText, _HasGet[Incomplete], Protocol
Iterable[Incomplete], _HasAttrib, _HasText, _HasGet[Incomplete], Protocol
): ...
class _ParentElement(Protocol[_T]):