Merge pull request #2 from rus07tam/release/v0.4.2

Release/v0.4.2
This commit is contained in:
rus07tam 2025-10-29 18:15:30 +03:00 committed by GitHub
commit dbc57b8ecb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
59 changed files with 450 additions and 322 deletions

View file

@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: ["3.12"] python-version: ["3.10"]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View file

@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: ["3.12"] python-version: ["3.10"]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View file

@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: ["3.12"] python-version: ["3.10"]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View file

@ -6,23 +6,20 @@
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
}; };
outputs = outputs = {
{ nixpkgs,
nixpkgs, flake-utils,
flake-utils, ...
... }:
}:
flake-utils.lib.eachDefaultSystem ( flake-utils.lib.eachDefaultSystem (
system: system: let
let
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system; inherit system;
}; };
in in {
{
devShells.default = pkgs.mkShell { devShells.default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
python312 python310
]; ];
buildInputs = with pkgs; [ buildInputs = with pkgs; [
@ -31,7 +28,7 @@
uv uv
isort isort
mypy mypy
python312Packages.pylint pylint
]; ];
}; };
} }

View file

@ -1,6 +1,6 @@
[project] [project]
name = "snakia" name = "snakia"
version = "0.4.1" version = "0.4.2"
description = "Modern python framework" description = "Modern python framework"
readme = "README.md" readme = "README.md"
authors = [ authors = [
@ -10,15 +10,19 @@ keywords = ["python3", "event system", "ecs", "reactive programming"]
classifiers = [ classifiers = [
"Development Status :: 3 - Alpha", "Development Status :: 3 - Alpha",
"Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Free Threading", "Programming Language :: Python :: Free Threading",
] ]
requires-python = ">=3.12" requires-python = ">=3.10"
dependencies = [ dependencies = [
"exceptiongroup>=1.3.0",
"networkx>=3.4.2", "networkx>=3.4.2",
"pydantic>=2.12.3", "pydantic>=2.12.3",
"typing-extensions>=4.15.0",
] ]
license = "CC0-1.0" license = "CC0-1.0"
license-files = ["LICENSE"] license-files = ["LICENSE"]
@ -38,4 +42,4 @@ disable = ["C0114", "C0115", "C0116", "R0801"]
max-args = 8 max-args = 8
max-positional-arguments = 7 max-positional-arguments = 7
min-public-methods = 1 min-public-methods = 1
fail-on = "error" fail-on = "error"

View file

@ -2,7 +2,9 @@
# uv pip compile pyproject.toml -o requirements.txt # uv pip compile pyproject.toml -o requirements.txt
annotated-types==0.7.0 annotated-types==0.7.0
# via pydantic # via pydantic
networkx==3.5 exceptiongroup==1.3.0
# via snakia (pyproject.toml)
networkx==3.4.2
# via snakia (pyproject.toml) # via snakia (pyproject.toml)
pydantic==2.12.3 pydantic==2.12.3
# via snakia (pyproject.toml) # via snakia (pyproject.toml)
@ -10,6 +12,8 @@ pydantic-core==2.41.4
# via pydantic # via pydantic
typing-extensions==4.15.0 typing-extensions==4.15.0
# via # via
# snakia (pyproject.toml)
# exceptiongroup
# pydantic # pydantic
# pydantic-core # pydantic-core
# typing-inspection # typing-inspection

View file

@ -3,7 +3,7 @@ from __future__ import annotations
from collections import defaultdict from collections import defaultdict
from collections.abc import Iterable from collections.abc import Iterable
from itertools import count from itertools import count
from typing import Any, cast, overload from typing import Any, TypeVar, cast, overload
import networkx as nx # type: ignore import networkx as nx # type: ignore
@ -12,6 +12,14 @@ from snakia.utils import nolock
from .component import Component from .component import Component
from .processor import Processor 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: class System:
""" """
@ -46,9 +54,7 @@ class System:
self.__entity_counter = count(start=1) self.__entity_counter = count(start=1)
self.__dead_entities = set() self.__dead_entities = set()
def get_processor[P: Processor]( def get_processor(self, processor_type: type[P], /) -> P | None:
self, processor_type: type[P], /
) -> P | None:
"""Returns the first processor of the given type.""" """Returns the first processor of the given type."""
for processor in self.__processors: for processor in self.__processors:
if isinstance(processor, processor_type): if isinstance(processor, processor_type):
@ -67,39 +73,31 @@ class System:
self.__processors.remove(processor) self.__processors.remove(processor)
@overload @overload
def get_components[A: Component]( def get_components(self, c1: type[A], /) -> Iterable[tuple[int, tuple[A]]]: ...
self, __c1: type[A], /
) -> Iterable[tuple[int, tuple[A]]]: ...
@overload @overload
def get_components[A: Component, B: Component]( def get_components(
self, __c1: type[A], __c2: type[B], / self, c1: type[A], c2: type[B], /
) -> Iterable[tuple[int, tuple[A, B]]]: ... ) -> Iterable[tuple[int, tuple[A, B]]]: ...
@overload @overload
def get_components[A: Component, B: Component, C: Component]( def get_components(
self, __c1: type[A], __c2: type[B], __c3: type[C], / self, c1: type[A], c2: type[B], c3: type[C], /
) -> Iterable[tuple[int, tuple[A, B, C]]]: ... ) -> Iterable[tuple[int, tuple[A, B, C]]]: ...
@overload @overload
def get_components[A: Component, B: Component, C: Component, D: Component]( def get_components(
self, __c1: type[A], __c2: type[B], __c3: type[C], __c4: type[D], / self, c1: type[A], c2: type[B], c3: type[C], c4: type[D], /
) -> Iterable[tuple[int, tuple[A, B, C, D]]]: ... ) -> Iterable[tuple[int, tuple[A, B, C, D]]]: ...
@overload @overload
def get_components[ def get_components(
A: Component,
B: Component,
C: Component,
D: Component,
E: Component,
](
self, self,
__c1: type[A], c1: type[A],
__c2: type[B], c2: type[B],
__c3: type[C], c3: type[C],
__c4: type[D], c4: type[D],
__c5: type[E], c5: type[E],
/, /,
) -> Iterable[tuple[int, tuple[A, B, C, D]]]: ... ) -> Iterable[tuple[int, tuple[A, B, C, D]]]: ...
@ -108,10 +106,7 @@ class System:
) -> Iterable[tuple[int, tuple[Component, ...]]]: ) -> Iterable[tuple[int, tuple[Component, ...]]]:
"""Returns all entities with the given components.""" """Returns all entities with the given components."""
entity_set = set.intersection( 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: for entity in entity_set:
yield ( yield (
@ -123,51 +118,40 @@ class System:
) )
@overload @overload
def get_components_of_entity[A: Component]( def get_components_of_entity(
self, entity: int, __c1: type[A], / self, entity: int, c1: type[A], /
) -> tuple[A | None]: ... ) -> tuple[A | None]: ...
@overload @overload
def get_components_of_entity[A: Component, B: Component]( def get_components_of_entity(
self, entity: int, __c1: type[A], __c2: type[B], / self, entity: int, c1: type[A], c2: type[B], /
) -> tuple[A | None, B | None]: ... ) -> tuple[A | None, B | None]: ...
@overload @overload
def get_components_of_entity[A: Component, B: Component, C: Component]( def get_components_of_entity(
self, entity: int, __c1: type[A], __c2: type[B], __c3: type[C], / self, entity: int, c1: type[A], c2: type[B], c3: type[C], /
) -> tuple[A | None, B | None, C | None]: ... ) -> tuple[A | None, B | None, C | None]: ...
@overload @overload
def get_components_of_entity[ def get_components_of_entity(
A: Component,
B: Component,
C: Component,
D: Component,
](
self, self,
entity: int, entity: int,
__c1: type[A], c1: type[A],
__c2: type[B], c2: type[B],
__c3: type[C], c3: type[C],
__c4: type[D], c4: type[D],
/, /,
) -> tuple[A | None, B | None, C | None, D | None]: ... ) -> tuple[A | None, B | None, C | None, D | None]: ...
@overload @overload
def get_components_of_entity[ def get_components_of_entity(
A: Component,
B: Component,
C: Component,
D: Component,
E: Component,
](
self, self,
entity: int, entity: int,
__c1: type[A], c1: type[A],
__c2: type[B], c2: type[B],
__c3: type[C], c3: type[C],
__c4: type[D], c4: type[D],
__c5: type[E], c5: type[E],
/, /,
) -> tuple[A | None, B | None, C | None, D | None, E | None]: ... ) -> tuple[A | None, B | None, C | None, D | None, E | None]: ...
@ -183,20 +167,18 @@ class System:
), ),
) )
def get_component[C: Component]( def get_component(self, component_type: type[C], /) -> Iterable[tuple[int, C]]:
self, component_type: type[C], /
) -> Iterable[tuple[int, C]]:
"""Returns all entities with the given component.""" """Returns all entities with the given component."""
for entity in self.__components[component_type].copy(): for entity in self.__components[component_type].copy():
yield entity, cast(C, self.__entitites[entity][component_type]) yield entity, cast(C, self.__entitites[entity][component_type])
@overload @overload
def get_component_of_entity[C: Component]( def get_component_of_entity(
self, entity: int, component_type: type[C], / self, entity: int, component_type: type[C], /
) -> C | None: ... ) -> C | None: ...
@overload @overload
def get_component_of_entity[C: Component, D: Any]( def get_component_of_entity(
self, entity: int, component_type: type[C], /, default: D self, entity: int, component_type: type[C], /, default: D
) -> C | D: ... ) -> C | D: ...
@ -216,24 +198,16 @@ class System:
self.__components[component_type].add(entity) self.__components[component_type].add(entity)
self.__entitites[entity][component_type] = component self.__entitites[entity][component_type] = component
def has_component( def has_component(self, entity: int, component_type: type[Component]) -> bool:
self, entity: int, component_type: type[Component]
) -> bool:
"""Returns True if the entity has the given component.""" """Returns True if the entity has the given component."""
return component_type in self.__entitites[entity] return component_type in self.__entitites[entity]
def has_components( def has_components(self, entity: int, *component_types: type[Component]) -> bool:
self, entity: int, *component_types: type[Component]
) -> bool:
"""Returns True if the entity has all the given components.""" """Returns True if the entity has all the given components."""
components_dict = self.__entitites[entity] components_dict = self.__entitites[entity]
return all( return all(comp_type in components_dict for comp_type in component_types)
comp_type in components_dict for comp_type in component_types
)
def remove_component[C: Component]( def remove_component(self, entity: int, component_type: type[C]) -> C | None:
self, entity: int, component_type: type[C]
) -> C | None:
"""Removes a component from an entity.""" """Removes a component from an entity."""
self.__components[component_type].discard(entity) self.__components[component_type].discard(entity)
if not self.__components[component_type]: if not self.__components[component_type]:
@ -265,9 +239,7 @@ class System:
def entity_exists(self, entity: int) -> bool: def entity_exists(self, entity: int) -> bool:
"""Returns True if the entity exists.""" """Returns True if the entity exists."""
return ( return entity in self.__entitites and entity not in self.__dead_entities
entity in self.__entitites and entity not in self.__dead_entities
)
def start(self) -> None: def start(self) -> None:
"""Starts the system.""" """Starts the system."""

View file

@ -15,9 +15,7 @@ class Engine:
self.__dispatcher_thread: threading.Thread | None = None self.__dispatcher_thread: threading.Thread | None = None
def start(self) -> None: def start(self) -> None:
self.__system_thread = threading.Thread( self.__system_thread = threading.Thread(target=self.system.start, daemon=False)
target=self.system.start, daemon=False
)
self.__dispatcher_thread = threading.Thread( self.__dispatcher_thread = threading.Thread(
target=self.dispatcher.start, daemon=False target=self.dispatcher.start, daemon=False
) )

View file

@ -1,7 +1,5 @@
from __future__ import annotations from __future__ import annotations
from typing import Self
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@ -9,27 +7,27 @@ class Action(BaseModel):
move: int = Field(default=1) move: int = Field(default=1)
@classmethod @classmethod
def stop(cls) -> Self: def stop(cls) -> Action:
"""Skip all handlers.""" """Skip all handlers."""
return cls(move=2**8) return cls(move=2**8)
@classmethod @classmethod
def go_start(cls) -> Self: def go_start(cls) -> Action:
"""Go to the first handler.""" """Go to the first handler."""
return cls(move=-(2**8)) return cls(move=-(2**8))
@classmethod @classmethod
def next(cls, count: int = 1) -> Self: def next(cls, count: int = 1) -> Action:
"""Skip one handler.""" """Skip one handler."""
return cls(move=count) return cls(move=count)
@classmethod @classmethod
def prev(cls, count: int = 1) -> Self: def prev(cls, count: int = 1) -> Action:
"""Go back one handler.""" """Go back one handler."""
return cls(move=-count) return cls(move=-count)
@classmethod @classmethod
def skip(cls, count: int = 1) -> Self: def skip(cls, count: int = 1) -> Action:
"""Skip n handlers. """Skip n handlers.
Args: Args:

View file

@ -2,7 +2,7 @@ from __future__ import annotations
import queue import queue
from collections import defaultdict from collections import defaultdict
from typing import Callable, Final from typing import Callable, Final, TypeVar
from snakia.utils import nolock from snakia.utils import nolock
@ -11,6 +11,8 @@ from .filter import Filter
from .handler import Handler from .handler import Handler
from .subscriber import Subscriber from .subscriber import Subscriber
T = TypeVar("T", bound=Event)
class Dispatcher: class Dispatcher:
""" """
@ -21,9 +23,9 @@ class Dispatcher:
def __init__(self) -> None: def __init__(self) -> None:
self.__queue: Final = queue.Queue[Event]() self.__queue: Final = queue.Queue[Event]()
self.__subscribers: Final[ self.__subscribers: Final[dict[type[Event], list[Subscriber[Event]]]] = (
dict[type[Event], list[Subscriber[Event]]] defaultdict(list)
] = defaultdict(list) )
self.__running = False self.__running = False
@property @property
@ -31,15 +33,11 @@ class Dispatcher:
"""Returns True if the dispatcher is running.""" """Returns True if the dispatcher is running."""
return self.__running return self.__running
def subscribe[T: Event]( def subscribe(self, event_type: type[T], subscriber: Subscriber[T]) -> None:
self, event_type: type[T], subscriber: Subscriber[T]
) -> None:
"""Subscribe to an event type.""" """Subscribe to an event type."""
self.__subscribers[event_type].append(subscriber) # type: ignore self.__subscribers[event_type].append(subscriber) # type: ignore
def unsubscribe[T: Event]( def unsubscribe(self, event_type: type[T], subscriber: Subscriber[T]) -> None:
self, event_type: type[T], subscriber: Subscriber[T]
) -> None:
"""Unsubscribe from an event type.""" """Unsubscribe from an event type."""
for sub in self.__subscribers[event_type].copy(): for sub in self.__subscribers[event_type].copy():
if sub.handler != subscriber.handler: if sub.handler != subscriber.handler:
@ -48,7 +46,7 @@ class Dispatcher:
continue continue
self.__subscribers[event_type].remove(sub) self.__subscribers[event_type].remove(sub)
def on[T: Event]( def on(
self, self,
event: type[T], event: type[T],
filter: Filter[T] | None = None, # noqa: W0622 # pylint: disable=W0622 filter: Filter[T] | None = None, # noqa: W0622 # pylint: disable=W0622
@ -95,9 +93,7 @@ class Dispatcher:
i = 0 i = 0
while i < len(subscribers): while i < len(subscribers):
subscriber = subscribers[i] subscriber = subscribers[i]
if subscriber.filters is not None and not subscriber.filters( if subscriber.filters is not None and not subscriber.filters(event):
event
):
continue continue
action = subscriber.handler(event) action = subscriber.handler(event)

View file

@ -1,11 +1,13 @@
from __future__ import annotations from __future__ import annotations
from typing import Protocol from typing import Generic, Protocol, TypeVar
from .event import Event 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.""" """Filter for an event."""
def __call__(self, event: T) -> bool: ... def __call__(self, event: T_contra) -> bool: ...

View file

@ -1,12 +1,14 @@
from __future__ import annotations from __future__ import annotations
from typing import Optional, Protocol from typing import Generic, Optional, Protocol, TypeVar
from .action import Action from .action import Action
from .event import Event 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.""" """Handler for an event."""
def __call__(self, event: T) -> Optional[Action]: ... def __call__(self, event: T_contra) -> Optional[Action]: ...

View file

@ -1,16 +1,18 @@
from __future__ import annotations from __future__ import annotations
from typing import NamedTuple from typing import Generic, NamedTuple, TypeVar
from .event import Event from .event import Event
from .filter import Filter from .filter import Filter
from .handler import Handler 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.""" Subscriber for an event."""
handler: Handler[T] handler: Handler[T_contra]
filters: Filter[T] | None filters: Filter[T_contra] | None
priority: int priority: int

View file

@ -1,11 +1,15 @@
import sys import sys
from types import TracebackType 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__( def __call__(
self, exception: T, frame: TracebackType | None, / self, exception: T_contra, frame: TracebackType | None, /
) -> bool | None: ... ) -> bool | None: ...
@ -15,13 +19,13 @@ class _ExceptionManager:
self.__hooks: list[tuple[type[BaseException], ExceptionHook[Any]]] = [] self.__hooks: list[tuple[type[BaseException], ExceptionHook[Any]]] = []
sys.excepthook = self._excepthook sys.excepthook = self._excepthook
def hook_exception[T: BaseException]( def hook_exception(
self, exception_type: type[T], func: ExceptionHook[T] self, exception_type: type[T], func: ExceptionHook[T]
) -> ExceptionHook[T]: ) -> ExceptionHook[T]:
self.__hooks.append((exception_type, func)) self.__hooks.append((exception_type, func))
return func return func
def on_exception[T: BaseException]( def on_exception(
self, exception_type: type[T] self, exception_type: type[T]
) -> Callable[[ExceptionHook[T]], ExceptionHook[T]]: ) -> Callable[[ExceptionHook[T]], ExceptionHook[T]]:
def inner(func: ExceptionHook[T]) -> ExceptionHook[T]: def inner(func: ExceptionHook[T]) -> ExceptionHook[T]:

View file

@ -23,16 +23,12 @@ class Meta(BaseModel):
max_length=32, max_length=32,
pattern="^[a-z0-9_]{4,32}$", pattern="^[a-z0-9_]{4,32}$",
) )
version: Version = Field( version: Version = Field(default_factory=lambda: Version(major=1, minor=0, patch=0))
default_factory=lambda: Version(major=1, minor=0, patch=0)
)
subscribers: tuple[tuple[type[Event], Subscriber[Event]], ...] = Field( subscribers: tuple[tuple[type[Event], Subscriber[Event]], ...] = Field(
default_factory=tuple default_factory=tuple
) )
processors: tuple[type[PluginProcessor], ...] = Field( processors: tuple[type[PluginProcessor], ...] = Field(default_factory=tuple)
default_factory=tuple
)
@property @property
def id(self) -> str: def id(self) -> str:

View file

@ -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 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. An asynchronous bindable.
""" """
@ -53,25 +55,19 @@ class AsyncBindable[T: Any](BaseBindable[T]):
if run_now: if run_now:
async def _run() -> None: async def _run() -> None:
await subscriber( await subscriber(ValueChanged(self.__default_value, self.value))
ValueChanged(self.__default_value, self.value)
)
return _run() return _run()
return None return None
def unsubscribe( def unsubscribe(self, subscriber: BindableSubscriber[T, Awaitable[Any]]) -> None:
self, subscriber: BindableSubscriber[T, Awaitable[Any]]
) -> None:
"""Unsubscribe from an value.""" """Unsubscribe from an value."""
self.__subscribers.remove(subscriber) self.__subscribers.remove(subscriber)
@overload @overload
def on( def on(
self, run_now: Literal[True] self, run_now: Literal[True]
) -> Callable[ ) -> Callable[[BindableSubscriber[T, Awaitable[Any]]], Awaitable[None]]: ...
[BindableSubscriber[T, Awaitable[Any]]], Awaitable[None]
]: ...
@overload @overload
def on( def on(
@ -91,9 +87,7 @@ class AsyncBindable[T: Any](BaseBindable[T]):
if run_now: if run_now:
async def _run() -> None: async def _run() -> None:
await subscriber( await subscriber(ValueChanged(self.__default_value, self.value))
ValueChanged(self.__default_value, self.value)
)
return _run() return _run()
return None return None

View file

@ -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 old_value: T
new_value: T new_value: T
class BindableSubscriber[T: Any, R: Any](Protocol): class BindableSubscriber(Protocol, Generic[T, R_co]):
def __call__(self, value: ValueChanged[T], /) -> R: ... 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: def __init__(self, default_value: T | None = None) -> None:
if default_value is not None: if default_value is not None:
self.__default_value: T = default_value self.__default_value: T = default_value

View file

@ -1,9 +1,11 @@
from typing import Any, Callable from typing import Any, Callable, Generic, TypeVar
from .base_bindable import BaseBindable, BindableSubscriber, ValueChanged 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. A bindable value.
""" """
@ -36,9 +38,7 @@ class Bindable[T: Any](BaseBindable[T]):
"""Unsubscribe from an value.""" """Unsubscribe from an value."""
self.__subscribers.remove(subscriber) self.__subscribers.remove(subscriber)
def on( def on(self, run_now: bool = False) -> Callable[[BindableSubscriber[T, Any]], None]:
self, run_now: bool = False
) -> Callable[[BindableSubscriber[T, Any]], None]:
"""Decorator to subscribe to an value.""" """Decorator to subscribe to an value."""
def wrapper(subscriber: BindableSubscriber[T, Any]) -> None: def wrapper(subscriber: BindableSubscriber[T, Any]) -> None:

View file

@ -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 @overload
def chain[**P, A](func1: Callable[P, A], /) -> Callable[P, A]: ... def chain(func1: Callable[P, A], /) -> Callable[P, A]: ...
@overload @overload
def chain[**P, A, B]( def chain(func1: Callable[P, A], func2: Callable[[A], B], /) -> Callable[P, B]: ...
func1: Callable[P, A], func2: Callable[[A], B], /
) -> Callable[P, B]: ...
@overload @overload
def chain[**P, A, B, C]( def chain(
func1: Callable[P, A], func2: Callable[[A], B], func3: Callable[[B], C], / func1: Callable[P, A], func2: Callable[[A], B], func3: Callable[[B], C], /
) -> Callable[P, C]: ... ) -> Callable[P, C]: ...
@overload @overload
def chain[**P, A, B, C, D]( def chain(
func1: Callable[P, A], func1: Callable[P, A],
func2: Callable[[A], B], func2: Callable[[A], B],
func3: Callable[[B], C], func3: Callable[[B], C],
@ -24,7 +30,7 @@ def chain[**P, A, B, C, D](
@overload @overload
def chain[**P, A, B, C, D, E]( def chain(
func1: Callable[P, A], func1: Callable[P, A],
func2: Callable[[A], B], func2: Callable[[A], B],
func3: Callable[[B], C], func3: Callable[[B], C],
@ -35,14 +41,12 @@ def chain[**P, A, B, C, D, E](
@overload @overload
def chain[**P]( def chain(
func1: Callable[P, Any], /, *funcs: Callable[[Any], Any] func1: Callable[P, Any], /, *funcs: Callable[[Any], Any]
) -> Callable[P, Any]: ... ) -> Callable[P, Any]: ...
def chain[**P]( def chain(func1: Callable[P, Any], /, *funcs: Callable[[Any], Any]) -> Callable[P, Any]:
func1: Callable[P, Any], /, *funcs: Callable[[Any], Any]
) -> Callable[P, Any]:
def inner(*args: P.args, **kwargs: P.kwargs) -> Any: def inner(*args: P.args, **kwargs: P.kwargs) -> Any:
v = func1(*args, **kwargs) v = func1(*args, **kwargs)

View file

@ -1,5 +1,5 @@
import operator import operator
from typing import Any, Callable, overload from typing import Any, Callable, TypeVar, overload
from snakia.utils import to_async from snakia.utils import to_async
@ -8,9 +8,16 @@ from .base_bindable import ValueChanged
from .bindable import Bindable from .bindable import Bindable
from .concat import concat from .concat import concat
A = TypeVar("A")
B = TypeVar("B")
C = TypeVar("C")
D = TypeVar("D")
E = TypeVar("E")
R = TypeVar("R")
@overload @overload
def combine[A, R]( def combine(
source1: Bindable[A] | AsyncBindable[A], source1: Bindable[A] | AsyncBindable[A],
/, /,
*, *,
@ -19,7 +26,7 @@ def combine[A, R](
@overload @overload
def combine[A, B, R]( def combine(
source1: Bindable[A] | AsyncBindable[A], source1: Bindable[A] | AsyncBindable[A],
source2: Bindable[B] | AsyncBindable[B], source2: Bindable[B] | AsyncBindable[B],
/, /,
@ -29,7 +36,7 @@ def combine[A, B, R](
@overload @overload
def combine[A, B, C, R]( def combine(
source1: Bindable[A] | AsyncBindable[A], source1: Bindable[A] | AsyncBindable[A],
source2: Bindable[B] | AsyncBindable[B], source2: Bindable[B] | AsyncBindable[B],
source3: Bindable[C] | AsyncBindable[C], source3: Bindable[C] | AsyncBindable[C],
@ -40,7 +47,7 @@ def combine[A, B, C, R](
@overload @overload
def combine[A, B, C, D, R]( def combine(
source1: Bindable[A] | AsyncBindable[A], source1: Bindable[A] | AsyncBindable[A],
source2: Bindable[B] | AsyncBindable[B], source2: Bindable[B] | AsyncBindable[B],
source3: Bindable[C] | AsyncBindable[C], source3: Bindable[C] | AsyncBindable[C],
@ -52,7 +59,7 @@ def combine[A, B, C, D, R](
@overload @overload
def combine[A, B, C, D, R]( def combine(
source1: Bindable[A] | AsyncBindable[A], source1: Bindable[A] | AsyncBindable[A],
source2: Bindable[B] | AsyncBindable[B], source2: Bindable[B] | AsyncBindable[B],
source3: Bindable[C] | AsyncBindable[C], source3: Bindable[C] | AsyncBindable[C],
@ -64,13 +71,13 @@ def combine[A, B, C, D, R](
@overload @overload
def combine[R]( def combine(
*sources: Bindable[Any] | AsyncBindable[Any], *sources: Bindable[Any] | AsyncBindable[Any],
combiner: Callable[..., R], combiner: Callable[..., R],
) -> Bindable[R]: ... ) -> Bindable[R]: ...
def combine[R]( def combine(
*sources: Bindable[Any] | AsyncBindable[Any], *sources: Bindable[Any] | AsyncBindable[Any],
combiner: Callable[..., R], combiner: Callable[..., R],
) -> Bindable[R]: ) -> Bindable[R]:

View file

@ -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: def inner(*args: P.args, **kwargs: P.kwargs) -> None:
for f in funcs: for f in funcs:
f(*args, **kwargs) f(*args, **kwargs)

View file

@ -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], condition: Callable[P, bool],
if_true: Callable[P, T], if_true: Callable[P, T],
if_false: Callable[P, F], if_false: Callable[P, F],
) -> Callable[P, T | F]: ) -> Callable[P, T | F]:
return lambda *args, **kw: ( return lambda *args, **kw: (
if_true(*args, **kw) if_true(*args, **kw) if condition(*args, **kw) else if_false(*args, **kw)
if condition(*args, **kw)
else if_false(*args, **kw)
) )

View file

@ -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 return lambda: value

View file

@ -1,9 +1,12 @@
import builtins 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 # noqa: W0622 # pylint: disable=W0622
def filter[S, T]( def filter(
f: Callable[[S], TypeGuard[T]], f: Callable[[S], TypeGuard[T]],
) -> Callable[[Iterable[S]], Iterable[T]]: ) -> Callable[[Iterable[S]], Iterable[T]]:
return lambda iterable: builtins.filter(f, iterable) return lambda iterable: builtins.filter(f, iterable)

View file

@ -1,9 +1,10 @@
import builtins 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 # noqa: W0622 # pylint: disable=W0622
def map[T: Any, U]( def map(func: Callable[[T], U], /) -> Callable[[Iterable[T]], Iterable[U]]:
func: Callable[[T], U], /
) -> Callable[[Iterable[T]], Iterable[U]]:
return lambda iterable: builtins.map(func, iterable) return lambda iterable: builtins.map(func, iterable)

View file

@ -1,8 +1,12 @@
from typing import TypeVar
from .async_bindable import AsyncBindable from .async_bindable import AsyncBindable
from .bindable import Bindable from .bindable import Bindable
T = TypeVar("T")
def merge[T](
def merge(
*sources: Bindable[T], *sources: Bindable[T],
) -> Bindable[T]: ) -> Bindable[T]:
merged = Bindable[T]() merged = Bindable[T]()
@ -11,7 +15,7 @@ def merge[T](
return merged return merged
async def async_merge[T]( async def async_merge(
*sources: AsyncBindable[T], *sources: AsyncBindable[T],
) -> AsyncBindable[T]: ) -> AsyncBindable[T]:
merged = AsyncBindable[T]() merged = AsyncBindable[T]()

View file

@ -40,9 +40,7 @@ class Canvas:
def get_column(self, x: int, /) -> Iterable[CanvasChar]: def get_column(self, x: int, /) -> Iterable[CanvasChar]:
"""Get the column at the given position.""" """Get the column at the given position."""
return ( return (self.__buffer[self._get_index(x, y)] for y in range(self.height))
self.__buffer[self._get_index(x, y)] for y in range(self.height)
)
def set(self, x: int, y: int, value: CanvasChar, /) -> None: def set(self, x: int, y: int, value: CanvasChar, /) -> None:
"""Set the character at the given position.""" """Set the character at the given position."""
@ -68,9 +66,7 @@ class Canvas:
value: CanvasChar, value: CanvasChar,
) -> None: ) -> None:
"""Set the area at the given position.""" """Set the area at the given position."""
for i in range( for i in range(self._get_index(x, y), self._get_index(x + width, y + height)):
self._get_index(x, y), self._get_index(x + width, y + height)
):
self.__buffer[i] = value self.__buffer[i] = value
def clear(self) -> None: def clear(self) -> None:

View file

@ -1,11 +1,13 @@
from abc import ABC, abstractmethod 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.core.rx import AsyncBindable, Bindable
from snakia.utils import to_async from snakia.utils import to_async
from .canvas import Canvas from .canvas import Canvas
T = TypeVar("T")
class Widget(ABC): class Widget(ABC):
def __init__(self) -> None: def __init__(self) -> None:
@ -24,13 +26,13 @@ class Widget(ABC):
return self.__cache return self.__cache
@final @final
def state[T](self, default_value: T) -> Bindable[T]: def state(self, default_value: T) -> Bindable[T]:
field = Bindable(default_value) field = Bindable(default_value)
field.subscribe(lambda _: self.dirty.set(True)) field.subscribe(lambda _: self.dirty.set(True))
return field return field
@final @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 = AsyncBindable(default_value)
field.subscribe(to_async(lambda _: self.dirty.set(True))) field.subscribe(to_async(lambda _: self.dirty.set(True)))
return field return field

View file

@ -8,9 +8,7 @@ from .container import ContainerWidget
class HorizontalSplitWidget(ContainerWidget): class HorizontalSplitWidget(ContainerWidget):
def __init__( def __init__(self, children: Iterable[Widget], splitter_char: str = "|") -> None:
self, children: Iterable[Widget], splitter_char: str = "|"
) -> None:
super().__init__(children) super().__init__(children)
self.splitter_char = splitter_char self.splitter_char = splitter_char
@ -21,9 +19,7 @@ class HorizontalSplitWidget(ContainerWidget):
child_canvases = [child.render() for child in children_list] child_canvases = [child.render() for child in children_list]
total_width = ( total_width = (
sum(canvas.width for canvas in child_canvases) sum(canvas.width for canvas in child_canvases) + len(child_canvases) - 1
+ len(child_canvases)
- 1
) )
max_height = max(canvas.height for canvas in child_canvases) max_height = max(canvas.height for canvas in child_canvases)

View file

@ -8,9 +8,7 @@ from .container import ContainerWidget
class VerticalSplitWidget(ContainerWidget): class VerticalSplitWidget(ContainerWidget):
def __init__( def __init__(self, children: Iterable[Widget], splitter_char: str = "-") -> None:
self, children: Iterable[Widget], splitter_char: str = "-"
) -> None:
super().__init__(children) super().__init__(children)
self.splitter_char = splitter_char self.splitter_char = splitter_char
@ -22,9 +20,7 @@ class VerticalSplitWidget(ContainerWidget):
child_canvases = [child.render() for child in children_list] child_canvases = [child.render() for child in children_list]
max_width = max(canvas.width for canvas in child_canvases) max_width = max(canvas.width for canvas in child_canvases)
total_height = ( total_height = (
sum(canvas.height for canvas in child_canvases) sum(canvas.height for canvas in child_canvases) + len(child_canvases) - 1
+ len(child_canvases)
- 1
) )
result = Canvas(max_width, total_height, CanvasChar()) result = Canvas(max_width, total_height, CanvasChar())

View file

@ -1,18 +1,20 @@
from typing import Callable from typing import Callable, ParamSpec, TypeVar
from .inject_replace import inject_replace 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] def inject_after(obj: T, target: Callable[P, R], hook: Callable[[R], R]) -> T:
) -> T:
def inner(*args: P.args, **kwargs: P.kwargs) -> R: def inner(*args: P.args, **kwargs: P.kwargs) -> R:
return hook(target(*args, **kwargs)) return hook(target(*args, **kwargs))
return inject_replace(obj, target, inner) return inject_replace(obj, target, inner)
def after_hook[**P, R]( def after_hook(
obj: object, target: Callable[P, R] obj: object, target: Callable[P, R]
) -> Callable[[Callable[[R], R]], Callable[[R], R]]: ) -> Callable[[Callable[[R], R]], Callable[[R], R]]:
def hook(new: Callable[[R], R]) -> Callable[[R], R]: def hook(new: Callable[[R], R]) -> Callable[[R], R]:

View file

@ -1,11 +1,13 @@
from typing import Any, Callable from typing import Any, Callable, ParamSpec, TypeVar
from .inject_replace import inject_replace 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] def inject_before(obj: T, target: Callable[P, R], hook: Callable[P, Any]) -> T:
) -> T:
def inner(*args: P.args, **kwargs: P.kwargs) -> R: def inner(*args: P.args, **kwargs: P.kwargs) -> R:
hook(*args, **kwargs) hook(*args, **kwargs)
return target(*args, **kwargs) return target(*args, **kwargs)
@ -13,7 +15,7 @@ def inject_before[T: object, **P, R](
return inject_replace(obj, target, inner) return inject_replace(obj, target, inner)
def before_hook[**P, R]( def before_hook(
obj: object, target: Callable[P, R] obj: object, target: Callable[P, R]
) -> Callable[[Callable[P, Any]], Callable[P, Any]]: ) -> Callable[[Callable[P, Any]], Callable[P, Any]]:

View file

@ -1,10 +1,12 @@
import sys import sys
from types import FunctionType 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): 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: def inner(func: T) -> T:
values = [*func.__code__.co_consts] values = [*func.__code__.co_consts]
for i, name in enumerate(func.__code__.co_varnames): for i, name in enumerate(func.__code__.co_varnames):
@ -26,7 +28,7 @@ if sys.version_info >= (3, 13):
else: 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: def inner(func: T) -> T:
values = [*func.__code__.co_consts] values = [*func.__code__.co_consts]
for i, name in enumerate(func.__code__.co_varnames): for i, name in enumerate(func.__code__.co_varnames):

View file

@ -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]( def inject_replace(obj: T, old: Callable[P, R], new: Callable[P, R]) -> T:
obj: T, old: Callable[P, R], new: Callable[P, R]
) -> T:
for k, v in obj.__dict__.items(): for k, v in obj.__dict__.items():
if v is old: if v is old:
setattr(obj, k, new) setattr(obj, k, new)
return obj return obj
def replace_hook[**P, R]( def replace_hook(
obj: object, old: Callable[P, R] obj: object, old: Callable[P, R]
) -> Callable[[Callable[P, R]], Callable[P, R]]: ) -> Callable[[Callable[P, R]], Callable[P, R]]:
def hook(new: Callable[P, R]) -> Callable[P, R]: def hook(new: Callable[P, R]) -> Callable[P, R]:

View file

@ -1,14 +1,17 @@
from __future__ import annotations 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 @overload
def pass_exceptions[**P]( def pass_exceptions(
*errors: type[Exception], *errors: type[Exception],
) -> Callable[[Callable[P, Any | None]], Callable[P, Any | None]]: ... ) -> Callable[[Callable[P, Any | None]], Callable[P, Any | None]]: ...
@overload @overload
def pass_exceptions[**P, R]( def pass_exceptions(
*errors: type[Exception], *errors: type[Exception],
default: R, default: R,
) -> Callable[[Callable[P, R]], Callable[P, R]]: ... ) -> Callable[[Callable[P, R]], Callable[P, R]]: ...

View file

@ -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() return cls()

View file

@ -1,23 +1,21 @@
import pickle import pickle
from typing import Final, override from typing import Final, Generic, TypeVar
from .field import Field from .field import Field
T = TypeVar("T")
class AutoField[T](Field[T]):
class AutoField(Field[T], Generic[T]):
__slots__ = ("__target_type",) __slots__ = ("__target_type",)
def __init__( def __init__(self, default_value: T, *, target_type: type[T] | None = None) -> None:
self, default_value: T, *, target_type: type[T] | None = None
) -> None:
super().__init__(default_value) super().__init__(default_value)
self.__target_type: Final = target_type self.__target_type: Final = target_type
@override
def serialize(self, value: T, /) -> bytes: def serialize(self, value: T, /) -> bytes:
return pickle.dumps(value) return pickle.dumps(value)
@override
def deserialize(self, serialized: bytes, /) -> T: def deserialize(self, serialized: bytes, /) -> T:
value = pickle.loads(serialized) value = pickle.loads(serialized)
if not isinstance(value, self.__target_type or object): if not isinstance(value, self.__target_type or object):

View file

@ -1,13 +1,9 @@
from typing import override
from .field import Field from .field import Field
class BoolField(Field[bool]): class BoolField(Field[bool]):
@override
def serialize(self, value: bool, /) -> bytes: def serialize(self, value: bool, /) -> bytes:
return b"\x01" if value else b"\x00" return b"\x01" if value else b"\x00"
@override
def deserialize(self, serialized: bytes, /) -> bool: def deserialize(self, serialized: bytes, /) -> bool:
return serialized == b"\x01" return serialized == b"\x01"

View file

@ -1,13 +1,16 @@
from __future__ import annotations from __future__ import annotations
from abc import ABC, abstractmethod 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.property.priv_property import PrivProperty
from snakia.utils import inherit 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: def __init__(self, default_value: T) -> None:
self.default_value: Final[T] = default_value self.default_value: Final[T] = default_value
super().__init__(default_value) super().__init__(default_value)
@ -34,23 +37,19 @@ class Field[T: Any](ABC, PrivProperty[T]):
@final @final
@classmethod @classmethod
def custom[R]( def custom(
cls: type[Field[Any]], cls: type[Field[Any]],
serialize: Callable[[Field[R], R], bytes], serialize: Callable[[Field[R], R], bytes],
deserialize: Callable[[Field[R], bytes], R], deserialize: Callable[[Field[R], bytes], R],
) -> type[Field[R]]: ) -> type[Field[R]]:
return inherit( return inherit(cls, {"serialize": serialize, "deserialize": deserialize})
cls, {"serialize": serialize, "deserialize": deserialize}
)
@final @final
@staticmethod @staticmethod
def get_fields(class_: type[Any] | Any, /) -> dict[str, Field[Any]]: def get_fields(class_: type[Any] | Any, /) -> dict[str, Field[Any]]:
if not isinstance(class_, type): if not isinstance(class_, type):
class_ = class_.__class__ class_ = class_.__class__
return { return {k: v for k, v in class_.__dict__.items() if isinstance(v, Field)}
k: v for k, v in class_.__dict__.items() if isinstance(v, Field)
}
if TYPE_CHECKING: if TYPE_CHECKING:

View file

@ -1,14 +1,11 @@
import struct import struct
from typing import override
from .field import Field from .field import Field
class FloatField(Field[float]): class FloatField(Field[float]):
@override
def serialize(self, value: float, /) -> bytes: def serialize(self, value: float, /) -> bytes:
return struct.pack(">f", value) return struct.pack(">f", value)
@override
def deserialize(self, serialized: bytes, /) -> float: def deserialize(self, serialized: bytes, /) -> float:
return struct.unpack(">f", serialized)[0] # type: ignore return struct.unpack(">f", serialized)[0] # type: ignore

View file

@ -1,14 +1,10 @@
from typing import override
from .field import Field from .field import Field
class IntField(Field[int]): class IntField(Field[int]):
@override
def serialize(self, value: int, /) -> bytes: def serialize(self, value: int, /) -> bytes:
length = (value.bit_length() + 7) // 8 length = (value.bit_length() + 7) // 8
return value.to_bytes(length, "little") return value.to_bytes(length, "little")
@override
def deserialize(self, serialized: bytes, /) -> int: def deserialize(self, serialized: bytes, /) -> int:
return int.from_bytes(serialized, "little") return int.from_bytes(serialized, "little")

View file

@ -1,4 +1,4 @@
from typing import Final, override from typing import Final
from .field import Field from .field import Field
@ -8,10 +8,8 @@ class StrField(Field[str]):
super().__init__(default_value) super().__init__(default_value)
self.encoding: Final = encoding self.encoding: Final = encoding
@override
def serialize(self, value: str, /) -> bytes: def serialize(self, value: str, /) -> bytes:
return value.encode(self.encoding) return value.encode(self.encoding)
@override
def deserialize(self, serialized: bytes, /) -> str: def deserialize(self, serialized: bytes, /) -> str:
return serialized.decode(self.encoding) return serialized.decode(self.encoding)

View file

@ -1,11 +1,13 @@
from __future__ import annotations from __future__ import annotations
from ctypes import CDLL, Array, c_char, c_char_p, create_string_buffer 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 .layer import PlatformLayer
from .os import PlatformOS from .os import PlatformOS
T = TypeVar("T")
class AndroidLayer(PlatformLayer[Literal[PlatformOS.ANDROID]]): class AndroidLayer(PlatformLayer[Literal[PlatformOS.ANDROID]]):
target = PlatformOS.ANDROID target = PlatformOS.ANDROID
@ -16,7 +18,7 @@ class AndroidLayer(PlatformLayer[Literal[PlatformOS.ANDROID]]):
def get_prop(self, name: str) -> str | None: ... def get_prop(self, name: str) -> str | None: ...
@overload @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: def get_prop(self, name: str, default: Any = None) -> Any:
buffer = create_string_buffer(self.PROP_VALUE_MAX) buffer = create_string_buffer(self.PROP_VALUE_MAX)

View file

@ -1,11 +1,15 @@
from __future__ import annotations 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 from .os import PlatformOS
T = TypeVar("T", bound=PlatformOS)
class PlatformLayer[T: PlatformOS]:
class PlatformLayer(Generic[T]):
target: ClassVar[PlatformOS] = PlatformOS.UNKNOWN target: ClassVar[PlatformOS] = PlatformOS.UNKNOWN
@final @final

View file

@ -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 from snakia.types import empty
type _Cell[T] = T | None T = TypeVar("T")
type _Getter[T] = Callable[[Any, _Cell[T]], T]
type _Setter[T] = Callable[[Any, _Cell[T], T], _Cell[T]] _Cell: TypeAlias = T | None
type _Deleter[T] = Callable[[Any, _Cell[T]], _Cell[T]]
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. A property that uses a cell to store its value.
""" """

View file

@ -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 from snakia.types import empty
T = TypeVar("T")
class ClassProperty[T]:
class ClassProperty(Generic[T]):
""" """
Class property Class property
""" """
@ -47,7 +53,7 @@ class ClassProperty[T]:
return self return self
def classproperty[T]( def classproperty(
fget: Callable[[Any], T], fget: Callable[[Any], T],
fset: Callable[[Any, T], None] = empty.func, fset: Callable[[Any, T], None] = empty.func,
fdel: Callable[[Any], 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. 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. fdel (Callable[[Any], None], optional): The deleter function. Defaults to empty.func.
Returns: Returns:
ClassProperty[T]: The class property. Self: The class property.
""" """
return ClassProperty(fget, fset, fdel) return ClassProperty(fget, fset, fdel)

View file

@ -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 snakia.types import empty
from .priv_property import PrivProperty 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. A property that calls a function when the property is set, get, or deleted.
""" """

View file

@ -1,9 +1,11 @@
from typing import Any from typing import Any, Generic, TypeVar
from .priv_property import PrivProperty 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.""" """Property that can only be set once."""
def __set__(self, instance: Any, value: T, /) -> None: def __set__(self, instance: Any, value: T, /) -> None:

View file

@ -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" __slots__ = "__name", "__default_value"
__name: str __name: str

View file

@ -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 from snakia.types import empty
T = TypeVar("T")
class Property[T]:
class Property(Generic[T]):
""" """
A property that can be set, get, and deleted. A property that can be set, get, and deleted.
""" """
@ -34,17 +38,17 @@ class Property[T]:
def __delete__(self, instance: Any, /) -> None: def __delete__(self, instance: Any, /) -> None:
return self.__fdel(instance) return self.__fdel(instance)
def getter(self, fget: Callable[[Any], T], /) -> Self: def getter(self, fget: Callable[[Any], T], /) -> Property[T]:
"""Descriptor getter.""" """Descriptor getter."""
self.__fget = fget self.__fget = fget
return self return self
def setter(self, fset: Callable[[Any, T], None], /) -> Self: def setter(self, fset: Callable[[Any, T], None], /) -> Property[T]:
"""Descriptor setter.""" """Descriptor setter."""
self.__fset = fset self.__fset = fset
return self return self
def deleter(self, fdel: Callable[[Any], None], /) -> Self: def deleter(self, fdel: Callable[[Any], None], /) -> Property[T]:
"""Descriptor deleter.""" """Descriptor deleter."""
self.__fdel = fdel self.__fdel = fdel
return self return self

View file

@ -1,11 +1,13 @@
from typing import Any, Callable from typing import Any, Callable, Generic, TypeVar
from snakia.utils import throw from snakia.utils import throw
from .property import Property from .property import Property
T = TypeVar("T")
class Readonly[T](Property[T]):
class Readonly(Property[T], Generic[T]):
""" """
Readonly property. 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. """Create a readonly property with the given value.
Args: Args:

View file

@ -9,7 +9,8 @@ class OSRandom(Random[None]):
""" """
def bits(self, k: int) -> int: 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: def get_state(self) -> None:
return None return None

View file

@ -1,8 +1,9 @@
import random import random
from typing import TypeAlias
from .random import Random 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]): class PythonRandom(Random[_State]):

View file

@ -1,9 +1,13 @@
import builtins import builtins
from abc import ABC, abstractmethod 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. A random number generator.
""" """
@ -45,12 +49,12 @@ class Random[S](ABC):
return self.bits(32) / (1 << 32) return self.bits(32) / (1 << 32)
@final @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 a random element from a non-empty sequence."""
return seq[self.below(len(seq))] return seq[self.below(len(seq))]
@final @final
def shuffle[T: MutableSequence[Any]](self, seq: T) -> T: def shuffle(self, seq: M) -> M:
"""Shuffle a sequence in place.""" """Shuffle a sequence in place."""
for i in range(len(seq) - 1, 0, -1): for i in range(len(seq) - 1, 0, -1):
j = self.below(i + 1) j = self.below(i + 1)

View file

@ -1,4 +1,6 @@
from typing import Any, final from typing import Any, TypeVar, final
T = TypeVar("T")
@final @final
@ -31,7 +33,7 @@ class UniqueType(type):
def __eq__(cls, other: Any) -> bool: def __eq__(cls, other: Any) -> bool:
return cls is other 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 return cls.__new__(cls) # noqa: E1120 # pylint: disable=E1120

View file

@ -1,9 +1,9 @@
from typing import Any from typing import Any, TypeVar
T = TypeVar("T", bound=type)
def inherit[T: type]( def inherit(type_: T, attrs: dict[str, Any] | None = None, /, **kwargs: Any) -> T:
type_: T, attrs: dict[str, Any] | None = None, /, **kwargs: Any
) -> T:
""" """
Create a new class that inherits from the given class. Create a new class that inherits from the given class.

View file

@ -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 from snakia.types.unset import Unset
T = TypeVar("T", bound=Exception)
@overload @overload
def throw[T: Exception]( def throw(
*exceptions: T, # pyright: ignore[reportInvalidTypeVarUse] *exceptions: T, # pyright: ignore[reportInvalidTypeVarUse]
from_: Unset | BaseException = Unset(), from_: Unset | BaseException = Unset(),
) -> NoReturn: ... ) -> NoReturn: ...

View file

@ -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.""" """Convert a sync function to an async function."""
async def inner(*args: P.args, **kwargs: P.kwargs) -> R: async def inner(*args: P.args, **kwargs: P.kwargs) -> R:

89
uv.lock generated
View file

@ -1,6 +1,10 @@
version = 1 version = 1
revision = 3 revision = 3
requires-python = ">=3.12" requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.11'",
"python_full_version < '3.11'",
]
[[package]] [[package]]
name = "annotated-types" 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" }, { 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]] [[package]]
name = "networkx" name = "networkx"
version = "3.5" version = "3.5"
source = { registry = "https://pypi.org/simple" } 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" } 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 = [ 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" }, { 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" } 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 = [ 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/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/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" }, { 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/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/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/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/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/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/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/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]] [[package]]
name = "snakia" name = "snakia"
version = "0.4.1" version = "0.4.2"
source = { editable = "." } source = { editable = "." }
dependencies = [ 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 = "pydantic" },
{ name = "typing-extensions" },
] ]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "exceptiongroup", specifier = ">=1.3.0" },
{ name = "networkx", specifier = ">=3.4.2" }, { name = "networkx", specifier = ">=3.4.2" },
{ name = "pydantic", specifier = ">=2.12.3" }, { name = "pydantic", specifier = ">=2.12.3" },
{ name = "typing-extensions", specifier = ">=4.15.0" },
] ]
[[package]] [[package]]