diff --git a/buildconfig/stubs/gen_stubs.py b/buildconfig/stubs/gen_stubs.py index 670db2f951..a762693f41 100644 --- a/buildconfig/stubs/gen_stubs.py +++ b/buildconfig/stubs/gen_stubs.py @@ -4,6 +4,7 @@ """ import pathlib +import shutil from typing import Any import pygame.constants @@ -51,6 +52,7 @@ "system", "geometry", "window", + "typing", ] # pygame classes that are autoimported into main namespace are kept in this dict @@ -151,3 +153,8 @@ def get_all(mod: Any): for element in get_all(pygame.locals): constant_type = getattr(pygame.locals, element).__class__.__name__ f.write(f"{element}: {constant_type}\n") + +# copy typing.py to typing.pyi for type checkers +typing_py_file = pathlib.Path(__file__).parent.parent.parent / "src_py" / "typing.py" +typing_stub_file = pathlib.Path(__file__).parent / "pygame" / "typing.pyi" +shutil.copyfile(typing_py_file, typing_stub_file) diff --git a/buildconfig/stubs/mypy_allow_list.txt b/buildconfig/stubs/mypy_allow_list.txt index c6f3ffa15b..4f2ef7834d 100644 --- a/buildconfig/stubs/mypy_allow_list.txt +++ b/buildconfig/stubs/mypy_allow_list.txt @@ -2,10 +2,6 @@ # listed here are not checked by the mypy stubtest program # This allowlist supports regex -# This is not a real typestub file, it is used only in the typestubs to export -# a few utility typestub definitions -pygame\._common - # cython files have this top level dunder pygame\._sdl2\..*\.__test__ diff --git a/buildconfig/stubs/pygame/__init__.pyi b/buildconfig/stubs/pygame/__init__.pyi index c05a428fb4..570a6e0b5e 100644 --- a/buildconfig/stubs/pygame/__init__.pyi +++ b/buildconfig/stubs/pygame/__init__.pyi @@ -38,6 +38,7 @@ from pygame import ( system as system, geometry as geometry, window as window, + typing as typing, ) from .rect import Rect as Rect, FRect as FRect diff --git a/buildconfig/stubs/pygame/_common.pyi b/buildconfig/stubs/pygame/_common.pyi deleted file mode 100644 index 0fba00a0bf..0000000000 --- a/buildconfig/stubs/pygame/_common.pyi +++ /dev/null @@ -1,101 +0,0 @@ -from os import PathLike -from typing import IO, Callable, Tuple, Union, TypeVar, Protocol, SupportsIndex - -# For functions that take a file name -AnyPath = Union[str, bytes, PathLike[str], PathLike[bytes]] - -# Most pygame functions that take a file argument should be able to handle -# a FileArg type -FileArg = Union[AnyPath, IO[bytes], IO[str]] - -_T = TypeVar("_T", covariant=True) - -class Sequence(Protocol[_T]): - """ - This is different from the standard python 'Sequence' abc. This is used in places - where only __getitem__ and __len__ is actually needed (basically almost all places - where a sequence is used). The standard 'Sequence' abc has some extra methods. - """ - def __getitem__(self, __i: SupportsIndex) -> _T: ... - def __len__(self) -> int: ... - -# Right now, it isn't possible to annotate sizes (popular tools don't support it) but -# when it is, the below types should be appropriately annotated - -# Yes, float. The reason being, pygame handles float without erroring in a lot of places -# where a coordinate is expected (usually by rounding to int). -# Also, 'Union[int, float] == float' -Coordinate = Sequence[float] - -# This is used in places where ints are strictly required -IntCoordinate = Sequence[int] - -# This typehint is used when a function would return an RGBA tuple -RGBAOutput = Tuple[int, int, int, int] -ColorValue = Union[int, str, Sequence[int]] - -_CanBeRect = Sequence[Union[float, Coordinate]] - -class _HasRectAttribute(Protocol): - # An object that has a rect attribute that is either a rect, or a function - # that returns a rect confirms to the rect protocol - rect: Union[RectValue, Callable[[], RectValue]] - -RectValue = Union[_CanBeRect, _HasRectAttribute] - -""" -# testing code -def a(b: Coordinate): - b[0] - b[1] - len(b) - e1, e2 = b - for i in b: - i -= 1 - - -import numpy -from pygame import Vector2 - -class MyAmoger: - def __init__(self): - pass - - def __getitem__(self, index): - if index not in (0, 1): - raise IndexError() - - return 42 if index else 69 - - def __len__(self): - return 2 - - -# should pass -a([1, 2]) -a([4.2, 5.2]) -a((1, 2)) -a((1.4, 2.8)) -a(MyAmoger()) -a(range(2, 4)) # yes, range object is a 'Sequence' -a(numpy.array([1.3, 2.1])) -a(b"ab") # weird but this actually works in code (this represents (97, 98) btw) -a(bytearray([1, 2])) -a(Vector2()) - -print("Done testing the passes!") - -# should technically error, but right now we can't annotate sizes so they pass on -# type testing -a([1, 2, 3]) -a([4.2, 5.2, 2, 4]) -a((1,)) -a(numpy.array([1.3, 2.1, 4.2])) - -# all of the below should always error -a({}) -a({1: 2}) -a("abc") -a({1, 2}) - -""" diff --git a/buildconfig/stubs/pygame/_sdl2/video.pyi b/buildconfig/stubs/pygame/_sdl2/video.pyi index dd12ae5f80..b34970d4eb 100644 --- a/buildconfig/stubs/pygame/_sdl2/video.pyi +++ b/buildconfig/stubs/pygame/_sdl2/video.pyi @@ -5,7 +5,7 @@ from pygame.rect import Rect from pygame.surface import Surface from pygame.window import Window as Window -from .._common import ColorValue, RectValue, Coordinate +from pygame.typing import ColorLike, RectLike, Coordinate WINDOWPOS_UNDEFINED: int WINDOWPOS_CENTERED: int @@ -60,13 +60,13 @@ class Texture: @property def color(self) -> Color: ... @color.setter - def color(self, value: ColorValue) -> None: ... + def color(self, value: ColorLike) -> None: ... def get_rect(self, **kwargs: Any) -> Rect: ... def draw( self, - srcrect: Optional[RectValue] = None, - dstrect: Optional[RectValue] = None, + srcrect: Optional[RectLike] = None, + dstrect: Optional[RectLike] = None, angle: float = 0.0, origin: Optional[Iterable[int]] = None, flip_x: bool = False, @@ -99,17 +99,17 @@ class Texture: p3_mod: Iterable[int] = (255, 255, 255, 255), p4_mod: Iterable[int] = (255, 255, 255, 255), ) -> None: ... - def update(self, surface: Surface, area: Optional[RectValue] = None) -> None: ... + def update(self, surface: Surface, area: Optional[RectLike] = None) -> None: ... class Image: def __init__( self, texture_or_image: Union[Texture, Image], - srcrect: Optional[RectValue] = None, + srcrect: Optional[RectLike] = None, ) -> None: ... def get_rect(self) -> Rect: ... def draw( - self, srcrect: Optional[RectValue] = None, dstrect: Optional[RectValue] = None + self, srcrect: Optional[RectLike] = None, dstrect: Optional[RectLike] = None ) -> None: ... angle: float origin: Optional[Iterable[float]] @@ -123,7 +123,7 @@ class Image: @property def color(self) -> Color: ... @color.setter - def color(self, value: ColorValue) -> None: ... + def color(self, value: ColorLike) -> None: ... class Renderer: def __init__( @@ -140,25 +140,25 @@ class Renderer: @property def draw_color(self) -> Color: ... @draw_color.setter - def draw_color(self, value: ColorValue) -> None: ... + def draw_color(self, value: ColorLike) -> None: ... def clear(self) -> None: ... def present(self) -> None: ... def get_viewport(self) -> Rect: ... - def set_viewport(self, area: Optional[RectValue]) -> None: ... + def set_viewport(self, area: Optional[RectLike]) -> None: ... logical_size: Iterable[int] scale: Iterable[float] target: Optional[Texture] def blit( self, source: Union[Texture, Image], - dest: Optional[RectValue] = None, - area: Optional[RectValue] = None, + dest: Optional[RectLike] = None, + area: Optional[RectLike] = None, special_flags: int = 0, ) -> Rect: ... def draw_line(self, p1: Coordinate, p2: Coordinate) -> None: ... def draw_point(self, point: Coordinate) -> None: ... - def draw_rect(self, rect: RectValue) -> None: ... - def fill_rect(self, rect: RectValue) -> None: ... + def draw_rect(self, rect: RectLike) -> None: ... + def fill_rect(self, rect: RectLike) -> None: ... def draw_triangle( self, p1: Coordinate, p2: Coordinate, p3: Coordinate ) -> None: ... @@ -172,7 +172,7 @@ class Renderer: self, p1: Coordinate, p2: Coordinate, p3: Coordinate, p4: Coordinate ) -> None: ... def to_surface( - self, surface: Optional[Surface] = None, area: Optional[RectValue] = None + self, surface: Optional[Surface] = None, area: Optional[RectLike] = None ) -> Surface: ... @staticmethod def compose_custom_blend_mode( diff --git a/buildconfig/stubs/pygame/camera.pyi b/buildconfig/stubs/pygame/camera.pyi index 5de3a41194..de5eac1767 100644 --- a/buildconfig/stubs/pygame/camera.pyi +++ b/buildconfig/stubs/pygame/camera.pyi @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod from typing import List, Optional, Tuple, Union, Literal -from ._common import IntCoordinate +from pygame.typing import IntCoordinate from pygame.surface import Surface diff --git a/buildconfig/stubs/pygame/color.pyi b/buildconfig/stubs/pygame/color.pyi index d9c2ef2ea7..635b05d4ea 100644 --- a/buildconfig/stubs/pygame/color.pyi +++ b/buildconfig/stubs/pygame/color.pyi @@ -2,7 +2,7 @@ import sys from typing import Any, Dict, Iterator, SupportsIndex, Tuple, Union, overload from typing_extensions import deprecated # added in 3.13 -from ._common import ColorValue +from pygame.typing import ColorLike if sys.version_info >= (3, 9): from collections.abc import Collection @@ -28,7 +28,7 @@ class Color(Collection[int]): @overload def __init__(self, r: int, g: int, b: int, a: int = 255) -> None: ... @overload - def __init__(self, rgbvalue: ColorValue) -> None: ... + def __init__(self, rgbvalue: ColorLike) -> None: ... @overload def __getitem__(self, i: SupportsIndex) -> int: ... @overload @@ -82,10 +82,10 @@ class Color(Collection[int]): def correct_gamma(self, gamma: float, /) -> Color: ... @deprecated("since 2.1.3. Use unpacking instead") def set_length(self, length: int, /) -> None: ... - def lerp(self, color: ColorValue, amount: float) -> Color: ... + def lerp(self, color: ColorLike, amount: float) -> Color: ... def premul_alpha(self) -> Color: ... def grayscale(self) -> Color: ... @overload def update(self, r: int, g: int, b: int, a: int = 255, /) -> None: ... @overload - def update(self, rgbvalue: ColorValue, /) -> None: ... + def update(self, rgbvalue: ColorLike, /) -> None: ... diff --git a/buildconfig/stubs/pygame/cursors.pyi b/buildconfig/stubs/pygame/cursors.pyi index 8ab51b003d..d75a66cc77 100644 --- a/buildconfig/stubs/pygame/cursors.pyi +++ b/buildconfig/stubs/pygame/cursors.pyi @@ -2,7 +2,7 @@ from typing import Any, Iterator, Literal, Tuple, Union, overload from pygame.surface import Surface -from ._common import FileArg, IntCoordinate, Sequence +from pygame.typing import FileLike, IntCoordinate, SequenceLike _Small_string = Tuple[ str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str @@ -47,13 +47,13 @@ sizer_xy_strings: _Small_string textmarker_strings: _Small_string def compile( - strings: Sequence[str], + strings: SequenceLike[str], black: str = "X", white: str = ".", xor: str = "o", ) -> Tuple[Tuple[int, ...], Tuple[int, ...]]: ... def load_xbm( - curs: FileArg, mask: FileArg + curs: FileLike, mask: FileLike ) -> Tuple[Tuple[int, int], Tuple[int, int], Tuple[int, ...], Tuple[int, ...]]: ... class Cursor: @@ -66,8 +66,8 @@ class Cursor: self, size: IntCoordinate, hotspot: IntCoordinate, - xormasks: Sequence[int], - andmasks: Sequence[int], + xormasks: SequenceLike[int], + andmasks: SequenceLike[int], ) -> None: ... @overload def __init__( diff --git a/buildconfig/stubs/pygame/display.pyi b/buildconfig/stubs/pygame/display.pyi index 94e2e389d1..eb9f29a5c1 100644 --- a/buildconfig/stubs/pygame/display.pyi +++ b/buildconfig/stubs/pygame/display.pyi @@ -6,13 +6,13 @@ from pygame.surface import Surface from pygame._sdl2 import Window -from ._common import ( - ColorValue, +from pygame.typing import ( + ColorLike, Coordinate, IntCoordinate, - RectValue, - RGBAOutput, - Sequence, + RectLike, + RGBATuple, + SequenceLike, ) class _VidInfo: @@ -21,9 +21,9 @@ class _VidInfo: video_mem: int bitsize: int bytesize: int - masks: RGBAOutput - shifts: RGBAOutput - losses: RGBAOutput + masks: RGBATuple + shifts: RGBATuple + losses: RGBATuple blit_hw: int blit_hw_CC: int blit_hw_A: int @@ -48,7 +48,7 @@ def get_surface() -> Surface: ... def flip() -> None: ... @overload def update( - rectangle: Optional[Union[RectValue, Iterable[Optional[RectValue]]]] = None, / + rectangle: Optional[Union[RectLike, Iterable[Optional[RectLike]]]] = None, / ) -> None: ... @overload def update(x: int, y: int, w: int, h: int, /) -> None: ... @@ -77,12 +77,12 @@ def toggle_fullscreen() -> int: ... def set_gamma(red: float, green: float = ..., blue: float = ..., /) -> int: ... @deprecated("since 2.1.4. Removed in SDL3") def set_gamma_ramp( - red: Sequence[int], green: Sequence[int], blue: Sequence[int], / + red: SequenceLike[int], green: SequenceLike[int], blue: SequenceLike[int], / ) -> int: ... def set_icon(surface: Surface, /) -> None: ... def set_caption(title: str, icontitle: Optional[str] = None, /) -> None: ... def get_caption() -> Tuple[str, str]: ... -def set_palette(palette: Sequence[ColorValue], /) -> None: ... +def set_palette(palette: SequenceLike[ColorLike], /) -> None: ... def get_num_displays() -> int: ... def get_window_size() -> Tuple[int, int]: ... def get_window_position() -> Tuple[int, int]:... @@ -99,7 +99,7 @@ def message_box( message: Optional[str] = None, message_type: Literal["info", "warn", "error"] = "info", parent_window: Optional[Window] = None, - buttons: Sequence[str] = ("OK",), + buttons: SequenceLike[str] = ("OK",), return_button: int = 0, escape_button: Optional[int] = None, ) -> int: ... diff --git a/buildconfig/stubs/pygame/draw.pyi b/buildconfig/stubs/pygame/draw.pyi index db2c345a4d..57bae66712 100644 --- a/buildconfig/stubs/pygame/draw.pyi +++ b/buildconfig/stubs/pygame/draw.pyi @@ -2,12 +2,12 @@ from pygame.rect import Rect from pygame.surface import Surface from typing import overload -from ._common import ColorValue, Coordinate, RectValue, Sequence +from pygame.typing import ColorLike, Coordinate, RectLike, SequenceLike def rect( surface: Surface, - color: ColorValue, - rect: RectValue, + color: ColorLike, + rect: RectLike, width: int = 0, border_radius: int = -1, border_top_left_radius: int = -1, @@ -17,13 +17,13 @@ def rect( ) -> Rect: ... def polygon( surface: Surface, - color: ColorValue, - points: Sequence[Coordinate], + color: ColorLike, + points: SequenceLike[Coordinate], width: int = 0, ) -> Rect: ... def circle( surface: Surface, - color: ColorValue, + color: ColorLike, center: Coordinate, radius: float, width: int = 0, @@ -35,7 +35,7 @@ def circle( @overload def aacircle( surface: Surface, - color: ColorValue, + color: ColorLike, center: Coordinate, radius: float, width: int = 0, @@ -43,7 +43,7 @@ def aacircle( @overload def aacircle( surface: Surface, - color: ColorValue, + color: ColorLike, center: Coordinate, radius: float, width: int = 0, @@ -53,39 +53,39 @@ def aacircle( draw_bottom_right: bool = False, ) -> Rect: ... def ellipse( - surface: Surface, color: ColorValue, rect: RectValue, width: int = 0 + surface: Surface, color: ColorLike, rect: RectLike, width: int = 0 ) -> Rect: ... def arc( surface: Surface, - color: ColorValue, - rect: RectValue, + color: ColorLike, + rect: RectLike, start_angle: float, stop_angle: float, width: int = 1, ) -> Rect: ... def line( surface: Surface, - color: ColorValue, + color: ColorLike, start_pos: Coordinate, end_pos: Coordinate, width: int = 1, ) -> Rect: ... def lines( surface: Surface, - color: ColorValue, + color: ColorLike, closed: bool, - points: Sequence[Coordinate], + points: SequenceLike[Coordinate], width: int = 1, ) -> Rect: ... def aaline( surface: Surface, - color: ColorValue, + color: ColorLike, start_pos: Coordinate, end_pos: Coordinate, ) -> Rect: ... def aalines( surface: Surface, - color: ColorValue, + color: ColorLike, closed: bool, - points: Sequence[Coordinate], + points: SequenceLike[Coordinate], ) -> Rect: ... diff --git a/buildconfig/stubs/pygame/event.pyi b/buildconfig/stubs/pygame/event.pyi index dc9a1c852f..1987f96de9 100644 --- a/buildconfig/stubs/pygame/event.pyi +++ b/buildconfig/stubs/pygame/event.pyi @@ -7,7 +7,7 @@ from typing import ( final, ) -from ._common import Sequence +from pygame.typing import SequenceLike @final class Event: @@ -23,7 +23,7 @@ class Event: def __delattr__(self, name: str) -> None: ... def __bool__(self) -> bool: ... -_EventTypes = Union[int, Sequence[int]] +_EventTypes = Union[int, SequenceLike[int]] def pump() -> None: ... def get( diff --git a/buildconfig/stubs/pygame/font.pyi b/buildconfig/stubs/pygame/font.pyi index 0312136de1..ea1074cee7 100644 --- a/buildconfig/stubs/pygame/font.pyi +++ b/buildconfig/stubs/pygame/font.pyi @@ -2,7 +2,7 @@ from typing import Callable, Hashable, Iterable, List, Literal, Optional, Tuple, from pygame.surface import Surface -from ._common import ColorValue, FileArg +from pygame.typing import ColorLike, FileLike # TODO: Figure out a way to type this attribute such that mypy knows it's not # always defined at runtime @@ -56,13 +56,13 @@ class Font: def point_size(self) -> int: ... @point_size.setter def point_size(self, value: int) -> None: ... - def __init__(self, filename: Optional[FileArg] = None, size: int = 20) -> None: ... + def __init__(self, filename: Optional[FileLike] = None, size: int = 20) -> None: ... def render( self, text: Union[str, bytes, None], antialias: bool, - color: ColorValue, - bgcolor: Optional[ColorValue] = None, + color: ColorLike, + bgcolor: Optional[ColorLike] = None, wraplength: int = 0, ) -> Surface: ... def size(self, text: Union[str, bytes], /) -> Tuple[int, int]: ... diff --git a/buildconfig/stubs/pygame/freetype.pyi b/buildconfig/stubs/pygame/freetype.pyi index 5f690d3150..8ce24499b8 100644 --- a/buildconfig/stubs/pygame/freetype.pyi +++ b/buildconfig/stubs/pygame/freetype.pyi @@ -5,7 +5,7 @@ from pygame.color import Color from pygame.rect import Rect from pygame.surface import Surface -from ._common import ColorValue, FileArg, RectValue +from pygame.typing import ColorLike, FileLike, RectLike def get_error() -> str: ... def get_version(linked: bool = True) -> Tuple[int, int, int]: ... @@ -123,18 +123,18 @@ class Font: @property def fgcolor(self) -> Color: ... @fgcolor.setter - def fgcolor(self, value: ColorValue) -> None: ... + def fgcolor(self, value: ColorLike) -> None: ... @property def bgcolor(self) -> Color: ... @bgcolor.setter - def bgcolor(self, value: ColorValue) -> None: ... + def bgcolor(self, value: ColorLike) -> None: ... @property def origin(self) -> bool: ... @origin.setter def origin(self, value: bool) -> None: ... def __init__( self, - file: Optional[FileArg], + file: Optional[FileLike], size: float = 0, font_index: int = 0, resolution: int = 0, @@ -158,8 +158,8 @@ class Font: def render( self, text: str, - fgcolor: Optional[ColorValue] = None, - bgcolor: Optional[ColorValue] = None, + fgcolor: Optional[ColorLike] = None, + bgcolor: Optional[ColorLike] = None, style: int = STYLE_DEFAULT, rotation: int = 0, size: float = 0, @@ -167,10 +167,10 @@ class Font: def render_to( self, surf: Surface, - dest: RectValue, + dest: RectLike, text: str, - fgcolor: Optional[ColorValue] = None, - bgcolor: Optional[ColorValue] = None, + fgcolor: Optional[ColorLike] = None, + bgcolor: Optional[ColorLike] = None, style: int = STYLE_DEFAULT, rotation: int = 0, size: float = 0, @@ -187,7 +187,7 @@ class Font: self, array: Any, text: str, - dest: Optional[RectValue] = None, + dest: Optional[RectLike] = None, style: int = STYLE_DEFAULT, rotation: int = 0, size: float = 0, diff --git a/buildconfig/stubs/pygame/geometry.pyi b/buildconfig/stubs/pygame/geometry.pyi index fb7c8de850..c82da74b2c 100644 --- a/buildconfig/stubs/pygame/geometry.pyi +++ b/buildconfig/stubs/pygame/geometry.pyi @@ -4,15 +4,14 @@ from typing import ( Callable, Protocol, Tuple, - Sequence, ) from pygame import Rect, FRect -from ._common import Coordinate, RectValue +from pygame.typing import Coordinate, RectLike, SequenceLike from .rect import Rect, FRect from .math import Vector2 -_CanBeCircle = Union[Circle, Tuple[Coordinate, float], Sequence[float]] +_CanBeCircle = Union[Circle, Tuple[Coordinate, float], SequenceLike[float]] class _HasCirclettribute(Protocol): # An object that has a circle attribute that is either a circle, or a function @@ -88,7 +87,7 @@ class Circle: @overload def collidecircle(self, center: Coordinate, r: float, /) -> bool: ... @overload - def colliderect(self, rect: RectValue, /) -> bool: ... + def colliderect(self, rect: RectLike, /) -> bool: ... @overload def colliderect(self, x: float, y: float, w: float, h: float, /) -> bool: ... @overload diff --git a/buildconfig/stubs/pygame/gfxdraw.pyi b/buildconfig/stubs/pygame/gfxdraw.pyi index d3592e6b7c..44a02d7d82 100644 --- a/buildconfig/stubs/pygame/gfxdraw.pyi +++ b/buildconfig/stubs/pygame/gfxdraw.pyi @@ -1,28 +1,28 @@ from pygame.surface import Surface -from ._common import ColorValue, Coordinate, RectValue, Sequence +from pygame.typing import ColorLike, Coordinate, RectLike, SequenceLike -def pixel(surface: Surface, x: int, y: int, color: ColorValue, /) -> None: ... -def hline(surface: Surface, x1: int, x2: int, y: int, color: ColorValue, /) -> None: ... -def vline(surface: Surface, x: int, y1: int, y2: int, color: ColorValue, /) -> None: ... +def pixel(surface: Surface, x: int, y: int, color: ColorLike, /) -> None: ... +def hline(surface: Surface, x1: int, x2: int, y: int, color: ColorLike, /) -> None: ... +def vline(surface: Surface, x: int, y1: int, y2: int, color: ColorLike, /) -> None: ... def line( - surface: Surface, x1: int, y1: int, x2: int, y2: int, color: ColorValue, / + surface: Surface, x1: int, y1: int, x2: int, y2: int, color: ColorLike, / ) -> None: ... -def rectangle(surface: Surface, rect: RectValue, color: ColorValue, /) -> None: ... -def box(surface: Surface, rect: RectValue, color: ColorValue, /) -> None: ... -def circle(surface: Surface, x: int, y: int, r: int, color: ColorValue, /) -> None: ... -def aacircle(surface: Surface, x: int, y: int, r: int, color: ColorValue, /) -> None: ... +def rectangle(surface: Surface, rect: RectLike, color: ColorLike, /) -> None: ... +def box(surface: Surface, rect: RectLike, color: ColorLike, /) -> None: ... +def circle(surface: Surface, x: int, y: int, r: int, color: ColorLike, /) -> None: ... +def aacircle(surface: Surface, x: int, y: int, r: int, color: ColorLike, /) -> None: ... def filled_circle( - surface: Surface, x: int, y: int, r: int, color: ColorValue, / + surface: Surface, x: int, y: int, r: int, color: ColorLike, / ) -> None: ... def ellipse( - surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorValue, / + surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorLike, / ) -> None: ... def aaellipse( - surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorValue, / + surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorLike, / ) -> None: ... def filled_ellipse( - surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorValue, / + surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorLike, / ) -> None: ... def arc( surface: Surface, @@ -31,7 +31,7 @@ def arc( r: int, start_angle: int, atp_angle: int, - color: ColorValue, / + color: ColorLike, / ) -> None: ... def pie( surface: Surface, @@ -40,7 +40,7 @@ def pie( r: int, start_angle: int, atp_angle: int, - color: ColorValue, / + color: ColorLike, / ) -> None: ... def trigon( surface: Surface, @@ -50,7 +50,7 @@ def trigon( y2: int, x3: int, y3: int, - color: ColorValue, / + color: ColorLike, / ) -> None: ... def aatrigon( surface: Surface, @@ -60,7 +60,7 @@ def aatrigon( y2: int, x3: int, y3: int, - color: ColorValue, / + color: ColorLike, / ) -> None: ... def filled_trigon( surface: Surface, @@ -70,20 +70,20 @@ def filled_trigon( y2: int, x3: int, y3: int, - color: ColorValue, / + color: ColorLike, / ) -> None: ... def polygon( - surface: Surface, points: Sequence[Coordinate], color: ColorValue, / + surface: Surface, points: SequenceLike[Coordinate], color: ColorLike, / ) -> None: ... def aapolygon( - surface: Surface, points: Sequence[Coordinate], color: ColorValue, / + surface: Surface, points: SequenceLike[Coordinate], color: ColorLike, / ) -> None: ... def filled_polygon( - surface: Surface, points: Sequence[Coordinate], color: ColorValue, / + surface: Surface, points: SequenceLike[Coordinate], color: ColorLike, / ) -> None: ... def textured_polygon( - surface: Surface, points: Sequence[Coordinate], texture: Surface, tx: int, ty: int, / + surface: Surface, points: SequenceLike[Coordinate], texture: Surface, tx: int, ty: int, / ) -> None: ... def bezier( - surface: Surface, points: Sequence[Coordinate], steps: int, color: ColorValue, / + surface: Surface, points: SequenceLike[Coordinate], steps: int, color: ColorLike, / ) -> None: ... diff --git a/buildconfig/stubs/pygame/image.pyi b/buildconfig/stubs/pygame/image.pyi index 7145b2ccd8..0e02f02271 100644 --- a/buildconfig/stubs/pygame/image.pyi +++ b/buildconfig/stubs/pygame/image.pyi @@ -4,7 +4,7 @@ from typing_extensions import deprecated # added in 3.13 from pygame.bufferproxy import BufferProxy from pygame.surface import Surface -from ._common import FileArg, IntCoordinate, Coordinate +from pygame.typing import FileLike, IntCoordinate, Coordinate _BufferStyle = Union[BufferProxy, bytes, bytearray, memoryview] _to_bytes_format = Literal[ @@ -13,9 +13,9 @@ _to_bytes_format = Literal[ _from_buffer_format = Literal["P", "RGB", "BGR", "BGRA", "RGBX", "RGBA", "ARGB"] _from_bytes_format = Literal["P", "RGB", "RGBX", "RGBA", "ARGB", "BGRA", "ABGR"] -def load(file: FileArg, namehint: str = "") -> Surface: ... -def load_sized_svg(file: FileArg, size: Coordinate) -> Surface: ... -def save(surface: Surface, file: FileArg, namehint: str = "") -> None: ... +def load(file: FileLike, namehint: str = "") -> Surface: ... +def load_sized_svg(file: FileLike, size: Coordinate) -> Surface: ... +def save(surface: Surface, file: FileLike, namehint: str = "") -> None: ... def get_sdl_image_version(linked: bool = True) -> Optional[Tuple[int, int, int]]: ... def get_extended() -> bool: ... @deprecated("since 2.3.0. Use `pygame.image.tobytes` instead") @@ -52,6 +52,6 @@ def frombuffer( format: _from_buffer_format, pitch: int = -1, ) -> Surface: ... -def load_basic(file: FileArg, /) -> Surface: ... -def load_extended(file: FileArg, namehint: str = "") -> Surface: ... -def save_extended(surface: Surface, file: FileArg, namehint: str = "") -> None: ... +def load_basic(file: FileLike, /) -> Surface: ... +def load_extended(file: FileLike, namehint: str = "") -> Surface: ... +def save_extended(surface: Surface, file: FileLike, namehint: str = "") -> None: ... diff --git a/buildconfig/stubs/pygame/key.pyi b/buildconfig/stubs/pygame/key.pyi index c55f1500d1..09aa380b35 100644 --- a/buildconfig/stubs/pygame/key.pyi +++ b/buildconfig/stubs/pygame/key.pyi @@ -1,6 +1,6 @@ from typing import Tuple -from ._common import RectValue +from pygame.typing import RectLike class ScancodeWrapper(Tuple[bool, ...]): ... @@ -16,4 +16,4 @@ def name(key: int, use_compat: bool = True) -> str: ... def key_code(name: str) -> int: ... def start_text_input() -> None: ... def stop_text_input() -> None: ... -def set_text_input_rect(rect: RectValue, /) -> None: ... +def set_text_input_rect(rect: RectLike, /) -> None: ... diff --git a/buildconfig/stubs/pygame/mask.pyi b/buildconfig/stubs/pygame/mask.pyi index 9ee01c9c07..0ea50adeb6 100644 --- a/buildconfig/stubs/pygame/mask.pyi +++ b/buildconfig/stubs/pygame/mask.pyi @@ -3,13 +3,13 @@ from typing import Any, List, Optional, Tuple, Union from pygame.rect import Rect from pygame.surface import Surface -from ._common import ColorValue, Coordinate, RectValue +from pygame.typing import ColorLike, Coordinate, RectLike def from_surface(surface: Surface, threshold: int = 127) -> Mask: ... def from_threshold( surface: Surface, - color: ColorValue, - threshold: ColorValue = (0, 0, 0, 255), + color: ColorLike, + threshold: ColorLike = (0, 0, 0, 255), othersurface: Optional[Surface] = None, palette_colors: int = 1, ) -> Mask: ... @@ -49,9 +49,9 @@ class Mask: surface: Optional[Surface] = None, setsurface: Optional[Surface] = None, unsetsurface: Optional[Surface] = None, - setcolor: Optional[ColorValue] = (255, 255, 255, 255), - unsetcolor: Optional[ColorValue] = (0, 0, 0, 255), - dest: Union[RectValue, Coordinate] = (0, 0), + setcolor: Optional[ColorLike] = (255, 255, 255, 255), + unsetcolor: Optional[ColorLike] = (0, 0, 0, 255), + dest: Union[RectLike, Coordinate] = (0, 0), ) -> Surface: ... MaskType = Mask diff --git a/buildconfig/stubs/pygame/math.pyi b/buildconfig/stubs/pygame/math.pyi index f307599c76..b994bcdab4 100644 --- a/buildconfig/stubs/pygame/math.pyi +++ b/buildconfig/stubs/pygame/math.pyi @@ -21,7 +21,7 @@ if sys.version_info >= (3, 9): else: from typing import Collection -from ._common import Sequence +from pygame.typing import SequenceLike def clamp(value: float, min: float, max: float, /) -> float: ... @@ -37,18 +37,18 @@ class _GenericVector(Collection[float]): @overload def __setitem__(self, key: int, value: float) -> None: ... @overload - def __setitem__(self, key: slice, value: Union[Sequence[float], _TVec]) -> None: ... + def __setitem__(self, key: slice, value: Union[SequenceLike[float], _TVec]) -> None: ... @overload def __getitem__(self, i: SupportsIndex) -> float: ... @overload def __getitem__(self, s: slice) -> List[float]: ... def __iter__(self) -> VectorIterator: ... - def __add__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ... - def __radd__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ... - def __sub__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ... - def __rsub__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ... + def __add__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ... + def __radd__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ... + def __sub__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ... + def __rsub__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ... @overload - def __mul__(self: _TVec, other: Union[Sequence[float], _TVec]) -> float: ... + def __mul__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> float: ... @overload def __mul__(self: _TVec, other: float) -> _TVec: ... def __rmul__(self: _TVec, other: float) -> _TVec: ... @@ -58,17 +58,17 @@ class _GenericVector(Collection[float]): def __neg__(self: _TVec) -> _TVec: ... def __pos__(self: _TVec) -> _TVec: ... def __bool__(self) -> bool: ... - def __iadd__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ... - def __isub__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ... + def __iadd__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ... + def __isub__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ... @overload - def __imul__(self: _TVec, other: Union[Sequence[float], _TVec]) -> float: ... + def __imul__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> float: ... @overload def __imul__(self: _TVec, other: float) -> _TVec: ... def __copy__(self: _TVec) -> _TVec: ... copy = __copy__ def __safe_for_unpickling__(self) -> Literal[True]: ... def __contains__(self, other: float) -> bool: ... # type: ignore[override] - def dot(self: _TVec, other: Union[Sequence[float], _TVec], /) -> float: ... + def dot(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> float: ... def magnitude(self) -> float: ... def magnitude_squared(self) -> float: ... def length(self) -> float: ... @@ -77,37 +77,37 @@ class _GenericVector(Collection[float]): def normalize_ip(self) -> None: ... def is_normalized(self) -> bool: ... def scale_to_length(self, value: float, /) -> None: ... - def reflect(self: _TVec, other: Union[Sequence[float], _TVec], /) -> _TVec: ... - def reflect_ip(self: _TVec, other: Union[Sequence[float], _TVec], /) -> None: ... - def distance_to(self: _TVec, other: Union[Sequence[float], _TVec], /) -> float: ... + def reflect(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> _TVec: ... + def reflect_ip(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> None: ... + def distance_to(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> float: ... def distance_squared_to( - self: _TVec, other: Union[Sequence[float], _TVec], / + self: _TVec, other: Union[SequenceLike[float], _TVec], / ) -> float: ... def lerp( self: _TVec, - other: Union[Sequence[float], _TVec], + other: Union[SequenceLike[float], _TVec], value: float, / ) -> _TVec: ... def slerp( self: _TVec, - other: Union[Sequence[float], _TVec], + other: Union[SequenceLike[float], _TVec], value: float, / ) -> _TVec: ... def smoothstep( self: _TVec, - other: Union[Sequence[float], _TVec], + other: Union[SequenceLike[float], _TVec], value: float, / ) -> _TVec: ... def elementwise(self: _TVec) -> VectorElementwiseProxy[_TVec]: ... - def angle_to(self: _TVec, other: Union[Sequence[float], _TVec], /) -> float: ... + def angle_to(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> float: ... def move_towards( self: _TVec, - target: Union[Sequence[float], _TVec], + target: Union[SequenceLike[float], _TVec], max_distance: float, / ) -> _TVec: ... def move_towards_ip( self: _TVec, - target: Union[Sequence[float], _TVec], + target: Union[SequenceLike[float], _TVec], max_distance: float, / ) -> None: ... @overload @@ -120,7 +120,7 @@ class _GenericVector(Collection[float]): def clamp_magnitude_ip(self, max_length: float, /) -> None: ... @overload def clamp_magnitude_ip(self, min_length: float, max_length: float, /) -> None: ... - def project(self: _TVec, other: Union[Sequence[float], _TVec], /) -> _TVec: ... + def project(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> _TVec: ... def __round__(self: _TVec, ndigits: Optional[int] = None, /) -> _TVec: ... # VectorElementwiseProxy is a generic, it can be an elementwiseproxy object for @@ -224,7 +224,7 @@ class Vector2(_GenericVector): @overload def __init__( self: _TVec, - x: Union[str, float, Sequence[float], _TVec] = 0, + x: Union[str, float, SequenceLike[float], _TVec] = 0, ) -> None: ... @overload def __init__(self, x: float, y: float) -> None: ... @@ -235,13 +235,13 @@ class Vector2(_GenericVector): def rotate_rad_ip(self, angle: float, /) -> None: ... @deprecated("since 2.1.1. Use `pygame.Vector2.rotate_rad_ip` instead") def rotate_ip_rad(self, angle: float, /) -> None: ... - def cross(self: _TVec, other: Union[Sequence[float], _TVec], /) -> float: ... + def cross(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> float: ... def as_polar(self) -> Tuple[float, float]: ... - def from_polar(self, polar_value: Sequence[float], /) -> None: ... + def from_polar(self, polar_value: SequenceLike[float], /) -> None: ... @overload def update( self: _TVec, - x: Union[str, float, Sequence[float], _TVec] = 0, + x: Union[str, float, SequenceLike[float], _TVec] = 0, ) -> None: ... @overload def update(self, x: float = 0, y: float = 0) -> None: ... @@ -289,27 +289,27 @@ class Vector3(_GenericVector): @overload def __init__( self: _TVec, - x: Union[str, float, Sequence[float], _TVec] = 0, + x: Union[str, float, SequenceLike[float], _TVec] = 0, ) -> None: ... @overload def __init__(self, x: float, y: float, z: float) -> None: ... def __reduce__(self: _TVec) -> Tuple[Type[_TVec], Tuple[float, float, float]]: ... - def cross(self: _TVec, other: Union[Sequence[float], _TVec], /) -> _TVec: ... + def cross(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> _TVec: ... def rotate( - self: _TVec, angle: float, axis: Union[Sequence[float], _TVec], / + self: _TVec, angle: float, axis: Union[SequenceLike[float], _TVec], / ) -> _TVec: ... def rotate_rad( - self: _TVec, angle: float, axis: Union[Sequence[float], _TVec], / + self: _TVec, angle: float, axis: Union[SequenceLike[float], _TVec], / ) -> _TVec: ... def rotate_ip( - self: _TVec, angle: float, axis: Union[Sequence[float], _TVec], / + self: _TVec, angle: float, axis: Union[SequenceLike[float], _TVec], / ) -> None: ... def rotate_rad_ip( - self: _TVec, angle: float, axis: Union[Sequence[float], _TVec], / + self: _TVec, angle: float, axis: Union[SequenceLike[float], _TVec], / ) -> None: ... @deprecated("since 2.1.1. Use `pygame.Vector3.rotate_rad_ip` instead") def rotate_ip_rad( - self: _TVec, angle: float, axis: Union[Sequence[float], _TVec], / + self: _TVec, angle: float, axis: Union[SequenceLike[float], _TVec], / ) -> None: ... def rotate_x(self: _TVec, angle: float, /) -> _TVec: ... def rotate_x_rad(self: _TVec, angle: float, /) -> _TVec: ... @@ -334,7 +334,7 @@ class Vector3(_GenericVector): @overload def update( self: _TVec, - x: Union[str, float, Sequence[float], _TVec] = 0, + x: Union[str, float, SequenceLike[float], _TVec] = 0, ) -> None: ... @overload def update(self, x: int, y: int, z: int) -> None: ... diff --git a/buildconfig/stubs/pygame/midi.pyi b/buildconfig/stubs/pygame/midi.pyi index 028342508c..22fc749f80 100644 --- a/buildconfig/stubs/pygame/midi.pyi +++ b/buildconfig/stubs/pygame/midi.pyi @@ -1,7 +1,7 @@ from typing import List, Tuple, Union from pygame.event import Event -from ._common import Sequence +from pygame.typing import SequenceLike MIDIIN: int MIDIOUT: int @@ -17,7 +17,7 @@ def get_default_input_id() -> int: ... def get_default_output_id() -> int: ... def get_device_info(an_id: int) -> Tuple[str, str, int, int, int]: ... def midis2events( - midis: Sequence[Sequence[Union[Sequence[int], int]]], device_id: int + midis: SequenceLike[SequenceLike[Union[SequenceLike[int], int]]], device_id: int ) -> List[Event]: ... def time() -> int: ... def frequency_to_midi(frequency: float) -> int: ... diff --git a/buildconfig/stubs/pygame/mixer.pyi b/buildconfig/stubs/pygame/mixer.pyi index 4d1cafc830..84646b419b 100644 --- a/buildconfig/stubs/pygame/mixer.pyi +++ b/buildconfig/stubs/pygame/mixer.pyi @@ -5,7 +5,7 @@ import numpy from pygame.event import Event from . import mixer_music -from ._common import FileArg +from pygame.typing import FileLike # export mixer_music as mixer.music music = mixer_music @@ -44,7 +44,7 @@ def get_sdl_mixer_version(linked: bool = True) -> Tuple[int, int, int]: ... class Sound: @overload - def __init__(self, file: FileArg) -> None: ... + def __init__(self, file: FileLike) -> None: ... @overload def __init__( self, buffer: Any diff --git a/buildconfig/stubs/pygame/mixer_music.pyi b/buildconfig/stubs/pygame/mixer_music.pyi index 08b2e8a11a..40cf46e2a9 100644 --- a/buildconfig/stubs/pygame/mixer_music.pyi +++ b/buildconfig/stubs/pygame/mixer_music.pyi @@ -1,8 +1,8 @@ from typing import Optional, Dict -from ._common import FileArg +from pygame.typing import FileLike -def load(filename: FileArg, namehint: Optional[str] = "") -> None: ... +def load(filename: FileLike, namehint: Optional[str] = "") -> None: ... def unload() -> None: ... def play(loops: int = 0, start: float = 0.0, fade_ms: int = 0) -> None: ... def rewind() -> None: ... @@ -15,7 +15,7 @@ def get_volume() -> float: ... def get_busy() -> bool: ... def set_pos(pos: float, /) -> None: ... def get_pos() -> int: ... -def queue(filename: FileArg, namehint: str = "", loops: int = 0) -> None: ... +def queue(filename: FileLike, namehint: str = "", loops: int = 0) -> None: ... def set_endevent(event_type: int, /) -> None: ... def get_endevent() -> int: ... -def get_metadata(filename: Optional[FileArg] = None, namehint: str = "") -> Dict[str, str]: ... +def get_metadata(filename: Optional[FileLike] = None, namehint: str = "") -> Dict[str, str]: ... diff --git a/buildconfig/stubs/pygame/mouse.pyi b/buildconfig/stubs/pygame/mouse.pyi index c3d3a64548..42ea298b45 100644 --- a/buildconfig/stubs/pygame/mouse.pyi +++ b/buildconfig/stubs/pygame/mouse.pyi @@ -4,7 +4,7 @@ from typing_extensions import deprecated # added in 3.13 from pygame.cursors import Cursor from pygame.surface import Surface -from ._common import Coordinate, Sequence, IntCoordinate +from pygame.typing import Coordinate, SequenceLike, IntCoordinate @overload def get_pressed(num_buttons: Literal[3] = 3) -> Tuple[bool, bool, bool]: ... @@ -29,8 +29,8 @@ def set_cursor(constant: int) -> None: ... def set_cursor( size: IntCoordinate, hotspot: IntCoordinate, - xormasks: Sequence[int], - andmasks: Sequence[int], + xormasks: SequenceLike[int], + andmasks: SequenceLike[int], ) -> None: ... @overload def set_cursor(hotspot: IntCoordinate, surface: Surface) -> None: ... diff --git a/buildconfig/stubs/pygame/pixelarray.pyi b/buildconfig/stubs/pygame/pixelarray.pyi index 935d4c01d6..a10bb66e42 100644 --- a/buildconfig/stubs/pygame/pixelarray.pyi +++ b/buildconfig/stubs/pygame/pixelarray.pyi @@ -2,7 +2,7 @@ from typing import Any, Dict, Tuple, Union, overload from pygame.surface import Surface -from ._common import ColorValue, Sequence +from pygame.typing import ColorLike, SequenceLike class PixelArray: surface: Surface @@ -34,22 +34,22 @@ class PixelArray: def make_surface(self) -> Surface: ... def replace( self, - color: ColorValue, - repcolor: ColorValue, + color: ColorLike, + repcolor: ColorLike, distance: float = 0, - weights: Sequence[float] = (0.299, 0.587, 0.114), + weights: SequenceLike[float] = (0.299, 0.587, 0.114), ) -> None: ... def extract( self, - color: ColorValue, + color: ColorLike, distance: float = 0, - weights: Sequence[float] = (0.299, 0.587, 0.114), + weights: SequenceLike[float] = (0.299, 0.587, 0.114), ) -> PixelArray: ... def compare( self, array: PixelArray, distance: float = 0, - weights: Sequence[float] = (0.299, 0.587, 0.114), + weights: SequenceLike[float] = (0.299, 0.587, 0.114), ) -> PixelArray: ... def transpose(self) -> PixelArray: ... def close(self) -> PixelArray: ... diff --git a/buildconfig/stubs/pygame/rect.pyi b/buildconfig/stubs/pygame/rect.pyi index 6cb28ca201..9899c84270 100644 --- a/buildconfig/stubs/pygame/rect.pyi +++ b/buildconfig/stubs/pygame/rect.pyi @@ -12,7 +12,7 @@ from typing import ( Optional, ) -from ._common import Coordinate, RectValue, Sequence +from pygame.typing import Coordinate, RectLike, SequenceLike if sys.version_info >= (3, 11): from typing import Self @@ -29,7 +29,7 @@ _K = TypeVar("_K") _V = TypeVar("_V") _T = TypeVar("_T") -_RectTypeCompatible_co = TypeVar("_RectTypeCompatible_co", bound=RectValue, covariant=True) +_RectTypeCompatible_co = TypeVar("_RectTypeCompatible_co", bound=RectLike, covariant=True) class _GenericRect(Collection[_N]): @property @@ -129,7 +129,7 @@ class _GenericRect(Collection[_N]): @overload def __init__(self, left_top: Coordinate, width_height: Coordinate) -> None: ... @overload - def __init__(self, single_arg: RectValue) -> None: ... + def __init__(self, single_arg: RectLike) -> None: ... @overload def __init__(self) -> None: ... def __len__(self) -> Literal[4]: ... @@ -141,7 +141,7 @@ class _GenericRect(Collection[_N]): @overload def __setitem__(self, key: int, value: float) -> None: ... @overload - def __setitem__(self, key: slice, value: Union[float, RectValue]) -> None: ... + def __setitem__(self, key: slice, value: Union[float, RectLike]) -> None: ... def __copy__(self) -> Self: ... copy = __copy__ @overload @@ -174,15 +174,15 @@ class _GenericRect(Collection[_N]): @overload def update(self, left_top: Coordinate, width_height: Coordinate, /) -> None: ... @overload - def update(self, single_arg: RectValue, /) -> None: ... + def update(self, single_arg: RectLike, /) -> None: ... @overload - def clamp(self, rect: RectValue, /) -> Self: ... + def clamp(self, rect: RectLike, /) -> Self: ... @overload def clamp(self, left_top: Coordinate, width_height: Coordinate, /) -> Self: ... @overload def clamp(self, left: float, top: float, width: float, height: float, /) -> Self: ... @overload - def clamp_ip(self, rect: RectValue, /) -> None: ... + def clamp_ip(self, rect: RectLike, /) -> None: ... @overload def clamp_ip(self, left_top: Coordinate, width_height: Coordinate, /) -> None: ... @overload @@ -190,7 +190,7 @@ class _GenericRect(Collection[_N]): self, left: float, top: float, width: float, height: float, / ) -> None: ... @overload - def clip(self, rect: RectValue, /) -> Self: ... + def clip(self, rect: RectLike, /) -> Self: ... @overload def clip(self, left_top: Coordinate, width_height: Coordinate, /) -> Self: ... @overload @@ -205,34 +205,34 @@ class _GenericRect(Collection[_N]): ) -> Union[Tuple[Tuple[_N, _N], Tuple[_N, _N]], Tuple[()]]: ... @overload def clipline( - self, rect_arg: RectValue, / + self, rect_arg: RectLike, / ) -> Union[Tuple[Tuple[_N, _N], Tuple[_N, _N]], Tuple[()]]: ... @overload - def union(self, rect: RectValue, /) -> Self: ... + def union(self, rect: RectLike, /) -> Self: ... @overload def union(self, left_top: Coordinate, width_height: Coordinate, /) -> Self: ... @overload def union(self, left: float, top: float, width: float, height: float, /) -> Self: ... @overload - def union_ip(self, rect: RectValue, /) -> None: ... + def union_ip(self, rect: RectLike, /) -> None: ... @overload def union_ip(self, left_top: Coordinate, width_height: Coordinate, /) -> None: ... @overload def union_ip( self, left: float, top: float, width: float, height: float, / ) -> None: ... - def unionall(self, rect: Sequence[_RectTypeCompatible_co], /) -> Self: ... - def unionall_ip(self, rect_sequence: Sequence[_RectTypeCompatible_co], /) -> None: ... + def unionall(self, rect: SequenceLike[_RectTypeCompatible_co], /) -> Self: ... + def unionall_ip(self, rect_SequenceLike: SequenceLike[_RectTypeCompatible_co], /) -> None: ... @overload - def fit(self, rect: RectValue, /) -> Self: ... + def fit(self, rect: RectLike, /) -> Self: ... @overload def fit(self, left_top: Coordinate, width_height: Coordinate, /) -> Self: ... @overload def fit(self, left: float, top: float, width: float, height: float, /) -> Self: ... def normalize(self) -> None: ... - def __contains__(self, rect: Union[RectValue, _N], /) -> bool: ... # type: ignore[override] + def __contains__(self, rect: Union[RectLike, _N], /) -> bool: ... # type: ignore[override] @overload - def contains(self, rect: RectValue, /) -> bool: ... + def contains(self, rect: RectLike, /) -> bool: ... @overload def contains(self, left_top: Coordinate, width_height: Coordinate, /) -> bool: ... @overload @@ -244,20 +244,20 @@ class _GenericRect(Collection[_N]): @overload def collidepoint(self, x_y: Coordinate, /) -> bool: ... @overload - def colliderect(self, rect: RectValue, /) -> bool: ... + def colliderect(self, rect: RectLike, /) -> bool: ... @overload def colliderect(self, left_top: Coordinate, width_height: Coordinate, /) -> bool: ... @overload def colliderect( self, left: float, top: float, width: float, height: float, / ) -> bool: ... - def collidelist(self, rect_list: Sequence[_RectTypeCompatible_co], /) -> int: ... - def collidelistall(self, rect_list: Sequence[_RectTypeCompatible_co], /) -> List[int]: ... + def collidelist(self, rect_list: SequenceLike[_RectTypeCompatible_co], /) -> int: ... + def collidelistall(self, rect_list: SequenceLike[_RectTypeCompatible_co], /) -> List[int]: ... def collideobjectsall( - self, objects: Sequence[_T], key: Optional[Callable[[_T], RectValue]] = None + self, objects: SequenceLike[_T], key: Optional[Callable[[_T], RectLike]] = None ) -> List[_T]: ... def collideobjects( - self, objects: Sequence[_T], key: Optional[Callable[[_T], RectValue]] = None + self, objects: SequenceLike[_T], key: Optional[Callable[[_T], RectLike]] = None ) -> Optional[_T]: ... @overload def collidedict( diff --git a/buildconfig/stubs/pygame/rwobject.pyi b/buildconfig/stubs/pygame/rwobject.pyi index 3688279b6c..38161fab81 100644 --- a/buildconfig/stubs/pygame/rwobject.pyi +++ b/buildconfig/stubs/pygame/rwobject.pyi @@ -1,16 +1,16 @@ from typing import Any, Optional, overload, Type -from ._common import AnyPath +from pygame.typing import PathLike def encode_string( - obj: Optional[AnyPath], + obj: Optional[PathLike], encoding: Optional[str] = "unicode_escape", errors: Optional[str] = "backslashreplace", etype: Optional[Type[Exception]] = UnicodeEncodeError, ) -> bytes: ... @overload def encode_file_path( - obj: Optional[AnyPath], etype: Optional[Type[Exception]] = UnicodeEncodeError + obj: Optional[PathLike], etype: Optional[Type[Exception]] = UnicodeEncodeError ) -> bytes: ... @overload def encode_file_path( diff --git a/buildconfig/stubs/pygame/sprite.pyi b/buildconfig/stubs/pygame/sprite.pyi index 7a96dadc44..9185018c1c 100644 --- a/buildconfig/stubs/pygame/sprite.pyi +++ b/buildconfig/stubs/pygame/sprite.pyi @@ -19,7 +19,7 @@ from pygame.rect import FRect, Rect from pygame.surface import Surface from pygame.mask import Mask -from ._common import RectValue, Coordinate +from pygame.typing import RectLike, Coordinate # non-generic Group, used in Sprite _Group = AbstractGroup[_SpriteSupportsGroup] @@ -232,8 +232,8 @@ class LayeredDirty(LayeredUpdates[_TDirtySprite]): ) -> List[Union[FRect, Rect]]: ... # clear breaks Liskov substitution principle in code def clear(self, surface: Surface, bgd: Surface) -> None: ... # type: ignore[override] - def repaint_rect(self, screen_rect: RectValue) -> None: ... - def set_clip(self, screen_rect: Optional[RectValue] = None) -> None: ... + def repaint_rect(self, screen_rect: RectLike) -> None: ... + def set_clip(self, screen_rect: Optional[RectLike] = None) -> None: ... def get_clip(self) -> Union[FRect, Rect]: ... def set_timing_threshold( self, time_ms: SupportsFloat diff --git a/buildconfig/stubs/pygame/surface.pyi b/buildconfig/stubs/pygame/surface.pyi index 7539ea9311..03bb9360db 100644 --- a/buildconfig/stubs/pygame/surface.pyi +++ b/buildconfig/stubs/pygame/surface.pyi @@ -5,12 +5,12 @@ from pygame.bufferproxy import BufferProxy from pygame.color import Color from pygame.rect import FRect, Rect -from ._common import ( - ColorValue, +from pygame.typing import ( + ColorLike, Coordinate, - RectValue, - RGBAOutput, - Sequence, + RectLike, + RGBATuple, + SequenceLike, ) _ViewKind = Literal[ @@ -54,7 +54,7 @@ class Surface: size: Coordinate, flags: int = 0, depth: int = 0, - masks: Optional[ColorValue] = None, + masks: Optional[ColorLike] = None, ) -> None: ... @overload def __init__( @@ -69,24 +69,24 @@ class Surface: def blit( self, source: Surface, - dest: Union[Coordinate, RectValue] = (0, 0), - area: Optional[RectValue] = None, + dest: Union[Coordinate, RectLike] = (0, 0), + area: Optional[RectLike] = None, special_flags: int = 0, ) -> Rect: ... def blits( self, - blit_sequence: Iterable[ + blit_SequenceLike: Iterable[ Union[ - Tuple[Surface, Union[Coordinate, RectValue]], - Tuple[Surface, Union[Coordinate, RectValue], Union[RectValue, int]], - Tuple[Surface, Union[Coordinate, RectValue], RectValue, int], + Tuple[Surface, Union[Coordinate, RectLike]], + Tuple[Surface, Union[Coordinate, RectLike], Union[RectLike, int]], + Tuple[Surface, Union[Coordinate, RectLike], RectLike, int], ] ], doreturn: Union[int, bool] = 1, ) -> Union[List[Rect], None]: ... def fblits( self, - blit_sequence: Iterable[Tuple[Surface, Union[Coordinate, RectValue]]], + blit_SequenceLike: Iterable[Tuple[Surface, Union[Coordinate, RectLike]]], special_flags: int = 0, / ) -> None: ... @overload @@ -94,22 +94,22 @@ class Surface: @overload def convert(self, depth: int, flags: int = 0, /) -> Surface: ... @overload - def convert(self, masks: ColorValue, flags: int = 0, /) -> Surface: ... + def convert(self, masks: ColorLike, flags: int = 0, /) -> Surface: ... @overload def convert(self) -> Surface: ... def convert_alpha(self) -> Surface: ... def fill( self, - color: ColorValue, - rect: Optional[RectValue] = None, + color: ColorLike, + rect: Optional[RectLike] = None, special_flags: int = 0, ) -> Rect: ... def scroll(self, dx: int = 0, dy: int = 0, /) -> None: ... @overload - def set_colorkey(self, color: ColorValue, flags: int = 0, /) -> None: ... + def set_colorkey(self, color: ColorLike, flags: int = 0, /) -> None: ... @overload def set_colorkey(self, color: None, /) -> None: ... - def get_colorkey(self) -> Optional[RGBAOutput]: ... + def get_colorkey(self) -> Optional[RGBATuple]: ... @overload def set_alpha(self, value: int, flags: int = 0, /) -> None: ... @overload @@ -121,18 +121,18 @@ class Surface: def get_locked(self) -> bool: ... def get_locks(self) -> Tuple[Any, ...]: ... def get_at(self, x_y: Coordinate, /) -> Color: ... - def set_at(self, x_y: Coordinate, color: ColorValue, /) -> None: ... + def set_at(self, x_y: Coordinate, color: ColorLike, /) -> None: ... def get_at_mapped(self, x_y: Coordinate, /) -> int: ... def get_palette(self) -> List[Color]: ... def get_palette_at(self, index: int, /) -> Color: ... - def set_palette(self, palette: Sequence[ColorValue], /) -> None: ... - def set_palette_at(self, index: int, color: ColorValue, /) -> None: ... - def map_rgb(self, color: ColorValue, /) -> int: ... + def set_palette(self, palette: SequenceLike[ColorLike], /) -> None: ... + def set_palette_at(self, index: int, color: ColorLike, /) -> None: ... + def map_rgb(self, color: ColorLike, /) -> int: ... def unmap_rgb(self, mapped_int: int, /) -> Color: ... - def set_clip(self, rect: Optional[RectValue], /) -> None: ... + def set_clip(self, rect: Optional[RectLike], /) -> None: ... def get_clip(self) -> Rect: ... @overload - def subsurface(self, rect: RectValue, /) -> Surface: ... + def subsurface(self, rect: RectLike, /) -> Surface: ... @overload def subsurface(self, left_top: Coordinate, width_height: Coordinate, /) -> Surface: ... @overload @@ -152,13 +152,13 @@ class Surface: def get_bytesize(self) -> int: ... def get_flags(self) -> int: ... def get_pitch(self) -> int: ... - def get_masks(self) -> RGBAOutput: ... + def get_masks(self) -> RGBATuple: ... @deprecated("since 2.0.0. Immutable in SDL2") - def set_masks(self, color: ColorValue, /) -> None: ... - def get_shifts(self) -> RGBAOutput: ... + def set_masks(self, color: ColorLike, /) -> None: ... + def get_shifts(self) -> RGBATuple: ... @deprecated("since 2.0.0. Immutable in SDL2") - def set_shifts(self, color: ColorValue, /) -> None: ... - def get_losses(self) -> RGBAOutput: ... + def set_shifts(self, color: ColorLike, /) -> None: ... + def get_losses(self) -> RGBATuple: ... def get_bounding_rect(self, min_alpha: int = 1) -> Rect: ... def get_view(self, kind: _ViewKind = "2", /) -> BufferProxy: ... def get_buffer(self) -> BufferProxy: ... diff --git a/buildconfig/stubs/pygame/transform.pyi b/buildconfig/stubs/pygame/transform.pyi index fd412ec948..64a6fabf16 100644 --- a/buildconfig/stubs/pygame/transform.pyi +++ b/buildconfig/stubs/pygame/transform.pyi @@ -3,7 +3,7 @@ from typing import Optional, Union, Literal from pygame.color import Color from pygame.surface import Surface -from ._common import ColorValue, Coordinate, RectValue, Sequence +from pygame.typing import ColorLike, Coordinate, RectLike, SequenceLike def flip(surface: Surface, flip_x: bool, flip_y: bool) -> Surface: ... def scale( @@ -13,7 +13,7 @@ def scale( ) -> Surface: ... def scale_by( surface: Surface, - factor: Union[float, Sequence[float]], + factor: Union[float, SequenceLike[float]], dest_surface: Optional[Surface] = None, ) -> Surface: ... def rotate(surface: Surface, angle: float) -> Surface: ... @@ -27,28 +27,28 @@ def smoothscale( ) -> Surface: ... def smoothscale_by( surface: Surface, - factor: Union[float, Sequence[float]], + factor: Union[float, SequenceLike[float]], dest_surface: Optional[Surface] = None, ) -> Surface: ... def get_smoothscale_backend() -> Literal["GENERIC", "SSE2", "NEON"]: ... def set_smoothscale_backend(backend: Literal["GENERIC", "SSE2", "NEON"]) -> None: ... -def chop(surface: Surface, rect: RectValue) -> Surface: ... +def chop(surface: Surface, rect: RectLike) -> Surface: ... def laplacian(surface: Surface, dest_surface: Optional[Surface] = None) -> Surface: ... def invert(surface: Surface, dest_surface: Optional[Surface] = None) -> Surface: ... def average_surfaces( - surfaces: Sequence[Surface], + surfaces: SequenceLike[Surface], dest_surface: Optional[Surface] = None, palette_colors: Union[bool, int] = 1, ) -> Surface: ... def average_color( - surface: Surface, rect: Optional[RectValue] = None, consider_alpha: bool = False + surface: Surface, rect: Optional[RectLike] = None, consider_alpha: bool = False ) -> Color: ... def threshold( dest_surface: Optional[Surface], surface: Surface, - search_color: Optional[ColorValue], - threshold: ColorValue = (0, 0, 0, 0), - set_color: Optional[ColorValue] = (0, 0, 0, 0), + search_color: Optional[ColorLike], + threshold: ColorLike = (0, 0, 0, 0), + set_color: Optional[ColorLike] = (0, 0, 0, 0), set_behavior: int = 1, search_surf: Optional[Surface] = None, inverse_set: bool = False, diff --git a/buildconfig/stubs/pygame/typing.pyi b/buildconfig/stubs/pygame/typing.pyi new file mode 100644 index 0000000000..883fe2def1 --- /dev/null +++ b/buildconfig/stubs/pygame/typing.pyi @@ -0,0 +1,59 @@ +"""Set of common pygame type aliases for proper typehint annotations""" + +# NOTE: `src_py/typing.py` and `buildconfig/stubs/pygame/typing.pyi` must be duplicates. +# Use the command `python buildconfig/stubs/gen_stubs.py` to copy typing.py to typing.pyi + +import sys +from typing import IO, Callable, Tuple, Union, TypeVar, Protocol, SupportsIndex + +if sys.version_info >= (3, 9): + from os import PathLike as _PathProtocol +else: + _T = TypeVar("_T", bound=Union[str, bytes]) + + class _PathProtocol(Protocol[_T]): + def __fspath__(self) -> _T: ... + + +# For functions that take a file name +PathLike = Union[str, bytes, _PathProtocol[str], _PathProtocol[bytes]] +# Most pygame functions that take a file argument should be able to handle a FileLike type +FileLike = Union[PathLike, IO[bytes], IO[str]] + +_T_co = TypeVar("_T_co", covariant=True) + + +class SequenceLike(Protocol[_T_co]): + """ + Variant of the standard `Sequence` ABC that only requires `__getitem__` and `__len__`. + """ + + def __getitem__(self, __i: SupportsIndex) -> _T_co: ... + def __len__(self) -> int: ... + + +# Modify typehints when it is possible to annotate sizes + +# Pygame handles float without errors in most cases where a coordinate is expected, +# usually rounding to int. Also, 'Union[int, float] == float' +Coordinate = SequenceLike[float] +# This is used where ints are strictly required +IntCoordinate = SequenceLike[int] + +# Used for functions that return an RGBA tuple +RGBATuple = Tuple[int, int, int, int] +ColorLike = Union[int, str, SequenceLike[int]] + +_CanBeRect = SequenceLike[Union[float, Coordinate]] + + +class _HasRectAttribute(Protocol): + # An object that has a rect attribute that is either a rect, or a function + # that returns a rect conforms to the rect protocol + rect: Union["RectLike", Callable[[], "RectLike"]] + + +RectLike = Union[_CanBeRect, _HasRectAttribute] + +# cleanup namespace +del sys, IO, Callable, Tuple, Union, TypeVar, Protocol, SupportsIndex diff --git a/buildconfig/stubs/pygame/window.pyi b/buildconfig/stubs/pygame/window.pyi index 1b18fa06cd..ab3e6aff30 100644 --- a/buildconfig/stubs/pygame/window.pyi +++ b/buildconfig/stubs/pygame/window.pyi @@ -1,7 +1,7 @@ from typing import Optional, Tuple, Union from typing_extensions import deprecated # added in 3.13 -from pygame._common import Coordinate, RectValue +from pygame.typing import Coordinate, RectLike from pygame.locals import WINDOWPOS_UNDEFINED from pygame.rect import Rect from pygame.surface import Surface @@ -48,7 +48,7 @@ class Window: @property def mouse_rect(self) -> Optional[Rect]: ... @mouse_rect.setter - def mouse_rect(self, value: Optional[RectValue]) -> None: ... + def mouse_rect(self, value: Optional[RectLike]) -> None: ... @property def size(self) -> Tuple[int, int]: ... @size.setter diff --git a/buildconfig/stubs/stubcheck.py b/buildconfig/stubs/stubcheck.py index 14477a31d5..1389e07595 100644 --- a/buildconfig/stubs/stubcheck.py +++ b/buildconfig/stubs/stubcheck.py @@ -11,9 +11,34 @@ STUBS_BASE_DIR = Path(__file__).parent -def main(): +def typing_check(): """ - Main entrypoint + Ensure type aliases in typing.py work as expected with type checkers + """ + mypy_version_args = [sys.executable, "-m", "mypy"] + try: + version = subprocess.run( + [*mypy_version_args, "--version"], capture_output=True, check=True, text=True + ).stdout.strip() + except subprocess.CalledProcessError: + print("ERROR: could not validate typing.py, make sure you have mypy installed") + return + + mypy_args = [*mypy_version_args, "typing_sample_app.py"] + cmd = " ".join(mypy_args) + print(f"Using mypy invocation: `{cmd}` (version: {version})") + prev_dir = os.getcwd() + try: + os.chdir(STUBS_BASE_DIR) + returncode = subprocess.run([*mypy_args]).returncode + if returncode != 0: + raise RuntimeError(f"mypy process finished with unsuccessful return code {returncode}") + finally: + os.chdir(prev_dir) + +def stubs_check(): + """ + Validate the stubs files """ for stubtest in ([sys.executable, "-m", "mypy.stubtest"], ["stubtest"]): try: @@ -41,5 +66,13 @@ def main(): sys.exit(1) +def main(): + """ + Main entrypoint + """ + typing_check() + stubs_check() + + if __name__ == "__main__": main() diff --git a/buildconfig/stubs/typing_sample_app.py b/buildconfig/stubs/typing_sample_app.py new file mode 100644 index 0000000000..dae94bd0ac --- /dev/null +++ b/buildconfig/stubs/typing_sample_app.py @@ -0,0 +1,109 @@ +""" +Sample app run by mypy to ensure typing.py aliases work as expected +""" +from pygame import typing +import pygame +import pathlib + +# validate SequenceLike +class MySequence: + def __getitem__(self, index): + if index > 20: + raise IndexError() + if index % 2 == 0: + return 1 + return 0 + + def __len__(self): + return 20 + +def validator_SequenceLike(sequence: typing.SequenceLike) -> int: + return 0 + +def validator_SequenceLikeTypes( + sequence_float: typing.SequenceLike[float], + sequence_int: typing.SequenceLike[int], + sequence_str: typing.SequenceLike[str], + sequence_sequence: typing.SequenceLike[typing.SequenceLike], +) -> int: + return 0 + +# must pass +validator_SequenceLike(MySequence()) +validator_SequenceLike([0, 1, 2, 3]) +validator_SequenceLike((0, 1, 2, 3)) +validator_SequenceLike(pygame.Rect(-10, 10, 40, 40)) +validator_SequenceLike(pygame.Vector2()) +validator_SequenceLike("1234567890") + +validator_SequenceLikeTypes( + (-1.5, -0.5, 0, 0.5, 2.5, 10), + (-2, -1, 0, 1, 2, 3), + "abcdefghijklmnopqrstuvwxyz", + [(0.5, 1.5), (-1, 1), "123", [(), (), ()]] +) + +# validate PathLike +class MyPath: + def __fspath__(self) -> str: + return "file.py" + +def validator_PathLike(path: typing.PathLike) -> int: + return 0 + +# must pass +validator_PathLike("file.py") +validator_PathLike(b"file.py") +validator_PathLike(pathlib.Path("file.py")) +validator_PathLike(MyPath()) + +# validate Coordinate, IntCoordinate + +def validator_Coordinate(coordinate: typing.Coordinate) -> int: + return 0 + +def validator_IntCoordinate(coordinate: typing.IntCoordinate) -> int: + return 0 + +# must pass +validator_Coordinate((1, 2)) +validator_Coordinate([3, -4]) +validator_Coordinate((5, -6.5)) +validator_Coordinate((-6.7, 8.9)) +validator_Coordinate(pygame.Vector2()) + +validator_IntCoordinate((3, 4)) +validator_IntCoordinate([-4, -3]) + +# validate RGBATuple, ColorLike +def validator_RGBATuple(rgba: typing.RGBATuple) -> int: + return 0 + +def validator_ColorLike(color: typing.ColorLike) -> int: + return 0 + +# must pass +validator_RGBATuple((100, 200, 50, 20)) +validator_ColorLike("green") +validator_ColorLike(1) +validator_ColorLike((255, 255, 255, 30)) +validator_ColorLike(pygame.Color(100, 100, 100, 100)) + +# validate RectLike +class MyObject1: + def __init__(self): + self.rect = pygame.Rect(10, 10, 20, 20) + +class MyObject2: + def __init__(self): + self.rect = lambda: pygame.Rect(5, 5, 10, 10) + +def validator_RectLike(rect: typing.RectLike) -> int: + return 0 + +# must pass +validator_RectLike((10, 10, 10, 10)) +validator_RectLike(((5, 5), (30, 30))) +validator_RectLike(pygame.Rect(1, 2, 3, 4)) +validator_RectLike(MyObject1()) +validator_RectLike(MyObject2()) diff --git a/docs/reST/index.rst b/docs/reST/index.rst index 53121de17a..9bbbf3ac68 100644 --- a/docs/reST/index.rst +++ b/docs/reST/index.rst @@ -206,6 +206,9 @@ Reference :doc:`ref/transform` Resize and move images. +:doc:`ref/typing` + Provide common typehints + :doc:`pygame C API ` The C api shared amongst pygame extension modules. diff --git a/docs/reST/ref/typing.rst b/docs/reST/ref/typing.rst new file mode 100644 index 0000000000..764f05a28d --- /dev/null +++ b/docs/reST/ref/typing.rst @@ -0,0 +1,85 @@ +.. include:: common.txt + +:mod:`pygame.typing` +==================== + +.. module:: pygame.typing + :synopsis: pygame module providing common typehints + +| :sl:`pygame module providing common typehints` + +.. versionadded:: 2.5.2 + +A lot of pygame functions and methods allow the user to provide different types +for the same value like colors or coordinates. This module exports the most common +type aliases for proper typehint annotations. + + .. data:: PathLike + + An object representing a file path, i.e.: + + * ``"my/string/path.txt"`` + * ``pathlib.Path("my/pathlib/path.txt")`` + * ``b"my/bytes/path.txt"`` + * Any object implementing the path protocol (with a ``__fspath__`` magic method) + + .. data:: FileLike + + An object representing a file. Same as :mod:`pygame.typing.PathLike` with + the addition of file buffers (``IO`` of strings or bytes) such as the + return value of ``open()``. + + .. data:: SequenceLike + + A variant of the standard ``Sequence`` ABC only requiring ``__getitem__`` + and ``__len__``. This includes custom sequences or builtin ones, i.e.: + + * ``"abcdefg"`` + * ``[a, b, c, d, ...]`` + * ``(a, b, c, d, ...)`` + + Being a generic, subscribing it will signal further precision such as + ``SequenceLike[str]`` or ``SequenceLike[float]``. + + .. data:: Coordinate + + A sequence of two numbers (floats or ints), i.e: + + * ``pygame.Vector2(a, b)`` + * ``[a, b]`` + * ``(a, b)`` + + .. data:: IntCoordinate + + A sequence of strictly two integers such as ``[a, b]`` or ``(a, b)``. + + .. data:: RGBATuple + + A tuple of four integers ``(r, g, b, a)`` in range 0-255 such as ``(20, 255, 0, 100)``. + + .. data:: ColorLike + + An object representing a color such as a mapped integer, a string or + a sequence of three or four integers in range 0-255, types supported by + every function accepting a color argument. i.e.: + + * ``pygame.Color(ColorLike)`` + * ``(r, g, b)`` + * ``(r, g, b, a)`` + * ``[r, g, b, a]`` + * ``"green"`` + * ``0`` (mapped color) + + .. data:: RectLike + + An object representing a rect such as a sequence of numbers or coordinates + or an object with a rect attribute or a method returning a rect. These types + are supported by every function accepting a rect as argument. i.e.: + + * ``(x, y, w, h)`` + * ``(Coordinate, Coordinate)`` + * ``pygame.Rect(RectLike)`` + * Any object with a ``.rect`` attribute which is a ``RectLike`` or a function + returning a ``RectLike`` + +.. ## pygame.typing ## diff --git a/src_c/doc/typing_doc.h b/src_c/doc/typing_doc.h new file mode 100644 index 0000000000..ed354c8439 --- /dev/null +++ b/src_c/doc/typing_doc.h @@ -0,0 +1,10 @@ +/* Auto generated file: with make_docs.py . Docs go in docs/reST/ref/ . */ +#define DOC_TYPING "pygame module providing common typehints" +#define DOC_TYPING_PATHLIKE "" +#define DOC_TYPING_FILELIKE "" +#define DOC_TYPING_SEQUENCELIKE "" +#define DOC_TYPING_COORDINATE "" +#define DOC_TYPING_INTCOORDINATE "" +#define DOC_TYPING_RGBATUPLE "" +#define DOC_TYPING_COLORLIKE "" +#define DOC_TYPING_RECTLIKE "" diff --git a/src_py/__init__.py b/src_py/__init__.py index 9cc924420c..ac29abd3c8 100644 --- a/src_py/__init__.py +++ b/src_py/__init__.py @@ -315,6 +315,12 @@ def Window(title="pygame window", size=(640, 480), position=None, **kwargs): # _attribute_undefined("pygame.Window") +try: + import pygame.typing +except (ImportError, OSError): + typing = MissingModule("typing", urgent=0) + + # there's also a couple "internal" modules not needed # by users, but putting them here helps "dependency finder" # programs get everything they need (like py2exe) diff --git a/src_py/meson.build b/src_py/meson.build index 561aebaadb..541c54cd69 100644 --- a/src_py/meson.build +++ b/src_py/meson.build @@ -18,6 +18,7 @@ python_sources = files( 'sprite.py', 'surfarray.py', 'sysfont.py', + 'typing.py', 'version.py', ) py.install_sources(python_sources, subdir: pg) diff --git a/src_py/typing.py b/src_py/typing.py new file mode 100644 index 0000000000..883fe2def1 --- /dev/null +++ b/src_py/typing.py @@ -0,0 +1,59 @@ +"""Set of common pygame type aliases for proper typehint annotations""" + +# NOTE: `src_py/typing.py` and `buildconfig/stubs/pygame/typing.pyi` must be duplicates. +# Use the command `python buildconfig/stubs/gen_stubs.py` to copy typing.py to typing.pyi + +import sys +from typing import IO, Callable, Tuple, Union, TypeVar, Protocol, SupportsIndex + +if sys.version_info >= (3, 9): + from os import PathLike as _PathProtocol +else: + _T = TypeVar("_T", bound=Union[str, bytes]) + + class _PathProtocol(Protocol[_T]): + def __fspath__(self) -> _T: ... + + +# For functions that take a file name +PathLike = Union[str, bytes, _PathProtocol[str], _PathProtocol[bytes]] +# Most pygame functions that take a file argument should be able to handle a FileLike type +FileLike = Union[PathLike, IO[bytes], IO[str]] + +_T_co = TypeVar("_T_co", covariant=True) + + +class SequenceLike(Protocol[_T_co]): + """ + Variant of the standard `Sequence` ABC that only requires `__getitem__` and `__len__`. + """ + + def __getitem__(self, __i: SupportsIndex) -> _T_co: ... + def __len__(self) -> int: ... + + +# Modify typehints when it is possible to annotate sizes + +# Pygame handles float without errors in most cases where a coordinate is expected, +# usually rounding to int. Also, 'Union[int, float] == float' +Coordinate = SequenceLike[float] +# This is used where ints are strictly required +IntCoordinate = SequenceLike[int] + +# Used for functions that return an RGBA tuple +RGBATuple = Tuple[int, int, int, int] +ColorLike = Union[int, str, SequenceLike[int]] + +_CanBeRect = SequenceLike[Union[float, Coordinate]] + + +class _HasRectAttribute(Protocol): + # An object that has a rect attribute that is either a rect, or a function + # that returns a rect conforms to the rect protocol + rect: Union["RectLike", Callable[[], "RectLike"]] + + +RectLike = Union[_CanBeRect, _HasRectAttribute] + +# cleanup namespace +del sys, IO, Callable, Tuple, Union, TypeVar, Protocol, SupportsIndex