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
strategy:
matrix:
python-version: ["3.12"]
python-version: ["3.10"]
steps:
- uses: actions/checkout@v4

View file

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

View file

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

View file

@ -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
];
};
}

View file

@ -1,6 +1,6 @@
[project]
name = "snakia"
version = "0.4.1"
version = "0.4.2"
description = "Modern python framework"
readme = "README.md"
authors = [
@ -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"]

View file

@ -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

View file

@ -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."""

View file

@ -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
)

View file

@ -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:

View file

@ -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)

View file

@ -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: ...

View file

@ -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]: ...

View file

@ -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

View file

@ -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]:

View file

@ -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:

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
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

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
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

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
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:

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
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)

View file

@ -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]:

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:
for f in funcs:
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],
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)
)

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

View file

@ -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)

View file

@ -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)

View file

@ -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]()

View file

@ -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:

View file

@ -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

View file

@ -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)

View file

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

View file

@ -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]:

View file

@ -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]]:

View file

@ -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):

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](
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]:

View file

@ -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]]: ...

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()

View file

@ -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):

View file

@ -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"

View file

@ -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:

View file

@ -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

View file

@ -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")

View file

@ -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)

View file

@ -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)

View file

@ -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

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
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.
"""

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
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)

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 .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.
"""

View file

@ -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:

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"
__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
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

View file

@ -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:

View file

@ -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

View file

@ -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]):

View file

@ -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)

View file

@ -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

View file

@ -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.

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
T = TypeVar("T", bound=Exception)
@overload
def throw[T: Exception](
def throw(
*exceptions: T, # pyright: ignore[reportInvalidTypeVarUse]
from_: Unset | BaseException = Unset(),
) -> 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."""
async def inner(*args: P.args, **kwargs: P.kwargs) -> R:

89
uv.lock generated
View file

@ -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]]