From 05834db3e203f337191b936e880bc8ef91601db4 Mon Sep 17 00:00:00 2001 From: Avasam Date: Sat, 31 Oct 2026 18:48:43 -0400 Subject: [PATCH] python-xlib: Fixes from Avasam/python-xlib#7 - `rq.List` accepts `rq.Str`, a `ValueField` instance, or any parse-only type with a `parse_binary` method (like `Xlib.ext.xinput.ClassInfo`) as its type. `rq.FixedList` accepts the same types. - Value fields accept `None` as their name. `rq.Field.name` is `str | None` to match the runtime class default, so the `*.name` stubtest allowlist entry is no longer needed. - `rq.Object` accepts a tuple, dict or `DictWrapper` as its default, and `rq.Object.default` has the same type. - `rq.Str.pack_value` accepts `bytes`. - `rq.Struct.fields` is a tuple of any length instead of a single field. --- .../python-xlib/@tests/stubtest_allowlist.txt | 2 -- stubs/python-xlib/Xlib/protocol/rq.pyi | 32 ++++++++++++------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/stubs/python-xlib/@tests/stubtest_allowlist.txt b/stubs/python-xlib/@tests/stubtest_allowlist.txt index 731b14c8c3df..78c51de68a51 100644 --- a/stubs/python-xlib/@tests/stubtest_allowlist.txt +++ b/stubs/python-xlib/@tests/stubtest_allowlist.txt @@ -17,8 +17,6 @@ Xlib.protocol.rq.GetAttrData.__setattr__ # Can be None or str once instantiated Xlib.protocol.rq.*.structcode -# Should only ever be str once instantiated -Xlib.protocol.rq.*.name # Iteration variable that bleeds into the global scope Xlib.protocol.rq.c diff --git a/stubs/python-xlib/Xlib/protocol/rq.pyi b/stubs/python-xlib/Xlib/protocol/rq.pyi index 1c89b6964ed0..2f05c4f7732b 100644 --- a/stubs/python-xlib/Xlib/protocol/rq.pyi +++ b/stubs/python-xlib/Xlib/protocol/rq.pyi @@ -4,13 +4,12 @@ from array import array # Avoid name collision with List.type from builtins import type as Type from collections.abc import Callable, Iterable, Sequence -from typing import Any, Final, Literal, SupportsIndex, TypeAlias, TypeVar, overload, type_check_only +from typing import Any, Final, Literal, Protocol, SupportsIndex, TypeAlias, TypeVar, overload, type_check_only from typing_extensions import LiteralString from Xlib._typing import ErrorHandler from Xlib.display import _BaseDisplay, _ResourceBaseClass from Xlib.error import XError -from Xlib.ext.xinput import ClassInfoClass from Xlib.protocol import display _T = TypeVar("_T") @@ -27,7 +26,7 @@ array_unsigned_codes: Final[dict[int, LiteralString]] struct_to_array_codes: Final[dict[str, LiteralString]] class Field: - name: str + name: str | None default: int | None pack_value: Callable[[Any], tuple[Any, int | None, int | None]] | None structcode: str | None @@ -82,7 +81,7 @@ class FormatField(Field): Format = FormatField class ValueField(Field): - def __init__(self, name: str, default: int | None = None) -> None: ... + def __init__(self, name: str | None, default: int | None = None) -> None: ... class Int8(ValueField): structcode: str @@ -203,13 +202,19 @@ class String16(ValueField): self, data: SliceableBuffer, display: Unused, length: int | Literal["odd", "even"], format: Unused ) -> tuple[tuple[Any, ...], SliceableBuffer]: ... +# Protocol for List types without a struct code that only parse, like Xlib.ext.xinput.ClassInfo +@type_check_only +class _ParsesBinary(Protocol): + structcode: None + def parse_binary(self, data: SliceableBuffer, display: display.Display | None, /) -> tuple[Any, SliceableBuffer]: ... + +_ListType: TypeAlias = Struct | ScalarObj | ResourceObj | StrClass | _ParsesBinary | ValueField | Type[ValueField] + class List(ValueField): structcode: None - type: Struct | ScalarObj | ResourceObj | ClassInfoClass | Type[ValueField] + type: _ListType pad: int - def __init__( - self, name: str, type: Struct | ScalarObj | ResourceObj | ClassInfoClass | Type[ValueField], pad: int = 1 - ) -> None: ... + def __init__(self, name: str, type: _ListType, pad: int = 1) -> None: ... def parse_binary_value( self, data: SliceableBuffer, display: display.Display | None, length: SupportsIndex | None, format: Unused ) -> tuple[list[DictWrapper | None], SliceableBuffer]: ... @@ -219,7 +224,7 @@ class List(ValueField): class FixedList(List): size: int - def __init__(self, name: str, size: int, type: Struct | ScalarObj, pad: int = 1) -> None: ... + def __init__(self, name: str, size: int, type: _ListType, pad: int = 1) -> None: ... def parse_binary_value( self, data: SliceableBuffer, display: display.Display | None, length: Unused, format: Unused ) -> tuple[list[DictWrapper | None], SliceableBuffer]: ... @@ -227,7 +232,10 @@ class FixedList(List): class Object(ValueField): type: Struct structcode: str | None - def __init__(self, name: str, type: Struct, default: int | None = None) -> None: ... + default: tuple[object, ...] | dict[str, Any] | DictWrapper | None # type: ignore[assignment] # Actually different from base class + def __init__( + self, name: str, type: Struct, default: tuple[object, ...] | dict[str, Any] | DictWrapper | None = None + ) -> None: ... def parse_binary_value( self, data: SliceableBuffer, display: display.Display | None, length: Unused, format: Unused ) -> tuple[DictWrapper, SliceableBuffer]: ... @@ -314,7 +322,7 @@ ColormapObj: ResourceObj class StrClass: structcode: None - def pack_value(self, val: str) -> bytes: ... + def pack_value(self, val: str | bytes) -> bytes: ... def parse_binary(self, data: bytes | bytearray, display: Unused) -> tuple[str, bytes | bytearray]: ... Str: StrClass @@ -323,7 +331,7 @@ class Struct: name: str check_value: Callable[[Any], Any] | None keyword_args: bool - fields: tuple[Field] + fields: tuple[Field, ...] static_codes: str static_values: int static_fields: list[Field]