From 4f5d3d550b4694495261adb87e14cb6986650041 Mon Sep 17 00:00:00 2001 From: rus07tam Date: Wed, 29 Oct 2025 15:04:05 +0000 Subject: [PATCH] refactor: replace typing.Self, ExceptionGroup and use explicit generics for 3.10 compat --- src/snakia/core/ecs/system.py | 126 +++++++----------- src/snakia/core/engine.py | 4 +- src/snakia/core/es/action.py | 12 +- src/snakia/core/es/dispatcher.py | 24 ++-- src/snakia/core/es/filter.py | 8 +- src/snakia/core/es/handler.py | 8 +- src/snakia/core/es/subscriber.py | 10 +- src/snakia/core/except_manager.py | 14 +- src/snakia/core/loader/meta.py | 8 +- src/snakia/core/rx/async_bindable.py | 22 ++- src/snakia/core/rx/base_bindable.py | 13 +- src/snakia/core/rx/bindable.py | 10 +- src/snakia/core/rx/chain.py | 28 ++-- src/snakia/core/rx/combine.py | 23 ++-- src/snakia/core/rx/concat.py | 6 +- src/snakia/core/rx/cond.py | 12 +- src/snakia/core/rx/const.py | 6 +- src/snakia/core/rx/filter.py | 7 +- src/snakia/core/rx/map.py | 9 +- src/snakia/core/rx/merge.py | 8 +- src/snakia/core/tui/canvas.py | 8 +- src/snakia/core/tui/widget.py | 8 +- .../core/tui/widgets/horizontal_split.py | 8 +- src/snakia/core/tui/widgets/vertical_split.py | 8 +- src/snakia/decorators/inject_after.py | 12 +- src/snakia/decorators/inject_before.py | 12 +- src/snakia/decorators/inject_const.py | 8 +- src/snakia/decorators/inject_replace.py | 12 +- src/snakia/decorators/pass_exceptions.py | 9 +- src/snakia/decorators/singleton.py | 7 +- src/snakia/field/auto.py | 12 +- src/snakia/field/bool.py | 4 - src/snakia/field/field.py | 17 ++- src/snakia/field/float.py | 3 - src/snakia/field/int.py | 4 - src/snakia/field/str.py | 4 +- src/snakia/platform/android.py | 6 +- src/snakia/platform/layer.py | 8 +- src/snakia/property/cell_property.py | 27 +++- src/snakia/property/classproperty.py | 14 +- src/snakia/property/hook_property.py | 10 +- src/snakia/property/initonly.py | 6 +- src/snakia/property/priv_property.py | 6 +- src/snakia/property/property.py | 14 +- src/snakia/property/readonly.py | 8 +- src/snakia/random/os.py | 3 +- src/snakia/random/python.py | 3 +- src/snakia/random/random.py | 12 +- src/snakia/types/unique.py | 6 +- src/snakia/utils/inherit.py | 8 +- src/snakia/utils/throw.py | 8 +- src/snakia/utils/to_async.py | 7 +- 52 files changed, 340 insertions(+), 300 deletions(-) diff --git a/src/snakia/core/ecs/system.py b/src/snakia/core/ecs/system.py index e023c72..af7df08 100644 --- a/src/snakia/core/ecs/system.py +++ b/src/snakia/core/ecs/system.py @@ -3,7 +3,7 @@ from __future__ import annotations from collections import defaultdict from collections.abc import Iterable from itertools import count -from typing import Any, cast, overload +from typing import Any, TypeVar, cast, overload import networkx as nx # type: ignore @@ -12,6 +12,14 @@ from snakia.utils import nolock from .component import Component from .processor import Processor +A = TypeVar("A", bound=Component) +B = TypeVar("B", bound=Component) +C = TypeVar("C", bound=Component) +D = TypeVar("D", bound=Component) +E = TypeVar("E", bound=Component) + +P = TypeVar("P", bound=Processor) + class System: """ @@ -46,9 +54,7 @@ class System: self.__entity_counter = count(start=1) self.__dead_entities = set() - def get_processor[P: Processor]( - self, processor_type: type[P], / - ) -> P | None: + def get_processor(self, processor_type: type[P], /) -> P | None: """Returns the first processor of the given type.""" for processor in self.__processors: if isinstance(processor, processor_type): @@ -67,39 +73,31 @@ class System: self.__processors.remove(processor) @overload - def get_components[A: Component]( - self, __c1: type[A], / - ) -> Iterable[tuple[int, tuple[A]]]: ... + def get_components(self, c1: type[A], /) -> Iterable[tuple[int, tuple[A]]]: ... @overload - def get_components[A: Component, B: Component]( - self, __c1: type[A], __c2: type[B], / + def get_components( + self, c1: type[A], c2: type[B], / ) -> Iterable[tuple[int, tuple[A, B]]]: ... @overload - def get_components[A: Component, B: Component, C: Component]( - self, __c1: type[A], __c2: type[B], __c3: type[C], / + def get_components( + self, c1: type[A], c2: type[B], c3: type[C], / ) -> Iterable[tuple[int, tuple[A, B, C]]]: ... @overload - def get_components[A: Component, B: Component, C: Component, D: Component]( - self, __c1: type[A], __c2: type[B], __c3: type[C], __c4: type[D], / + def get_components( + self, c1: type[A], c2: type[B], c3: type[C], c4: type[D], / ) -> Iterable[tuple[int, tuple[A, B, C, D]]]: ... @overload - def get_components[ - A: Component, - B: Component, - C: Component, - D: Component, - E: Component, - ]( + def get_components( self, - __c1: type[A], - __c2: type[B], - __c3: type[C], - __c4: type[D], - __c5: type[E], + c1: type[A], + c2: type[B], + c3: type[C], + c4: type[D], + c5: type[E], /, ) -> Iterable[tuple[int, tuple[A, B, C, D]]]: ... @@ -108,10 +106,7 @@ class System: ) -> Iterable[tuple[int, tuple[Component, ...]]]: """Returns all entities with the given components.""" entity_set = set.intersection( - *( - self.__components[component_type] - for component_type in component_types - ) + *(self.__components[component_type] for component_type in component_types) ) for entity in entity_set: yield ( @@ -123,51 +118,40 @@ class System: ) @overload - def get_components_of_entity[A: Component]( - self, entity: int, __c1: type[A], / + def get_components_of_entity( + self, entity: int, c1: type[A], / ) -> tuple[A | None]: ... @overload - def get_components_of_entity[A: Component, B: Component]( - self, entity: int, __c1: type[A], __c2: type[B], / + def get_components_of_entity( + self, entity: int, c1: type[A], c2: type[B], / ) -> tuple[A | None, B | None]: ... @overload - def get_components_of_entity[A: Component, B: Component, C: Component]( - self, entity: int, __c1: type[A], __c2: type[B], __c3: type[C], / + def get_components_of_entity( + self, entity: int, c1: type[A], c2: type[B], c3: type[C], / ) -> tuple[A | None, B | None, C | None]: ... @overload - def get_components_of_entity[ - A: Component, - B: Component, - C: Component, - D: Component, - ]( + def get_components_of_entity( self, entity: int, - __c1: type[A], - __c2: type[B], - __c3: type[C], - __c4: type[D], + c1: type[A], + c2: type[B], + c3: type[C], + c4: type[D], /, ) -> tuple[A | None, B | None, C | None, D | None]: ... @overload - def get_components_of_entity[ - A: Component, - B: Component, - C: Component, - D: Component, - E: Component, - ]( + def get_components_of_entity( self, entity: int, - __c1: type[A], - __c2: type[B], - __c3: type[C], - __c4: type[D], - __c5: type[E], + c1: type[A], + c2: type[B], + c3: type[C], + c4: type[D], + c5: type[E], /, ) -> tuple[A | None, B | None, C | None, D | None, E | None]: ... @@ -183,20 +167,18 @@ class System: ), ) - def get_component[C: Component]( - self, component_type: type[C], / - ) -> Iterable[tuple[int, C]]: + def get_component(self, component_type: type[C], /) -> Iterable[tuple[int, C]]: """Returns all entities with the given component.""" for entity in self.__components[component_type].copy(): yield entity, cast(C, self.__entitites[entity][component_type]) @overload - def get_component_of_entity[C: Component]( + def get_component_of_entity( self, entity: int, component_type: type[C], / ) -> C | None: ... @overload - def get_component_of_entity[C: Component, D: Any]( + def get_component_of_entity( self, entity: int, component_type: type[C], /, default: D ) -> C | D: ... @@ -216,24 +198,16 @@ class System: self.__components[component_type].add(entity) self.__entitites[entity][component_type] = component - def has_component( - self, entity: int, component_type: type[Component] - ) -> bool: + def has_component(self, entity: int, component_type: type[Component]) -> bool: """Returns True if the entity has the given component.""" return component_type in self.__entitites[entity] - def has_components( - self, entity: int, *component_types: type[Component] - ) -> bool: + def has_components(self, entity: int, *component_types: type[Component]) -> bool: """Returns True if the entity has all the given components.""" components_dict = self.__entitites[entity] - return all( - comp_type in components_dict for comp_type in component_types - ) + return all(comp_type in components_dict for comp_type in component_types) - def remove_component[C: Component]( - self, entity: int, component_type: type[C] - ) -> C | None: + def remove_component(self, entity: int, component_type: type[C]) -> C | None: """Removes a component from an entity.""" self.__components[component_type].discard(entity) if not self.__components[component_type]: @@ -265,9 +239,7 @@ class System: def entity_exists(self, entity: int) -> bool: """Returns True if the entity exists.""" - return ( - entity in self.__entitites and entity not in self.__dead_entities - ) + return entity in self.__entitites and entity not in self.__dead_entities def start(self) -> None: """Starts the system.""" diff --git a/src/snakia/core/engine.py b/src/snakia/core/engine.py index 78c8120..26507c2 100644 --- a/src/snakia/core/engine.py +++ b/src/snakia/core/engine.py @@ -15,9 +15,7 @@ class Engine: self.__dispatcher_thread: threading.Thread | None = None def start(self) -> None: - self.__system_thread = threading.Thread( - target=self.system.start, daemon=False - ) + self.__system_thread = threading.Thread(target=self.system.start, daemon=False) self.__dispatcher_thread = threading.Thread( target=self.dispatcher.start, daemon=False ) diff --git a/src/snakia/core/es/action.py b/src/snakia/core/es/action.py index 0f805a1..742be4f 100644 --- a/src/snakia/core/es/action.py +++ b/src/snakia/core/es/action.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Self - from pydantic import BaseModel, Field @@ -9,27 +7,27 @@ class Action(BaseModel): move: int = Field(default=1) @classmethod - def stop(cls) -> Self: + def stop(cls) -> Action: """Skip all handlers.""" return cls(move=2**8) @classmethod - def go_start(cls) -> Self: + def go_start(cls) -> Action: """Go to the first handler.""" return cls(move=-(2**8)) @classmethod - def next(cls, count: int = 1) -> Self: + def next(cls, count: int = 1) -> Action: """Skip one handler.""" return cls(move=count) @classmethod - def prev(cls, count: int = 1) -> Self: + def prev(cls, count: int = 1) -> Action: """Go back one handler.""" return cls(move=-count) @classmethod - def skip(cls, count: int = 1) -> Self: + def skip(cls, count: int = 1) -> Action: """Skip n handlers. Args: diff --git a/src/snakia/core/es/dispatcher.py b/src/snakia/core/es/dispatcher.py index 9874f41..8a1af48 100644 --- a/src/snakia/core/es/dispatcher.py +++ b/src/snakia/core/es/dispatcher.py @@ -2,7 +2,7 @@ from __future__ import annotations import queue from collections import defaultdict -from typing import Callable, Final +from typing import Callable, Final, TypeVar from snakia.utils import nolock @@ -11,6 +11,8 @@ from .filter import Filter from .handler import Handler from .subscriber import Subscriber +T = TypeVar("T", bound=Event) + class Dispatcher: """ @@ -21,9 +23,9 @@ class Dispatcher: def __init__(self) -> None: self.__queue: Final = queue.Queue[Event]() - self.__subscribers: Final[ - dict[type[Event], list[Subscriber[Event]]] - ] = defaultdict(list) + self.__subscribers: Final[dict[type[Event], list[Subscriber[Event]]]] = ( + defaultdict(list) + ) self.__running = False @property @@ -31,15 +33,11 @@ class Dispatcher: """Returns True if the dispatcher is running.""" return self.__running - def subscribe[T: Event]( - self, event_type: type[T], subscriber: Subscriber[T] - ) -> None: + def subscribe(self, event_type: type[T], subscriber: Subscriber[T]) -> None: """Subscribe to an event type.""" self.__subscribers[event_type].append(subscriber) # type: ignore - def unsubscribe[T: Event]( - self, event_type: type[T], subscriber: Subscriber[T] - ) -> None: + def unsubscribe(self, event_type: type[T], subscriber: Subscriber[T]) -> None: """Unsubscribe from an event type.""" for sub in self.__subscribers[event_type].copy(): if sub.handler != subscriber.handler: @@ -48,7 +46,7 @@ class Dispatcher: continue self.__subscribers[event_type].remove(sub) - def on[T: Event]( + def on( self, event: type[T], filter: Filter[T] | None = None, # noqa: W0622 # pylint: disable=W0622 @@ -95,9 +93,7 @@ class Dispatcher: i = 0 while i < len(subscribers): subscriber = subscribers[i] - if subscriber.filters is not None and not subscriber.filters( - event - ): + if subscriber.filters is not None and not subscriber.filters(event): continue action = subscriber.handler(event) diff --git a/src/snakia/core/es/filter.py b/src/snakia/core/es/filter.py index 41ea6e7..32108cc 100644 --- a/src/snakia/core/es/filter.py +++ b/src/snakia/core/es/filter.py @@ -1,11 +1,13 @@ from __future__ import annotations -from typing import Protocol +from typing import Generic, Protocol, TypeVar from .event import Event +T_contra = TypeVar("T_contra", bound=Event, contravariant=True) -class Filter[T: Event](Protocol): + +class Filter(Protocol, Generic[T_contra]): """Filter for an event.""" - def __call__(self, event: T) -> bool: ... + def __call__(self, event: T_contra) -> bool: ... diff --git a/src/snakia/core/es/handler.py b/src/snakia/core/es/handler.py index 6cdb1ba..d968947 100644 --- a/src/snakia/core/es/handler.py +++ b/src/snakia/core/es/handler.py @@ -1,12 +1,14 @@ from __future__ import annotations -from typing import Optional, Protocol +from typing import Generic, Optional, Protocol, TypeVar from .action import Action from .event import Event +T_contra = TypeVar("T_contra", bound=Event, contravariant=True) -class Handler[T: Event](Protocol): + +class Handler(Protocol, Generic[T_contra]): """Handler for an event.""" - def __call__(self, event: T) -> Optional[Action]: ... + def __call__(self, event: T_contra) -> Optional[Action]: ... diff --git a/src/snakia/core/es/subscriber.py b/src/snakia/core/es/subscriber.py index 6bc9aad..e8be622 100644 --- a/src/snakia/core/es/subscriber.py +++ b/src/snakia/core/es/subscriber.py @@ -1,16 +1,18 @@ from __future__ import annotations -from typing import NamedTuple +from typing import Generic, NamedTuple, TypeVar from .event import Event from .filter import Filter from .handler import Handler +T_contra = TypeVar("T_contra", bound=Event, contravariant=True) -class Subscriber[T: Event](NamedTuple): + +class Subscriber(NamedTuple, Generic[T_contra]): """ Subscriber for an event.""" - handler: Handler[T] - filters: Filter[T] | None + handler: Handler[T_contra] + filters: Filter[T_contra] | None priority: int diff --git a/src/snakia/core/except_manager.py b/src/snakia/core/except_manager.py index a74d571..d32d53b 100644 --- a/src/snakia/core/except_manager.py +++ b/src/snakia/core/except_manager.py @@ -1,11 +1,15 @@ import sys from types import TracebackType -from typing import Any, Callable, Protocol, final +from typing import Any, Callable, Generic, Protocol, TypeVar, final + +T = TypeVar("T", bound=BaseException) +T_contra = TypeVar("T_contra", contravariant=True) -class ExceptionHook[T: BaseException](Protocol): +class ExceptionHook(Protocol, Generic[T_contra]): + def __call__( - self, exception: T, frame: TracebackType | None, / + self, exception: T_contra, frame: TracebackType | None, / ) -> bool | None: ... @@ -15,13 +19,13 @@ class _ExceptionManager: self.__hooks: list[tuple[type[BaseException], ExceptionHook[Any]]] = [] sys.excepthook = self._excepthook - def hook_exception[T: BaseException]( + def hook_exception( self, exception_type: type[T], func: ExceptionHook[T] ) -> ExceptionHook[T]: self.__hooks.append((exception_type, func)) return func - def on_exception[T: BaseException]( + def on_exception( self, exception_type: type[T] ) -> Callable[[ExceptionHook[T]], ExceptionHook[T]]: def inner(func: ExceptionHook[T]) -> ExceptionHook[T]: diff --git a/src/snakia/core/loader/meta.py b/src/snakia/core/loader/meta.py index f0b66d3..429d4b4 100644 --- a/src/snakia/core/loader/meta.py +++ b/src/snakia/core/loader/meta.py @@ -23,16 +23,12 @@ class Meta(BaseModel): max_length=32, pattern="^[a-z0-9_]{4,32}$", ) - version: Version = Field( - default_factory=lambda: Version(major=1, minor=0, patch=0) - ) + version: Version = Field(default_factory=lambda: Version(major=1, minor=0, patch=0)) subscribers: tuple[tuple[type[Event], Subscriber[Event]], ...] = Field( default_factory=tuple ) - processors: tuple[type[PluginProcessor], ...] = Field( - default_factory=tuple - ) + processors: tuple[type[PluginProcessor], ...] = Field(default_factory=tuple) @property def id(self) -> str: diff --git a/src/snakia/core/rx/async_bindable.py b/src/snakia/core/rx/async_bindable.py index a31c2df..81d4698 100644 --- a/src/snakia/core/rx/async_bindable.py +++ b/src/snakia/core/rx/async_bindable.py @@ -1,9 +1,11 @@ -from typing import Any, Awaitable, Callable, Literal, overload +from typing import Any, Awaitable, Callable, Generic, Literal, TypeVar, overload from .base_bindable import BaseBindable, BindableSubscriber, ValueChanged +T = TypeVar("T") -class AsyncBindable[T: Any](BaseBindable[T]): + +class AsyncBindable(BaseBindable[T], Generic[T]): """ An asynchronous bindable. """ @@ -53,25 +55,19 @@ class AsyncBindable[T: Any](BaseBindable[T]): if run_now: async def _run() -> None: - await subscriber( - ValueChanged(self.__default_value, self.value) - ) + await subscriber(ValueChanged(self.__default_value, self.value)) return _run() return None - def unsubscribe( - self, subscriber: BindableSubscriber[T, Awaitable[Any]] - ) -> None: + def unsubscribe(self, subscriber: BindableSubscriber[T, Awaitable[Any]]) -> None: """Unsubscribe from an value.""" self.__subscribers.remove(subscriber) @overload def on( self, run_now: Literal[True] - ) -> Callable[ - [BindableSubscriber[T, Awaitable[Any]]], Awaitable[None] - ]: ... + ) -> Callable[[BindableSubscriber[T, Awaitable[Any]]], Awaitable[None]]: ... @overload def on( @@ -91,9 +87,7 @@ class AsyncBindable[T: Any](BaseBindable[T]): if run_now: async def _run() -> None: - await subscriber( - ValueChanged(self.__default_value, self.value) - ) + await subscriber(ValueChanged(self.__default_value, self.value)) return _run() return None diff --git a/src/snakia/core/rx/base_bindable.py b/src/snakia/core/rx/base_bindable.py index 58c557d..830713c 100644 --- a/src/snakia/core/rx/base_bindable.py +++ b/src/snakia/core/rx/base_bindable.py @@ -1,16 +1,19 @@ -from typing import Any, NamedTuple, Protocol +from typing import Generic, NamedTuple, Protocol, TypeVar + +T = TypeVar("T") +R_co = TypeVar("R_co", covariant=True) -class ValueChanged[T](NamedTuple): +class ValueChanged(NamedTuple, Generic[T]): old_value: T new_value: T -class BindableSubscriber[T: Any, R: Any](Protocol): - def __call__(self, value: ValueChanged[T], /) -> R: ... +class BindableSubscriber(Protocol, Generic[T, R_co]): + def __call__(self, value: ValueChanged[T], /) -> R_co: ... -class BaseBindable[T: Any]: +class BaseBindable(Generic[T]): def __init__(self, default_value: T | None = None) -> None: if default_value is not None: self.__default_value: T = default_value diff --git a/src/snakia/core/rx/bindable.py b/src/snakia/core/rx/bindable.py index fe2a448..161632c 100644 --- a/src/snakia/core/rx/bindable.py +++ b/src/snakia/core/rx/bindable.py @@ -1,9 +1,11 @@ -from typing import Any, Callable +from typing import Any, Callable, Generic, TypeVar from .base_bindable import BaseBindable, BindableSubscriber, ValueChanged +T = TypeVar("T") -class Bindable[T: Any](BaseBindable[T]): + +class Bindable(BaseBindable[T], Generic[T]): """ A bindable value. """ @@ -36,9 +38,7 @@ class Bindable[T: Any](BaseBindable[T]): """Unsubscribe from an value.""" self.__subscribers.remove(subscriber) - def on( - self, run_now: bool = False - ) -> Callable[[BindableSubscriber[T, Any]], None]: + def on(self, run_now: bool = False) -> Callable[[BindableSubscriber[T, Any]], None]: """Decorator to subscribe to an value.""" def wrapper(subscriber: BindableSubscriber[T, Any]) -> None: diff --git a/src/snakia/core/rx/chain.py b/src/snakia/core/rx/chain.py index 81ce12a..0672e96 100644 --- a/src/snakia/core/rx/chain.py +++ b/src/snakia/core/rx/chain.py @@ -1,20 +1,26 @@ -from typing import Any, Callable, overload +from typing import Any, Callable, ParamSpec, TypeVar, overload + +P = ParamSpec("P") + +A = TypeVar("A") +B = TypeVar("B") +C = TypeVar("C") +D = TypeVar("D") +E = TypeVar("E") @overload -def chain[**P, A](func1: Callable[P, A], /) -> Callable[P, A]: ... +def chain(func1: Callable[P, A], /) -> Callable[P, A]: ... @overload -def chain[**P, A, B]( - func1: Callable[P, A], func2: Callable[[A], B], / -) -> Callable[P, B]: ... +def chain(func1: Callable[P, A], func2: Callable[[A], B], /) -> Callable[P, B]: ... @overload -def chain[**P, A, B, C]( +def chain( func1: Callable[P, A], func2: Callable[[A], B], func3: Callable[[B], C], / ) -> Callable[P, C]: ... @overload -def chain[**P, A, B, C, D]( +def chain( func1: Callable[P, A], func2: Callable[[A], B], func3: Callable[[B], C], @@ -24,7 +30,7 @@ def chain[**P, A, B, C, D]( @overload -def chain[**P, A, B, C, D, E]( +def chain( func1: Callable[P, A], func2: Callable[[A], B], func3: Callable[[B], C], @@ -35,14 +41,12 @@ def chain[**P, A, B, C, D, E]( @overload -def chain[**P]( +def chain( func1: Callable[P, Any], /, *funcs: Callable[[Any], Any] ) -> Callable[P, Any]: ... -def chain[**P]( - func1: Callable[P, Any], /, *funcs: Callable[[Any], Any] -) -> Callable[P, Any]: +def chain(func1: Callable[P, Any], /, *funcs: Callable[[Any], Any]) -> Callable[P, Any]: def inner(*args: P.args, **kwargs: P.kwargs) -> Any: v = func1(*args, **kwargs) diff --git a/src/snakia/core/rx/combine.py b/src/snakia/core/rx/combine.py index 0cc5b1a..7b84f6d 100644 --- a/src/snakia/core/rx/combine.py +++ b/src/snakia/core/rx/combine.py @@ -1,5 +1,5 @@ import operator -from typing import Any, Callable, overload +from typing import Any, Callable, TypeVar, overload from snakia.utils import to_async @@ -8,9 +8,16 @@ from .base_bindable import ValueChanged from .bindable import Bindable from .concat import concat +A = TypeVar("A") +B = TypeVar("B") +C = TypeVar("C") +D = TypeVar("D") +E = TypeVar("E") +R = TypeVar("R") + @overload -def combine[A, R]( +def combine( source1: Bindable[A] | AsyncBindable[A], /, *, @@ -19,7 +26,7 @@ def combine[A, R]( @overload -def combine[A, B, R]( +def combine( source1: Bindable[A] | AsyncBindable[A], source2: Bindable[B] | AsyncBindable[B], /, @@ -29,7 +36,7 @@ def combine[A, B, R]( @overload -def combine[A, B, C, R]( +def combine( source1: Bindable[A] | AsyncBindable[A], source2: Bindable[B] | AsyncBindable[B], source3: Bindable[C] | AsyncBindable[C], @@ -40,7 +47,7 @@ def combine[A, B, C, R]( @overload -def combine[A, B, C, D, R]( +def combine( source1: Bindable[A] | AsyncBindable[A], source2: Bindable[B] | AsyncBindable[B], source3: Bindable[C] | AsyncBindable[C], @@ -52,7 +59,7 @@ def combine[A, B, C, D, R]( @overload -def combine[A, B, C, D, R]( +def combine( source1: Bindable[A] | AsyncBindable[A], source2: Bindable[B] | AsyncBindable[B], source3: Bindable[C] | AsyncBindable[C], @@ -64,13 +71,13 @@ def combine[A, B, C, D, R]( @overload -def combine[R]( +def combine( *sources: Bindable[Any] | AsyncBindable[Any], combiner: Callable[..., R], ) -> Bindable[R]: ... -def combine[R]( +def combine( *sources: Bindable[Any] | AsyncBindable[Any], combiner: Callable[..., R], ) -> Bindable[R]: diff --git a/src/snakia/core/rx/concat.py b/src/snakia/core/rx/concat.py index d96cec1..6a15ab3 100644 --- a/src/snakia/core/rx/concat.py +++ b/src/snakia/core/rx/concat.py @@ -1,7 +1,9 @@ -from typing import Any, Callable +from typing import Any, Callable, ParamSpec + +P = ParamSpec("P") -def concat[**P](*funcs: Callable[P, Any]) -> Callable[P, None]: +def concat(*funcs: Callable[P, Any]) -> Callable[P, None]: def inner(*args: P.args, **kwargs: P.kwargs) -> None: for f in funcs: f(*args, **kwargs) diff --git a/src/snakia/core/rx/cond.py b/src/snakia/core/rx/cond.py index fce6cdb..8429943 100644 --- a/src/snakia/core/rx/cond.py +++ b/src/snakia/core/rx/cond.py @@ -1,13 +1,15 @@ -from typing import Callable +from typing import Callable, ParamSpec, TypeVar + +P = ParamSpec("P") +T = TypeVar("T") +F = TypeVar("F") -def cond[**P, T, F]( +def cond( condition: Callable[P, bool], if_true: Callable[P, T], if_false: Callable[P, F], ) -> Callable[P, T | F]: return lambda *args, **kw: ( - if_true(*args, **kw) - if condition(*args, **kw) - else if_false(*args, **kw) + if_true(*args, **kw) if condition(*args, **kw) else if_false(*args, **kw) ) diff --git a/src/snakia/core/rx/const.py b/src/snakia/core/rx/const.py index f0781d9..7e0a4ea 100644 --- a/src/snakia/core/rx/const.py +++ b/src/snakia/core/rx/const.py @@ -1,5 +1,7 @@ -from typing import Callable +from typing import Callable, TypeVar + +T = TypeVar("T") -def const[T](value: T) -> Callable[[], T]: +def const(value: T) -> Callable[[], T]: return lambda: value diff --git a/src/snakia/core/rx/filter.py b/src/snakia/core/rx/filter.py index f7ab74c..5bb761c 100644 --- a/src/snakia/core/rx/filter.py +++ b/src/snakia/core/rx/filter.py @@ -1,9 +1,12 @@ import builtins -from typing import Callable, Iterable, TypeGuard +from typing import Callable, Iterable, TypeGuard, TypeVar + +S = TypeVar("S") +T = TypeVar("T") # noqa: W0622 # pylint: disable=W0622 -def filter[S, T]( +def filter( f: Callable[[S], TypeGuard[T]], ) -> Callable[[Iterable[S]], Iterable[T]]: return lambda iterable: builtins.filter(f, iterable) diff --git a/src/snakia/core/rx/map.py b/src/snakia/core/rx/map.py index 86d7bf1..c0a4fb9 100644 --- a/src/snakia/core/rx/map.py +++ b/src/snakia/core/rx/map.py @@ -1,9 +1,10 @@ import builtins -from typing import Any, Callable, Iterable +from typing import Callable, Iterable, TypeVar + +T = TypeVar("T") +U = TypeVar("U") # noqa: W0622 # pylint: disable=W0622 -def map[T: Any, U]( - func: Callable[[T], U], / -) -> Callable[[Iterable[T]], Iterable[U]]: +def map(func: Callable[[T], U], /) -> Callable[[Iterable[T]], Iterable[U]]: return lambda iterable: builtins.map(func, iterable) diff --git a/src/snakia/core/rx/merge.py b/src/snakia/core/rx/merge.py index fb5118e..9bf32f5 100644 --- a/src/snakia/core/rx/merge.py +++ b/src/snakia/core/rx/merge.py @@ -1,8 +1,12 @@ +from typing import TypeVar + from .async_bindable import AsyncBindable from .bindable import Bindable +T = TypeVar("T") -def merge[T]( + +def merge( *sources: Bindable[T], ) -> Bindable[T]: merged = Bindable[T]() @@ -11,7 +15,7 @@ def merge[T]( return merged -async def async_merge[T]( +async def async_merge( *sources: AsyncBindable[T], ) -> AsyncBindable[T]: merged = AsyncBindable[T]() diff --git a/src/snakia/core/tui/canvas.py b/src/snakia/core/tui/canvas.py index c44a99a..11f6ff4 100644 --- a/src/snakia/core/tui/canvas.py +++ b/src/snakia/core/tui/canvas.py @@ -40,9 +40,7 @@ class Canvas: def get_column(self, x: int, /) -> Iterable[CanvasChar]: """Get the column at the given position.""" - return ( - self.__buffer[self._get_index(x, y)] for y in range(self.height) - ) + return (self.__buffer[self._get_index(x, y)] for y in range(self.height)) def set(self, x: int, y: int, value: CanvasChar, /) -> None: """Set the character at the given position.""" @@ -68,9 +66,7 @@ class Canvas: value: CanvasChar, ) -> None: """Set the area at the given position.""" - for i in range( - self._get_index(x, y), self._get_index(x + width, y + height) - ): + for i in range(self._get_index(x, y), self._get_index(x + width, y + height)): self.__buffer[i] = value def clear(self) -> None: diff --git a/src/snakia/core/tui/widget.py b/src/snakia/core/tui/widget.py index b64a263..3a86c1d 100644 --- a/src/snakia/core/tui/widget.py +++ b/src/snakia/core/tui/widget.py @@ -1,11 +1,13 @@ from abc import ABC, abstractmethod -from typing import Final, final +from typing import Final, TypeVar, final from snakia.core.rx import AsyncBindable, Bindable from snakia.utils import to_async from .canvas import Canvas +T = TypeVar("T") + class Widget(ABC): def __init__(self) -> None: @@ -24,13 +26,13 @@ class Widget(ABC): return self.__cache @final - def state[T](self, default_value: T) -> Bindable[T]: + def state(self, default_value: T) -> Bindable[T]: field = Bindable(default_value) field.subscribe(lambda _: self.dirty.set(True)) return field @final - def async_state[T](self, default_value: T) -> AsyncBindable[T]: + def async_state(self, default_value: T) -> AsyncBindable[T]: field = AsyncBindable(default_value) field.subscribe(to_async(lambda _: self.dirty.set(True))) return field diff --git a/src/snakia/core/tui/widgets/horizontal_split.py b/src/snakia/core/tui/widgets/horizontal_split.py index a267625..4a3c450 100644 --- a/src/snakia/core/tui/widgets/horizontal_split.py +++ b/src/snakia/core/tui/widgets/horizontal_split.py @@ -8,9 +8,7 @@ from .container import ContainerWidget class HorizontalSplitWidget(ContainerWidget): - def __init__( - self, children: Iterable[Widget], splitter_char: str = "|" - ) -> None: + def __init__(self, children: Iterable[Widget], splitter_char: str = "|") -> None: super().__init__(children) self.splitter_char = splitter_char @@ -21,9 +19,7 @@ class HorizontalSplitWidget(ContainerWidget): child_canvases = [child.render() for child in children_list] total_width = ( - sum(canvas.width for canvas in child_canvases) - + len(child_canvases) - - 1 + sum(canvas.width for canvas in child_canvases) + len(child_canvases) - 1 ) max_height = max(canvas.height for canvas in child_canvases) diff --git a/src/snakia/core/tui/widgets/vertical_split.py b/src/snakia/core/tui/widgets/vertical_split.py index 5cdf08a..66e3686 100644 --- a/src/snakia/core/tui/widgets/vertical_split.py +++ b/src/snakia/core/tui/widgets/vertical_split.py @@ -8,9 +8,7 @@ from .container import ContainerWidget class VerticalSplitWidget(ContainerWidget): - def __init__( - self, children: Iterable[Widget], splitter_char: str = "-" - ) -> None: + def __init__(self, children: Iterable[Widget], splitter_char: str = "-") -> None: super().__init__(children) self.splitter_char = splitter_char @@ -22,9 +20,7 @@ class VerticalSplitWidget(ContainerWidget): child_canvases = [child.render() for child in children_list] max_width = max(canvas.width for canvas in child_canvases) total_height = ( - sum(canvas.height for canvas in child_canvases) - + len(child_canvases) - - 1 + sum(canvas.height for canvas in child_canvases) + len(child_canvases) - 1 ) result = Canvas(max_width, total_height, CanvasChar()) diff --git a/src/snakia/decorators/inject_after.py b/src/snakia/decorators/inject_after.py index c62974f..d9e397e 100644 --- a/src/snakia/decorators/inject_after.py +++ b/src/snakia/decorators/inject_after.py @@ -1,18 +1,20 @@ -from typing import Callable +from typing import Callable, ParamSpec, TypeVar from .inject_replace import inject_replace +P = ParamSpec("P") +T = TypeVar("T") +R = TypeVar("R") -def inject_after[T: object, **P, R]( - obj: T, target: Callable[P, R], hook: Callable[[R], R] -) -> T: + +def inject_after(obj: T, target: Callable[P, R], hook: Callable[[R], R]) -> T: def inner(*args: P.args, **kwargs: P.kwargs) -> R: return hook(target(*args, **kwargs)) return inject_replace(obj, target, inner) -def after_hook[**P, R]( +def after_hook( obj: object, target: Callable[P, R] ) -> Callable[[Callable[[R], R]], Callable[[R], R]]: def hook(new: Callable[[R], R]) -> Callable[[R], R]: diff --git a/src/snakia/decorators/inject_before.py b/src/snakia/decorators/inject_before.py index 8f30dc1..769b12a 100644 --- a/src/snakia/decorators/inject_before.py +++ b/src/snakia/decorators/inject_before.py @@ -1,11 +1,13 @@ -from typing import Any, Callable +from typing import Any, Callable, ParamSpec, TypeVar from .inject_replace import inject_replace +P = ParamSpec("P") +T = TypeVar("T") +R = TypeVar("R") -def inject_before[T: object, **P, R]( - obj: T, target: Callable[P, R], hook: Callable[P, Any] -) -> T: + +def inject_before(obj: T, target: Callable[P, R], hook: Callable[P, Any]) -> T: def inner(*args: P.args, **kwargs: P.kwargs) -> R: hook(*args, **kwargs) return target(*args, **kwargs) @@ -13,7 +15,7 @@ def inject_before[T: object, **P, R]( return inject_replace(obj, target, inner) -def before_hook[**P, R]( +def before_hook( obj: object, target: Callable[P, R] ) -> Callable[[Callable[P, Any]], Callable[P, Any]]: diff --git a/src/snakia/decorators/inject_const.py b/src/snakia/decorators/inject_const.py index d6610d8..e281360 100644 --- a/src/snakia/decorators/inject_const.py +++ b/src/snakia/decorators/inject_const.py @@ -1,10 +1,12 @@ import sys from types import FunctionType -from typing import Any, Callable, cast +from typing import Any, Callable, TypeVar, cast + +T = TypeVar("T", bound=Callable[..., Any]) if sys.version_info >= (3, 13): - def inject_const[T: Callable[..., Any]](**consts: Any) -> Callable[[T], T]: + def inject_const(**consts: Any) -> Callable[[T], T]: def inner(func: T) -> T: values = [*func.__code__.co_consts] for i, name in enumerate(func.__code__.co_varnames): @@ -26,7 +28,7 @@ if sys.version_info >= (3, 13): else: - def inject_const[T: Callable[..., Any]](**consts: Any) -> Callable[[T], T]: + def inject_const(**consts: Any) -> Callable[[T], T]: def inner(func: T) -> T: values = [*func.__code__.co_consts] for i, name in enumerate(func.__code__.co_varnames): diff --git a/src/snakia/decorators/inject_replace.py b/src/snakia/decorators/inject_replace.py index d85e445..4d459c5 100644 --- a/src/snakia/decorators/inject_replace.py +++ b/src/snakia/decorators/inject_replace.py @@ -1,16 +1,18 @@ -from typing import Callable +from typing import Callable, ParamSpec, TypeVar + +P = ParamSpec("P") +T = TypeVar("T") +R = TypeVar("R") -def inject_replace[T: object, **P, R]( - obj: T, old: Callable[P, R], new: Callable[P, R] -) -> T: +def inject_replace(obj: T, old: Callable[P, R], new: Callable[P, R]) -> T: for k, v in obj.__dict__.items(): if v is old: setattr(obj, k, new) return obj -def replace_hook[**P, R]( +def replace_hook( obj: object, old: Callable[P, R] ) -> Callable[[Callable[P, R]], Callable[P, R]]: def hook(new: Callable[P, R]) -> Callable[P, R]: diff --git a/src/snakia/decorators/pass_exceptions.py b/src/snakia/decorators/pass_exceptions.py index cd31a89..73c9c25 100644 --- a/src/snakia/decorators/pass_exceptions.py +++ b/src/snakia/decorators/pass_exceptions.py @@ -1,14 +1,17 @@ from __future__ import annotations -from typing import Any, Callable, overload +from typing import Any, Callable, ParamSpec, TypeVar, overload + +P = ParamSpec("P") +R = TypeVar("R") @overload -def pass_exceptions[**P]( +def pass_exceptions( *errors: type[Exception], ) -> Callable[[Callable[P, Any | None]], Callable[P, Any | None]]: ... @overload -def pass_exceptions[**P, R]( +def pass_exceptions( *errors: type[Exception], default: R, ) -> Callable[[Callable[P, R]], Callable[P, R]]: ... diff --git a/src/snakia/decorators/singleton.py b/src/snakia/decorators/singleton.py index 5a1b21a..d8b644c 100644 --- a/src/snakia/decorators/singleton.py +++ b/src/snakia/decorators/singleton.py @@ -1,2 +1,7 @@ -def singleton[T](cls: type[T]) -> T: +from typing import TypeVar + +T = TypeVar("T") + + +def singleton(cls: type[T]) -> T: return cls() diff --git a/src/snakia/field/auto.py b/src/snakia/field/auto.py index 2ed3945..1216a30 100644 --- a/src/snakia/field/auto.py +++ b/src/snakia/field/auto.py @@ -1,23 +1,21 @@ import pickle -from typing import Final, override +from typing import Final, Generic, TypeVar from .field import Field +T = TypeVar("T") -class AutoField[T](Field[T]): + +class AutoField(Field[T], Generic[T]): __slots__ = ("__target_type",) - def __init__( - self, default_value: T, *, target_type: type[T] | None = None - ) -> None: + def __init__(self, default_value: T, *, target_type: type[T] | None = None) -> None: super().__init__(default_value) self.__target_type: Final = target_type - @override def serialize(self, value: T, /) -> bytes: return pickle.dumps(value) - @override def deserialize(self, serialized: bytes, /) -> T: value = pickle.loads(serialized) if not isinstance(value, self.__target_type or object): diff --git a/src/snakia/field/bool.py b/src/snakia/field/bool.py index 1cad3f8..f6637f7 100644 --- a/src/snakia/field/bool.py +++ b/src/snakia/field/bool.py @@ -1,13 +1,9 @@ -from typing import override - from .field import Field class BoolField(Field[bool]): - @override def serialize(self, value: bool, /) -> bytes: return b"\x01" if value else b"\x00" - @override def deserialize(self, serialized: bytes, /) -> bool: return serialized == b"\x01" diff --git a/src/snakia/field/field.py b/src/snakia/field/field.py index 4fdc9d6..628b7c7 100644 --- a/src/snakia/field/field.py +++ b/src/snakia/field/field.py @@ -1,13 +1,16 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, Callable, Final, final +from typing import TYPE_CHECKING, Any, Callable, Final, Generic, TypeVar, final from snakia.property.priv_property import PrivProperty from snakia.utils import inherit +T = TypeVar("T") +R = TypeVar("R") -class Field[T: Any](ABC, PrivProperty[T]): + +class Field(ABC, PrivProperty[T], Generic[T]): def __init__(self, default_value: T) -> None: self.default_value: Final[T] = default_value super().__init__(default_value) @@ -34,23 +37,19 @@ class Field[T: Any](ABC, PrivProperty[T]): @final @classmethod - def custom[R]( + def custom( cls: type[Field[Any]], serialize: Callable[[Field[R], R], bytes], deserialize: Callable[[Field[R], bytes], R], ) -> type[Field[R]]: - return inherit( - cls, {"serialize": serialize, "deserialize": deserialize} - ) + return inherit(cls, {"serialize": serialize, "deserialize": deserialize}) @final @staticmethod def get_fields(class_: type[Any] | Any, /) -> dict[str, Field[Any]]: if not isinstance(class_, type): class_ = class_.__class__ - return { - k: v for k, v in class_.__dict__.items() if isinstance(v, Field) - } + return {k: v for k, v in class_.__dict__.items() if isinstance(v, Field)} if TYPE_CHECKING: diff --git a/src/snakia/field/float.py b/src/snakia/field/float.py index 10f852a..e25a2ff 100644 --- a/src/snakia/field/float.py +++ b/src/snakia/field/float.py @@ -1,14 +1,11 @@ import struct -from typing import override from .field import Field class FloatField(Field[float]): - @override def serialize(self, value: float, /) -> bytes: return struct.pack(">f", value) - @override def deserialize(self, serialized: bytes, /) -> float: return struct.unpack(">f", serialized)[0] # type: ignore diff --git a/src/snakia/field/int.py b/src/snakia/field/int.py index ca268aa..890f522 100644 --- a/src/snakia/field/int.py +++ b/src/snakia/field/int.py @@ -1,14 +1,10 @@ -from typing import override - from .field import Field class IntField(Field[int]): - @override def serialize(self, value: int, /) -> bytes: length = (value.bit_length() + 7) // 8 return value.to_bytes(length, "little") - @override def deserialize(self, serialized: bytes, /) -> int: return int.from_bytes(serialized, "little") diff --git a/src/snakia/field/str.py b/src/snakia/field/str.py index 1bdacfb..152a938 100644 --- a/src/snakia/field/str.py +++ b/src/snakia/field/str.py @@ -1,4 +1,4 @@ -from typing import Final, override +from typing import Final from .field import Field @@ -8,10 +8,8 @@ class StrField(Field[str]): super().__init__(default_value) self.encoding: Final = encoding - @override def serialize(self, value: str, /) -> bytes: return value.encode(self.encoding) - @override def deserialize(self, serialized: bytes, /) -> str: return serialized.decode(self.encoding) diff --git a/src/snakia/platform/android.py b/src/snakia/platform/android.py index ea9621f..ab60652 100644 --- a/src/snakia/platform/android.py +++ b/src/snakia/platform/android.py @@ -1,11 +1,13 @@ from __future__ import annotations from ctypes import CDLL, Array, c_char, c_char_p, create_string_buffer -from typing import Any, Final, Literal, cast, overload +from typing import Any, Final, Literal, TypeVar, cast, overload from .layer import PlatformLayer from .os import PlatformOS +T = TypeVar("T") + class AndroidLayer(PlatformLayer[Literal[PlatformOS.ANDROID]]): target = PlatformOS.ANDROID @@ -16,7 +18,7 @@ class AndroidLayer(PlatformLayer[Literal[PlatformOS.ANDROID]]): def get_prop(self, name: str) -> str | None: ... @overload - def get_prop[T](self, name: str, default: T) -> str | T: ... + def get_prop(self, name: str, default: T) -> str | T: ... def get_prop(self, name: str, default: Any = None) -> Any: buffer = create_string_buffer(self.PROP_VALUE_MAX) diff --git a/src/snakia/platform/layer.py b/src/snakia/platform/layer.py index 8420b43..31521e3 100644 --- a/src/snakia/platform/layer.py +++ b/src/snakia/platform/layer.py @@ -1,11 +1,15 @@ from __future__ import annotations -from typing import ClassVar, Self, final, overload +from typing import ClassVar, Generic, TypeVar, final, overload + +from typing_extensions import Self from .os import PlatformOS +T = TypeVar("T", bound=PlatformOS) -class PlatformLayer[T: PlatformOS]: + +class PlatformLayer(Generic[T]): target: ClassVar[PlatformOS] = PlatformOS.UNKNOWN @final diff --git a/src/snakia/property/cell_property.py b/src/snakia/property/cell_property.py index 84a0924..8eacea6 100644 --- a/src/snakia/property/cell_property.py +++ b/src/snakia/property/cell_property.py @@ -1,14 +1,29 @@ -from typing import Any, Callable, Self +from __future__ import annotations + +from typing import Any, Generic, Protocol, TypeAlias, TypeVar + +from typing_extensions import Self from snakia.types import empty -type _Cell[T] = T | None -type _Getter[T] = Callable[[Any, _Cell[T]], T] -type _Setter[T] = Callable[[Any, _Cell[T], T], _Cell[T]] -type _Deleter[T] = Callable[[Any, _Cell[T]], _Cell[T]] +T = TypeVar("T") + +_Cell: TypeAlias = T | None -class CellProperty[T]: +class _Getter(Protocol, Generic[T]): + def __call__(self, instance: Any, cell: _Cell[T], /) -> T: ... + + +class _Setter(Protocol, Generic[T]): + def __call__(self, instance: Any, cell: _Cell[T], value: T, /) -> _Cell[T]: ... + + +class _Deleter(Protocol, Generic[T]): + def __call__(self, instance: Any, cell: _Cell[T], /) -> _Cell[T]: ... + + +class CellProperty(Generic[T]): """ A property that uses a cell to store its value. """ diff --git a/src/snakia/property/classproperty.py b/src/snakia/property/classproperty.py index a9b2413..afba619 100644 --- a/src/snakia/property/classproperty.py +++ b/src/snakia/property/classproperty.py @@ -1,9 +1,15 @@ -from typing import Any, Callable, Self +from __future__ import annotations + +from typing import Any, Callable, Generic, TypeVar + +from typing_extensions import Self from snakia.types import empty +T = TypeVar("T") -class ClassProperty[T]: + +class ClassProperty(Generic[T]): """ Class property """ @@ -47,7 +53,7 @@ class ClassProperty[T]: return self -def classproperty[T]( +def classproperty( fget: Callable[[Any], T], fset: Callable[[Any, T], None] = empty.func, fdel: Callable[[Any], None] = empty.func, @@ -59,6 +65,6 @@ def classproperty[T]( fset (Callable[[Any, T], None], optional): The setter function. Defaults to empty.func. fdel (Callable[[Any], None], optional): The deleter function. Defaults to empty.func. Returns: - ClassProperty[T]: The class property. + Self: The class property. """ return ClassProperty(fget, fset, fdel) diff --git a/src/snakia/property/hook_property.py b/src/snakia/property/hook_property.py index c6fa676..568ee01 100644 --- a/src/snakia/property/hook_property.py +++ b/src/snakia/property/hook_property.py @@ -1,11 +1,17 @@ -from typing import Any, Callable, Self +from __future__ import annotations + +from typing import Any, Callable, Generic, TypeVar + +from typing_extensions import Self from snakia.types import empty from .priv_property import PrivProperty +T = TypeVar("T") -class HookProperty[T](PrivProperty[T]): + +class HookProperty(PrivProperty[T], Generic[T]): """ A property that calls a function when the property is set, get, or deleted. """ diff --git a/src/snakia/property/initonly.py b/src/snakia/property/initonly.py index 2193b4f..e5638c5 100644 --- a/src/snakia/property/initonly.py +++ b/src/snakia/property/initonly.py @@ -1,9 +1,11 @@ -from typing import Any +from typing import Any, Generic, TypeVar from .priv_property import PrivProperty +T = TypeVar("T") -class Initonly[T](PrivProperty[T]): + +class Initonly(PrivProperty[T], Generic[T]): """Property that can only be set once.""" def __set__(self, instance: Any, value: T, /) -> None: diff --git a/src/snakia/property/priv_property.py b/src/snakia/property/priv_property.py index 2628ec9..1a85e09 100644 --- a/src/snakia/property/priv_property.py +++ b/src/snakia/property/priv_property.py @@ -1,7 +1,9 @@ -from typing import Any +from typing import Any, Generic, TypeVar + +T = TypeVar("T") -class PrivProperty[T]: +class PrivProperty(Generic[T]): __slots__ = "__name", "__default_value" __name: str diff --git a/src/snakia/property/property.py b/src/snakia/property/property.py index 53b30bc..969cc04 100644 --- a/src/snakia/property/property.py +++ b/src/snakia/property/property.py @@ -1,9 +1,13 @@ -from typing import Any, Callable, Self +from __future__ import annotations + +from typing import Any, Callable, Generic, TypeVar from snakia.types import empty +T = TypeVar("T") -class Property[T]: + +class Property(Generic[T]): """ A property that can be set, get, and deleted. """ @@ -34,17 +38,17 @@ class Property[T]: def __delete__(self, instance: Any, /) -> None: return self.__fdel(instance) - def getter(self, fget: Callable[[Any], T], /) -> Self: + def getter(self, fget: Callable[[Any], T], /) -> Property[T]: """Descriptor getter.""" self.__fget = fget return self - def setter(self, fset: Callable[[Any, T], None], /) -> Self: + def setter(self, fset: Callable[[Any, T], None], /) -> Property[T]: """Descriptor setter.""" self.__fset = fset return self - def deleter(self, fdel: Callable[[Any], None], /) -> Self: + def deleter(self, fdel: Callable[[Any], None], /) -> Property[T]: """Descriptor deleter.""" self.__fdel = fdel return self diff --git a/src/snakia/property/readonly.py b/src/snakia/property/readonly.py index d9e8943..7084fe7 100644 --- a/src/snakia/property/readonly.py +++ b/src/snakia/property/readonly.py @@ -1,11 +1,13 @@ -from typing import Any, Callable +from typing import Any, Callable, Generic, TypeVar from snakia.utils import throw from .property import Property +T = TypeVar("T") -class Readonly[T](Property[T]): + +class Readonly(Property[T], Generic[T]): """ Readonly property. """ @@ -26,7 +28,7 @@ class Readonly[T](Property[T]): ) -def readonly[T](value: T, *, strict: bool = False) -> Readonly[T]: +def readonly(value: T, *, strict: bool = False) -> Readonly[T]: """Create a readonly property with the given value. Args: diff --git a/src/snakia/random/os.py b/src/snakia/random/os.py index 558afd2..6f3b20a 100644 --- a/src/snakia/random/os.py +++ b/src/snakia/random/os.py @@ -9,7 +9,8 @@ class OSRandom(Random[None]): """ def bits(self, k: int) -> int: - return int.from_bytes(os.urandom((k + 7) // 8)) & ((1 << k) - 1) + v = os.urandom((k + 7) // 8) + return int.from_bytes(v, "little") & ((1 << k) - 1) def get_state(self) -> None: return None diff --git a/src/snakia/random/python.py b/src/snakia/random/python.py index 0a680d2..61698af 100644 --- a/src/snakia/random/python.py +++ b/src/snakia/random/python.py @@ -1,8 +1,9 @@ import random +from typing import TypeAlias from .random import Random -type _State = tuple[int, tuple[int, ...], int | float | None] +_State: TypeAlias = tuple[int, tuple[int, ...], int | float | None] class PythonRandom(Random[_State]): diff --git a/src/snakia/random/random.py b/src/snakia/random/random.py index 5fcbec1..3c32d04 100644 --- a/src/snakia/random/random.py +++ b/src/snakia/random/random.py @@ -1,9 +1,13 @@ import builtins from abc import ABC, abstractmethod -from typing import Any, MutableSequence, Sequence, final +from typing import Any, Generic, MutableSequence, Sequence, TypeVar, final + +S = TypeVar("S") +T = TypeVar("T") +M = TypeVar("M", bound=MutableSequence[Any]) -class Random[S](ABC): +class Random(ABC, Generic[S]): """ A random number generator. """ @@ -45,12 +49,12 @@ class Random[S](ABC): return self.bits(32) / (1 << 32) @final - def choice[T](self, seq: Sequence[T]) -> T: + def choice(self, seq: Sequence[T]) -> T: """Return a random element from a non-empty sequence.""" return seq[self.below(len(seq))] @final - def shuffle[T: MutableSequence[Any]](self, seq: T) -> T: + def shuffle(self, seq: M) -> M: """Shuffle a sequence in place.""" for i in range(len(seq) - 1, 0, -1): j = self.below(i + 1) diff --git a/src/snakia/types/unique.py b/src/snakia/types/unique.py index 52aaf6f..2b3f52f 100644 --- a/src/snakia/types/unique.py +++ b/src/snakia/types/unique.py @@ -1,4 +1,6 @@ -from typing import Any, final +from typing import Any, TypeVar, final + +T = TypeVar("T") @final @@ -31,7 +33,7 @@ class UniqueType(type): def __eq__(cls, other: Any) -> bool: return cls is other - def __call__[T](cls: type[T]) -> T: + def __call__(cls: type[T]) -> T: return cls.__new__(cls) # noqa: E1120 # pylint: disable=E1120 diff --git a/src/snakia/utils/inherit.py b/src/snakia/utils/inherit.py index a971f62..a396524 100644 --- a/src/snakia/utils/inherit.py +++ b/src/snakia/utils/inherit.py @@ -1,9 +1,9 @@ -from typing import Any +from typing import Any, TypeVar + +T = TypeVar("T", bound=type) -def inherit[T: type]( - type_: T, attrs: dict[str, Any] | None = None, /, **kwargs: Any -) -> T: +def inherit(type_: T, attrs: dict[str, Any] | None = None, /, **kwargs: Any) -> T: """ Create a new class that inherits from the given class. diff --git a/src/snakia/utils/throw.py b/src/snakia/utils/throw.py index b93d8ac..b1a6d4c 100644 --- a/src/snakia/utils/throw.py +++ b/src/snakia/utils/throw.py @@ -1,10 +1,14 @@ -from typing import Any, NoReturn, overload +from typing import Any, NoReturn, TypeVar, overload + +from exceptiongroup import ExceptionGroup from snakia.types.unset import Unset +T = TypeVar("T", bound=Exception) + @overload -def throw[T: Exception]( +def throw( *exceptions: T, # pyright: ignore[reportInvalidTypeVarUse] from_: Unset | BaseException = Unset(), ) -> NoReturn: ... diff --git a/src/snakia/utils/to_async.py b/src/snakia/utils/to_async.py index 1866a01..e701a22 100644 --- a/src/snakia/utils/to_async.py +++ b/src/snakia/utils/to_async.py @@ -1,7 +1,10 @@ -from typing import Awaitable, Callable +from typing import Awaitable, Callable, ParamSpec, TypeVar + +P = ParamSpec("P") +R = TypeVar("R") -def to_async[**P, R](func: Callable[P, R]) -> Callable[P, Awaitable[R]]: +def to_async(func: Callable[P, R]) -> Callable[P, Awaitable[R]]: """Convert a sync function to an async function.""" async def inner(*args: P.args, **kwargs: P.kwargs) -> R: