From 521aa7c323ded81a9697f915acd40156683d9b42 Mon Sep 17 00:00:00 2001 From: rus07tam Date: Wed, 29 Oct 2025 15:03:12 +0000 Subject: [PATCH 1/4] ci: lower minimum python version from 3.12 to 3.10 --- .github/workflows/docs-publish.yml | 2 +- .github/workflows/pylint.yml | 2 +- .github/workflows/pypi-publish.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docs-publish.yml b/.github/workflows/docs-publish.yml index fae2a4f..7807037 100644 --- a/.github/workflows/docs-publish.yml +++ b/.github/workflows/docs-publish.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.12"] + python-version: ["3.10"] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index b7ad910..fce59c2 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.12"] + python-version: ["3.10"] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 9e948a6..b6bcaaa 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.12"] + python-version: ["3.10"] steps: - uses: actions/checkout@v4 -- 2.52.0 From 4f5d3d550b4694495261adb87e14cb6986650041 Mon Sep 17 00:00:00 2001 From: rus07tam Date: Wed, 29 Oct 2025 15:04:05 +0000 Subject: [PATCH 2/4] 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: -- 2.52.0 From dc33a78700bd134b11021e8ef0e7cfcb0eda458f Mon Sep 17 00:00:00 2001 From: rus07tam Date: Wed, 29 Oct 2025 15:04:49 +0000 Subject: [PATCH 3/4] chore: update pyproject.toml classifiers --- pyproject.toml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c781094..904f07c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,15 +10,19 @@ keywords = ["python3", "event system", "ecs", "reactive programming"] classifiers = [ "Development Status :: 3 - Alpha", "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Free Threading", ] -requires-python = ">=3.12" +requires-python = ">=3.10" dependencies = [ + "exceptiongroup>=1.3.0", "networkx>=3.4.2", "pydantic>=2.12.3", + "typing-extensions>=4.15.0", ] license = "CC0-1.0" license-files = ["LICENSE"] @@ -38,4 +42,4 @@ disable = ["C0114", "C0115", "C0116", "R0801"] max-args = 8 max-positional-arguments = 7 min-public-methods = 1 -fail-on = "error" \ No newline at end of file +fail-on = "error" -- 2.52.0 From 7f25bcbcb487c63348457a0ad59b7f90bca80239 Mon Sep 17 00:00:00 2001 From: rus07tam Date: Wed, 29 Oct 2025 15:05:43 +0000 Subject: [PATCH 4/4] chore: bump version to 0.4.2 --- flake.nix | 21 +++++------- pyproject.toml | 2 +- requirements.txt | 6 +++- uv.lock | 89 ++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 101 insertions(+), 17 deletions(-) diff --git a/flake.nix b/flake.nix index 541e3f5..34c8081 100644 --- a/flake.nix +++ b/flake.nix @@ -6,23 +6,20 @@ flake-utils.url = "github:numtide/flake-utils"; }; - outputs = - { - nixpkgs, - flake-utils, - ... - }: + outputs = { + nixpkgs, + flake-utils, + ... + }: flake-utils.lib.eachDefaultSystem ( - system: - let + system: let pkgs = import nixpkgs { inherit system; }; - in - { + in { devShells.default = pkgs.mkShell { nativeBuildInputs = with pkgs; [ - python312 + python310 ]; buildInputs = with pkgs; [ @@ -31,7 +28,7 @@ uv isort mypy - python312Packages.pylint + pylint ]; }; } diff --git a/pyproject.toml b/pyproject.toml index 904f07c..ea0c76b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "snakia" -version = "0.4.1" +version = "0.4.2" description = "Modern python framework" readme = "README.md" authors = [ diff --git a/requirements.txt b/requirements.txt index a56e901..6cf3c8a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,9 @@ # uv pip compile pyproject.toml -o requirements.txt annotated-types==0.7.0 # via pydantic -networkx==3.5 +exceptiongroup==1.3.0 + # via snakia (pyproject.toml) +networkx==3.4.2 # via snakia (pyproject.toml) pydantic==2.12.3 # via snakia (pyproject.toml) @@ -10,6 +12,8 @@ pydantic-core==2.41.4 # via pydantic typing-extensions==4.15.0 # via + # snakia (pyproject.toml) + # exceptiongroup # pydantic # pydantic-core # typing-inspection diff --git a/uv.lock b/uv.lock index b6deda9..a4cfaf1 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,10 @@ version = 1 revision = 3 -requires-python = ">=3.12" +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version < '3.11'", +] [[package]] name = "annotated-types" @@ -11,10 +15,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, +] + [[package]] name = "networkx" version = "3.5" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, @@ -44,6 +75,33 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/3d/9b8ca77b0f76fcdbf8bc6b72474e264283f461284ca84ac3fde570c6c49a/pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e", size = 2111197, upload-time = "2025-10-14T10:19:43.303Z" }, + { url = "https://files.pythonhosted.org/packages/59/92/b7b0fe6ed4781642232755cb7e56a86e2041e1292f16d9ae410a0ccee5ac/pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b", size = 1917909, upload-time = "2025-10-14T10:19:45.194Z" }, + { url = "https://files.pythonhosted.org/packages/52/8c/3eb872009274ffa4fb6a9585114e161aa1a0915af2896e2d441642929fe4/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd", size = 1969905, upload-time = "2025-10-14T10:19:46.567Z" }, + { url = "https://files.pythonhosted.org/packages/f4/21/35adf4a753bcfaea22d925214a0c5b880792e3244731b3f3e6fec0d124f7/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945", size = 2051938, upload-time = "2025-10-14T10:19:48.237Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d0/cdf7d126825e36d6e3f1eccf257da8954452934ede275a8f390eac775e89/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706", size = 2250710, upload-time = "2025-10-14T10:19:49.619Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1c/af1e6fd5ea596327308f9c8d1654e1285cc3d8de0d584a3c9d7705bf8a7c/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba", size = 2367445, upload-time = "2025-10-14T10:19:51.269Z" }, + { url = "https://files.pythonhosted.org/packages/d3/81/8cece29a6ef1b3a92f956ea6da6250d5b2d2e7e4d513dd3b4f0c7a83dfea/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b", size = 2072875, upload-time = "2025-10-14T10:19:52.671Z" }, + { url = "https://files.pythonhosted.org/packages/e3/37/a6a579f5fc2cd4d5521284a0ab6a426cc6463a7b3897aeb95b12f1ba607b/pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d", size = 2191329, upload-time = "2025-10-14T10:19:54.214Z" }, + { url = "https://files.pythonhosted.org/packages/ae/03/505020dc5c54ec75ecba9f41119fd1e48f9e41e4629942494c4a8734ded1/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700", size = 2151658, upload-time = "2025-10-14T10:19:55.843Z" }, + { url = "https://files.pythonhosted.org/packages/cb/5d/2c0d09fb53aa03bbd2a214d89ebfa6304be7df9ed86ee3dc7770257f41ee/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6", size = 2316777, upload-time = "2025-10-14T10:19:57.607Z" }, + { url = "https://files.pythonhosted.org/packages/ea/4b/c2c9c8f5e1f9c864b57d08539d9d3db160e00491c9f5ee90e1bfd905e644/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9", size = 2320705, upload-time = "2025-10-14T10:19:59.016Z" }, + { url = "https://files.pythonhosted.org/packages/28/c3/a74c1c37f49c0a02c89c7340fafc0ba816b29bd495d1a31ce1bdeacc6085/pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57", size = 1975464, upload-time = "2025-10-14T10:20:00.581Z" }, + { url = "https://files.pythonhosted.org/packages/d6/23/5dd5c1324ba80303368f7569e2e2e1a721c7d9eb16acb7eb7b7f85cb1be2/pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc", size = 2024497, upload-time = "2025-10-14T10:20:03.018Z" }, + { url = "https://files.pythonhosted.org/packages/62/4c/f6cbfa1e8efacd00b846764e8484fe173d25b8dab881e277a619177f3384/pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80", size = 2109062, upload-time = "2025-10-14T10:20:04.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/f8/40b72d3868896bfcd410e1bd7e516e762d326201c48e5b4a06446f6cf9e8/pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae", size = 1916301, upload-time = "2025-10-14T10:20:06.857Z" }, + { url = "https://files.pythonhosted.org/packages/94/4d/d203dce8bee7faeca791671c88519969d98d3b4e8f225da5b96dad226fc8/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827", size = 1968728, upload-time = "2025-10-14T10:20:08.353Z" }, + { url = "https://files.pythonhosted.org/packages/65/f5/6a66187775df87c24d526985b3a5d78d861580ca466fbd9d4d0e792fcf6c/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f", size = 2050238, upload-time = "2025-10-14T10:20:09.766Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b9/78336345de97298cf53236b2f271912ce11f32c1e59de25a374ce12f9cce/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def", size = 2249424, upload-time = "2025-10-14T10:20:11.732Z" }, + { url = "https://files.pythonhosted.org/packages/99/bb/a4584888b70ee594c3d374a71af5075a68654d6c780369df269118af7402/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2", size = 2366047, upload-time = "2025-10-14T10:20:13.647Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8d/17fc5de9d6418e4d2ae8c675f905cdafdc59d3bf3bf9c946b7ab796a992a/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8", size = 2071163, upload-time = "2025-10-14T10:20:15.307Z" }, + { url = "https://files.pythonhosted.org/packages/54/e7/03d2c5c0b8ed37a4617430db68ec5e7dbba66358b629cd69e11b4d564367/pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265", size = 2190585, upload-time = "2025-10-14T10:20:17.3Z" }, + { url = "https://files.pythonhosted.org/packages/be/fc/15d1c9fe5ad9266a5897d9b932b7f53d7e5cfc800573917a2c5d6eea56ec/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c", size = 2150109, upload-time = "2025-10-14T10:20:19.143Z" }, + { url = "https://files.pythonhosted.org/packages/26/ef/e735dd008808226c83ba56972566138665b71477ad580fa5a21f0851df48/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a", size = 2315078, upload-time = "2025-10-14T10:20:20.742Z" }, + { url = "https://files.pythonhosted.org/packages/90/00/806efdcf35ff2ac0f938362350cd9827b8afb116cc814b6b75cf23738c7c/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e", size = 2318737, upload-time = "2025-10-14T10:20:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/41/7e/6ac90673fe6cb36621a2283552897838c020db343fa86e513d3f563b196f/pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03", size = 1974160, upload-time = "2025-10-14T10:20:23.817Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9d/7c5e24ee585c1f8b6356e1d11d40ab807ffde44d2db3b7dfd6d20b09720e/pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e", size = 2021883, upload-time = "2025-10-14T10:20:25.48Z" }, + { url = "https://files.pythonhosted.org/packages/33/90/5c172357460fc28b2871eb4a0fb3843b136b429c6fa827e4b588877bf115/pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db", size = 1968026, upload-time = "2025-10-14T10:20:27.039Z" }, { url = "https://files.pythonhosted.org/packages/e9/81/d3b3e95929c4369d30b2a66a91db63c8ed0a98381ae55a45da2cd1cc1288/pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887", size = 2099043, upload-time = "2025-10-14T10:20:28.561Z" }, { url = "https://files.pythonhosted.org/packages/58/da/46fdac49e6717e3a94fc9201403e08d9d61aa7a770fab6190b8740749047/pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2", size = 1910699, upload-time = "2025-10-14T10:20:30.217Z" }, { url = "https://files.pythonhosted.org/packages/1e/63/4d948f1b9dd8e991a5a98b77dd66c74641f5f2e5225fee37994b2e07d391/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999", size = 1952121, upload-time = "2025-10-14T10:20:32.246Z" }, @@ -96,25 +154,50 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa", size = 2041183, upload-time = "2025-10-14T10:22:08.812Z" }, { url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d", size = 1993542, upload-time = "2025-10-14T10:22:11.332Z" }, { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" }, + { url = "https://files.pythonhosted.org/packages/b0/12/5ba58daa7f453454464f92b3ca7b9d7c657d8641c48e370c3ebc9a82dd78/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b", size = 2122139, upload-time = "2025-10-14T10:22:47.288Z" }, + { url = "https://files.pythonhosted.org/packages/21/fb/6860126a77725c3108baecd10fd3d75fec25191d6381b6eb2ac660228eac/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42", size = 1936674, upload-time = "2025-10-14T10:22:49.555Z" }, + { url = "https://files.pythonhosted.org/packages/de/be/57dcaa3ed595d81f8757e2b44a38240ac5d37628bce25fb20d02c7018776/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee", size = 1956398, upload-time = "2025-10-14T10:22:52.19Z" }, + { url = "https://files.pythonhosted.org/packages/2f/1d/679a344fadb9695f1a6a294d739fbd21d71fa023286daeea8c0ed49e7c2b/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c", size = 2138674, upload-time = "2025-10-14T10:22:54.499Z" }, { url = "https://files.pythonhosted.org/packages/c4/48/ae937e5a831b7c0dc646b2ef788c27cd003894882415300ed21927c21efa/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537", size = 2112087, upload-time = "2025-10-14T10:22:56.818Z" }, { url = "https://files.pythonhosted.org/packages/5e/db/6db8073e3d32dae017da7e0d16a9ecb897d0a4d92e00634916e486097961/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94", size = 1920387, upload-time = "2025-10-14T10:22:59.342Z" }, { url = "https://files.pythonhosted.org/packages/0d/c1/dd3542d072fcc336030d66834872f0328727e3b8de289c662faa04aa270e/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c", size = 1951495, upload-time = "2025-10-14T10:23:02.089Z" }, { url = "https://files.pythonhosted.org/packages/2b/c6/db8d13a1f8ab3f1eb08c88bd00fd62d44311e3456d1e85c0e59e0a0376e7/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335", size = 2139008, upload-time = "2025-10-14T10:23:04.539Z" }, + { url = "https://files.pythonhosted.org/packages/5d/d4/912e976a2dd0b49f31c98a060ca90b353f3b73ee3ea2fd0030412f6ac5ec/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00", size = 2106739, upload-time = "2025-10-14T10:23:06.934Z" }, + { url = "https://files.pythonhosted.org/packages/71/f0/66ec5a626c81eba326072d6ee2b127f8c139543f1bf609b4842978d37833/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9", size = 1932549, upload-time = "2025-10-14T10:23:09.24Z" }, + { url = "https://files.pythonhosted.org/packages/c4/af/625626278ca801ea0a658c2dcf290dc9f21bb383098e99e7c6a029fccfc0/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2", size = 2135093, upload-time = "2025-10-14T10:23:11.626Z" }, + { url = "https://files.pythonhosted.org/packages/20/f6/2fba049f54e0f4975fef66be654c597a1d005320fa141863699180c7697d/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258", size = 2187971, upload-time = "2025-10-14T10:23:14.437Z" }, + { url = "https://files.pythonhosted.org/packages/0e/80/65ab839a2dfcd3b949202f9d920c34f9de5a537c3646662bdf2f7d999680/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347", size = 2147939, upload-time = "2025-10-14T10:23:16.831Z" }, + { url = "https://files.pythonhosted.org/packages/44/58/627565d3d182ce6dfda18b8e1c841eede3629d59c9d7cbc1e12a03aeb328/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa", size = 2311400, upload-time = "2025-10-14T10:23:19.234Z" }, + { url = "https://files.pythonhosted.org/packages/24/06/8a84711162ad5a5f19a88cead37cca81b4b1f294f46260ef7334ae4f24d3/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a", size = 2316840, upload-time = "2025-10-14T10:23:21.738Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8b/b7bb512a4682a2f7fbfae152a755d37351743900226d29bd953aaf870eaa/pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d", size = 2149135, upload-time = "2025-10-14T10:23:24.379Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7d/138e902ed6399b866f7cfe4435d22445e16fff888a1c00560d9dc79a780f/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5", size = 2104721, upload-time = "2025-10-14T10:23:26.906Z" }, + { url = "https://files.pythonhosted.org/packages/47/13/0525623cf94627f7b53b4c2034c81edc8491cbfc7c28d5447fa318791479/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2", size = 1931608, upload-time = "2025-10-14T10:23:29.306Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f9/744bc98137d6ef0a233f808bfc9b18cf94624bf30836a18d3b05d08bf418/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd", size = 2132986, upload-time = "2025-10-14T10:23:32.057Z" }, + { url = "https://files.pythonhosted.org/packages/17/c8/629e88920171173f6049386cc71f893dff03209a9ef32b4d2f7e7c264bcf/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c", size = 2187516, upload-time = "2025-10-14T10:23:34.871Z" }, + { url = "https://files.pythonhosted.org/packages/2e/0f/4f2734688d98488782218ca61bcc118329bf5de05bb7fe3adc7dd79b0b86/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405", size = 2146146, upload-time = "2025-10-14T10:23:37.342Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f2/ab385dbd94a052c62224b99cf99002eee99dbec40e10006c78575aead256/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8", size = 2311296, upload-time = "2025-10-14T10:23:40.145Z" }, + { url = "https://files.pythonhosted.org/packages/fc/8e/e4f12afe1beeb9823bba5375f8f258df0cc61b056b0195fb1cf9f62a1a58/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308", size = 2315386, upload-time = "2025-10-14T10:23:42.624Z" }, + { url = "https://files.pythonhosted.org/packages/48/f7/925f65d930802e3ea2eb4d5afa4cb8730c8dc0d2cb89a59dc4ed2fcb2d74/pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f", size = 2147775, upload-time = "2025-10-14T10:23:45.406Z" }, ] [[package]] name = "snakia" -version = "0.4.1" +version = "0.4.2" source = { editable = "." } dependencies = [ - { name = "networkx" }, + { name = "exceptiongroup" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "networkx", version = "3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pydantic" }, + { name = "typing-extensions" }, ] [package.metadata] requires-dist = [ + { name = "exceptiongroup", specifier = ">=1.3.0" }, { name = "networkx", specifier = ">=3.4.2" }, { name = "pydantic", specifier = ">=2.12.3" }, + { name = "typing-extensions", specifier = ">=4.15.0" }, ] [[package]] -- 2.52.0