From e87a4a7f23dcca196a8dbf922593e92f1dfd2206 Mon Sep 17 00:00:00 2001 From: Vivid Date: Thu, 21 May 2026 13:14:37 +0000 Subject: [PATCH 01/11] Add type stub for colour package --- stubs/colour/@tests/stubtest_allowlist.txt | 1 + stubs/colour/METADATA.toml | 2 + stubs/colour/colour.pyi | 98 ++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 stubs/colour/@tests/stubtest_allowlist.txt create mode 100644 stubs/colour/METADATA.toml create mode 100644 stubs/colour/colour.pyi diff --git a/stubs/colour/@tests/stubtest_allowlist.txt b/stubs/colour/@tests/stubtest_allowlist.txt new file mode 100644 index 000000000000..3de0f7e02e80 --- /dev/null +++ b/stubs/colour/@tests/stubtest_allowlist.txt @@ -0,0 +1 @@ +colour.Color.set_hex_l diff --git a/stubs/colour/METADATA.toml b/stubs/colour/METADATA.toml new file mode 100644 index 000000000000..bd433d77c277 --- /dev/null +++ b/stubs/colour/METADATA.toml @@ -0,0 +1,2 @@ +version = "0.1.5" +upstream-repository = "https://github.com/vaab/colour" diff --git a/stubs/colour/colour.pyi b/stubs/colour/colour.pyi new file mode 100644 index 000000000000..4670b42b3d30 --- /dev/null +++ b/stubs/colour/colour.pyi @@ -0,0 +1,98 @@ +import re +from collections.abc import Callable, Generator, Hashable +from typing import Any, overload +from typing_extensions import Self + +FLOAT_ERROR: float = 0.0000005 + +RGB_TO_COLOR_NAMES: dict[tuple[int, int, int], list[str]] + +COLOR_NAME_TO_RGB: dict[list[str], tuple[int, int, int]] + +LONG_HEX_COLOR: re.Pattern[str] +SHORT_HEX_COLOR: re.Pattern[str] + +class C_HSL: + def __getattr__(self, value: str) -> tuple[float, float, float]: ... + +class C_RGB: + def __getattr__(self, value: str) -> tuple[float, float, float]: ... + +class C_HEX: + def __getattr__(self, value: str) -> str: ... + +HSL: C_HSL +RGB: C_RGB +HEX: C_HEX + +def hsl2rgb(hsl: tuple[float, float, float]) -> tuple[float, float, float]: ... +def rgb2hsl(rgb: tuple[float, float, float]) -> tuple[float, float, float]: ... +def _hue2rgb(v1: float, v2: float, vH: float) -> float: ... +def rgb2hex(rgb: tuple[float, float, float], force_long: bool = False) -> str: ... +def hex2rgb(str_rgb: str) -> tuple[float, float, float]: ... +def hex2web(hex: str) -> str: ... +def web2hex(web: str, force_long: bool = False) -> str: ... + +hsl2hex: Callable[[tuple[float, float, float]], str] +hex2hsl: Callable[[str], tuple[float, float, float]] +rgb2web: Callable[[tuple[float, float, float]], str] +web2rgb: Callable[[str], tuple[float, float, float]] +web2hsl: Callable[[str], tuple[float, float, float]] +hsl2web: Callable[[tuple[float, float, float]], str] + +def color_scale( + begin_hsl: tuple[float, float, float], end_hsl: tuple[float, float, float], nb: int +) -> tuple[float, float, float]: ... +def RGB_color_picker(obj: Any) -> Color: ... + +@overload +def hash_or_str(obj: Hashable) -> int: ... +@overload +def hash_or_str(obj: object) -> str: ... # type: ignore[overload-cannot-match] + +class Color: + def __init__( + self, + color: Self | str | None = None, + pick_for: object | None = None, + picker: Callable[[object], Color] = ..., + pick_key: Callable[[object], int | str] = ..., + **kwargs: Any, + ) -> None: ... + def __getattr__(self, label: str) -> Any: ... + def __setattr__(self, label: str, value: Any) -> None: ... + def get_hsl(self) -> tuple[float, float, float]: ... + def get_hex(self) -> str: ... + def get_hex_l(self) -> str: ... + def get_rgb(self) -> tuple[float, float, float]: ... + def get_hue(self) -> float: ... + def get_saturation(self) -> float: ... + def get_luminance(self) -> float: ... + def get_red(self) -> float: ... + def get_green(self) -> float: ... + def get_blue(self) -> float: ... + def get_web(self) -> str: ... + def set_hsl(self, value: float) -> None: ... + def set_rgb(self, value: float) -> None: ... + def set_hue(self, value: float) -> None: ... + def set_saturation(self, value: float) -> None: ... + def set_luminance(self, value: float) -> None: ... + def set_red(self, value: float) -> None: ... + def set_green(self, value: float) -> None: ... + def set_blue(self, value: float) -> None: ... + def set_hex(self, value: str) -> None: ... + + set_hex_l: Callable[[str], None] = ... + + def set_web(self, value: str) -> None: ... + def range_to(self, value: str | Color | None, steps: int) -> Generator[Color]: ... + def __eq__(self, other: object) -> bool | type[NotImplemented]: ... # type: ignore[override, valid-type] # pyright: ignore[reportInvalidTypeForm] + +RGB_equivalence: Callable[[Color, Color], bool] +HSL_equivalence: Callable[[Color, Color], bool] + +def make_color_factory( + **kwargs_defaults: Any, +) -> Callable[ + [Color | str | None, object | None, Callable[[object], Color], object | None, Callable[[object], int | str]], Color +]: ... From 3c03e51e29de22432dc948c37d5160fdb2f93449 Mon Sep 17 00:00:00 2001 From: Vivid Date: Tue, 26 May 2026 21:50:46 +0000 Subject: [PATCH 02/11] Use Final --- stubs/colour/colour.pyi | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/stubs/colour/colour.pyi b/stubs/colour/colour.pyi index 4670b42b3d30..0a19cacafc43 100644 --- a/stubs/colour/colour.pyi +++ b/stubs/colour/colour.pyi @@ -1,29 +1,32 @@ import re from collections.abc import Callable, Generator, Hashable -from typing import Any, overload +from typing import Any, Final, final, overload from typing_extensions import Self -FLOAT_ERROR: float = 0.0000005 +FLOAT_ERROR: Final[float] = 0.0000005 -RGB_TO_COLOR_NAMES: dict[tuple[int, int, int], list[str]] +RGB_TO_COLOR_NAMES: Final[dict[tuple[int, int, int], list[str]]] -COLOR_NAME_TO_RGB: dict[list[str], tuple[int, int, int]] +COLOR_NAME_TO_RGB: Final[dict[list[str], tuple[int, int, int]]] -LONG_HEX_COLOR: re.Pattern[str] -SHORT_HEX_COLOR: re.Pattern[str] +LONG_HEX_COLOR: Final[re.Pattern[str]] +SHORT_HEX_COLOR: Final[re.Pattern[str]] +@final class C_HSL: def __getattr__(self, value: str) -> tuple[float, float, float]: ... +@final class C_RGB: def __getattr__(self, value: str) -> tuple[float, float, float]: ... +@final class C_HEX: def __getattr__(self, value: str) -> str: ... -HSL: C_HSL -RGB: C_RGB -HEX: C_HEX +HSL: Final[C_HSL] +RGB: Final[C_RGB] +HEX: Final[C_HEX] def hsl2rgb(hsl: tuple[float, float, float]) -> tuple[float, float, float]: ... def rgb2hsl(rgb: tuple[float, float, float]) -> tuple[float, float, float]: ... @@ -33,12 +36,12 @@ def hex2rgb(str_rgb: str) -> tuple[float, float, float]: ... def hex2web(hex: str) -> str: ... def web2hex(web: str, force_long: bool = False) -> str: ... -hsl2hex: Callable[[tuple[float, float, float]], str] -hex2hsl: Callable[[str], tuple[float, float, float]] -rgb2web: Callable[[tuple[float, float, float]], str] -web2rgb: Callable[[str], tuple[float, float, float]] -web2hsl: Callable[[str], tuple[float, float, float]] -hsl2web: Callable[[tuple[float, float, float]], str] +hsl2hex: Final[Callable[[tuple[float, float, float]], str]] +hex2hsl: Final[Callable[[str], tuple[float, float, float]]] +rgb2web: Final[Callable[[tuple[float, float, float]], str]] +web2rgb: Final[Callable[[str], tuple[float, float, float]]] +web2hsl: Final[Callable[[str], tuple[float, float, float]]] +hsl2web: Final[Callable[[tuple[float, float, float]], str]] def color_scale( begin_hsl: tuple[float, float, float], end_hsl: tuple[float, float, float], nb: int @@ -82,14 +85,14 @@ class Color: def set_blue(self, value: float) -> None: ... def set_hex(self, value: str) -> None: ... - set_hex_l: Callable[[str], None] = ... + set_hex_l: Final[Callable[[str], None]] = ... def set_web(self, value: str) -> None: ... def range_to(self, value: str | Color | None, steps: int) -> Generator[Color]: ... def __eq__(self, other: object) -> bool | type[NotImplemented]: ... # type: ignore[override, valid-type] # pyright: ignore[reportInvalidTypeForm] -RGB_equivalence: Callable[[Color, Color], bool] -HSL_equivalence: Callable[[Color, Color], bool] +RGB_equivalence: Final[Callable[[Color, Color], bool]] +HSL_equivalence: Final[Callable[[Color, Color], bool]] def make_color_factory( **kwargs_defaults: Any, From be75a9aff7ab6da89183e1903a85ac673575a23c Mon Sep 17 00:00:00 2001 From: Vivid Date: Mon, 22 Jun 2026 20:25:06 +0000 Subject: [PATCH 03/11] Use types.NotImplementedType for Color.__eq__ --- stubs/colour/colour.pyi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stubs/colour/colour.pyi b/stubs/colour/colour.pyi index 0a19cacafc43..ba7dcb97218d 100644 --- a/stubs/colour/colour.pyi +++ b/stubs/colour/colour.pyi @@ -1,5 +1,6 @@ import re from collections.abc import Callable, Generator, Hashable +from types import NotImplementedType from typing import Any, Final, final, overload from typing_extensions import Self @@ -89,7 +90,7 @@ class Color: def set_web(self, value: str) -> None: ... def range_to(self, value: str | Color | None, steps: int) -> Generator[Color]: ... - def __eq__(self, other: object) -> bool | type[NotImplemented]: ... # type: ignore[override, valid-type] # pyright: ignore[reportInvalidTypeForm] + def __eq__(self, other: object) -> bool | NotImplementedType: ... # type: ignore[override] RGB_equivalence: Final[Callable[[Color, Color], bool]] HSL_equivalence: Final[Callable[[Color, Color], bool]] From a1fbf54c7619957d233e6e7a33459e58ec9f9dc3 Mon Sep 17 00:00:00 2001 From: Vivid Date: Wed, 29 Jul 2026 23:41:41 +0000 Subject: [PATCH 04/11] Remove generic parameter from FLOAT_ERROR final constant --- stubs/colour/colour.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stubs/colour/colour.pyi b/stubs/colour/colour.pyi index ba7dcb97218d..dce9e895975a 100644 --- a/stubs/colour/colour.pyi +++ b/stubs/colour/colour.pyi @@ -4,7 +4,7 @@ from types import NotImplementedType from typing import Any, Final, final, overload from typing_extensions import Self -FLOAT_ERROR: Final[float] = 0.0000005 +FLOAT_ERROR: Final = 0.0000005 RGB_TO_COLOR_NAMES: Final[dict[tuple[int, int, int], list[str]]] From bc0c3ec47b86e1d00f8273e25c2d630820648ad1 Mon Sep 17 00:00:00 2001 From: Vivid Date: Wed, 29 Jul 2026 23:42:09 +0000 Subject: [PATCH 05/11] Change RGB_color_picker argument from Any to object --- stubs/colour/colour.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stubs/colour/colour.pyi b/stubs/colour/colour.pyi index dce9e895975a..fc3a02229520 100644 --- a/stubs/colour/colour.pyi +++ b/stubs/colour/colour.pyi @@ -47,7 +47,7 @@ hsl2web: Final[Callable[[tuple[float, float, float]], str]] def color_scale( begin_hsl: tuple[float, float, float], end_hsl: tuple[float, float, float], nb: int ) -> tuple[float, float, float]: ... -def RGB_color_picker(obj: Any) -> Color: ... +def RGB_color_picker(obj: object) -> Color: ... @overload def hash_or_str(obj: Hashable) -> int: ... From 74a746885d892737077884c8a96de7ded3d267ec Mon Sep 17 00:00:00 2001 From: Vivid Date: Wed, 29 Jul 2026 23:50:54 +0000 Subject: [PATCH 06/11] Change __getattr__ and __setattr__ to be properties The way Color.__getattr__ and Color.__setattr__ work is better expressed using properties for type checking. --- stubs/colour/colour.pyi | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/stubs/colour/colour.pyi b/stubs/colour/colour.pyi index fc3a02229520..0844b86cdb74 100644 --- a/stubs/colour/colour.pyi +++ b/stubs/colour/colour.pyi @@ -63,8 +63,6 @@ class Color: pick_key: Callable[[object], int | str] = ..., **kwargs: Any, ) -> None: ... - def __getattr__(self, label: str) -> Any: ... - def __setattr__(self, label: str, value: Any) -> None: ... def get_hsl(self) -> tuple[float, float, float]: ... def get_hex(self) -> str: ... def get_hex_l(self) -> str: ... @@ -89,6 +87,19 @@ class Color: set_hex_l: Final[Callable[[str], None]] = ... def set_web(self, value: str) -> None: ... + + hsl = property(get_hsl, set_hsl) + hex = property(get_hex, set_hex) + hex_l = property(get_hex_l, set_hex_l) + rgb = property(get_rgb, set_rgb) + hue = property(get_hue, set_hue) + saturation = property(get_saturation, set_saturation) + luminance = property(get_luminance, set_luminance) + red = property(get_red, set_red) + green = property(get_green, set_green) + blue = property(get_blue, set_blue) + web = property(get_web, set_web) + def range_to(self, value: str | Color | None, steps: int) -> Generator[Color]: ... def __eq__(self, other: object) -> bool | NotImplementedType: ... # type: ignore[override] From 8add95e278639ede532cf7d0001c3ad825228e53 Mon Sep 17 00:00:00 2001 From: Vivid Date: Wed, 29 Jul 2026 23:53:26 +0000 Subject: [PATCH 07/11] Document use of `kwargs: Any` in Color.__init__ --- stubs/colour/colour.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stubs/colour/colour.pyi b/stubs/colour/colour.pyi index 0844b86cdb74..69428155f3bf 100644 --- a/stubs/colour/colour.pyi +++ b/stubs/colour/colour.pyi @@ -61,7 +61,7 @@ class Color: pick_for: object | None = None, picker: Callable[[object], Color] = ..., pick_key: Callable[[object], int | str] = ..., - **kwargs: Any, + **kwargs: Any, # additional arguments become attributes ) -> None: ... def get_hsl(self) -> tuple[float, float, float]: ... def get_hex(self) -> str: ... From 5d2cf9697fe954fc91ccd2c749af1e3a37702fe8 Mon Sep 17 00:00:00 2001 From: Vivid Date: Wed, 29 Jul 2026 23:53:59 +0000 Subject: [PATCH 08/11] Remove NotImplementedType --- stubs/colour/colour.pyi | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/stubs/colour/colour.pyi b/stubs/colour/colour.pyi index 69428155f3bf..fb4cfd48e274 100644 --- a/stubs/colour/colour.pyi +++ b/stubs/colour/colour.pyi @@ -1,6 +1,5 @@ import re from collections.abc import Callable, Generator, Hashable -from types import NotImplementedType from typing import Any, Final, final, overload from typing_extensions import Self @@ -101,7 +100,7 @@ class Color: web = property(get_web, set_web) def range_to(self, value: str | Color | None, steps: int) -> Generator[Color]: ... - def __eq__(self, other: object) -> bool | NotImplementedType: ... # type: ignore[override] + def __eq__(self, other: object) -> bool: ... RGB_equivalence: Final[Callable[[Color, Color], bool]] HSL_equivalence: Final[Callable[[Color, Color], bool]] From 4e25139a7c7f0a6306a558fc2537ba111958b652 Mon Sep 17 00:00:00 2001 From: Vivid Date: Wed, 29 Jul 2026 23:58:04 +0000 Subject: [PATCH 09/11] Create _ColorFactory protocol as suggested --- stubs/colour/colour.pyi | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/stubs/colour/colour.pyi b/stubs/colour/colour.pyi index fb4cfd48e274..c4ff8c87d478 100644 --- a/stubs/colour/colour.pyi +++ b/stubs/colour/colour.pyi @@ -1,6 +1,6 @@ import re from collections.abc import Callable, Generator, Hashable -from typing import Any, Final, final, overload +from typing import Any, Final, Protocol, final, overload, type_check_only from typing_extensions import Self FLOAT_ERROR: Final = 0.0000005 @@ -105,8 +105,17 @@ class Color: RGB_equivalence: Final[Callable[[Color, Color], bool]] HSL_equivalence: Final[Callable[[Color, Color], bool]] +@type_check_only +class _ColorFactory(Protocol): + def __call__( + self, + color: Self | str | None = None, + pick_for: object | None = None, + picker: Callable[[object], Color] = ..., + pick_key: Callable[[object], int | str] = ..., + **kwargs: Any # additional arguments become attributes on the returned `Color` instance + ) -> Color: ... + def make_color_factory( - **kwargs_defaults: Any, -) -> Callable[ - [Color | str | None, object | None, Callable[[object], Color], object | None, Callable[[object], int | str]], Color -]: ... + **kwargs_defaults: Any, # sets default kwargs for the returned `Color` factory +) -> _ColorFactory: ... From ca1b7c8049606616668c3b9835587b12e68fec10 Mon Sep 17 00:00:00 2001 From: Vivid Date: Thu, 30 Jul 2026 00:01:20 +0000 Subject: [PATCH 10/11] Correct signature of Color.set_hex_l --- stubs/colour/@tests/stubtest_allowlist.txt | 1 - stubs/colour/colour.pyi | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 stubs/colour/@tests/stubtest_allowlist.txt diff --git a/stubs/colour/@tests/stubtest_allowlist.txt b/stubs/colour/@tests/stubtest_allowlist.txt deleted file mode 100644 index 3de0f7e02e80..000000000000 --- a/stubs/colour/@tests/stubtest_allowlist.txt +++ /dev/null @@ -1 +0,0 @@ -colour.Color.set_hex_l diff --git a/stubs/colour/colour.pyi b/stubs/colour/colour.pyi index c4ff8c87d478..2683dd02d66d 100644 --- a/stubs/colour/colour.pyi +++ b/stubs/colour/colour.pyi @@ -83,7 +83,7 @@ class Color: def set_blue(self, value: float) -> None: ... def set_hex(self, value: str) -> None: ... - set_hex_l: Final[Callable[[str], None]] = ... + set_hex_l: Callable[[Self, str], None] = ... def set_web(self, value: str) -> None: ... From 150e789d2b5d4b54137818aebfe8f22f0a340c1f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 02:31:27 +0000 Subject: [PATCH 11/11] [pre-commit.ci] auto fixes from pre-commit.com hooks --- stubs/colour/colour.pyi | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/stubs/colour/colour.pyi b/stubs/colour/colour.pyi index 2683dd02d66d..1144997c5910 100644 --- a/stubs/colour/colour.pyi +++ b/stubs/colour/colour.pyi @@ -113,9 +113,7 @@ class _ColorFactory(Protocol): pick_for: object | None = None, picker: Callable[[object], Color] = ..., pick_key: Callable[[object], int | str] = ..., - **kwargs: Any # additional arguments become attributes on the returned `Color` instance + **kwargs: Any, # additional arguments become attributes on the returned `Color` instance ) -> Color: ... -def make_color_factory( - **kwargs_defaults: Any, # sets default kwargs for the returned `Color` factory -) -> _ColorFactory: ... +def make_color_factory(**kwargs_defaults: Any) -> _ColorFactory: ... # sets default kwargs for the returned `Color` factory