From dac649dfd058203e37379544ad42c17f32cf06fd Mon Sep 17 00:00:00 2001 From: HeeJae Chang Date: Tue, 28 Jul 2026 02:21:32 -0700 Subject: [PATCH 1/2] Add partial bundled type stubs Ship complete sidecar stubs for self-contained modules while leaving ConfigObj- and tabulate-bound modules untouched. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- MANIFEST.in | 1 + cli_helpers/compat.pyi | 11 +++ cli_helpers/py.typed | 0 .../delimited_output_adapter.pyi | 31 +++++++ .../tabular_output/json_output_adapter.pyi | 24 +++++ cli_helpers/tabular_output/preprocessors.pyi | 88 +++++++++++++++++++ .../tabular_output/tsv_output_adapter.pyi | 20 +++++ .../tabular_output/vertical_table_adapter.pyi | 24 +++++ cli_helpers/utils.pyi | 42 +++++++++ 9 files changed, 241 insertions(+) create mode 100644 cli_helpers/compat.pyi create mode 100644 cli_helpers/py.typed create mode 100644 cli_helpers/tabular_output/delimited_output_adapter.pyi create mode 100644 cli_helpers/tabular_output/json_output_adapter.pyi create mode 100644 cli_helpers/tabular_output/preprocessors.pyi create mode 100644 cli_helpers/tabular_output/tsv_output_adapter.pyi create mode 100644 cli_helpers/tabular_output/vertical_table_adapter.pyi create mode 100644 cli_helpers/utils.pyi diff --git a/MANIFEST.in b/MANIFEST.in index e1cdca0..e0d4513 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ include *.txt *.rst *.py include AUTHORS CHANGELOG LICENSE include tox.ini +recursive-include cli_helpers *.pyi py.typed recursive-include docs *.py recursive-include docs *.rst recursive-include docs Makefile diff --git a/cli_helpers/compat.pyi b/cli_helpers/compat.pyi new file mode 100644 index 0000000..c5d7236 --- /dev/null +++ b/cli_helpers/compat.pyi @@ -0,0 +1,11 @@ +from decimal import Decimal + +PY2: bool +WIN: bool +MAC: bool +text_type: type[str] +binary_type: type[bytes] +long_type: type[int] +int_types: tuple[type[int]] +HAS_PYGMENTS: bool +float_types: tuple[type[float], type[Decimal]] diff --git a/cli_helpers/py.typed b/cli_helpers/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/cli_helpers/tabular_output/delimited_output_adapter.pyi b/cli_helpers/tabular_output/delimited_output_adapter.pyi new file mode 100644 index 0000000..47e59e2 --- /dev/null +++ b/cli_helpers/tabular_output/delimited_output_adapter.pyi @@ -0,0 +1,31 @@ +from collections.abc import Iterable, Iterator, Sequence +from typing import Literal, Protocol + +class _Preprocessor(Protocol): + def __call__( + self, + data: Iterable[Sequence[object]], + headers: Sequence[object], + **kwargs: object + ) -> tuple[Iterable[Sequence[object]], Sequence[object]]: ... + +supported_formats: tuple[ + Literal["csv"], + Literal["csv-tab"], + Literal["csv-noheader"], + Literal["csv-tab-noheader"], +] +preprocessors: tuple[_Preprocessor, _Preprocessor] + +class linewriter: + line: str | None + def __init__(self) -> None: ... + def reset(self) -> None: ... + def write(self, d: str) -> None: ... + +def adapter( + data: Iterable[Sequence[object]], + headers: Sequence[object], + table_format: Literal["csv", "csv-tab", "csv-noheader", "csv-tab-noheader"] = ..., + **kwargs: object +) -> Iterator[str]: ... diff --git a/cli_helpers/tabular_output/json_output_adapter.pyi b/cli_helpers/tabular_output/json_output_adapter.pyi new file mode 100644 index 0000000..4b8d59f --- /dev/null +++ b/cli_helpers/tabular_output/json_output_adapter.pyi @@ -0,0 +1,24 @@ +import json +from collections.abc import Iterable, Iterator, Sequence +from typing import Literal, Protocol + +class _Preprocessor(Protocol): + def __call__( + self, + data: Iterable[Sequence[object]], + headers: Sequence[object], + **kwargs: object + ) -> tuple[Iterable[Sequence[object]], Sequence[object]]: ... + +supported_formats: tuple[Literal["jsonl"], Literal["jsonl_escaped"]] +preprocessors: tuple[_Preprocessor,] + +class CustomEncoder(json.JSONEncoder): + def default(self, o: object) -> object: ... + +def adapter( + data: Iterable[Sequence[object]], + headers: Iterable[str | int | float | bool | None], + table_format: Literal["jsonl", "jsonl_escaped"] = ..., + **_kwargs: object +) -> Iterator[str]: ... diff --git a/cli_helpers/tabular_output/preprocessors.pyi b/cli_helpers/tabular_output/preprocessors.pyi new file mode 100644 index 0000000..d8f690e --- /dev/null +++ b/cli_helpers/tabular_output/preprocessors.pyi @@ -0,0 +1,88 @@ +from collections.abc import Iterable, Iterator, Mapping, Sequence +from typing import TypeVar, overload + +from pygments.style import StyleMeta +from pygments.token import _TokenType + +_T = TypeVar("_T") +_H = TypeVar("_H") + +def truncate_string( + data: Iterable[Sequence[_T]], + headers: Sequence[_H], + max_field_width: int | None = ..., + skip_multiline_string: bool = ..., + **_: object +) -> tuple[Iterator[list[_T]], list[_H]]: ... +def convert_to_string( + data: Iterable[Sequence[object]], headers: Sequence[object], **_: object +) -> tuple[Iterator[list[str]], list[str]]: ... +def convert_to_undecoded_string( + data: Iterable[Sequence[object]], headers: Sequence[object], **_: object +) -> tuple[Iterator[list[str | None]], list[str | None]]: ... +def override_missing_value( + data: Iterable[Sequence[_T]], + headers: Sequence[_H], + style: str | StyleMeta | None = ..., + missing_value_token: _TokenType | None = ..., + missing_value: str = ..., + **_: object +) -> tuple[Iterator[list[_T | str]], Sequence[_H]]: ... +def override_tab_value( + data: Iterable[Sequence[_T]], + headers: Sequence[_H], + new_value: str = ..., + **_: object +) -> tuple[Iterator[list[_T]], Sequence[_H]]: ... +def escape_newlines( + data: Iterable[Sequence[_T]], headers: Sequence[_H], **_: object +) -> tuple[Iterator[list[_T]], Sequence[_H]]: ... +def bytes_to_string( + data: Iterable[Sequence[_T]], headers: Sequence[_H], **_: object +) -> tuple[Iterator[list[_T | str]], list[_H | str]]: ... +def align_decimals( + data: Iterable[Sequence[_T]], + headers: Sequence[_H], + column_types: Sequence[type[object]] = ..., + **_: object +) -> tuple[Iterator[list[_T | str]], Sequence[_H]]: ... +def quote_whitespaces( + data: Iterable[Sequence[object]], + headers: Sequence[_H], + quotestyle: str = ..., + **_: object +) -> tuple[Iterator[list[str]], Sequence[_H]]: ... +@overload +def style_output( + data: Iterable[Sequence[_T]], + headers: Sequence[_H], + style: None = ..., + header_token: _TokenType | None = ..., + odd_row_token: _TokenType | None = ..., + even_row_token: _TokenType | None = ..., + **_: object +) -> tuple[Iterator[Sequence[_T]], Sequence[_H]]: ... +@overload +def style_output( + data: Iterable[Sequence[str]], + headers: Sequence[str], + style: str | StyleMeta, + header_token: _TokenType | None = ..., + odd_row_token: _TokenType | None = ..., + even_row_token: _TokenType | None = ..., + **_: object +) -> tuple[Iterator[Sequence[str]], Sequence[str]]: ... +def format_numbers( + data: Iterable[Sequence[_T]], + headers: Sequence[_H], + column_types: Sequence[type[object]] = ..., + integer_format: str | None = ..., + float_format: str | None = ..., + **_: object +) -> tuple[Iterator[list[_T | str]], Sequence[_H]]: ... +def format_timestamps( + data: Iterable[Sequence[_T]], + headers: Sequence[_H], + column_date_formats: Mapping[_H, str] | None = ..., + **_: object +) -> tuple[Iterator[list[_T | str]], Sequence[_H]]: ... diff --git a/cli_helpers/tabular_output/tsv_output_adapter.pyi b/cli_helpers/tabular_output/tsv_output_adapter.pyi new file mode 100644 index 0000000..1f35e5f --- /dev/null +++ b/cli_helpers/tabular_output/tsv_output_adapter.pyi @@ -0,0 +1,20 @@ +from collections.abc import Iterable, Iterator, Sequence +from typing import Literal, Protocol + +class _Preprocessor(Protocol): + def __call__( + self, + data: Iterable[Sequence[object]], + headers: Sequence[object], + **kwargs: object + ) -> tuple[Iterable[Sequence[object]], Sequence[object]]: ... + +supported_formats: tuple[Literal["tsv"], Literal["tsv_noheader"]] +preprocessors: tuple[_Preprocessor, _Preprocessor, _Preprocessor] + +def adapter( + data: Iterable[Sequence[str]], + headers: Sequence[str], + table_format: Literal["tsv", "tsv_noheader"] = ..., + **kwargs: object +) -> Iterator[str]: ... diff --git a/cli_helpers/tabular_output/vertical_table_adapter.pyi b/cli_helpers/tabular_output/vertical_table_adapter.pyi new file mode 100644 index 0000000..02ac596 --- /dev/null +++ b/cli_helpers/tabular_output/vertical_table_adapter.pyi @@ -0,0 +1,24 @@ +from collections.abc import Iterable, Iterator, Sequence +from typing import Literal, Protocol + +class _Preprocessor(Protocol): + def __call__( + self, + data: Iterable[Sequence[object]], + headers: Sequence[object], + **kwargs: object + ) -> tuple[Iterable[Sequence[object]], Sequence[object]]: ... + +supported_formats: tuple[Literal["vertical"]] +preprocessors: tuple[_Preprocessor, _Preprocessor, _Preprocessor] + +def vertical_table( + data: Iterable[Sequence[str]], + headers: Sequence[str], + sep_title: str = ..., + sep_character: str = ..., + sep_length: int | tuple[int, int] = ..., +) -> Iterator[str]: ... +def adapter( + data: Iterable[Sequence[str]], headers: Sequence[str], **kwargs: object +) -> Iterator[str]: ... diff --git a/cli_helpers/utils.pyi b/cli_helpers/utils.pyi new file mode 100644 index 0000000..e347317 --- /dev/null +++ b/cli_helpers/utils.pyi @@ -0,0 +1,42 @@ +from collections.abc import Container, Hashable, Iterable, Mapping +from typing import TypeVar, overload + +from pygments.style import StyleMeta +from pygments.token import _TokenType + +_K = TypeVar("_K") +_V = TypeVar("_V") +_T = TypeVar("_T", bound=Hashable) + +@overload +def bytes_to_string(b: bytes) -> str: ... +@overload +def bytes_to_string(b: object) -> object: ... +@overload +def to_hex_if_bin(b: bytes) -> str: ... +@overload +def to_hex_if_bin(b: object) -> object: ... +def to_string(value: object) -> str: ... +def to_undecoded_string(value: object) -> str | None: ... +@overload +def truncate_string( + value: str, + max_width: int | None = ..., + skip_multiline_string: bool = ..., +) -> str: ... +@overload +def truncate_string( + value: object, + max_width: int | None = ..., + skip_multiline_string: bool = ..., +) -> object: ... +def intlen(n: str) -> int: ... +def filter_dict_by_key(d: Mapping[_K, _V], keys: Container[_K]) -> dict[_K, _V]: ... +def unique_items(seq: Iterable[_T]) -> list[_T]: ... +def strip_ansi(value: str) -> str: ... +def replace(s: str, replace: Iterable[tuple[str, str]]) -> str: ... +def style_field(token: _TokenType, field: str, style: str | StyleMeta) -> str: ... +def filter_style_table( + style: str | StyleMeta | None, *relevant_styles: _TokenType | None +) -> dict[_TokenType | None, str]: ... +def version_as_tuple(version: str) -> tuple[int, int, int]: ... From 1764fd76fcb919d5348a380bed17011288d0776d Mon Sep 17 00:00:00 2001 From: HeeJae Chang Date: Tue, 28 Jul 2026 14:33:25 -0700 Subject: [PATCH 2/2] Refine bundled stub contracts Type the consumed CSV and vertical adapter options, constrain formatter column markers to the runtime set, and expose the JSON encoder's successful float result. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../tabular_output/delimited_output_adapter.pyi | 15 +++++++++++++++ .../tabular_output/json_output_adapter.pyi | 2 +- cli_helpers/tabular_output/preprocessors.pyi | 14 ++++++++++++-- .../tabular_output/vertical_table_adapter.pyi | 8 +++++++- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/cli_helpers/tabular_output/delimited_output_adapter.pyi b/cli_helpers/tabular_output/delimited_output_adapter.pyi index 47e59e2..92f6fe6 100644 --- a/cli_helpers/tabular_output/delimited_output_adapter.pyi +++ b/cli_helpers/tabular_output/delimited_output_adapter.pyi @@ -1,6 +1,12 @@ +import sys from collections.abc import Iterable, Iterator, Sequence from typing import Literal, Protocol +if sys.version_info >= (3, 12): + _Quoting = Literal[0, 1, 2, 3, 4, 5] +else: + _Quoting = Literal[0, 1, 2, 3] + class _Preprocessor(Protocol): def __call__( self, @@ -27,5 +33,14 @@ def adapter( data: Iterable[Sequence[object]], headers: Sequence[object], table_format: Literal["csv", "csv-tab", "csv-noheader", "csv-tab-noheader"] = ..., + *, + dialect: object = ..., + delimiter: str = ..., + doublequote: bool = ..., + escapechar: str | None = ..., + quotechar: str | None = ..., + quoting: _Quoting = ..., + skipinitialspace: bool = ..., + strict: bool = ..., **kwargs: object ) -> Iterator[str]: ... diff --git a/cli_helpers/tabular_output/json_output_adapter.pyi b/cli_helpers/tabular_output/json_output_adapter.pyi index 4b8d59f..7f5a7f6 100644 --- a/cli_helpers/tabular_output/json_output_adapter.pyi +++ b/cli_helpers/tabular_output/json_output_adapter.pyi @@ -14,7 +14,7 @@ supported_formats: tuple[Literal["jsonl"], Literal["jsonl_escaped"]] preprocessors: tuple[_Preprocessor,] class CustomEncoder(json.JSONEncoder): - def default(self, o: object) -> object: ... + def default(self, o: object) -> float: ... def adapter( data: Iterable[Sequence[object]], diff --git a/cli_helpers/tabular_output/preprocessors.pyi b/cli_helpers/tabular_output/preprocessors.pyi index d8f690e..bf18e28 100644 --- a/cli_helpers/tabular_output/preprocessors.pyi +++ b/cli_helpers/tabular_output/preprocessors.pyi @@ -1,3 +1,4 @@ +from decimal import Decimal from collections.abc import Iterable, Iterator, Mapping, Sequence from typing import TypeVar, overload @@ -6,6 +7,15 @@ from pygments.token import _TokenType _T = TypeVar("_T") _H = TypeVar("_H") +_ColumnType = ( + type[None] + | type[bool] + | type[int] + | type[float] + | type[Decimal] + | type[bytes] + | type[str] +) def truncate_string( data: Iterable[Sequence[_T]], @@ -43,7 +53,7 @@ def bytes_to_string( def align_decimals( data: Iterable[Sequence[_T]], headers: Sequence[_H], - column_types: Sequence[type[object]] = ..., + column_types: Sequence[_ColumnType] = ..., **_: object ) -> tuple[Iterator[list[_T | str]], Sequence[_H]]: ... def quote_whitespaces( @@ -75,7 +85,7 @@ def style_output( def format_numbers( data: Iterable[Sequence[_T]], headers: Sequence[_H], - column_types: Sequence[type[object]] = ..., + column_types: Sequence[_ColumnType] = ..., integer_format: str | None = ..., float_format: str | None = ..., **_: object diff --git a/cli_helpers/tabular_output/vertical_table_adapter.pyi b/cli_helpers/tabular_output/vertical_table_adapter.pyi index 02ac596..6faa5de 100644 --- a/cli_helpers/tabular_output/vertical_table_adapter.pyi +++ b/cli_helpers/tabular_output/vertical_table_adapter.pyi @@ -20,5 +20,11 @@ def vertical_table( sep_length: int | tuple[int, int] = ..., ) -> Iterator[str]: ... def adapter( - data: Iterable[Sequence[str]], headers: Sequence[str], **kwargs: object + data: Iterable[Sequence[str]], + headers: Sequence[str], + *, + sep_title: str = ..., + sep_character: str = ..., + sep_length: int | tuple[int, int] = ..., + **kwargs: object ) -> Iterator[str]: ...