Skip to content

[tkinter.ttk] Added type annotations for Style, fix some other incomplete parts #14348

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

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions stdlib/@tests/stubtest_allowlists/py310.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ posixpath.join
ntpath.join
os.path.join

# this is implemented with *args having a minimum size so arguments before it must be positional (but stubtest doesn't see that)
tkinter.ttk.Style.element_create

# typing.IO uses positional-or-keyword arguments, but in the stubs we prefer
# to mark these as positional-only for compatibility with existing sub-classes.
typing(_extensions)?\.BinaryIO\.write
Expand Down
3 changes: 3 additions & 0 deletions stdlib/@tests/stubtest_allowlists/py311.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ posixpath.join
ntpath.join
os.path.join

# this is implemented with *args having a minimum size so arguments before it must be positional (but stubtest doesn't see that)
tkinter.ttk.Style.element_create

# typing.IO uses positional-or-keyword arguments, but in the stubs we prefer
# to mark these as positional-only for compatibility with existing sub-classes.
typing(_extensions)?\.BinaryIO\.write
Expand Down
3 changes: 3 additions & 0 deletions stdlib/@tests/stubtest_allowlists/py312.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ posixpath.join
ntpath.join
os.path.join

# this is implemented with *args having a minimum size so arguments before it must be positional (but stubtest doesn't see that)
tkinter.ttk.Style.element_create

# typing.IO uses positional-or-keyword arguments, but in the stubs we prefer
# to mark these as positional-only for compatibility with existing sub-classes.
typing(_extensions)?\.BinaryIO\.write
Expand Down
3 changes: 3 additions & 0 deletions stdlib/@tests/stubtest_allowlists/py313.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ posixpath.join
ntpath.join
os.path.join

# this is implemented with *args having a minimum size so arguments before it must be positional (but stubtest doesn't see that)
tkinter.ttk.Style.element_create

# typing.IO uses positional-or-keyword arguments, but in the stubs we prefer
# to mark these as positional-only for compatibility with existing sub-classes.
typing(_extensions)?\.BinaryIO\.write
Expand Down
3 changes: 3 additions & 0 deletions stdlib/@tests/stubtest_allowlists/py314.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ posixpath.join
ntpath.join
os.path.join

# this is implemented with *args having a minimum size so arguments before it must be positional (but stubtest doesn't see that)
tkinter.ttk.Style.element_create

# typing.IO uses positional-or-keyword arguments, but in the stubs we prefer
# to mark these as positional-only for compatibility with existing sub-classes.
typing(_extensions)?\.BinaryIO\.write
Expand Down
165 changes: 150 additions & 15 deletions stdlib/tkinter/ttk.pyi
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import _tkinter
import sys
import tkinter
from _typeshed import Incomplete, MaybeNone
from collections.abc import Callable
from _typeshed import MaybeNone
from collections.abc import Callable, Iterable
from tkinter.font import _FontDescription
from typing import Any, Literal, TypedDict, overload, type_check_only
from typing_extensions import TypeAlias
from typing_extensions import Never, TypeAlias, Unpack

__all__ = [
"Button",
Expand Down Expand Up @@ -35,7 +36,7 @@ __all__ = [
]

def tclobjs_to_py(adict: dict[Any, Any]) -> dict[Any, Any]: ...
def setup_master(master=None): ...
def setup_master(master: tkinter.Misc | None = None): ...

_Padding: TypeAlias = (
tkinter._ScreenUnits
Expand All @@ -48,19 +49,153 @@ _Padding: TypeAlias = (
# from ttk_widget (aka ttk::widget) manual page, differs from tkinter._Compound
_TtkCompound: TypeAlias = Literal["", "text", "image", tkinter._Compound]

# Last item (option value to apply) varies between different options so use Any.
# It could also be any iterable with items matching the tuple, but that case
# hasn't been added here for consistency with _Padding above.
_Statespec: TypeAlias = tuple[Unpack[tuple[str, ...]], Any]
_ImageStatespec: TypeAlias = tuple[Unpack[tuple[str, ...]], tkinter._ImageSpec]
_VsapiStatespec: TypeAlias = tuple[Unpack[tuple[str, ...]], int]

class _Layout(TypedDict, total=False):
side: Literal["left", "right", "top", "bottom"]
sticky: str # consists of letters 'n', 's', 'w', 'e', may contain repeats, may be empty
unit: Literal[0, 1] | bool
children: _LayoutSpec
# Note: there seem to be some other undocumented keys sometimes

# This could be any sequence when passed as a parameter but will always be a list when returned.
_LayoutSpec: TypeAlias = list[tuple[str, _Layout | None]]

# Keep these in sync with the appropriate methods in Style
class _ElementCreateImageKwargs(TypedDict, total=False):
border: _Padding
height: tkinter._ScreenUnits
padding: _Padding
sticky: str
width: tkinter._ScreenUnits

_ElementCreateArgsCrossPlatform: TypeAlias = (
# Could be any sequence here but types are not homogenous so just type it as tuple
tuple[Literal["image"], tkinter._ImageSpec, Unpack[tuple[_ImageStatespec, ...]], _ElementCreateImageKwargs]
| tuple[Literal["from"], str, str]
| tuple[Literal["from"], str] # (fromelement is optional)
)
if sys.platform == "win32" and sys.version_info >= (3, 13):
class _ElementCreateVsapiKwargsPadding(TypedDict, total=False):
padding: _Padding

class _ElementCreateVsapiKwargsMargin(TypedDict, total=False):
padding: _Padding

class _ElementCreateVsapiKwargsSize(TypedDict):
width: tkinter._ScreenUnits
height: tkinter._ScreenUnits

_ElementCreateVsapiKwargsDict: TypeAlias = (
_ElementCreateVsapiKwargsPadding | _ElementCreateVsapiKwargsMargin | _ElementCreateVsapiKwargsSize
)
_ElementCreateArgs: TypeAlias = ( # noqa: Y047 # It doesn't recognise the usage below for whatever reason
_ElementCreateArgsCrossPlatform
| tuple[Literal["vsapi"], str, int, _ElementCreateVsapiKwargsDict]
| tuple[Literal["vsapi"], str, int, _VsapiStatespec, _ElementCreateVsapiKwargsDict]
)
else:
_ElementCreateArgs: TypeAlias = _ElementCreateArgsCrossPlatform
_ThemeSettingsValue = TypedDict(
"_ThemeSettingsValue",
{
"configure": dict[str, Any],
"map": dict[str, Iterable[_Statespec]],
"layout": _LayoutSpec,
"element create": _ElementCreateArgs,
},
total=False,
)
_ThemeSettings: TypeAlias = dict[str, _ThemeSettingsValue]

class Style:
master: Incomplete
master: tkinter.Misc
tk: _tkinter.TkappType
def __init__(self, master: tkinter.Misc | None = None) -> None: ...
def configure(self, style, query_opt=None, **kw): ...
def map(self, style, query_opt=None, **kw): ...
def lookup(self, style, option, state=None, default=None): ...
def layout(self, style, layoutspec=None): ...
def element_create(self, elementname, etype, *args, **kw) -> None: ...
def element_names(self): ...
def element_options(self, elementname): ...
def theme_create(self, themename, parent=None, settings=None) -> None: ...
def theme_settings(self, themename, settings) -> None: ...
# For these methods, values given vary between options. Returned values
# seem to be str, but this might not always be the case.
@overload
def configure(self, style: str) -> dict[str, Any] | None: ... # Returns None if no configuration.
@overload
def configure(self, style: str, query_opt: str, **kw: Any) -> Any: ...
@overload
def configure(self, style: str, query_opt: None = None, **kw: Any) -> None: ...
@overload
def map(self, style: str, query_opt: str) -> _Statespec: ...
@overload
def map(self, style: str, query_opt: None = None, **kw: Iterable[_Statespec]) -> dict[str, _Statespec]: ...
def lookup(self, style: str, option: str, state: Iterable[str] | None = None, default: Any | None = None) -> Any: ...
@overload
def layout(self, style: str, layoutspec: _LayoutSpec) -> list[Never]: ... # Always seems to return an empty list
@overload
def layout(self, style: str, layoutspec: None = None) -> _LayoutSpec: ...
@overload
def element_create(
self,
elementname: str,
etype: Literal["image"],
default_image: tkinter._ImageSpec,
/,
*imagespec: _ImageStatespec,
Copy link
Contributor

Choose a reason for hiding this comment

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

Kinda awkward but you could do *imagespec: *tuple[tkinter._ImageSpec, *tuple[_ImageStatespec, ...]]. Not sure if this would be problematic to use though for code completion though.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you should just ignore the stubtest error.

border: _Padding = ...,
height: tkinter._ScreenUnits = ...,
padding: _Padding = ...,
sticky: str = ...,
width: tkinter._ScreenUnits = ...,
) -> None: ...
@overload
def element_create(self, elementname: str, etype: Literal["from"], themename: str, fromelement: str = ..., /) -> None: ...
if sys.platform == "win32" and sys.version_info >= (3, 13): # and tk version >= 8.6
# margin, padding, and (width + height) are mutually exclusive. width
# and height must either both be present or not present at all. Note:
# There are other undocumented options if you look at ttk's source code.
@overload
def element_create(
self,
elementname: str,
etype: Literal["vsapi"],
class_: str,
part: int,
vs_statespec: _VsapiStatespec = ...,
/,
*,
padding: _Padding = ...,
) -> None: ...
@overload
def element_create(
self,
elementname: str,
etype: Literal["vsapi"],
class_: str,
part: int,
vs_statespec: _VsapiStatespec = ...,
/,
*,
margin: _Padding = ...,
) -> None: ...
@overload
def element_create(
self,
elementname: str,
etype: Literal["vsapi"],
class_: str,
part: int,
vs_statespec: _VsapiStatespec = ...,
/,
*,
width: tkinter._ScreenUnits,
height: tkinter._ScreenUnits,
) -> None: ...

def element_names(self) -> tuple[str, ...]: ...
def element_options(self, elementname: str) -> tuple[str, ...]: ...
def theme_create(self, themename: str, parent: str | None = None, settings: _ThemeSettings | None = None) -> None: ...
def theme_settings(self, themename: str, settings: _ThemeSettings) -> None: ...
def theme_names(self) -> tuple[str, ...]: ...
@overload
def theme_use(self, themename: str) -> None: ...
Expand Down Expand Up @@ -615,7 +750,7 @@ class Panedwindow(Widget, tkinter.PanedWindow):
) -> dict[str, tuple[str, str, str, Any, Any]] | None: ...
@overload
def config(self, cnf: str) -> tuple[str, str, str, Any, Any]: ...
forget: Incomplete
forget = tkinter.PanedWindow.forget
def insert(self, pos, child, **kw) -> None: ...
def pane(self, pane, option=None, **kw): ...
def sashpos(self, index, newpos=None): ...
Expand Down
Loading