From 4c6e98ed0c22227c24f2fa87c3c77d3e40aad207 Mon Sep 17 00:00:00 2001 From: Screwtapello Date: Sun, 12 Sep 2021 18:42:12 +1000 Subject: [PATCH] Add many missing tkinter type annotations (#6002) Co-authored-by: Sebastian Rittau Co-authored-by: Akuli --- stdlib/tkinter/__init__.pyi | 64 +++++++++++++++++++----- stdlib/tkinter/font.pyi | 13 ++++- stdlib/tkinter/ttk.pyi | 46 ++++++++++------- tests/stubtest_allowlists/py3_common.txt | 2 + 4 files changed, 92 insertions(+), 33 deletions(-) diff --git a/stdlib/tkinter/__init__.pyi b/stdlib/tkinter/__init__.pyi index d58ed0717..b7a5d3a11 100644 --- a/stdlib/tkinter/__init__.pyi +++ b/stdlib/tkinter/__init__.pyi @@ -97,6 +97,7 @@ _Cursor = Union[str, Tuple[str], Tuple[str, str], Tuple[str, str, str], Tuple[st _EntryValidateCommand = Union[ Callable[[], bool], str, _TkinterSequence[str] ] # example when it's sequence: entry['invalidcommand'] = [entry.register(print), '%P'] +_GridIndex = Union[int, str, Literal["all"]] _ImageSpec = Union[_Image, str] # str can be from e.g. tkinter.image_names() _Padding = Union[ _ScreenUnits, @@ -222,6 +223,12 @@ getdouble: Any def getboolean(s): ... +class _GridIndexInfo(TypedDict, total=False): + minsize: _ScreenUnits + pad: _ScreenUnits + uniform: str | None + weight: int + class Misc: master: Misc | None tk: _tkinter.TkappType @@ -262,8 +269,8 @@ class Misc: def clipboard_append(self, string: str, *, displayof: Misc = ..., format: str = ..., type: str = ...): ... def grab_current(self): ... def grab_release(self): ... - def grab_set(self): ... - def grab_set_global(self): ... + def grab_set(self) -> None: ... + def grab_set_global(self) -> None: ... def grab_status(self): ... def option_add(self, pattern, value, priority: Any | None = ...): ... def option_clear(self): ... @@ -384,8 +391,26 @@ class Misc: @overload def grid_bbox(self, column: int, row: int, col2: int, row2: int) -> Tuple[int, int, int, int] | None: ... bbox = grid_bbox - def grid_columnconfigure(self, index, cnf=..., **kw): ... # TODO - def grid_rowconfigure(self, index, cnf=..., **kw): ... # TODO + def grid_columnconfigure( + self, + index: _GridIndex, + cnf: _GridIndexInfo = ..., + *, + minsize: _ScreenUnits = ..., + pad: _ScreenUnits = ..., + uniform: str = ..., + weight: int = ..., + ) -> _GridIndexInfo | Any: ... # can be None but annoying to check + def grid_rowconfigure( + self, + index: _GridIndex, + cnf: _GridIndexInfo = ..., + *, + minsize: _ScreenUnits = ..., + pad: _ScreenUnits = ..., + uniform: str = ..., + weight: int = ..., + ) -> _GridIndexInfo | Any: ... # can be None but annoyying to check columnconfigure = grid_columnconfigure rowconfigure = grid_rowconfigure def grid_location(self, x: _ScreenUnits, y: _ScreenUnits) -> Tuple[int, int]: ... @@ -452,14 +477,26 @@ class CallWrapper: def __call__(self, *args): ... class XView: - def xview(self, *args): ... - def xview_moveto(self, fraction): ... - def xview_scroll(self, number, what): ... + @overload + def xview(self) -> Tuple[float, float]: ... + @overload + def xview(self, *args: Any) -> Any: ... + def xview_moveto(self, fraction: float) -> None: ... + @overload + def xview_scroll(self, number: int, what: Literal["units", "pages"]) -> None: ... + @overload + def xview_scroll(self, number: _ScreenUnits, what: Literal["pixels"]) -> None: ... class YView: - def yview(self, *args): ... - def yview_moveto(self, fraction): ... - def yview_scroll(self, number, what): ... + @overload + def yview(self) -> Tuple[float, float]: ... + @overload + def yview(self, *args: Any) -> Any: ... + def yview_moveto(self, fraction: float) -> None: ... + @overload + def yview_scroll(self, number: int, what: Literal["units", "pages"]) -> None: ... + @overload + def yview_scroll(self, number: _ScreenUnits, what: Literal["pixels"]) -> None: ... class Wm: @overload @@ -1085,7 +1122,10 @@ class Canvas(Widget, XView, YView): ) -> Tuple[_CanvasItemId, ...]: ... def find_overlapping(self, x1: _ScreenUnits, y1: _ScreenUnits, x2: _ScreenUnits, y2: float) -> Tuple[_CanvasItemId, ...]: ... def find_withtag(self, tagOrId: str | _CanvasItemId) -> Tuple[_CanvasItemId, ...]: ... - def bbox(self, *args): ... + # Canvas.bbox() args are `str | _CanvasItemId`, but mypy rejects that + # description because it's incompatible with Misc.bbox(), an alias for + # Misc.grid_bbox(). Yes it is, but there's not much we can do about it. + def bbox(self, *args: str | _CanvasItemId) -> Tuple[int, int, int, int]: ... # type: ignore @overload def tag_bind( self, @@ -3265,7 +3305,7 @@ class PanedWindow(Widget): @overload def configure(self, cnf: str) -> Tuple[str, str, str, Any, Any]: ... config = configure - def add(self, child, **kw): ... + def add(self, child: Widget, **kw): ... def remove(self, child): ... forget: Any def identify(self, x, y): ... diff --git a/stdlib/tkinter/font.pyi b/stdlib/tkinter/font.pyi index 7e875a968..df828c448 100644 --- a/stdlib/tkinter/font.pyi +++ b/stdlib/tkinter/font.pyi @@ -1,3 +1,4 @@ +import _tkinter import sys import tkinter from typing import Any, Tuple, Union, overload @@ -8,8 +9,16 @@ ROMAN: Literal["roman"] BOLD: Literal["bold"] ITALIC: Literal["italic"] -# Can contain e.g. nested sequences ('FONT DESCRIPTIONS' in font man page) -_FontDescription = Union[str, Font, tkinter._TkinterSequence[Any]] +_FontDescription = Union[ + # "Helvetica 12" + str, + # A font object constructed in Python + Font, + # ("Helvetica", 12, BOLD) + tkinter._TkinterSequence[Any], + # A font object constructed in Tcl + _tkinter.Tcl_Obj, +] class _FontDict(TypedDict): family: str diff --git a/stdlib/tkinter/ttk.pyi b/stdlib/tkinter/ttk.pyi index dced3b5f4..9569239a7 100644 --- a/stdlib/tkinter/ttk.pyi +++ b/stdlib/tkinter/ttk.pyi @@ -509,7 +509,18 @@ class Notebook(Widget): @overload def configure(self, cnf: str) -> Tuple[str, str, str, Any, Any]: ... config = configure - def add(self, child, **kw): ... + def add( + self, + child: tkinter.Widget, + *, + state: Literal["normal", "disabled", "hidden"] = ..., + sticky: str = ..., # consists of letters 'n', 's', 'w', 'e', no repeats, may be empty + padding: tkinter._Padding = ..., + text: str = ..., + image: Any = ..., # Sequence of an image name, followed by zero or more (sequences of one or more state names followed by an image name) + compound: tkinter._Compound = ..., + underline: int = ..., + ) -> None: ... def forget(self, tab_id): ... def hide(self, tab_id): ... def identify(self, x, y): ... @@ -518,7 +529,7 @@ class Notebook(Widget): def select(self, tab_id: Any | None = ...): ... def tab(self, tab_id, option: Any | None = ..., **kw): ... def tabs(self): ... - def enable_traversal(self): ... + def enable_traversal(self) -> None: ... class Panedwindow(Widget, tkinter.PanedWindow): def __init__( @@ -535,6 +546,7 @@ class Panedwindow(Widget, tkinter.PanedWindow): takefocus: tkinter._TakeFocusValue = ..., width: int = ..., ) -> None: ... + def add(self, child: tkinter.Widget, *, weight: int = ..., **kw) -> None: ... @overload # type: ignore def configure( self, @@ -882,11 +894,11 @@ class _TreeviewItemDict(TypedDict): tags: list[str] class _TreeviewTagDict(TypedDict): - text: str - image: Literal[""] | str # not wrapped in list :D - anchor: tkinter._Anchor - background: tkinter._Color + # There is also 'text' and 'anchor', but they don't seem to do anything, using them is likely a bug foreground: tkinter._Color + background: tkinter._Color + font: _FontDescription + image: Literal[""] | str # not wrapped in list :D class _TreeviewHeaderDict(TypedDict): text: str @@ -997,7 +1009,7 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): *, text: str = ..., image: tkinter._ImageSpec = ..., - anochor: tkinter._Anchor = ..., + anchor: tkinter._Anchor = ..., command: str | Callable[[], Any] = ..., ) -> _TreeviewHeaderDict | None: ... def identify(self, component, x, y): ... @@ -1074,27 +1086,23 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): @overload def tag_bind(self, tagname: str, *, callback: str) -> None: ... @overload - def tag_configure(self, tagname: str, option: Literal["text"]) -> str: ... - @overload - def tag_configure(self, tagname: str, option: Literal["image"]) -> str: ... - @overload - def tag_configure(self, tagname: str, option: Literal["anchor"]) -> tkinter._Anchor | Literal[""]: ... - @overload def tag_configure(self, tagname: str, option: Literal["foreground", "background"]) -> tkinter._Color: ... @overload - def tag_configure(self, tagname: str, option: str) -> Any: ... + def tag_configure(self, tagname: str, option: Literal["font"]) -> _FontDescription: ... + @overload + def tag_configure(self, tagname: str, option: Literal["image"]) -> str: ... @overload def tag_configure( self, tagname: str, option: None = ..., *, - text: str = ..., - image: tkinter._ImageSpec = ..., - anchor: tkinter._Anchor = ..., - background: tkinter._Color = ..., + # There is also 'text' and 'anchor', but they don't seem to do anything, using them is likely a bug foreground: tkinter._Color = ..., - ) -> _TreeviewTagDict | None: ... + background: tkinter._Color = ..., + font: _FontDescription = ..., + image: tkinter._ImageSpec = ..., + ) -> _TreeviewTagDict | Any: ... # can be None but annoying to check @overload def tag_has(self, tagname: str, item: None = ...) -> Tuple[str, ...]: ... @overload diff --git a/tests/stubtest_allowlists/py3_common.txt b/tests/stubtest_allowlists/py3_common.txt index 758834b33..08e086a2c 100644 --- a/tests/stubtest_allowlists/py3_common.txt +++ b/tests/stubtest_allowlists/py3_common.txt @@ -190,6 +190,8 @@ threading.Condition.release # Condition functions are exported in __init__ threading.Lock # A factory function that returns 'most efficient lock'. Marking it as a function will make it harder for users to mark the Lock type. tkinter.Misc.grid_propagate # The noarg placeholder is a set value list tkinter.Misc.pack_propagate # The noarg placeholder is a set value list +tkinter.Misc.grid_columnconfigure # an empty dict literal is actually a valid default for a TypedDict(total=False) parameter +tkinter.Misc.grid_rowconfigure # an empty dict literal is actually a valid default for a TypedDict(total=False) parameter tkinter.Tk.eval # from __getattr__ tkinter.Tk.report_callback_exception # A bit of a lie, since it's actually a method, but typing it as an attribute allows it to be assigned to tkinter.Wm.wm_iconphoto # Default value of argument can't be used without runtime error