diff --git a/docs/docs/tutorials/tot/tot.ipynb b/docs/docs/tutorials/tot/tot.ipynb index 29fe435bc..cb6e967a0 100644 --- a/docs/docs/tutorials/tot/tot.ipynb +++ b/docs/docs/tutorials/tot/tot.ipynb @@ -282,8 +282,8 @@ "from langgraph.graph import StateGraph\n", "\n", "from langchain_core.runnables import RunnableConfig\n", - "from langgraph.constants import Send\n", "from langgraph.checkpoint.memory import MemorySaver\n", + "from langgraph.types import Send\n", "\n", "\n", "def update_candidates(\n", diff --git a/libs/cli/pyproject.toml b/libs/cli/pyproject.toml index 4cb4a561a..839f714bd 100644 --- a/libs/cli/pyproject.toml +++ b/libs/cli/pyproject.toml @@ -19,7 +19,7 @@ dependencies = [ [project.optional-dependencies] inmem = [ "langgraph-api>=0.2.67 ; python_version >= '3.11'", - "langgraph-runtime-inmem>=0.3.0 ; python_version >= '3.11'", + "langgraph-runtime-inmem>=0.3.4 ; python_version >= '3.11'", "python-dotenv>=0.8.0", ] diff --git a/libs/cli/uv.lock b/libs/cli/uv.lock index b256a721c..bf7ad385f 100644 --- a/libs/cli/uv.lock +++ b/libs/cli/uv.lock @@ -531,7 +531,7 @@ dev = [ requires-dist = [ { name = "click", specifier = ">=8.1.7" }, { name = "langgraph-api", marker = "python_full_version >= '3.11' and extra == 'inmem'", specifier = ">=0.2.67" }, - { name = "langgraph-runtime-inmem", marker = "python_full_version >= '3.11' and extra == 'inmem'", specifier = ">=0.3.0" }, + { name = "langgraph-runtime-inmem", marker = "python_full_version >= '3.11' and extra == 'inmem'", specifier = ">=0.3.4" }, { name = "langgraph-sdk", marker = "python_full_version >= '3.11'", specifier = ">=0.1.0" }, { name = "python-dotenv", marker = "extra == 'inmem'", specifier = ">=0.8.0" }, ] @@ -564,7 +564,7 @@ wheels = [ [[package]] name = "langgraph-runtime-inmem" -version = "0.3.3" +version = "0.3.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "blockbuster", marker = "python_full_version >= '3.11'" }, @@ -574,9 +574,9 @@ dependencies = [ { name = "starlette", marker = "python_full_version >= '3.11'" }, { name = "structlog", marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/c6/c6c515df38179517a187b11cc9506caac9bc7c25ab0adc1e21b463e338fd/langgraph_runtime_inmem-0.3.3.tar.gz", hash = "sha256:1b5bc8b05989f48c64c826d826e576a6e77f42349eca3382f38f8ed1d1f026e4", size = 77443, upload-time = "2025-06-24T19:50:36.235Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/17/7ff669ff44a53ab342903c2996fff75a77494af9fe56abcfbca64fe2342b/langgraph_runtime_inmem-0.3.4.tar.gz", hash = "sha256:eda7828f3ea07126e5265024b74a3fa9bf611633ad83ba3296ab9f51d89b7c0c", size = 77424, upload-time = "2025-07-01T14:45:07.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/87/1b47cf8a9a9ab8e6460203921e05f59e1e95dc0636e64a7429cd0e579a79/langgraph_runtime_inmem-0.3.3-py3-none-any.whl", hash = "sha256:ed485d520870a96a4a2e81188f2dbf993e7b96c5c65804bcdfdcb39df71be3ce", size = 29146, upload-time = "2025-06-24T19:50:35.083Z" }, + { url = "https://files.pythonhosted.org/packages/95/0e/39c13ca7229a9425a0e5744a1d3817f80d38dd5ca7703494fb9cf836ba45/langgraph_runtime_inmem-0.3.4-py3-none-any.whl", hash = "sha256:dcb9ac68ac90b3fb1ddaf666d14a367ab70e69d5bb5589b77a72c318e29104ae", size = 29139, upload-time = "2025-07-01T14:45:06.472Z" }, ] [[package]] diff --git a/libs/langgraph/bench/fanout_to_subgraph.py b/libs/langgraph/bench/fanout_to_subgraph.py index ade894acc..f71a43077 100644 --- a/libs/langgraph/bench/fanout_to_subgraph.py +++ b/libs/langgraph/bench/fanout_to_subgraph.py @@ -3,8 +3,9 @@ from typing import Annotated from typing_extensions import TypedDict -from langgraph.constants import END, START, Send +from langgraph.constants import END, START from langgraph.graph.state import StateGraph +from langgraph.types import Send def fanout_to_subgraph() -> StateGraph: diff --git a/libs/langgraph/bench/sequential.py b/libs/langgraph/bench/sequential.py index bfdad823e..886f51ba3 100644 --- a/libs/langgraph/bench/sequential.py +++ b/libs/langgraph/bench/sequential.py @@ -1,7 +1,7 @@ """Create a sequential no-op graph consisting of a few hundred nodes.""" +from langgraph._internal._runnable import RunnableCallable from langgraph.graph import MessagesState, StateGraph -from langgraph.utils.runnable import RunnableCallable def create_sequential(number_nodes: int) -> StateGraph: diff --git a/libs/langgraph/langgraph/_internal/__init__.py b/libs/langgraph/langgraph/_internal/__init__.py new file mode 100644 index 000000000..2e71cdc23 --- /dev/null +++ b/libs/langgraph/langgraph/_internal/__init__.py @@ -0,0 +1,4 @@ +"""Internal modules for LangGraph. + +This module is not part of the public API, and thus stability is not guaranteed. +""" diff --git a/libs/langgraph/langgraph/utils/cache.py b/libs/langgraph/langgraph/_internal/_cache.py similarity index 100% rename from libs/langgraph/langgraph/utils/cache.py rename to libs/langgraph/langgraph/_internal/_cache.py diff --git a/libs/langgraph/langgraph/utils/config.py b/libs/langgraph/langgraph/_internal/_config.py similarity index 99% rename from libs/langgraph/langgraph/utils/config.py rename to libs/langgraph/langgraph/_internal/_config.py index ace98cd91..1c1428bb0 100644 --- a/libs/langgraph/langgraph/utils/config.py +++ b/libs/langgraph/langgraph/_internal/_config.py @@ -19,7 +19,6 @@ from langchain_core.runnables.config import ( ) from langgraph.checkpoint.base import CheckpointMetadata -from langgraph.config import get_config, get_store, get_stream_writer # noqa from langgraph.constants import ( CONF, CONFIG_KEY_CHECKPOINT_ID, diff --git a/libs/langgraph/langgraph/utils/fields.py b/libs/langgraph/langgraph/_internal/_fields.py similarity index 100% rename from libs/langgraph/langgraph/utils/fields.py rename to libs/langgraph/langgraph/_internal/_fields.py diff --git a/libs/langgraph/langgraph/utils/future.py b/libs/langgraph/langgraph/_internal/_future.py similarity index 100% rename from libs/langgraph/langgraph/utils/future.py rename to libs/langgraph/langgraph/_internal/_future.py diff --git a/libs/langgraph/langgraph/utils/pydantic.py b/libs/langgraph/langgraph/_internal/_pydantic.py similarity index 100% rename from libs/langgraph/langgraph/utils/pydantic.py rename to libs/langgraph/langgraph/_internal/_pydantic.py diff --git a/libs/langgraph/langgraph/utils/queue.py b/libs/langgraph/langgraph/_internal/_queue.py similarity index 99% rename from libs/langgraph/langgraph/utils/queue.py rename to libs/langgraph/langgraph/_internal/_queue.py index c0717fe34..b495e15c7 100644 --- a/libs/langgraph/langgraph/utils/queue.py +++ b/libs/langgraph/langgraph/_internal/_queue.py @@ -128,6 +128,3 @@ class SyncQueue: return len(self._queue) __class_getitem__ = classmethod(types.GenericAlias) - - -__all__ = ["AsyncQueue", "SyncQueue"] diff --git a/libs/langgraph/langgraph/_internal/_retry.py b/libs/langgraph/langgraph/_internal/_retry.py new file mode 100644 index 000000000..8d4e41fd7 --- /dev/null +++ b/libs/langgraph/langgraph/_internal/_retry.py @@ -0,0 +1,29 @@ +def default_retry_on(exc: Exception) -> bool: + import httpx + import requests + + if isinstance(exc, ConnectionError): + return True + if isinstance(exc, httpx.HTTPStatusError): + return 500 <= exc.response.status_code < 600 + if isinstance(exc, requests.HTTPError): + return 500 <= exc.response.status_code < 600 if exc.response else True + if isinstance( + exc, + ( + ValueError, + TypeError, + ArithmeticError, + ImportError, + LookupError, + NameError, + SyntaxError, + RuntimeError, + ReferenceError, + StopIteration, + StopAsyncIteration, + OSError, + ), + ): + return False + return True diff --git a/libs/langgraph/langgraph/utils/runnable.py b/libs/langgraph/langgraph/_internal/_runnable.py similarity index 99% rename from libs/langgraph/langgraph/utils/runnable.py rename to libs/langgraph/langgraph/_internal/_runnable.py index 3ca48aec3..fe24301f6 100644 --- a/libs/langgraph/langgraph/utils/runnable.py +++ b/libs/langgraph/langgraph/_internal/_runnable.py @@ -42,6 +42,12 @@ from langchain_core.runnables.utils import Input, Output from langchain_core.tracers.langchain import LangChainTracer from typing_extensions import TypeGuard +from langgraph._internal._config import ( + ensure_config, + get_async_callback_manager_for_config, + get_callback_manager_for_config, + patch_config, +) from langgraph.constants import ( CONF, CONFIG_KEY_PREVIOUS, @@ -50,12 +56,6 @@ from langgraph.constants import ( ) from langgraph.store.base import BaseStore from langgraph.types import StreamWriter -from langgraph.utils.config import ( - ensure_config, - get_async_callback_manager_for_config, - get_callback_manager_for_config, - patch_config, -) try: from langchain_core.tracers._streaming import _StreamingCallbackHandler diff --git a/libs/langgraph/langgraph/_typing.py b/libs/langgraph/langgraph/_internal/_typing.py similarity index 100% rename from libs/langgraph/langgraph/_typing.py rename to libs/langgraph/langgraph/_internal/_typing.py diff --git a/libs/langgraph/langgraph/channels/__init__.py b/libs/langgraph/langgraph/channels/__init__.py index cdb193484..a69c230b5 100644 --- a/libs/langgraph/langgraph/channels/__init__.py +++ b/libs/langgraph/langgraph/channels/__init__.py @@ -1,15 +1,27 @@ from langgraph.channels.any_value import AnyValue +from langgraph.channels.base import BaseChannel from langgraph.channels.binop import BinaryOperatorAggregate from langgraph.channels.ephemeral_value import EphemeralValue -from langgraph.channels.last_value import LastValue +from langgraph.channels.last_value import LastValue, LastValueAfterFinish +from langgraph.channels.named_barrier_value import ( + NamedBarrierValue, + NamedBarrierValueAfterFinish, +) from langgraph.channels.topic import Topic from langgraph.channels.untracked_value import UntrackedValue -__all__ = [ +__all__ = ( + # base + "BaseChannel", + # value types + "AnyValue", "LastValue", - "Topic", - "BinaryOperatorAggregate", + "LastValueAfterFinish", "UntrackedValue", "EphemeralValue", - "AnyValue", -] + "BinaryOperatorAggregate", + "NamedBarrierValue", + "NamedBarrierValueAfterFinish", + # topics + "Topic", +) diff --git a/libs/langgraph/langgraph/channels/any_value.py b/libs/langgraph/langgraph/channels/any_value.py index ec597dacb..18b008a34 100644 --- a/libs/langgraph/langgraph/channels/any_value.py +++ b/libs/langgraph/langgraph/channels/any_value.py @@ -7,6 +7,8 @@ from langgraph.channels.base import BaseChannel, Value from langgraph.constants import MISSING from langgraph.errors import EmptyChannelError +__all__ = ("AnyValue",) + class AnyValue(Generic[Value], BaseChannel[Value, Value, Value]): """Stores the last value received, assumes that if multiple values are diff --git a/libs/langgraph/langgraph/channels/base.py b/libs/langgraph/langgraph/channels/base.py index 4d6335bc1..5f6ae3f1a 100644 --- a/libs/langgraph/langgraph/channels/base.py +++ b/libs/langgraph/langgraph/channels/base.py @@ -5,12 +5,14 @@ from typing import Any, Generic, TypeVar from typing_extensions import Self from langgraph.constants import MISSING -from langgraph.errors import EmptyChannelError, InvalidUpdateError +from langgraph.errors import EmptyChannelError Value = TypeVar("Value") Update = TypeVar("Update") C = TypeVar("C") +__all__ = ("BaseChannel",) + class BaseChannel(Generic[Value, Update, C], ABC): """Base class for all channels.""" @@ -99,10 +101,3 @@ class BaseChannel(Generic[Value, Update, C], ABC): Returns True if the channel was updated, False otherwise. """ return False - - -__all__ = [ - "BaseChannel", - "EmptyChannelError", - "InvalidUpdateError", -] diff --git a/libs/langgraph/langgraph/channels/binop.py b/libs/langgraph/langgraph/channels/binop.py index e974c5fba..6b34b5533 100644 --- a/libs/langgraph/langgraph/channels/binop.py +++ b/libs/langgraph/langgraph/channels/binop.py @@ -8,6 +8,8 @@ from langgraph.channels.base import BaseChannel, Value from langgraph.constants import MISSING from langgraph.errors import EmptyChannelError +__all__ = ("BinaryOperatorAggregate",) + # Adapted from typing_extensions def _strip_extras(t): # type: ignore[no-untyped-def] diff --git a/libs/langgraph/langgraph/channels/ephemeral_value.py b/libs/langgraph/langgraph/channels/ephemeral_value.py index 7448be106..98d4f41dd 100644 --- a/libs/langgraph/langgraph/channels/ephemeral_value.py +++ b/libs/langgraph/langgraph/channels/ephemeral_value.py @@ -7,6 +7,8 @@ from langgraph.channels.base import BaseChannel, Value from langgraph.constants import MISSING from langgraph.errors import EmptyChannelError, InvalidUpdateError +__all__ = ("EphemeralValue",) + class EphemeralValue(Generic[Value], BaseChannel[Value, Value, Value]): """Stores the value received in the step immediately preceding, clears after.""" diff --git a/libs/langgraph/langgraph/channels/last_value.py b/libs/langgraph/langgraph/channels/last_value.py index 59c8d3c1b..1c07fc7ab 100644 --- a/libs/langgraph/langgraph/channels/last_value.py +++ b/libs/langgraph/langgraph/channels/last_value.py @@ -12,6 +12,8 @@ from langgraph.errors import ( create_error_message, ) +__all__ = ("LastValue", "LastValueAfterFinish") + class LastValue(Generic[Value], BaseChannel[Value, Value, Value]): """Stores the last value received, can receive at most one value per step.""" diff --git a/libs/langgraph/langgraph/channels/named_barrier_value.py b/libs/langgraph/langgraph/channels/named_barrier_value.py index e5e96a7fb..7f9c8baa0 100644 --- a/libs/langgraph/langgraph/channels/named_barrier_value.py +++ b/libs/langgraph/langgraph/channels/named_barrier_value.py @@ -7,6 +7,8 @@ from langgraph.channels.base import BaseChannel, Value from langgraph.constants import MISSING from langgraph.errors import EmptyChannelError, InvalidUpdateError +__all__ = ("NamedBarrierValue", "NamedBarrierValueAfterFinish") + class NamedBarrierValue(Generic[Value], BaseChannel[Value, Value, set[Value]]): """A channel that waits until all named values are received before making the value available.""" diff --git a/libs/langgraph/langgraph/channels/py.typed b/libs/langgraph/langgraph/channels/py.typed deleted file mode 100644 index e69de29bb..000000000 diff --git a/libs/langgraph/langgraph/channels/topic.py b/libs/langgraph/langgraph/channels/topic.py index fa96bcd59..4b9113570 100644 --- a/libs/langgraph/langgraph/channels/topic.py +++ b/libs/langgraph/langgraph/channels/topic.py @@ -9,8 +9,10 @@ from langgraph.channels.base import BaseChannel, Value from langgraph.constants import MISSING from langgraph.errors import EmptyChannelError +__all__ = ("Topic",) -def flatten(values: Sequence[Value | list[Value]]) -> Iterator[Value]: + +def _flatten(values: Sequence[Value | list[Value]]) -> Iterator[Value]: for value in values: if isinstance(value, list): yield from value @@ -77,7 +79,7 @@ class Topic( if not self.accumulate: updated = bool(self.values) self.values = list[Value]() - if flat_values := tuple(flatten(values)): + if flat_values := tuple(_flatten(values)): updated = True self.values.extend(flat_values) return updated diff --git a/libs/langgraph/langgraph/channels/untracked_value.py b/libs/langgraph/langgraph/channels/untracked_value.py index e0c9cb676..e339920dc 100644 --- a/libs/langgraph/langgraph/channels/untracked_value.py +++ b/libs/langgraph/langgraph/channels/untracked_value.py @@ -7,6 +7,8 @@ from langgraph.channels.base import BaseChannel, Value from langgraph.constants import MISSING from langgraph.errors import EmptyChannelError, InvalidUpdateError +__all__ = ("UntrackedValue",) + class UntrackedValue(Generic[Value], BaseChannel[Value, Value, Value]): """Stores the last value received, never checkpointed.""" diff --git a/libs/langgraph/langgraph/constants.py b/libs/langgraph/langgraph/constants.py index ac53718e1..e0b5452ba 100644 --- a/libs/langgraph/langgraph/constants.py +++ b/libs/langgraph/langgraph/constants.py @@ -1,23 +1,43 @@ import sys -from collections.abc import Mapping -from types import MappingProxyType from typing import Any, Literal, cast +from warnings import warn -from langgraph.types import Interrupt, Send # noqa: F401 +from langgraph.warnings import LangGraphDeprecatedSinceV10 -# Interrupt, Send re-exported for backwards compatibility +__all__ = ( + "TAG_NOSTREAM", + "TAG_HIDDEN", + "START", + "END", + "SELF", + "PREVIOUS", +) + + +def __getattr__(name: str) -> Any: + if name in ["Send", "Interrupt"]: + warn( + f"Importing {name} from langgraph.constants is deprecated. " + f"Please use 'from langgraph.types import {name}' instead.", + LangGraphDeprecatedSinceV10, + stacklevel=2, + ) + + from importlib import import_module + + module = import_module("langgraph.types") + return getattr(module, name) + + raise AttributeError(f"module has no attribute '{name}'") # --- Empty read-only containers --- -EMPTY_MAP: Mapping[str, Any] = MappingProxyType({}) EMPTY_SEQ: tuple[str, ...] = tuple() MISSING = object() # --- Public constants --- TAG_NOSTREAM = sys.intern("nostream") """Tag to disable streaming for a chat model.""" -TAG_NOSTREAM_ALT = sys.intern("langsmith:nostream") -"""Tag to disable streaming for a chat model. (Deprecated in favour of "nostream")""" TAG_HIDDEN = sys.intern("langsmith:hidden") """Tag to hide a node/edge from certain tracing/streaming environments.""" START = sys.intern("__start__") diff --git a/libs/langgraph/langgraph/errors.py b/libs/langgraph/langgraph/errors.py index 6213ff68e..e1121c1bf 100644 --- a/libs/langgraph/langgraph/errors.py +++ b/libs/langgraph/langgraph/errors.py @@ -2,10 +2,22 @@ from collections.abc import Sequence from enum import Enum from typing import Any +# EmptyChannelError is re-exported from langgraph.channels.base from langgraph.checkpoint.base import EmptyChannelError # noqa: F401 from langgraph.types import Command, Interrupt -# EmptyChannelError re-exported for backwards compatibility +__all__ = ( + "EmptyChannelError", + "ErrorCode", + "GraphRecursionError", + "InvalidUpdateError", + "GraphBubbleUp", + "GraphInterrupt", + "NodeInterrupt", + "ParentCommand", + "EmptyInputError", + "TaskNotFound", +) class ErrorCode(Enum): diff --git a/libs/langgraph/langgraph/func/__init__.py b/libs/langgraph/langgraph/func/__init__.py index ba0b3c4cb..90d43c191 100644 --- a/libs/langgraph/langgraph/func/__init__.py +++ b/libs/langgraph/langgraph/func/__init__.py @@ -19,14 +19,14 @@ from typing import ( from typing_extensions import Unpack -from langgraph._typing import UNSET, DeprecatedKwargs +from langgraph._internal._typing import UNSET, DeprecatedKwargs from langgraph.cache.base import BaseCache from langgraph.channels.ephemeral_value import EphemeralValue from langgraph.channels.last_value import LastValue from langgraph.checkpoint.base import BaseCheckpointSaver from langgraph.constants import CACHE_NS_WRITES, END, PREVIOUS, START from langgraph.pregel import Pregel -from langgraph.pregel.call import ( +from langgraph.pregel._call import ( P, SyncAsyncFuture, T, @@ -34,14 +34,16 @@ from langgraph.pregel.call import ( get_runnable_for_entrypoint, identifier, ) -from langgraph.pregel.read import PregelNode -from langgraph.pregel.write import ChannelWrite, ChannelWriteEntry +from langgraph.pregel._read import PregelNode +from langgraph.pregel._write import ChannelWrite, ChannelWriteEntry from langgraph.store.base import BaseStore from langgraph.types import _DC_KWARGS, CachePolicy, RetryPolicy, StreamMode from langgraph.warnings import LangGraphDeprecatedSinceV05 +__all__ = ("task", "entrypoint") -class TaskFunction(Generic[P, T]): + +class _TaskFunction(Generic[P, T]): def __init__( self, func: Callable[P, T], @@ -97,14 +99,14 @@ def task( **kwargs: Unpack[DeprecatedKwargs], ) -> Callable[ [Callable[P, Awaitable[T]] | Callable[P, T]], - TaskFunction[P, T], + _TaskFunction[P, T], ]: ... @overload def task( __func_or_none__: Callable[P, Awaitable[T]] | Callable[P, T], -) -> TaskFunction[P, T]: ... +) -> _TaskFunction[P, T]: ... def task( @@ -115,8 +117,8 @@ def task( cache_policy: CachePolicy[Callable[P, str | bytes]] | None = None, **kwargs: Unpack[DeprecatedKwargs], ) -> ( - Callable[[Callable[P, Awaitable[T]] | Callable[P, T]], TaskFunction[P, T]] - | TaskFunction[P, T] + Callable[[Callable[P, Awaitable[T]] | Callable[P, T]], _TaskFunction[P, T]] + | _TaskFunction[P, T] ): """Define a LangGraph task using the `task` decorator. @@ -195,7 +197,7 @@ def task( def decorator( func: Callable[P, Awaitable[T]] | Callable[P, T], ) -> Callable[P, concurrent.futures.Future[T]] | Callable[P, asyncio.Future[T]]: - return TaskFunction( + return _TaskFunction( func, retry_policy=retry_policies, cache_policy=cache_policy, name=name ) diff --git a/libs/langgraph/langgraph/func/py.typed b/libs/langgraph/langgraph/func/py.typed deleted file mode 100644 index e69de29bb..000000000 diff --git a/libs/langgraph/langgraph/graph/__init__.py b/libs/langgraph/langgraph/graph/__init__.py index 2581713c3..7bea3fc82 100644 --- a/libs/langgraph/langgraph/graph/__init__.py +++ b/libs/langgraph/langgraph/graph/__init__.py @@ -2,11 +2,11 @@ from langgraph.constants import END, START from langgraph.graph.message import MessageGraph, MessagesState, add_messages from langgraph.graph.state import StateGraph -__all__ = [ +__all__ = ( "END", "START", "StateGraph", - "MessageGraph", "add_messages", "MessagesState", -] + "MessageGraph", +) diff --git a/libs/langgraph/langgraph/graph/branch.py b/libs/langgraph/langgraph/graph/_branch.py similarity index 96% rename from libs/langgraph/langgraph/graph/branch.py rename to libs/langgraph/langgraph/graph/_branch.py index f120167d6..90bb68b88 100644 --- a/libs/langgraph/langgraph/graph/branch.py +++ b/libs/langgraph/langgraph/graph/_branch.py @@ -26,15 +26,15 @@ from langchain_core.runnables import ( RunnableLambda, ) -from langgraph.constants import END, START -from langgraph.errors import InvalidUpdateError -from langgraph.pregel.write import PASSTHROUGH, ChannelWrite, ChannelWriteEntry -from langgraph.types import Send -from langgraph.utils.runnable import ( +from langgraph._internal._runnable import ( RunnableCallable, ) +from langgraph.constants import END, START +from langgraph.errors import InvalidUpdateError +from langgraph.pregel._write import PASSTHROUGH, ChannelWrite, ChannelWriteEntry +from langgraph.types import Send -Writer = Callable[ +_Writer = Callable[ [Sequence[Union[str, Send]], bool], Sequence[Union[ChannelWriteEntry, Send]], ] @@ -82,7 +82,7 @@ def _get_branch_path_input_schema( return input -class Branch(NamedTuple): +class BranchSpec(NamedTuple): path: Runnable[Any, Hashable | list[Hashable]] ends: dict[Hashable, str] | None input_schema: type[Any] | None = None @@ -93,7 +93,7 @@ class Branch(NamedTuple): path: Runnable[Any, Hashable | list[Hashable]], path_map: dict[Hashable, str] | list[str] | None, infer_schema: bool = False, - ) -> Branch: + ) -> BranchSpec: # coerce path_map to a dictionary path_map_: dict[Hashable, str] | None = None try: @@ -123,7 +123,7 @@ class Branch(NamedTuple): def run( self, - writer: Writer, + writer: _Writer, reader: Callable[[RunnableConfig], Any] | None = None, ) -> RunnableCallable: return ChannelWrite.register_writer( @@ -152,7 +152,7 @@ class Branch(NamedTuple): config: RunnableConfig, *, reader: Callable[[RunnableConfig], Any] | None, - writer: Writer, + writer: _Writer, ) -> Runnable: if reader: value = reader(config) @@ -175,7 +175,7 @@ class Branch(NamedTuple): config: RunnableConfig, *, reader: Callable[[RunnableConfig], Any] | None, - writer: Writer, + writer: _Writer, ) -> Runnable: if reader: value = reader(config) @@ -194,7 +194,7 @@ class Branch(NamedTuple): def _finish( self, - writer: Writer, + writer: _Writer, input: Any, result: Any, config: RunnableConfig, diff --git a/libs/langgraph/langgraph/graph/_node.py b/libs/langgraph/langgraph/graph/_node.py new file mode 100644 index 000000000..f63d29dbe --- /dev/null +++ b/libs/langgraph/langgraph/graph/_node.py @@ -0,0 +1,84 @@ +from __future__ import annotations + +from collections.abc import Sequence +from typing import Any, NamedTuple, Protocol, Union + +from langchain_core.runnables import Runnable, RunnableConfig +from typing_extensions import TypeAlias + +from langgraph.constants import EMPTY_SEQ +from langgraph.store.base import BaseStore +from langgraph.types import CachePolicy, RetryPolicy, StreamWriter +from langgraph.typing import StateT_contra + + +class _Node(Protocol[StateT_contra]): + def __call__(self, state: StateT_contra) -> Any: ... + + +class _NodeWithConfig(Protocol[StateT_contra]): + def __call__(self, state: StateT_contra, config: RunnableConfig) -> Any: ... + + +class _NodeWithWriter(Protocol[StateT_contra]): + def __call__(self, state: StateT_contra, *, writer: StreamWriter) -> Any: ... + + +class _NodeWithStore(Protocol[StateT_contra]): + def __call__(self, state: StateT_contra, *, store: BaseStore) -> Any: ... + + +class _NodeWithWriterStore(Protocol[StateT_contra]): + def __call__( + self, state: StateT_contra, *, writer: StreamWriter, store: BaseStore + ) -> Any: ... + + +class _NodeWithConfigWriter(Protocol[StateT_contra]): + def __call__( + self, state: StateT_contra, *, config: RunnableConfig, writer: StreamWriter + ) -> Any: ... + + +class _NodeWithConfigStore(Protocol[StateT_contra]): + def __call__( + self, state: StateT_contra, *, config: RunnableConfig, store: BaseStore + ) -> Any: ... + + +class _NodeWithConfigWriterStore(Protocol[StateT_contra]): + def __call__( + self, + state: StateT_contra, + *, + config: RunnableConfig, + writer: StreamWriter, + store: BaseStore, + ) -> Any: ... + + +# TODO: we probably don't want to explicitly support the config / store signatures once +# we move to adding a context arg. Maybe what we do is we add support for kwargs with param spec +# this is purely for typing purposes though, so can easily change in the coming weeks. +StateNode: TypeAlias = Union[ + _Node[StateT_contra], + _NodeWithConfig[StateT_contra], + _NodeWithWriter[StateT_contra], + _NodeWithStore[StateT_contra], + _NodeWithWriterStore[StateT_contra], + _NodeWithConfigWriter[StateT_contra], + _NodeWithConfigStore[StateT_contra], + _NodeWithConfigWriterStore[StateT_contra], + Runnable[StateT_contra, Any], +] + + +# TODO: use a dataclass generic on NodeInputType +class StateNodeSpec(NamedTuple): + runnable: StateNode + metadata: dict[str, Any] | None + input_schema: type[Any] + retry_policy: RetryPolicy | Sequence[RetryPolicy] | None + cache_policy: CachePolicy | None + ends: tuple[str, ...] | dict[str, str] | None = EMPTY_SEQ + defer: bool = False diff --git a/libs/langgraph/langgraph/graph/message.py b/libs/langgraph/langgraph/graph/message.py index e50bcfec2..fc3355eb6 100644 --- a/libs/langgraph/langgraph/graph/message.py +++ b/libs/langgraph/langgraph/graph/message.py @@ -27,6 +27,12 @@ from typing_extensions import TypedDict from langgraph.constants import CONF, CONFIG_KEY_SEND from langgraph.graph.state import StateGraph +__all__ = ( + "add_messages", + "MessagesState", + "MessageGraph", +) + Messages = Union[list[MessageLikeRepresentation], MessageLikeRepresentation] REMOVE_ALL_MESSAGES = "__remove_all__" @@ -315,7 +321,7 @@ def push_message( from langgraph.config import get_config from langgraph.constants import NS_SEP - from langgraph.pregel.messages import StreamMessagesHandler + from langgraph.pregel._messages import StreamMessagesHandler config = get_config() message = next(x for x in convert_to_messages([message])) diff --git a/libs/langgraph/langgraph/graph/py.typed b/libs/langgraph/langgraph/graph/py.typed deleted file mode 100644 index e69de29bb..000000000 diff --git a/libs/langgraph/langgraph/graph/state.py b/libs/langgraph/langgraph/graph/state.py index 151b1b278..6ad7dce00 100644 --- a/libs/langgraph/langgraph/graph/state.py +++ b/libs/langgraph/langgraph/graph/state.py @@ -14,8 +14,6 @@ from typing import ( Callable, Generic, Literal, - NamedTuple, - Protocol, Union, cast, get_args, @@ -26,9 +24,16 @@ from typing import ( from langchain_core.runnables import Runnable, RunnableConfig from pydantic import BaseModel -from typing_extensions import Self, TypeAlias, Unpack +from typing_extensions import Self, Unpack -from langgraph._typing import UNSET, DeprecatedKwargs +from langgraph._internal._fields import ( + get_cached_annotated_keys, + get_field_default, + get_update_as_tuples, +) +from langgraph._internal._pydantic import create_model +from langgraph._internal._runnable import coerce_to_runnable +from langgraph._internal._typing import UNSET, DeprecatedKwargs from langgraph.cache.base import BaseCache from langgraph.channels.base import BaseChannel from langgraph.channels.binop import BinaryOperatorAggregate @@ -56,14 +61,15 @@ from langgraph.errors import ( ParentCommand, create_error_message, ) -from langgraph.graph.branch import Branch +from langgraph.graph._branch import BranchSpec +from langgraph.graph._node import StateNode, StateNodeSpec from langgraph.managed.base import ( ManagedValueSpec, is_managed_value, ) from langgraph.pregel import Pregel -from langgraph.pregel.read import ChannelRead, PregelNode -from langgraph.pregel.write import ( +from langgraph.pregel._read import ChannelRead, PregelNode +from langgraph.pregel._write import ( ChannelWrite, ChannelWriteEntry, ChannelWriteTupleEntry, @@ -76,20 +82,16 @@ from langgraph.types import ( Command, RetryPolicy, Send, - StreamWriter, ) -from langgraph.typing import InputT, OutputT, StateT, StateT_contra -from langgraph.utils.fields import ( - get_cached_annotated_keys, - get_field_default, - get_update_as_tuples, -) -from langgraph.utils.pydantic import create_model -from langgraph.utils.runnable import coerce_to_runnable +from langgraph.typing import InputT, OutputT, StateT from langgraph.warnings import LangGraphDeprecatedSinceV05 +__all__ = ("StateGraph", "CompiledStateGraph") + logger = logging.getLogger(__name__) +_CHANNEL_BRANCH_TO = "branch:to:{}" + def _warn_invalid_state_schema(schema: type[Any] | Any) -> None: if isinstance(schema, type): @@ -103,67 +105,6 @@ def _warn_invalid_state_schema(schema: type[Any] | Any) -> None: ) -class _StateNode(Protocol[StateT_contra]): - def __call__(self, state: StateT_contra) -> Any: ... - - -class _NodeWithConfig(Protocol[StateT_contra]): - def __call__(self, state: StateT_contra, config: RunnableConfig) -> Any: ... - - -class _NodeWithWriter(Protocol[StateT_contra]): - def __call__(self, state: StateT_contra, *, writer: StreamWriter) -> Any: ... - - -class _NodeWithStore(Protocol[StateT_contra]): - def __call__(self, state: StateT_contra, *, store: BaseStore) -> Any: ... - - -class _NodeWithWriterStore(Protocol[StateT_contra]): - def __call__( - self, state: StateT_contra, *, writer: StreamWriter, store: BaseStore - ) -> Any: ... - - -class _NodeWithConfigWriter(Protocol[StateT_contra]): - def __call__( - self, state: StateT_contra, *, config: RunnableConfig, writer: StreamWriter - ) -> Any: ... - - -class _NodeWithConfigStore(Protocol[StateT_contra]): - def __call__( - self, state: StateT_contra, *, config: RunnableConfig, store: BaseStore - ) -> Any: ... - - -class _NodeWithConfigWriterStore(Protocol[StateT_contra]): - def __call__( - self, - state: StateT_contra, - *, - config: RunnableConfig, - writer: StreamWriter, - store: BaseStore, - ) -> Any: ... - - -# TODO: we probably don't want to explicitly support the config / store signatures once -# we move to adding a context arg. Maybe what we do is we add support for kwargs with param spec -# this is purely for typing purposes though, so can easily change in the coming weeks. -StateNode: TypeAlias = Union[ - _StateNode[StateT_contra], - _NodeWithConfig[StateT_contra], - _NodeWithWriter[StateT_contra], - _NodeWithStore[StateT_contra], - _NodeWithWriterStore[StateT_contra], - _NodeWithConfigWriter[StateT_contra], - _NodeWithConfigStore[StateT_contra], - _NodeWithConfigWriterStore[StateT_contra], - Runnable[StateT_contra, Any], -] - - def _get_node_name(node: StateNode) -> str: try: return getattr(node, "__name__", node.__class__.__name__) @@ -171,20 +112,6 @@ def _get_node_name(node: StateNode) -> str: raise TypeError(f"Unsupported node type: {type(node)}") -class StateNodeSpec(NamedTuple): - # TODO: rename this callable, also move away from NamedTuple so that we can use - # a generic StateNode, so maybe a dataclass - runnable: StateNode - metadata: dict[str, Any] | None - # TODO: rename to input_schema, though we really just want to modify this structure to - # be a dataclass - input: type[Any] - retry_policy: RetryPolicy | Sequence[RetryPolicy] | None - cache_policy: CachePolicy | None - ends: tuple[str, ...] | dict[str, str] | None = EMPTY_SEQ - defer: bool = False - - class StateGraph(Generic[StateT, InputT, OutputT]): """A graph whose nodes communicate by reading and writing to a shared state. The signature of each node is State -> Partial. @@ -239,7 +166,7 @@ class StateGraph(Generic[StateT, InputT, OutputT]): edges: set[tuple[str, str]] nodes: dict[str, StateNodeSpec] - branches: defaultdict[str, dict[str, Branch]] + branches: defaultdict[str, dict[str, BranchSpec]] channels: dict[str, BaseChannel] managed: dict[str, ManagedValueSpec] schemas: dict[type[Any], dict[str, BaseChannel | ManagedValueSpec]] @@ -538,7 +465,7 @@ class StateGraph(Generic[StateT, InputT, OutputT]): self.nodes[node] = StateNodeSpec( coerce_to_runnable(action, name=node, trace=False), metadata, - input=input_schema or self.state_schema, + input_schema=input_schema or self.state_schema, retry_policy=retry_policy, cache_policy=cache_policy, ends=ends, @@ -641,7 +568,7 @@ class StateGraph(Generic[StateT, InputT, OutputT]): f"Branch with name `{path.name}` already exists for node `{source}`" ) # save it - self.branches[source][name] = Branch.from_path(path, path_map, True) + self.branches[source][name] = BranchSpec.from_path(path, path_map, True) if schema := self.branches[source][name].input_schema: self._add_schema(schema) return self @@ -994,7 +921,7 @@ class CompiledStateGraph( writers=[ChannelWrite(write_entries)], ) elif node is not None: - input_schema = node.input if node else self.builder._state_schema + input_schema = node.input_schema if node else self.builder._state_schema input_channels = list(self.builder.schemas[input_schema]) is_single_input = len(input_channels) == 1 and "__root__" in input_channels if input_schema in self.schema_to_mapper: @@ -1003,7 +930,7 @@ class CompiledStateGraph( mapper = _pick_mapper(input_channels, input_schema) self.schema_to_mapper[input_schema] = mapper - branch_channel = CHANNEL_BRANCH_TO.format(key) + branch_channel = _CHANNEL_BRANCH_TO.format(key) self.channels[branch_channel] = ( LastValueAfterFinish(Any) if node.defer @@ -1031,7 +958,7 @@ class CompiledStateGraph( if end != END: self.nodes[starts].writers.append( ChannelWrite( - (ChannelWriteEntry(CHANNEL_BRANCH_TO.format(end), None),) + (ChannelWriteEntry(_CHANNEL_BRANCH_TO.format(end), None),) ) ) elif end != END: @@ -1052,7 +979,7 @@ class CompiledStateGraph( ) def attach_branch( - self, start: str, name: str, branch: Branch, *, with_reader: bool = True + self, start: str, name: str, branch: BranchSpec, *, with_reader: bool = True ) -> None: def get_writes( packets: Sequence[str | Send], static: bool = False @@ -1060,7 +987,7 @@ class CompiledStateGraph( writes = [ ( ChannelWriteEntry( - p if p == END else CHANNEL_BRANCH_TO.format(p), None + p if p == END else _CHANNEL_BRANCH_TO.format(p), None ) if not isinstance(p, Send) else p @@ -1075,7 +1002,7 @@ class CompiledStateGraph( if with_reader: # get schema schema = branch.input_schema or ( - self.builder.nodes[start].input + self.builder.nodes[start].input_schema if start in self.builder.nodes else self.builder.state_schema ) @@ -1237,12 +1164,12 @@ def _control_branch(value: Any) -> Sequence[tuple[str, Any]]: if isinstance(command.goto, Send): rtn.append((TASKS, command.goto)) elif isinstance(command.goto, str): - rtn.append((CHANNEL_BRANCH_TO.format(command.goto), None)) + rtn.append((_CHANNEL_BRANCH_TO.format(command.goto), None)) else: rtn.extend( (TASKS, go) if isinstance(go, Send) - else (CHANNEL_BRANCH_TO.format(go), None) + else (_CHANNEL_BRANCH_TO.format(go), None) for go in command.goto ) return rtn @@ -1253,12 +1180,12 @@ def _control_static( ) -> Sequence[tuple[str, Any, str | None]]: if isinstance(ends, dict): return [ - (k if k == END else CHANNEL_BRANCH_TO.format(k), None, label) + (k if k == END else _CHANNEL_BRANCH_TO.format(k), None, label) for k, label in ends.items() ] else: return [ - (e if e == END else CHANNEL_BRANCH_TO.format(e), None, None) for e in ends + (e if e == END else _CHANNEL_BRANCH_TO.format(e), None, None) for e in ends ] @@ -1415,6 +1342,3 @@ def _get_schema( if k in channels and isinstance(channels[k], BaseChannel) }, ) - - -CHANNEL_BRANCH_TO = "branch:to:{}" diff --git a/libs/langgraph/langgraph/graph/ui.py b/libs/langgraph/langgraph/graph/ui.py index 181587740..e829eff2e 100644 --- a/libs/langgraph/langgraph/graph/ui.py +++ b/libs/langgraph/langgraph/graph/ui.py @@ -6,8 +6,17 @@ from uuid import uuid4 from langchain_core.messages import AnyMessage from typing_extensions import TypedDict +from langgraph.config import get_config, get_stream_writer from langgraph.constants import CONF, CONFIG_KEY_SEND -from langgraph.utils.config import get_config, get_stream_writer + +__all__ = ( + "UIMessage", + "RemoveUIMessage", + "AnyUIMessage", + "push_ui_message", + "delete_ui_message", + "ui_message_reducer", +) class UIMessage(TypedDict): diff --git a/libs/langgraph/langgraph/managed/__init__.py b/libs/langgraph/langgraph/managed/__init__.py index 966348e6f..2d50f323b 100644 --- a/libs/langgraph/langgraph/managed/__init__.py +++ b/libs/langgraph/langgraph/managed/__init__.py @@ -1,3 +1,3 @@ from langgraph.managed.is_last_step import IsLastStep, RemainingSteps -__all__ = ["IsLastStep", "RemainingSteps"] +__all__ = ("IsLastStep", "RemainingSteps") diff --git a/libs/langgraph/langgraph/managed/base.py b/libs/langgraph/langgraph/managed/base.py index aa8f507b6..3b5de24d5 100644 --- a/libs/langgraph/langgraph/managed/base.py +++ b/libs/langgraph/langgraph/managed/base.py @@ -8,11 +8,13 @@ from typing import ( from typing_extensions import TypeGuard -from langgraph.types import PregelScratchpad +from langgraph.pregel._scratchpad import PregelScratchpad V = TypeVar("V") U = TypeVar("U") +__all__ = ("ManagedValueSpec", "ManagedValueMapping") + class ManagedValue(ABC, Generic[V]): @staticmethod diff --git a/libs/langgraph/langgraph/managed/is_last_step.py b/libs/langgraph/langgraph/managed/is_last_step.py index ccfaea038..6ffa4df16 100644 --- a/libs/langgraph/langgraph/managed/is_last_step.py +++ b/libs/langgraph/langgraph/managed/is_last_step.py @@ -1,7 +1,9 @@ from typing import Annotated from langgraph.managed.base import ManagedValue -from langgraph.types import PregelScratchpad +from langgraph.pregel._scratchpad import PregelScratchpad + +__all__ = ("IsLastStep", "RemainingStepsManager") class IsLastStepManager(ManagedValue[bool]): diff --git a/libs/langgraph/langgraph/managed/py.typed b/libs/langgraph/langgraph/managed/py.typed deleted file mode 100644 index e69de29bb..000000000 diff --git a/libs/langgraph/langgraph/pregel/__init__.py b/libs/langgraph/langgraph/pregel/__init__.py index 67c938611..90eb44b85 100644 --- a/libs/langgraph/langgraph/pregel/__init__.py +++ b/libs/langgraph/langgraph/pregel/__init__.py @@ -1,3057 +1,3 @@ -from __future__ import annotations +from langgraph.pregel.main import NodeBuilder, Pregel -import asyncio -import concurrent -import concurrent.futures -import queue -import weakref -from collections import defaultdict, deque -from collections.abc import AsyncIterator, Iterator, Mapping, Sequence -from functools import partial -from typing import Any, Callable, Generic, Union, cast, get_type_hints -from uuid import UUID, uuid5 - -from langchain_core.globals import get_debug -from langchain_core.runnables import ( - RunnableSequence, -) -from langchain_core.runnables.base import Input, Output -from langchain_core.runnables.config import ( - RunnableConfig, - get_async_callback_manager_for_config, - get_callback_manager_for_config, -) -from langchain_core.runnables.graph import Graph -from pydantic import BaseModel -from typing_extensions import Self - -from langgraph.cache.base import BaseCache -from langgraph.channels.base import BaseChannel -from langgraph.channels.topic import Topic -from langgraph.checkpoint.base import ( - BaseCheckpointSaver, - Checkpoint, - CheckpointTuple, -) -from langgraph.config import get_config -from langgraph.constants import ( - CACHE_NS_WRITES, - CONF, - CONFIG_KEY_CACHE, - CONFIG_KEY_CHECKPOINT_DURING, - CONFIG_KEY_CHECKPOINT_ID, - CONFIG_KEY_CHECKPOINT_NS, - CONFIG_KEY_CHECKPOINTER, - CONFIG_KEY_NODE_FINISHED, - CONFIG_KEY_READ, - CONFIG_KEY_RUNNER_SUBMIT, - CONFIG_KEY_SEND, - CONFIG_KEY_STORE, - CONFIG_KEY_STREAM, - CONFIG_KEY_STREAM_WRITER, - CONFIG_KEY_TASK_ID, - CONFIG_KEY_THREAD_ID, - END, - ERROR, - INPUT, - INTERRUPT, - NS_END, - NS_SEP, - NULL_TASK_ID, - PUSH, - TASKS, -) -from langgraph.errors import ( - ErrorCode, - GraphRecursionError, - InvalidUpdateError, - create_error_message, -) -from langgraph.managed.base import ManagedValueSpec -from langgraph.pregel.algo import ( - PregelTaskWrites, - _scratchpad, - apply_writes, - local_read, - prepare_next_tasks, -) -from langgraph.pregel.call import identifier -from langgraph.pregel.checkpoint import ( - channels_from_checkpoint, - copy_checkpoint, - create_checkpoint, - empty_checkpoint, -) -from langgraph.pregel.debug import get_bolded_text, get_colored_text, tasks_w_writes -from langgraph.pregel.draw import draw_graph -from langgraph.pregel.io import map_input, read_channels -from langgraph.pregel.loop import AsyncPregelLoop, StreamProtocol, SyncPregelLoop -from langgraph.pregel.messages import StreamMessagesHandler -from langgraph.pregel.protocol import PregelProtocol -from langgraph.pregel.read import DEFAULT_BOUND, PregelNode -from langgraph.pregel.retry import RetryPolicy -from langgraph.pregel.runner import PregelRunner -from langgraph.pregel.utils import get_new_channel_versions -from langgraph.pregel.validate import validate_graph, validate_keys -from langgraph.pregel.write import ChannelWrite, ChannelWriteEntry -from langgraph.store.base import BaseStore -from langgraph.types import ( - All, - CachePolicy, - Checkpointer, - Interrupt, - Send, - StateSnapshot, - StateUpdate, - StreamChunk, - StreamMode, -) -from langgraph.typing import InputT, OutputT, StateT -from langgraph.utils.config import ( - ensure_config, - merge_configs, - patch_checkpoint_map, - patch_config, - patch_configurable, - recast_checkpoint_ns, -) -from langgraph.utils.pydantic import create_model -from langgraph.utils.queue import AsyncQueue, SyncQueue # type: ignore[attr-defined] -from langgraph.utils.runnable import ( - Runnable, - RunnableLike, - RunnableSeq, - coerce_to_runnable, -) - -try: - from langchain_core.tracers._streaming import _StreamingCallbackHandler -except ImportError: - _StreamingCallbackHandler = None # type: ignore - -WriteValue = Union[Callable[[Input], Output], Any] - - -class NodeBuilder: - __slots__ = ( - "_channels", - "_triggers", - "_tags", - "_metadata", - "_writes", - "_bound", - "_retry_policy", - "_cache_policy", - ) - - _channels: str | list[str] - _triggers: list[str] - _tags: list[str] - _metadata: dict[str, Any] - _writes: list[ChannelWriteEntry] - _bound: Runnable - _retry_policy: list[RetryPolicy] - _cache_policy: CachePolicy | None - - def __init__( - self, - ) -> None: - self._channels = [] - self._triggers = [] - self._tags = [] - self._metadata = {} - self._writes = [] - self._bound = DEFAULT_BOUND - self._retry_policy = [] - self._cache_policy = None - - def subscribe_only( - self, - channel: str, - ) -> Self: - """Subscribe to a single channel.""" - if not self._channels: - self._channels = channel - else: - raise ValueError( - "Cannot subscribe to single channels when other channels are already subscribed to" - ) - - self._triggers.append(channel) - - return self - - def subscribe_to( - self, - *channels: str, - read: bool = True, - ) -> Self: - """Add channels to subscribe to. Node will be invoked when any of these - channels are updated, with a dict of the channel values as input. - - Args: - channels: Channel name(s) to subscribe to - read: If True, the channels will be included in the input to the node. - Otherwise, they will trigger the node without being sent in input. - - Returns: - Self for chaining - """ - if isinstance(self._channels, str): - raise ValueError( - "Cannot subscribe to channels when subscribed to a single channel" - ) - if read: - if not self._channels: - self._channels = list(channels) - else: - self._channels.extend(channels) - - if isinstance(channels, str): - self._triggers.append(channels) - else: - self._triggers.extend(channels) - - return self - - def read_from( - self, - *channels: str, - ) -> Self: - """Adds the specified channels to read from, without subscribing to them.""" - assert isinstance(self._channels, list), ( - "Cannot read additional channels when subscribed to single channels" - ) - self._channels.extend(channels) - return self - - def do( - self, - node: RunnableLike, - ) -> Self: - """Adds the specified node.""" - if self._bound is not DEFAULT_BOUND: - self._bound = RunnableSeq( - self._bound, coerce_to_runnable(node, name=None, trace=True) - ) - else: - self._bound = coerce_to_runnable(node, name=None, trace=True) - return self - - def write_to( - self, - *channels: str | ChannelWriteEntry, - **kwargs: WriteValue, - ) -> Self: - """Add channel writes. - - Args: - *channels: Channel names to write to - **kwargs: Channel name and value mappings - - Returns: - Self for chaining - """ - self._writes.extend( - ChannelWriteEntry(c) if isinstance(c, str) else c for c in channels - ) - self._writes.extend( - ChannelWriteEntry(k, mapper=v) - if callable(v) - else ChannelWriteEntry(k, value=v) - for k, v in kwargs.items() - ) - - return self - - def meta(self, *tags: str, **metadata: Any) -> Self: - """Add tags or metadata to the node.""" - self._tags.extend(tags) - self._metadata.update(metadata) - return self - - def add_retry_policies(self, *policies: RetryPolicy) -> Self: - """Adds retry policies to the node.""" - self._retry_policy.extend(policies) - return self - - def add_cache_policy(self, policy: CachePolicy) -> Self: - """Adds cache policies to the node.""" - self._cache_policy = policy - return self - - def build(self) -> PregelNode: - """Builds the node.""" - return PregelNode( - channels=self._channels, - triggers=self._triggers, - tags=self._tags, - metadata=self._metadata, - writers=[ChannelWrite(self._writes)], - bound=self._bound, - retry_policy=self._retry_policy, - cache_policy=self._cache_policy, - ) - - -class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, OutputT]): - """Pregel manages the runtime behavior for LangGraph applications. - - ## Overview - - Pregel combines [**actors**](https://en.wikipedia.org/wiki/Actor_model) - and **channels** into a single application. - **Actors** read data from channels and write data to channels. - Pregel organizes the execution of the application into multiple steps, - following the **Pregel Algorithm**/**Bulk Synchronous Parallel** model. - - Each step consists of three phases: - - - **Plan**: Determine which **actors** to execute in this step. For example, - in the first step, select the **actors** that subscribe to the special - **input** channels; in subsequent steps, - select the **actors** that subscribe to channels updated in the previous step. - - **Execution**: Execute all selected **actors** in parallel, - until all complete, or one fails, or a timeout is reached. During this - phase, channel updates are invisible to actors until the next step. - - **Update**: Update the channels with the values written by the **actors** - in this step. - - Repeat until no **actors** are selected for execution, or a maximum number of - steps is reached. - - ## Actors - - An **actor** is a `PregelNode`. - It subscribes to channels, reads data from them, and writes data to them. - It can be thought of as an **actor** in the Pregel algorithm. - `PregelNodes` implement LangChain's - Runnable interface. - - ## Channels - - Channels are used to communicate between actors (`PregelNodes`). - Each channel has a value type, an update type, and an update function – which - takes a sequence of updates and - modifies the stored value. Channels can be used to send data from one chain to - another, or to send data from a chain to itself in a future step. LangGraph - provides a number of built-in channels: - - ### Basic channels: LastValue and Topic - - - `LastValue`: The default channel, stores the last value sent to the channel, - useful for input and output values, or for sending data from one step to the next - - `Topic`: A configurable PubSub Topic, useful for sending multiple values - between *actors*, or for accumulating output. Can be configured to deduplicate - values, and/or to accumulate values over the course of multiple steps. - - ### Advanced channels: Context and BinaryOperatorAggregate - - - `Context`: exposes the value of a context manager, managing its lifecycle. - Useful for accessing external resources that require setup and/or teardown. eg. - `client = Context(httpx.Client)` - - `BinaryOperatorAggregate`: stores a persistent value, updated by applying - a binary operator to the current value and each update - sent to the channel, useful for computing aggregates over multiple steps. eg. - `total = BinaryOperatorAggregate(int, operator.add)` - - ## Examples - - Most users will interact with Pregel via a - [StateGraph (Graph API)][langgraph.graph.StateGraph] or via an - [entrypoint (Functional API)][langgraph.func.entrypoint]. - - However, for **advanced** use cases, Pregel can be used directly. If you're - not sure whether you need to use Pregel directly, then the answer is probably no - – you should use the Graph API or Functional API instead. These are higher-level - interfaces that will compile down to Pregel under the hood. - - Here are some examples to give you a sense of how it works: - - Example: Single node application - ```python - from langgraph.channels import EphemeralValue - from langgraph.pregel import Pregel, NodeBuilder - - node1 = ( - NodeBuilder().subscribe_only("a") - .do(lambda x: x + x) - .write_to("b") - ) - - app = Pregel( - nodes={"node1": node1}, - channels={ - "a": EphemeralValue(str), - "b": EphemeralValue(str), - }, - input_channels=["a"], - output_channels=["b"], - ) - - app.invoke({"a": "foo"}) - ``` - - ```con - {'b': 'foofoo'} - ``` - - Example: Using multiple nodes and multiple output channels - ```python - from langgraph.channels import LastValue, EphemeralValue - from langgraph.pregel import Pregel, NodeBuilder - - node1 = ( - NodeBuilder().subscribe_only("a") - .do(lambda x: x + x) - .write_to("b") - ) - - node2 = ( - NodeBuilder().subscribe_to("b") - .do(lambda x: x["b"] + x["b"]) - .write_to("c") - ) - - - app = Pregel( - nodes={"node1": node1, "node2": node2}, - channels={ - "a": EphemeralValue(str), - "b": LastValue(str), - "c": EphemeralValue(str), - }, - input_channels=["a"], - output_channels=["b", "c"], - ) - - app.invoke({"a": "foo"}) - ``` - - ```con - {'b': 'foofoo', 'c': 'foofoofoofoo'} - ``` - - Example: Using a Topic channel - ```python - from langgraph.channels import LastValue, EphemeralValue, Topic - from langgraph.pregel import Pregel, NodeBuilder - - node1 = ( - NodeBuilder().subscribe_only("a") - .do(lambda x: x + x) - .write_to("b", "c") - ) - - node2 = ( - NodeBuilder().subscribe_only("b") - .do(lambda x: x + x) - .write_to("c") - ) - - - app = Pregel( - nodes={"node1": node1, "node2": node2}, - channels={ - "a": EphemeralValue(str), - "b": EphemeralValue(str), - "c": Topic(str, accumulate=True), - }, - input_channels=["a"], - output_channels=["c"], - ) - - app.invoke({"a": "foo"}) - ``` - - ```pycon - {'c': ['foofoo', 'foofoofoofoo']} - ``` - - Example: Using a BinaryOperatorAggregate channel - ```python - from langgraph.channels import EphemeralValue, BinaryOperatorAggregate - from langgraph.pregel import Pregel, NodeBuilder - - - node1 = ( - NodeBuilder().subscribe_only("a") - .do(lambda x: x + x) - .write_to("b", "c") - ) - - node2 = ( - NodeBuilder().subscribe_only("b") - .do(lambda x: x + x) - .write_to("c") - ) - - - def reducer(current, update): - if current: - return current + " | " + update - else: - return update - - app = Pregel( - nodes={"node1": node1, "node2": node2}, - channels={ - "a": EphemeralValue(str), - "b": EphemeralValue(str), - "c": BinaryOperatorAggregate(str, operator=reducer), - }, - input_channels=["a"], - output_channels=["c"] - ) - - app.invoke({"a": "foo"}) - ``` - - ```con - {'c': 'foofoo | foofoofoofoo'} - ``` - - Example: Introducing a cycle - This example demonstrates how to introduce a cycle in the graph, by having - a chain write to a channel it subscribes to. Execution will continue - until a None value is written to the channel. - - ```python - from langgraph.channels import EphemeralValue - from langgraph.pregel import Pregel, NodeBuilder, ChannelWriteEntry - - example_node = ( - NodeBuilder().subscribe_only("value") - .do(lambda x: x + x if len(x) < 10 else None) - .write_to(ChannelWriteEntry(channel="value", skip_none=True)) - ) - - app = Pregel( - nodes={"example_node": example_node}, - channels={ - "value": EphemeralValue(str), - }, - input_channels=["value"], - output_channels=["value"] - ) - - app.invoke({"value": "a"}) - ``` - - ```con - {'value': 'aaaaaaaaaaaaaaaa'} - ``` - """ - - nodes: dict[str, PregelNode] - - channels: dict[str, BaseChannel | ManagedValueSpec] - - stream_mode: StreamMode = "values" - """Mode to stream output, defaults to 'values'.""" - - stream_eager: bool = False - """Whether to force emitting stream events eagerly, automatically turned on - for stream_mode "messages" and "custom".""" - - output_channels: str | Sequence[str] - - stream_channels: str | Sequence[str] | None = None - """Channels to stream, defaults to all channels not in reserved channels""" - - interrupt_after_nodes: All | Sequence[str] - - interrupt_before_nodes: All | Sequence[str] - - input_channels: str | Sequence[str] - - step_timeout: float | None = None - """Maximum time to wait for a step to complete, in seconds. Defaults to None.""" - - debug: bool - """Whether to print debug information during execution. Defaults to False.""" - - checkpointer: Checkpointer = None - """Checkpointer used to save and load graph state. Defaults to None.""" - - store: BaseStore | None = None - """Memory store to use for SharedValues. Defaults to None.""" - - cache: BaseCache | None = None - """Cache to use for storing node results. Defaults to None.""" - - retry_policy: Sequence[RetryPolicy] = () - """Retry policies to use when running tasks. Empty set disables retries.""" - - cache_policy: CachePolicy | None = None - """Cache policy to use for all nodes. Can be overridden by individual nodes. - Defaults to None.""" - - config_type: type[Any] | None = None - - config: RunnableConfig | None = None - - name: str = "LangGraph" - - trigger_to_nodes: Mapping[str, Sequence[str]] - - def __init__( - self, - *, - nodes: dict[str, PregelNode | NodeBuilder], - channels: dict[str, BaseChannel | ManagedValueSpec] | None, - auto_validate: bool = True, - stream_mode: StreamMode = "values", - stream_eager: bool = False, - output_channels: str | Sequence[str], - stream_channels: str | Sequence[str] | None = None, - interrupt_after_nodes: All | Sequence[str] = (), - interrupt_before_nodes: All | Sequence[str] = (), - input_channels: str | Sequence[str], - step_timeout: float | None = None, - debug: bool | None = None, - checkpointer: BaseCheckpointSaver | None = None, - store: BaseStore | None = None, - cache: BaseCache | None = None, - retry_policy: RetryPolicy | Sequence[RetryPolicy] = (), - cache_policy: CachePolicy | None = None, - config_type: type[Any] | None = None, - config: RunnableConfig | None = None, - trigger_to_nodes: Mapping[str, Sequence[str]] | None = None, - name: str = "LangGraph", - ) -> None: - self.nodes = { - k: v.build() if isinstance(v, NodeBuilder) else v for k, v in nodes.items() - } - self.channels = channels or {} - if TASKS in self.channels and not isinstance(self.channels[TASKS], Topic): - raise ValueError( - f"Channel '{TASKS}' is reserved and cannot be used in the graph." - ) - else: - self.channels[TASKS] = Topic(Send, accumulate=False) - self.stream_mode = stream_mode - self.stream_eager = stream_eager - self.output_channels = output_channels - self.stream_channels = stream_channels - self.interrupt_after_nodes = interrupt_after_nodes - self.interrupt_before_nodes = interrupt_before_nodes - self.input_channels = input_channels - self.step_timeout = step_timeout - self.debug = debug if debug is not None else get_debug() - self.checkpointer = checkpointer - self.store = store - self.cache = cache - self.retry_policy = ( - (retry_policy,) if isinstance(retry_policy, RetryPolicy) else retry_policy - ) - self.cache_policy = cache_policy - self.config_type = config_type - self.config = config - self.trigger_to_nodes = trigger_to_nodes or {} - self.name = name - if auto_validate: - self.validate() - - def get_graph( - self, config: RunnableConfig | None = None, *, xray: int | bool = False - ) -> Graph: - """Return a drawable representation of the computation graph.""" - # gather subgraphs - if xray: - subgraphs = { - k: v.get_graph( - config, - xray=xray if isinstance(xray, bool) or xray <= 0 else xray - 1, - ) - for k, v in self.get_subgraphs() - } - else: - subgraphs = {} - - return draw_graph( - merge_configs(self.config, config), - nodes=self.nodes, - specs=self.channels, - input_channels=self.input_channels, - interrupt_after_nodes=self.interrupt_after_nodes, - interrupt_before_nodes=self.interrupt_before_nodes, - trigger_to_nodes=self.trigger_to_nodes, - checkpointer=self.checkpointer, - subgraphs=subgraphs, - ) - - async def aget_graph( - self, config: RunnableConfig | None = None, *, xray: int | bool = False - ) -> Graph: - """Return a drawable representation of the computation graph.""" - - # gather subgraphs - if xray: - subpregels: dict[str, PregelProtocol] = { - k: v async for k, v in self.aget_subgraphs() - } - subgraphs = { - k: v - for k, v in zip( - subpregels, - await asyncio.gather( - *( - p.aget_graph( - config, - xray=xray - if isinstance(xray, bool) or xray <= 0 - else xray - 1, - ) - for p in subpregels.values() - ) - ), - ) - } - else: - subgraphs = {} - - return draw_graph( - merge_configs(self.config, config), - nodes=self.nodes, - specs=self.channels, - input_channels=self.input_channels, - interrupt_after_nodes=self.interrupt_after_nodes, - interrupt_before_nodes=self.interrupt_before_nodes, - trigger_to_nodes=self.trigger_to_nodes, - checkpointer=self.checkpointer, - subgraphs=subgraphs, - ) - - def _repr_mimebundle_(self, **kwargs: Any) -> dict[str, Any]: - """Mime bundle used by Jupyter to display the graph""" - return { - "text/plain": repr(self), - "image/png": self.get_graph().draw_mermaid_png(), - } - - def copy(self, update: dict[str, Any] | None = None) -> Self: - attrs = {k: v for k, v in self.__dict__.items() if k != "__orig_class__"} - attrs.update(update or {}) - return self.__class__(**attrs) - - def with_config(self, config: RunnableConfig | None = None, **kwargs: Any) -> Self: - """Create a copy of the Pregel object with an updated config.""" - return self.copy( - {"config": merge_configs(self.config, config, cast(RunnableConfig, kwargs))} - ) - - def validate(self) -> Self: - validate_graph( - self.nodes, - {k: v for k, v in self.channels.items() if isinstance(v, BaseChannel)}, - {k: v for k, v in self.channels.items() if not isinstance(v, BaseChannel)}, - self.input_channels, - self.output_channels, - self.stream_channels, - self.interrupt_after_nodes, - self.interrupt_before_nodes, - ) - self.trigger_to_nodes = _trigger_to_nodes(self.nodes) - return self - - def config_schema(self, *, include: Sequence[str] | None = None) -> type[BaseModel]: - include = include or [] - fields = { - **({"configurable": (self.config_type, None)} if self.config_type else {}), - **{ - field_name: (field_type, None) - for field_name, field_type in get_type_hints(RunnableConfig).items() - if field_name in [i for i in include if i != "configurable"] - }, - } - return create_model(self.get_name("Config"), field_definitions=fields) - - def get_config_jsonschema( - self, *, include: Sequence[str] | None = None - ) -> dict[str, Any]: - schema = self.config_schema(include=include) - if hasattr(schema, "model_json_schema"): - return schema.model_json_schema() - else: - return schema.schema() - - @property - def InputType(self) -> Any: - if isinstance(self.input_channels, str): - channel = self.channels[self.input_channels] - if isinstance(channel, BaseChannel): - return channel.UpdateType - - def get_input_schema(self, config: RunnableConfig | None = None) -> type[BaseModel]: - config = merge_configs(self.config, config) - if isinstance(self.input_channels, str): - return super().get_input_schema(config) - else: - return create_model( - self.get_name("Input"), - field_definitions={ - k: (c.UpdateType, None) - for k in self.input_channels or self.channels.keys() - if (c := self.channels[k]) and isinstance(c, BaseChannel) - }, - ) - - def get_input_jsonschema( - self, config: RunnableConfig | None = None - ) -> dict[str, Any]: - schema = self.get_input_schema(config) - if hasattr(schema, "model_json_schema"): - return schema.model_json_schema() - else: - return schema.schema() - - @property - def OutputType(self) -> Any: - if isinstance(self.output_channels, str): - channel = self.channels[self.output_channels] - if isinstance(channel, BaseChannel): - return channel.ValueType - - def get_output_schema( - self, config: RunnableConfig | None = None - ) -> type[BaseModel]: - config = merge_configs(self.config, config) - if isinstance(self.output_channels, str): - return super().get_output_schema(config) - else: - return create_model( - self.get_name("Output"), - field_definitions={ - k: (c.ValueType, None) - for k in self.output_channels - if (c := self.channels[k]) and isinstance(c, BaseChannel) - }, - ) - - def get_output_jsonschema( - self, config: RunnableConfig | None = None - ) -> dict[str, Any]: - schema = self.get_output_schema(config) - if hasattr(schema, "model_json_schema"): - return schema.model_json_schema() - else: - return schema.schema() - - @property - def stream_channels_list(self) -> Sequence[str]: - stream_channels = self.stream_channels_asis - return ( - [stream_channels] if isinstance(stream_channels, str) else stream_channels - ) - - @property - def stream_channels_asis(self) -> str | Sequence[str]: - return self.stream_channels or [ - k for k in self.channels if isinstance(self.channels[k], BaseChannel) - ] - - def get_subgraphs( - self, *, namespace: str | None = None, recurse: bool = False - ) -> Iterator[tuple[str, PregelProtocol]]: - """Get the subgraphs of the graph. - - Args: - namespace: The namespace to filter the subgraphs by. - recurse: Whether to recurse into the subgraphs. - If False, only the immediate subgraphs will be returned. - - Returns: - Iterator[tuple[str, PregelProtocol]]: An iterator of the (namespace, subgraph) pairs. - """ - for name, node in self.nodes.items(): - # filter by prefix - if namespace is not None: - if not namespace.startswith(name): - continue - - # find the subgraph, if any - graph = node.subgraphs[0] if node.subgraphs else None - - # if found, yield recursively - if graph: - if name == namespace: - yield name, graph - return # we found it, stop searching - if namespace is None: - yield name, graph - if recurse and isinstance(graph, Pregel): - if namespace is not None: - namespace = namespace[len(name) + 1 :] - yield from ( - (f"{name}{NS_SEP}{n}", s) - for n, s in graph.get_subgraphs( - namespace=namespace, recurse=recurse - ) - ) - - async def aget_subgraphs( - self, *, namespace: str | None = None, recurse: bool = False - ) -> AsyncIterator[tuple[str, PregelProtocol]]: - """Get the subgraphs of the graph. - - Args: - namespace: The namespace to filter the subgraphs by. - recurse: Whether to recurse into the subgraphs. - If False, only the immediate subgraphs will be returned. - - Returns: - AsyncIterator[tuple[str, PregelProtocol]]: An iterator of the (namespace, subgraph) pairs. - """ - for name, node in self.get_subgraphs(namespace=namespace, recurse=recurse): - yield name, node - - def _migrate_checkpoint(self, checkpoint: Checkpoint) -> None: - """Migrate a saved checkpoint to new channel layout.""" - if checkpoint["v"] < 4 and checkpoint.get("pending_sends"): - pending_sends: list[Send] = checkpoint.pop("pending_sends") - checkpoint["channel_values"][TASKS] = pending_sends - checkpoint["channel_versions"][TASKS] = max( - checkpoint["channel_versions"].values() - ) - - def _prepare_state_snapshot( - self, - config: RunnableConfig, - saved: CheckpointTuple | None, - recurse: BaseCheckpointSaver | None = None, - apply_pending_writes: bool = False, - ) -> StateSnapshot: - if not saved: - return StateSnapshot( - values={}, - next=(), - config=config, - metadata=None, - created_at=None, - parent_config=None, - tasks=(), - interrupts=(), - ) - - # migrate checkpoint if needed - self._migrate_checkpoint(saved.checkpoint) - - step = saved.metadata.get("step", -1) + 1 - stop = step + 2 - channels, managed = channels_from_checkpoint( - self.channels, - saved.checkpoint, - ) - # tasks for this checkpoint - next_tasks = prepare_next_tasks( - saved.checkpoint, - saved.pending_writes or [], - self.nodes, - channels, - managed, - saved.config, - step, - stop, - for_execution=True, - store=self.store, - checkpointer=( - self.checkpointer - if isinstance(self.checkpointer, BaseCheckpointSaver) - else None - ), - manager=None, - ) - # get the subgraphs - subgraphs = dict(self.get_subgraphs()) - parent_ns = saved.config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") - task_states: dict[str, RunnableConfig | StateSnapshot] = {} - for task in next_tasks.values(): - if task.name not in subgraphs: - continue - # assemble checkpoint_ns for this task - task_ns = f"{task.name}{NS_END}{task.id}" - if parent_ns: - task_ns = f"{parent_ns}{NS_SEP}{task_ns}" - if not recurse: - # set config as signal that subgraph checkpoints exist - config = { - CONF: { - "thread_id": saved.config[CONF]["thread_id"], - CONFIG_KEY_CHECKPOINT_NS: task_ns, - } - } - task_states[task.id] = config - else: - # get the state of the subgraph - config = { - CONF: { - CONFIG_KEY_CHECKPOINTER: recurse, - "thread_id": saved.config[CONF]["thread_id"], - CONFIG_KEY_CHECKPOINT_NS: task_ns, - } - } - task_states[task.id] = subgraphs[task.name].get_state( - config, subgraphs=True - ) - # apply pending writes - if null_writes := [ - w[1:] for w in saved.pending_writes or [] if w[0] == NULL_TASK_ID - ]: - apply_writes( - saved.checkpoint, - channels, - [PregelTaskWrites((), INPUT, null_writes, [])], - None, - self.trigger_to_nodes, - ) - if apply_pending_writes and saved.pending_writes: - for tid, k, v in saved.pending_writes: - if k in (ERROR, INTERRUPT): - continue - if tid not in next_tasks: - continue - next_tasks[tid].writes.append((k, v)) - if tasks := [t for t in next_tasks.values() if t.writes]: - apply_writes( - saved.checkpoint, channels, tasks, None, self.trigger_to_nodes - ) - tasks_with_writes = tasks_w_writes( - next_tasks.values(), - saved.pending_writes, - task_states, - self.stream_channels_asis, - ) - # assemble the state snapshot - return StateSnapshot( - read_channels(channels, self.stream_channels_asis), - tuple(t.name for t in next_tasks.values() if not t.writes), - patch_checkpoint_map(saved.config, saved.metadata), - saved.metadata, - saved.checkpoint["ts"], - patch_checkpoint_map(saved.parent_config, saved.metadata), - tasks_with_writes, - tuple([i for task in tasks_with_writes for i in task.interrupts]), - ) - - async def _aprepare_state_snapshot( - self, - config: RunnableConfig, - saved: CheckpointTuple | None, - recurse: BaseCheckpointSaver | None = None, - apply_pending_writes: bool = False, - ) -> StateSnapshot: - if not saved: - return StateSnapshot( - values={}, - next=(), - config=config, - metadata=None, - created_at=None, - parent_config=None, - tasks=(), - interrupts=(), - ) - - # migrate checkpoint if needed - self._migrate_checkpoint(saved.checkpoint) - - step = saved.metadata.get("step", -1) + 1 - stop = step + 2 - channels, managed = channels_from_checkpoint( - self.channels, - saved.checkpoint, - ) - # tasks for this checkpoint - next_tasks = prepare_next_tasks( - saved.checkpoint, - saved.pending_writes or [], - self.nodes, - channels, - managed, - saved.config, - step, - stop, - for_execution=True, - store=self.store, - checkpointer=( - self.checkpointer - if isinstance(self.checkpointer, BaseCheckpointSaver) - else None - ), - manager=None, - ) - # get the subgraphs - subgraphs = {n: g async for n, g in self.aget_subgraphs()} - parent_ns = saved.config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") - task_states: dict[str, RunnableConfig | StateSnapshot] = {} - for task in next_tasks.values(): - if task.name not in subgraphs: - continue - # assemble checkpoint_ns for this task - task_ns = f"{task.name}{NS_END}{task.id}" - if parent_ns: - task_ns = f"{parent_ns}{NS_SEP}{task_ns}" - if not recurse: - # set config as signal that subgraph checkpoints exist - config = { - CONF: { - "thread_id": saved.config[CONF]["thread_id"], - CONFIG_KEY_CHECKPOINT_NS: task_ns, - } - } - task_states[task.id] = config - else: - # get the state of the subgraph - config = { - CONF: { - CONFIG_KEY_CHECKPOINTER: recurse, - "thread_id": saved.config[CONF]["thread_id"], - CONFIG_KEY_CHECKPOINT_NS: task_ns, - } - } - task_states[task.id] = await subgraphs[task.name].aget_state( - config, subgraphs=True - ) - # apply pending writes - if null_writes := [ - w[1:] for w in saved.pending_writes or [] if w[0] == NULL_TASK_ID - ]: - apply_writes( - saved.checkpoint, - channels, - [PregelTaskWrites((), INPUT, null_writes, [])], - None, - self.trigger_to_nodes, - ) - if apply_pending_writes and saved.pending_writes: - for tid, k, v in saved.pending_writes: - if k in (ERROR, INTERRUPT): - continue - if tid not in next_tasks: - continue - next_tasks[tid].writes.append((k, v)) - if tasks := [t for t in next_tasks.values() if t.writes]: - apply_writes( - saved.checkpoint, channels, tasks, None, self.trigger_to_nodes - ) - - tasks_with_writes = tasks_w_writes( - next_tasks.values(), - saved.pending_writes, - task_states, - self.stream_channels_asis, - ) - # assemble the state snapshot - return StateSnapshot( - read_channels(channels, self.stream_channels_asis), - tuple(t.name for t in next_tasks.values() if not t.writes), - patch_checkpoint_map(saved.config, saved.metadata), - saved.metadata, - saved.checkpoint["ts"], - patch_checkpoint_map(saved.parent_config, saved.metadata), - tasks_with_writes, - tuple([i for task in tasks_with_writes for i in task.interrupts]), - ) - - def get_state( - self, config: RunnableConfig, *, subgraphs: bool = False - ) -> StateSnapshot: - """Get the current state of the graph.""" - checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( - CONFIG_KEY_CHECKPOINTER, self.checkpointer - ) - if not checkpointer: - raise ValueError("No checkpointer set") - - if ( - checkpoint_ns := config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") - ) and CONFIG_KEY_CHECKPOINTER not in config[CONF]: - # remove task_ids from checkpoint_ns - recast = recast_checkpoint_ns(checkpoint_ns) - # find the subgraph with the matching name - for _, pregel in self.get_subgraphs(namespace=recast, recurse=True): - return pregel.get_state( - patch_configurable(config, {CONFIG_KEY_CHECKPOINTER: checkpointer}), - subgraphs=subgraphs, - ) - else: - raise ValueError(f"Subgraph {recast} not found") - - config = merge_configs(self.config, config) if self.config else config - if self.checkpointer is True: - ns = cast(str, config[CONF][CONFIG_KEY_CHECKPOINT_NS]) - config = merge_configs( - config, {CONF: {CONFIG_KEY_CHECKPOINT_NS: recast_checkpoint_ns(ns)}} - ) - thread_id = config[CONF][CONFIG_KEY_THREAD_ID] - if not isinstance(thread_id, str): - config[CONF][CONFIG_KEY_THREAD_ID] = str(thread_id) - - saved = checkpointer.get_tuple(config) - return self._prepare_state_snapshot( - config, - saved, - recurse=checkpointer if subgraphs else None, - apply_pending_writes=CONFIG_KEY_CHECKPOINT_ID not in config[CONF], - ) - - async def aget_state( - self, config: RunnableConfig, *, subgraphs: bool = False - ) -> StateSnapshot: - """Get the current state of the graph.""" - checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( - CONFIG_KEY_CHECKPOINTER, self.checkpointer - ) - if not checkpointer: - raise ValueError("No checkpointer set") - - if ( - checkpoint_ns := config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") - ) and CONFIG_KEY_CHECKPOINTER not in config[CONF]: - # remove task_ids from checkpoint_ns - recast = recast_checkpoint_ns(checkpoint_ns) - # find the subgraph with the matching name - async for _, pregel in self.aget_subgraphs(namespace=recast, recurse=True): - return await pregel.aget_state( - patch_configurable(config, {CONFIG_KEY_CHECKPOINTER: checkpointer}), - subgraphs=subgraphs, - ) - else: - raise ValueError(f"Subgraph {recast} not found") - - config = merge_configs(self.config, config) if self.config else config - if self.checkpointer is True: - ns = cast(str, config[CONF][CONFIG_KEY_CHECKPOINT_NS]) - config = merge_configs( - config, {CONF: {CONFIG_KEY_CHECKPOINT_NS: recast_checkpoint_ns(ns)}} - ) - thread_id = config[CONF][CONFIG_KEY_THREAD_ID] - if not isinstance(thread_id, str): - config[CONF][CONFIG_KEY_THREAD_ID] = str(thread_id) - - saved = await checkpointer.aget_tuple(config) - return await self._aprepare_state_snapshot( - config, - saved, - recurse=checkpointer if subgraphs else None, - apply_pending_writes=CONFIG_KEY_CHECKPOINT_ID not in config[CONF], - ) - - def get_state_history( - self, - config: RunnableConfig, - *, - filter: dict[str, Any] | None = None, - before: RunnableConfig | None = None, - limit: int | None = None, - ) -> Iterator[StateSnapshot]: - """Get the history of the state of the graph.""" - config = ensure_config(config) - checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( - CONFIG_KEY_CHECKPOINTER, self.checkpointer - ) - if not checkpointer: - raise ValueError("No checkpointer set") - - if ( - checkpoint_ns := config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") - ) and CONFIG_KEY_CHECKPOINTER not in config[CONF]: - # remove task_ids from checkpoint_ns - recast = recast_checkpoint_ns(checkpoint_ns) - # find the subgraph with the matching name - for _, pregel in self.get_subgraphs(namespace=recast, recurse=True): - yield from pregel.get_state_history( - patch_configurable(config, {CONFIG_KEY_CHECKPOINTER: checkpointer}), - filter=filter, - before=before, - limit=limit, - ) - return - else: - raise ValueError(f"Subgraph {recast} not found") - - config = merge_configs( - self.config, - config, - { - CONF: { - CONFIG_KEY_CHECKPOINT_NS: checkpoint_ns, - CONFIG_KEY_THREAD_ID: str(config[CONF][CONFIG_KEY_THREAD_ID]), - } - }, - ) - # eagerly consume list() to avoid holding up the db cursor - for checkpoint_tuple in list( - checkpointer.list(config, before=before, limit=limit, filter=filter) - ): - yield self._prepare_state_snapshot( - checkpoint_tuple.config, checkpoint_tuple - ) - - async def aget_state_history( - self, - config: RunnableConfig, - *, - filter: dict[str, Any] | None = None, - before: RunnableConfig | None = None, - limit: int | None = None, - ) -> AsyncIterator[StateSnapshot]: - """Asynchronously get the history of the state of the graph.""" - config = ensure_config(config) - checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( - CONFIG_KEY_CHECKPOINTER, self.checkpointer - ) - if not checkpointer: - raise ValueError("No checkpointer set") - - if ( - checkpoint_ns := config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") - ) and CONFIG_KEY_CHECKPOINTER not in config[CONF]: - # remove task_ids from checkpoint_ns - recast = recast_checkpoint_ns(checkpoint_ns) - # find the subgraph with the matching name - async for _, pregel in self.aget_subgraphs(namespace=recast, recurse=True): - async for state in pregel.aget_state_history( - patch_configurable(config, {CONFIG_KEY_CHECKPOINTER: checkpointer}), - filter=filter, - before=before, - limit=limit, - ): - yield state - return - else: - raise ValueError(f"Subgraph {recast} not found") - - config = merge_configs( - self.config, - config, - { - CONF: { - CONFIG_KEY_CHECKPOINT_NS: checkpoint_ns, - CONFIG_KEY_THREAD_ID: str(config[CONF][CONFIG_KEY_THREAD_ID]), - } - }, - ) - # eagerly consume list() to avoid holding up the db cursor - for checkpoint_tuple in [ - c - async for c in checkpointer.alist( - config, before=before, limit=limit, filter=filter - ) - ]: - yield await self._aprepare_state_snapshot( - checkpoint_tuple.config, checkpoint_tuple - ) - - def bulk_update_state( - self, - config: RunnableConfig, - supersteps: Sequence[Sequence[StateUpdate]], - ) -> RunnableConfig: - """Apply updates to the graph state in bulk. Requires a checkpointer to be set. - - Args: - config: The config to apply the updates to. - supersteps: A list of supersteps, each including a list of updates to apply sequentially to a graph state. - Each update is a tuple of the form `(values, as_node, task_id)` where task_id is optional. - - Raises: - ValueError: If no checkpointer is set or no updates are provided. - InvalidUpdateError: If an invalid update is provided. - - Returns: - RunnableConfig: The updated config. - """ - - checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( - CONFIG_KEY_CHECKPOINTER, self.checkpointer - ) - if not checkpointer: - raise ValueError("No checkpointer set") - - if len(supersteps) == 0: - raise ValueError("No supersteps provided") - - if any(len(u) == 0 for u in supersteps): - raise ValueError("No updates provided") - - # delegate to subgraph - if ( - checkpoint_ns := config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") - ) and CONFIG_KEY_CHECKPOINTER not in config[CONF]: - # remove task_ids from checkpoint_ns - recast = recast_checkpoint_ns(checkpoint_ns) - # find the subgraph with the matching name - for _, pregel in self.get_subgraphs(namespace=recast, recurse=True): - return pregel.bulk_update_state( - patch_configurable(config, {CONFIG_KEY_CHECKPOINTER: checkpointer}), - supersteps, - ) - else: - raise ValueError(f"Subgraph {recast} not found") - - def perform_superstep( - input_config: RunnableConfig, updates: Sequence[StateUpdate] - ) -> RunnableConfig: - # get last checkpoint - config = ensure_config(self.config, input_config) - saved = checkpointer.get_tuple(config) - if saved is not None: - self._migrate_checkpoint(saved.checkpoint) - checkpoint = ( - copy_checkpoint(saved.checkpoint) if saved else empty_checkpoint() - ) - checkpoint_previous_versions = ( - saved.checkpoint["channel_versions"].copy() if saved else {} - ) - step = saved.metadata.get("step", -1) if saved else -1 - # merge configurable fields with previous checkpoint config - checkpoint_config = patch_configurable( - config, - { - CONFIG_KEY_CHECKPOINT_NS: config[CONF].get( - CONFIG_KEY_CHECKPOINT_NS, "" - ) - }, - ) - if saved: - checkpoint_config = patch_configurable(config, saved.config[CONF]) - channels, managed = channels_from_checkpoint( - self.channels, - checkpoint, - ) - values, as_node = updates[0][:2] - - # no values as END, just clear all tasks - if values is None and as_node == END: - if len(updates) > 1: - raise InvalidUpdateError( - "Cannot apply multiple updates when clearing state" - ) - - if saved is not None: - # tasks for this checkpoint - next_tasks = prepare_next_tasks( - checkpoint, - saved.pending_writes or [], - self.nodes, - channels, - managed, - saved.config, - step + 1, - step + 3, - for_execution=True, - store=self.store, - checkpointer=checkpointer, - manager=None, - ) - # apply null writes - if null_writes := [ - w[1:] - for w in saved.pending_writes or [] - if w[0] == NULL_TASK_ID - ]: - apply_writes( - checkpoint, - channels, - [PregelTaskWrites((), INPUT, null_writes, [])], - checkpointer.get_next_version, - self.trigger_to_nodes, - ) - # apply writes from tasks that already ran - for tid, k, v in saved.pending_writes or []: - if k in (ERROR, INTERRUPT): - continue - if tid not in next_tasks: - continue - next_tasks[tid].writes.append((k, v)) - # clear all current tasks - apply_writes( - checkpoint, - channels, - next_tasks.values(), - checkpointer.get_next_version, - self.trigger_to_nodes, - ) - # save checkpoint - next_config = checkpointer.put( - checkpoint_config, - create_checkpoint(checkpoint, channels, step), - { - "source": "update", - "step": step + 1, - "parents": saved.metadata.get("parents", {}) if saved else {}, - }, - get_new_channel_versions( - checkpoint_previous_versions, - checkpoint["channel_versions"], - ), - ) - return patch_checkpoint_map( - next_config, saved.metadata if saved else None - ) - - # act as an input - if as_node == INPUT: - if len(updates) > 1: - raise InvalidUpdateError( - "Cannot apply multiple updates when updating as input" - ) - - if input_writes := deque(map_input(self.input_channels, values)): - apply_writes( - checkpoint, - channels, - [PregelTaskWrites((), INPUT, input_writes, [])], - checkpointer.get_next_version, - self.trigger_to_nodes, - ) - - # apply input write to channels - next_step = ( - step + 1 - if saved and saved.metadata.get("step") is not None - else -1 - ) - next_config = checkpointer.put( - checkpoint_config, - create_checkpoint(checkpoint, channels, next_step), - { - "source": "input", - "step": next_step, - "parents": saved.metadata.get("parents", {}) - if saved - else {}, - }, - get_new_channel_versions( - checkpoint_previous_versions, - checkpoint["channel_versions"], - ), - ) - - # store the writes - checkpointer.put_writes( - next_config, - input_writes, - str(uuid5(UUID(checkpoint["id"]), INPUT)), - ) - - return patch_checkpoint_map( - next_config, saved.metadata if saved else None - ) - else: - raise InvalidUpdateError( - f"Received no input writes for {self.input_channels}" - ) - - # copy checkpoint - if as_node == "__copy__": - if len(updates) > 1: - raise InvalidUpdateError( - "Cannot copy checkpoint with multiple updates" - ) - - if saved is None: - raise InvalidUpdateError("Cannot copy a non-existent checkpoint") - - next_checkpoint = create_checkpoint(checkpoint, None, step) - - # copy checkpoint - next_config = checkpointer.put( - saved.parent_config - or patch_configurable( - saved.config, {CONFIG_KEY_CHECKPOINT_ID: None} - ), - next_checkpoint, - { - "source": "fork", - "step": step + 1, - "parents": saved.metadata.get("parents", {}), - }, - {}, - ) - - # we want to both clone a checkpoint and update state in one go. - # reuse the same task ID if possible. - if isinstance(values, list) and len(values) > 0: - # figure out the task IDs for the next update checkpoint - next_tasks = prepare_next_tasks( - next_checkpoint, - saved.pending_writes or [], - self.nodes, - channels, - managed, - next_config, - step + 2, - step + 4, - for_execution=True, - store=self.store, - checkpointer=checkpointer, - manager=None, - ) - - tasks_group_by = defaultdict(list) - user_group_by: dict[str, list[StateUpdate]] = defaultdict(list) - - for task in next_tasks.values(): - tasks_group_by[task.name].append(task.id) - - for item in values: - if not isinstance(item, Sequence): - raise InvalidUpdateError( - f"Invalid update item: {item} when copying checkpoint" - ) - - values, as_node = item[:2] - - user_group = user_group_by[as_node] - tasks_group = tasks_group_by[as_node] - - target_idx = len(user_group) - task_id = ( - tasks_group[target_idx] - if target_idx < len(tasks_group) - else None - ) - - user_group_by[as_node].append( - StateUpdate(values=values, as_node=as_node, task_id=task_id) - ) - - return perform_superstep( - patch_checkpoint_map(next_config, saved.metadata), - [item for lst in user_group_by.values() for item in lst], - ) - - return patch_checkpoint_map(next_config, saved.metadata) - - # apply pending writes, if not on specific checkpoint - if ( - CONFIG_KEY_CHECKPOINT_ID not in config[CONF] - and saved is not None - and saved.pending_writes - ): - # tasks for this checkpoint - next_tasks = prepare_next_tasks( - checkpoint, - saved.pending_writes, - self.nodes, - channels, - managed, - saved.config, - step + 1, - step + 3, - for_execution=True, - store=self.store, - checkpointer=checkpointer, - manager=None, - ) - # apply null writes - if null_writes := [ - w[1:] for w in saved.pending_writes or [] if w[0] == NULL_TASK_ID - ]: - apply_writes( - checkpoint, - channels, - [PregelTaskWrites((), INPUT, null_writes, [])], - checkpointer.get_next_version, - self.trigger_to_nodes, - ) - # apply writes - for tid, k, v in saved.pending_writes: - if k in (ERROR, INTERRUPT): - continue - if tid not in next_tasks: - continue - next_tasks[tid].writes.append((k, v)) - if tasks := [t for t in next_tasks.values() if t.writes]: - apply_writes( - checkpoint, - channels, - tasks, - checkpointer.get_next_version, - self.trigger_to_nodes, - ) - valid_updates: list[tuple[str, dict[str, Any] | None, str | None]] = [] - if len(updates) == 1: - values, as_node, task_id = updates[0] - # find last node that updated the state, if not provided - if as_node is None and len(self.nodes) == 1: - as_node = tuple(self.nodes)[0] - elif as_node is None and not any( - v - for vv in checkpoint["versions_seen"].values() - for v in vv.values() - ): - if ( - isinstance(self.input_channels, str) - and self.input_channels in self.nodes - ): - as_node = self.input_channels - elif as_node is None: - last_seen_by_node = sorted( - (v, n) - for n, seen in checkpoint["versions_seen"].items() - if n in self.nodes - for v in seen.values() - ) - # if two nodes updated the state at the same time, it's ambiguous - if last_seen_by_node: - if len(last_seen_by_node) == 1: - as_node = last_seen_by_node[0][1] - elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]: - as_node = last_seen_by_node[-1][1] - if as_node is None: - raise InvalidUpdateError("Ambiguous update, specify as_node") - if as_node not in self.nodes: - raise InvalidUpdateError(f"Node {as_node} does not exist") - valid_updates.append((as_node, values, task_id)) - else: - for values, as_node, task_id in updates: - if as_node is None: - raise InvalidUpdateError( - "as_node is required when applying multiple updates" - ) - if as_node not in self.nodes: - raise InvalidUpdateError(f"Node {as_node} does not exist") - - valid_updates.append((as_node, values, task_id)) - - run_tasks: list[PregelTaskWrites] = [] - run_task_ids: list[str] = [] - - for as_node, values, provided_task_id in valid_updates: - # create task to run all writers of the chosen node - writers = self.nodes[as_node].flat_writers - if not writers: - raise InvalidUpdateError(f"Node {as_node} has no writers") - writes: deque[tuple[str, Any]] = deque() - task = PregelTaskWrites((), as_node, writes, [INTERRUPT]) - task_id = provided_task_id or str( - uuid5(UUID(checkpoint["id"]), INTERRUPT) - ) - run_tasks.append(task) - run_task_ids.append(task_id) - run = RunnableSequence(*writers) if len(writers) > 1 else writers[0] - # execute task - run.invoke( - values, - patch_config( - config, - run_name=self.name + "UpdateState", - configurable={ - # deque.extend is thread-safe - CONFIG_KEY_SEND: writes.extend, - CONFIG_KEY_TASK_ID: task_id, - CONFIG_KEY_READ: partial( - local_read, - _scratchpad( - None, - [], - task_id, - "", - None, - step, - step + 2, - ), - channels, - managed, - task, - ), - }, - ), - ) - # save task writes - for task_id, task in zip(run_task_ids, run_tasks): - # channel writes are saved to current checkpoint - channel_writes = [w for w in task.writes if w[0] != PUSH] - if saved and channel_writes: - checkpointer.put_writes(checkpoint_config, channel_writes, task_id) - # apply to checkpoint and save - apply_writes( - checkpoint, - channels, - run_tasks, - checkpointer.get_next_version, - self.trigger_to_nodes, - ) - checkpoint = create_checkpoint(checkpoint, channels, step + 1) - next_config = checkpointer.put( - checkpoint_config, - checkpoint, - { - "source": "update", - "step": step + 1, - "parents": saved.metadata.get("parents", {}) if saved else {}, - }, - get_new_channel_versions( - checkpoint_previous_versions, checkpoint["channel_versions"] - ), - ) - for task_id, task in zip(run_task_ids, run_tasks): - # save push writes - if push_writes := [w for w in task.writes if w[0] == PUSH]: - checkpointer.put_writes(next_config, push_writes, task_id) - - return patch_checkpoint_map(next_config, saved.metadata if saved else None) - - current_config = patch_configurable( - config, {CONFIG_KEY_THREAD_ID: str(config[CONF][CONFIG_KEY_THREAD_ID])} - ) - for superstep in supersteps: - current_config = perform_superstep(current_config, superstep) - return current_config - - async def abulk_update_state( - self, - config: RunnableConfig, - supersteps: Sequence[Sequence[StateUpdate]], - ) -> RunnableConfig: - """Asynchronously apply updates to the graph state in bulk. Requires a checkpointer to be set. - - Args: - config: The config to apply the updates to. - supersteps: A list of supersteps, each including a list of updates to apply sequentially to a graph state. - Each update is a tuple of the form `(values, as_node, task_id)` where task_id is optional. - - Raises: - ValueError: If no checkpointer is set or no updates are provided. - InvalidUpdateError: If an invalid update is provided. - - Returns: - RunnableConfig: The updated config. - """ - - checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( - CONFIG_KEY_CHECKPOINTER, self.checkpointer - ) - if not checkpointer: - raise ValueError("No checkpointer set") - - if len(supersteps) == 0: - raise ValueError("No supersteps provided") - - if any(len(u) == 0 for u in supersteps): - raise ValueError("No updates provided") - - # delegate to subgraph - if ( - checkpoint_ns := config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") - ) and CONFIG_KEY_CHECKPOINTER not in config[CONF]: - # remove task_ids from checkpoint_ns - recast = recast_checkpoint_ns(checkpoint_ns) - # find the subgraph with the matching name - async for _, pregel in self.aget_subgraphs(namespace=recast, recurse=True): - return await pregel.abulk_update_state( - patch_configurable(config, {CONFIG_KEY_CHECKPOINTER: checkpointer}), - supersteps, - ) - else: - raise ValueError(f"Subgraph {recast} not found") - - async def aperform_superstep( - input_config: RunnableConfig, updates: Sequence[StateUpdate] - ) -> RunnableConfig: - # get last checkpoint - config = ensure_config(self.config, input_config) - saved = await checkpointer.aget_tuple(config) - if saved is not None: - self._migrate_checkpoint(saved.checkpoint) - checkpoint = ( - copy_checkpoint(saved.checkpoint) if saved else empty_checkpoint() - ) - checkpoint_previous_versions = ( - saved.checkpoint["channel_versions"].copy() if saved else {} - ) - step = saved.metadata.get("step", -1) if saved else -1 - # merge configurable fields with previous checkpoint config - checkpoint_config = patch_configurable( - config, - { - CONFIG_KEY_CHECKPOINT_NS: config[CONF].get( - CONFIG_KEY_CHECKPOINT_NS, "" - ) - }, - ) - if saved: - checkpoint_config = patch_configurable(config, saved.config[CONF]) - channels, managed = channels_from_checkpoint( - self.channels, - checkpoint, - ) - values, as_node = updates[0][:2] - # no values, just clear all tasks - if values is None and as_node == END: - if len(updates) > 1: - raise InvalidUpdateError( - "Cannot apply multiple updates when clearing state" - ) - if saved is not None: - # tasks for this checkpoint - next_tasks = prepare_next_tasks( - checkpoint, - saved.pending_writes or [], - self.nodes, - channels, - managed, - saved.config, - step + 1, - step + 3, - for_execution=True, - store=self.store, - checkpointer=checkpointer, - manager=None, - ) - # apply null writes - if null_writes := [ - w[1:] - for w in saved.pending_writes or [] - if w[0] == NULL_TASK_ID - ]: - apply_writes( - checkpoint, - channels, - [PregelTaskWrites((), INPUT, null_writes, [])], - checkpointer.get_next_version, - self.trigger_to_nodes, - ) - # apply writes from tasks that already ran - for tid, k, v in saved.pending_writes or []: - if k in (ERROR, INTERRUPT): - continue - if tid not in next_tasks: - continue - next_tasks[tid].writes.append((k, v)) - # clear all current tasks - apply_writes( - checkpoint, - channels, - next_tasks.values(), - checkpointer.get_next_version, - self.trigger_to_nodes, - ) - # save checkpoint - next_config = await checkpointer.aput( - checkpoint_config, - create_checkpoint(checkpoint, channels, step), - { - "source": "update", - "step": step + 1, - "parents": saved.metadata.get("parents", {}) if saved else {}, - }, - get_new_channel_versions( - checkpoint_previous_versions, checkpoint["channel_versions"] - ), - ) - return patch_checkpoint_map( - next_config, saved.metadata if saved else None - ) - - # act as an input - if as_node == INPUT: - if len(updates) > 1: - raise InvalidUpdateError( - "Cannot apply multiple updates when updating as input" - ) - - if input_writes := deque(map_input(self.input_channels, values)): - apply_writes( - checkpoint, - channels, - [PregelTaskWrites((), INPUT, input_writes, [])], - checkpointer.get_next_version, - self.trigger_to_nodes, - ) - - # apply input write to channels - next_step = ( - step + 1 - if saved and saved.metadata.get("step") is not None - else -1 - ) - next_config = await checkpointer.aput( - checkpoint_config, - create_checkpoint(checkpoint, channels, next_step), - { - "source": "input", - "step": next_step, - "parents": saved.metadata.get("parents", {}) - if saved - else {}, - }, - get_new_channel_versions( - checkpoint_previous_versions, - checkpoint["channel_versions"], - ), - ) - - # store the writes - await checkpointer.aput_writes( - next_config, - input_writes, - str(uuid5(UUID(checkpoint["id"]), INPUT)), - ) - - return patch_checkpoint_map( - next_config, saved.metadata if saved else None - ) - else: - raise InvalidUpdateError( - f"Received no input writes for {self.input_channels}" - ) - - # no values, copy checkpoint - if as_node == "__copy__": - if len(updates) > 1: - raise InvalidUpdateError( - "Cannot copy checkpoint with multiple updates" - ) - - if saved is None: - raise InvalidUpdateError("Cannot copy a non-existent checkpoint") - - next_checkpoint = create_checkpoint(checkpoint, None, step) - - # copy checkpoint - next_config = await checkpointer.aput( - saved.parent_config - or patch_configurable( - saved.config, {CONFIG_KEY_CHECKPOINT_ID: None} - ), - next_checkpoint, - { - "source": "fork", - "step": step + 1, - "parents": saved.metadata.get("parents", {}), - }, - {}, - ) - - # we want to both clone a checkpoint and update state in one go. - # reuse the same task ID if possible. - if isinstance(values, list) and len(values) > 0: - # figure out the task IDs for the next update checkpoint - next_tasks = prepare_next_tasks( - next_checkpoint, - saved.pending_writes or [], - self.nodes, - channels, - managed, - next_config, - step + 2, - step + 4, - for_execution=True, - store=self.store, - checkpointer=checkpointer, - manager=None, - ) - - tasks_group_by = defaultdict(list) - user_group_by: dict[str, list[StateUpdate]] = defaultdict(list) - - for task in next_tasks.values(): - tasks_group_by[task.name].append(task.id) - - for item in values: - if not isinstance(item, Sequence): - raise InvalidUpdateError( - f"Invalid update item: {item} when copying checkpoint" - ) - - values, as_node = item[:2] - user_group = user_group_by[as_node] - tasks_group = tasks_group_by[as_node] - - target_idx = len(user_group) - task_id = ( - tasks_group[target_idx] - if target_idx < len(tasks_group) - else None - ) - - user_group_by[as_node].append( - StateUpdate(values=values, as_node=as_node, task_id=task_id) - ) - - return await aperform_superstep( - patch_checkpoint_map(next_config, saved.metadata), - [item for lst in user_group_by.values() for item in lst], - ) - - return patch_checkpoint_map( - next_config, saved.metadata if saved else None - ) - # apply pending writes, if not on specific checkpoint - if ( - CONFIG_KEY_CHECKPOINT_ID not in config[CONF] - and saved is not None - and saved.pending_writes - ): - # tasks for this checkpoint - next_tasks = prepare_next_tasks( - checkpoint, - saved.pending_writes, - self.nodes, - channels, - managed, - saved.config, - step + 1, - step + 3, - for_execution=True, - store=self.store, - checkpointer=checkpointer, - manager=None, - ) - # apply null writes - if null_writes := [ - w[1:] for w in saved.pending_writes or [] if w[0] == NULL_TASK_ID - ]: - apply_writes( - checkpoint, - channels, - [PregelTaskWrites((), INPUT, null_writes, [])], - checkpointer.get_next_version, - self.trigger_to_nodes, - ) - for tid, k, v in saved.pending_writes: - if k in (ERROR, INTERRUPT): - continue - if tid not in next_tasks: - continue - next_tasks[tid].writes.append((k, v)) - if tasks := [t for t in next_tasks.values() if t.writes]: - apply_writes( - checkpoint, - channels, - tasks, - checkpointer.get_next_version, - self.trigger_to_nodes, - ) - valid_updates: list[tuple[str, dict[str, Any] | None, str | None]] = [] - if len(updates) == 1: - values, as_node, task_id = updates[0] - # find last node that updated the state, if not provided - if as_node is None and len(self.nodes) == 1: - as_node = tuple(self.nodes)[0] - elif as_node is None and not saved: - if ( - isinstance(self.input_channels, str) - and self.input_channels in self.nodes - ): - as_node = self.input_channels - elif as_node is None: - last_seen_by_node = sorted( - (v, n) - for n, seen in checkpoint["versions_seen"].items() - if n in self.nodes - for v in seen.values() - ) - # if two nodes updated the state at the same time, it's ambiguous - if last_seen_by_node: - if len(last_seen_by_node) == 1: - as_node = last_seen_by_node[0][1] - elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]: - as_node = last_seen_by_node[-1][1] - if as_node is None: - raise InvalidUpdateError("Ambiguous update, specify as_node") - if as_node not in self.nodes: - raise InvalidUpdateError(f"Node {as_node} does not exist") - valid_updates.append((as_node, values, task_id)) - else: - for values, as_node, task_id in updates: - if as_node is None: - raise InvalidUpdateError( - "as_node is required when applying multiple updates" - ) - if as_node not in self.nodes: - raise InvalidUpdateError(f"Node {as_node} does not exist") - - valid_updates.append((as_node, values, task_id)) - - run_tasks: list[PregelTaskWrites] = [] - run_task_ids: list[str] = [] - - for as_node, values, provided_task_id in valid_updates: - # create task to run all writers of the chosen node - writers = self.nodes[as_node].flat_writers - if not writers: - raise InvalidUpdateError(f"Node {as_node} has no writers") - writes: deque[tuple[str, Any]] = deque() - task = PregelTaskWrites((), as_node, writes, [INTERRUPT]) - task_id = provided_task_id or str( - uuid5(UUID(checkpoint["id"]), INTERRUPT) - ) - run_tasks.append(task) - run_task_ids.append(task_id) - run = RunnableSequence(*writers) if len(writers) > 1 else writers[0] - # execute task - await run.ainvoke( - values, - patch_config( - config, - run_name=self.name + "UpdateState", - configurable={ - # deque.extend is thread-safe - CONFIG_KEY_SEND: writes.extend, - CONFIG_KEY_TASK_ID: task_id, - CONFIG_KEY_READ: partial( - local_read, - _scratchpad( - None, - [], - task_id, - "", - None, - step, - step + 2, - ), - channels, - managed, - task, - ), - }, - ), - ) - # save task writes - for task_id, task in zip(run_task_ids, run_tasks): - # channel writes are saved to current checkpoint - channel_writes = [w for w in task.writes if w[0] != PUSH] - if saved and channel_writes: - await checkpointer.aput_writes( - checkpoint_config, channel_writes, task_id - ) - # apply to checkpoint and save - apply_writes( - checkpoint, - channels, - run_tasks, - checkpointer.get_next_version, - self.trigger_to_nodes, - ) - checkpoint = create_checkpoint(checkpoint, channels, step + 1) - # save checkpoint, after applying writes - next_config = await checkpointer.aput( - checkpoint_config, - checkpoint, - { - "source": "update", - "step": step + 1, - "parents": saved.metadata.get("parents", {}) if saved else {}, - }, - get_new_channel_versions( - checkpoint_previous_versions, checkpoint["channel_versions"] - ), - ) - for task_id, task in zip(run_task_ids, run_tasks): - # save push writes - if push_writes := [w for w in task.writes if w[0] == PUSH]: - await checkpointer.aput_writes(next_config, push_writes, task_id) - return patch_checkpoint_map(next_config, saved.metadata if saved else None) - - current_config = patch_configurable( - config, {CONFIG_KEY_THREAD_ID: str(config[CONF][CONFIG_KEY_THREAD_ID])} - ) - for superstep in supersteps: - current_config = await aperform_superstep(current_config, superstep) - return current_config - - def update_state( - self, - config: RunnableConfig, - values: dict[str, Any] | Any | None, - as_node: str | None = None, - task_id: str | None = None, - ) -> RunnableConfig: - """Update the state of the graph with the given values, as if they came from - node `as_node`. If `as_node` is not provided, it will be set to the last node - that updated the state, if not ambiguous. - """ - return self.bulk_update_state(config, [[StateUpdate(values, as_node, task_id)]]) - - async def aupdate_state( - self, - config: RunnableConfig, - values: dict[str, Any] | Any, - as_node: str | None = None, - task_id: str | None = None, - ) -> RunnableConfig: - """Asynchronously update the state of the graph with the given values, as if they came from - node `as_node`. If `as_node` is not provided, it will be set to the last node - that updated the state, if not ambiguous. - """ - return await self.abulk_update_state( - config, [[StateUpdate(values, as_node, task_id)]] - ) - - def _defaults( - self, - config: RunnableConfig, - *, - stream_mode: StreamMode | Sequence[StreamMode], - print_mode: StreamMode | Sequence[StreamMode], - output_keys: str | Sequence[str] | None, - interrupt_before: All | Sequence[str] | None, - interrupt_after: All | Sequence[str] | None, - ) -> tuple[ - set[StreamMode], - str | Sequence[str], - All | Sequence[str], - All | Sequence[str], - BaseCheckpointSaver | None, - BaseStore | None, - BaseCache | None, - ]: - if config["recursion_limit"] < 1: - raise ValueError("recursion_limit must be at least 1") - if output_keys is None: - output_keys = self.stream_channels_asis - else: - validate_keys(output_keys, self.channels) - interrupt_before = interrupt_before or self.interrupt_before_nodes - interrupt_after = interrupt_after or self.interrupt_after_nodes - if not isinstance(stream_mode, list): - stream_modes = {stream_mode} - else: - stream_modes = set(stream_mode) - if isinstance(print_mode, str): - stream_modes.add(print_mode) - else: - stream_modes.update(print_mode) - if self.checkpointer is False: - checkpointer: BaseCheckpointSaver | None = None - elif CONFIG_KEY_CHECKPOINTER in config.get(CONF, {}): - checkpointer = config[CONF][CONFIG_KEY_CHECKPOINTER] - elif self.checkpointer is True: - raise RuntimeError("checkpointer=True cannot be used for root graphs.") - else: - checkpointer = self.checkpointer - if checkpointer and not config.get(CONF): - raise ValueError( - "Checkpointer requires one or more of the following 'configurable' " - "keys: thread_id, checkpoint_ns, checkpoint_id" - ) - if CONFIG_KEY_STORE in config.get(CONF, {}): - store: BaseStore | None = config[CONF][CONFIG_KEY_STORE] - else: - store = self.store - if CONFIG_KEY_CACHE in config.get(CONF, {}): - cache: BaseCache | None = config[CONF][CONFIG_KEY_CACHE] - else: - cache = self.cache - return ( - stream_modes, - output_keys, - interrupt_before, - interrupt_after, - checkpointer, - store, - cache, - ) - - def stream( - self, - input: InputT, - config: RunnableConfig | None = None, - *, - stream_mode: StreamMode | Sequence[StreamMode] | None = None, - print_mode: StreamMode | Sequence[StreamMode] = (), - output_keys: str | Sequence[str] | None = None, - interrupt_before: All | Sequence[str] | None = None, - interrupt_after: All | Sequence[str] | None = None, - checkpoint_during: bool | None = None, - debug: bool | None = None, - subgraphs: bool = False, - ) -> Iterator[dict[str, Any] | Any]: - """Stream graph steps for a single input. - - Args: - input: The input to the graph. - config: The configuration to use for the run. - stream_mode: The mode to stream output, defaults to `self.stream_mode`. - Options are: - - - `"values"`: Emit all values in the state after each step, including interrupts. - When used with functional API, values are emitted once at the end of the workflow. - - `"updates"`: Emit only the node or task names and updates returned by the nodes or tasks after each step. - If multiple updates are made in the same step (e.g. multiple nodes are run) then those updates are emitted separately. - - `"custom"`: Emit custom data from inside nodes or tasks using `StreamWriter`. - - `"messages"`: Emit LLM messages token-by-token together with metadata for any LLM invocations inside nodes or tasks. - Will be emitted as 2-tuples `(LLM token, metadata)`. - - `"checkpoints"`: Emit an event when a checkpoint is created, in the same format as returned by get_state(). - - `"tasks"`: Emit events when tasks start and finish, including their results and errors. - - You can pass a list as the `stream_mode` parameter to stream multiple modes at once. - The streamed outputs will be tuples of `(mode, data)`. - - See [LangGraph streaming guide](https://langchain-ai.github.io/langgraph/how-tos/streaming/) for more details. - print_mode: Accepts the same values as `stream_mode`, but only prints the output to the console, for debugging purposes. Does not affect the output of the graph in any way. - output_keys: The keys to stream, defaults to all non-context channels. - interrupt_before: Nodes to interrupt before, defaults to all nodes in the graph. - interrupt_after: Nodes to interrupt after, defaults to all nodes in the graph. - checkpoint_during: Whether to checkpoint intermediate steps, defaults to False. If False, only the final checkpoint is saved. - subgraphs: Whether to stream events from inside subgraphs, defaults to False. - If True, the events will be emitted as tuples `(namespace, data)`, - or `(namespace, mode, data)` if `stream_mode` is a list, - where `namespace` is a tuple with the path to the node where a subgraph is invoked, - e.g. `("parent_node:", "child_node:")`. - - See [LangGraph streaming guide](https://langchain-ai.github.io/langgraph/how-tos/streaming/) for more details. - - Yields: - The output of each step in the graph. The output shape depends on the stream_mode. - """ - - if stream_mode is None: - # if being called as a node in another graph, default to values mode - # but don't overwrite stream_mode arg if provided - stream_mode = ( - "values" - if config is not None and CONFIG_KEY_TASK_ID in config.get(CONF, {}) - else self.stream_mode - ) - if debug or self.debug: - print_mode = ["updates", "values"] - - stream = SyncQueue() - - config = ensure_config(self.config, config) - callback_manager = get_callback_manager_for_config(config) - run_manager = callback_manager.on_chain_start( - None, - input, - name=config.get("run_name", self.get_name()), - run_id=config.get("run_id"), - ) - try: - # assign defaults - ( - stream_modes, - output_keys, - interrupt_before_, - interrupt_after_, - checkpointer, - store, - cache, - ) = self._defaults( - config, - stream_mode=stream_mode, - print_mode=print_mode, - output_keys=output_keys, - interrupt_before=interrupt_before, - interrupt_after=interrupt_after, - ) - # set up subgraph checkpointing - if self.checkpointer is True: - ns = cast(str, config[CONF][CONFIG_KEY_CHECKPOINT_NS]) - config[CONF][CONFIG_KEY_CHECKPOINT_NS] = recast_checkpoint_ns(ns) - # set up messages stream mode - if "messages" in stream_modes: - run_manager.inheritable_handlers.append( - StreamMessagesHandler(stream.put, subgraphs) - ) - # set up custom stream mode - if "custom" in stream_modes: - config[CONF][CONFIG_KEY_STREAM_WRITER] = lambda c: stream.put( - ( - tuple( - get_config()[CONF][CONFIG_KEY_CHECKPOINT_NS].split(NS_SEP)[ - :-1 - ] - ), - "custom", - c, - ) - ) - elif ( - CONFIG_KEY_STREAM not in config[CONF] - and CONFIG_KEY_STREAM_WRITER in config[CONF] - ): - # remove parent graph stream writer if subgraph streaming not requested - del config[CONF][CONFIG_KEY_STREAM_WRITER] - # set checkpointing mode for subgraphs - if checkpoint_during is not None: - config[CONF][CONFIG_KEY_CHECKPOINT_DURING] = checkpoint_during - with SyncPregelLoop( - input, - stream=StreamProtocol(stream.put, stream_modes), - config=config, - store=store, - cache=cache, - checkpointer=checkpointer, - nodes=self.nodes, - specs=self.channels, - output_keys=output_keys, - input_keys=self.input_channels, - stream_keys=self.stream_channels_asis, - interrupt_before=interrupt_before_, - interrupt_after=interrupt_after_, - manager=run_manager, - checkpoint_during=checkpoint_during - if checkpoint_during is not None - else config[CONF].get(CONFIG_KEY_CHECKPOINT_DURING, True), - trigger_to_nodes=self.trigger_to_nodes, - migrate_checkpoint=self._migrate_checkpoint, - retry_policy=self.retry_policy, - cache_policy=self.cache_policy, - ) as loop: - # create runner - runner = PregelRunner( - submit=config[CONF].get( - CONFIG_KEY_RUNNER_SUBMIT, weakref.WeakMethod(loop.submit) - ), - put_writes=weakref.WeakMethod(loop.put_writes), - node_finished=config[CONF].get(CONFIG_KEY_NODE_FINISHED), - ) - # enable subgraph streaming - if subgraphs: - loop.config[CONF][CONFIG_KEY_STREAM] = loop.stream - # enable concurrent streaming - if ( - self.stream_eager - or subgraphs - or "messages" in stream_modes - or "custom" in stream_modes - ): - # we are careful to have a single waiter live at any one time - # because on exit we increment semaphore count by exactly 1 - waiter: concurrent.futures.Future | None = None - # because sync futures cannot be cancelled, we instead - # release the stream semaphore on exit, which will cause - # a pending waiter to return immediately - loop.stack.callback(stream._count.release) - - def get_waiter() -> concurrent.futures.Future[None]: - nonlocal waiter - if waiter is None or waiter.done(): - waiter = loop.submit(stream.wait) - return waiter - else: - return waiter - - else: - get_waiter = None # type: ignore[assignment] - # Similarly to Bulk Synchronous Parallel / Pregel model - # computation proceeds in steps, while there are channel updates. - # Channel updates from step N are only visible in step N+1 - # channels are guaranteed to be immutable for the duration of the step, - # with channel updates applied only at the transition between steps. - while loop.tick(): - for task in loop.match_cached_writes(): - loop.output_writes(task.id, task.writes, cached=True) - for _ in runner.tick( - [t for t in loop.tasks.values() if not t.writes], - timeout=self.step_timeout, - get_waiter=get_waiter, - schedule_task=loop.accept_push, - ): - # emit output - yield from _output( - stream_mode, print_mode, subgraphs, stream.get, queue.Empty - ) - loop.after_tick() - # emit output - yield from _output( - stream_mode, print_mode, subgraphs, stream.get, queue.Empty - ) - # handle exit - if loop.status == "out_of_steps": - msg = create_error_message( - message=( - f"Recursion limit of {config['recursion_limit']} reached " - "without hitting a stop condition. You can increase the " - "limit by setting the `recursion_limit` config key." - ), - error_code=ErrorCode.GRAPH_RECURSION_LIMIT, - ) - raise GraphRecursionError(msg) - # set final channel values as run output - run_manager.on_chain_end(loop.output) - except BaseException as e: - run_manager.on_chain_error(e) - raise - - async def astream( - self, - input: InputT, - config: RunnableConfig | None = None, - *, - stream_mode: StreamMode | Sequence[StreamMode] | None = None, - print_mode: StreamMode | Sequence[StreamMode] = (), - output_keys: str | Sequence[str] | None = None, - interrupt_before: All | Sequence[str] | None = None, - interrupt_after: All | Sequence[str] | None = None, - checkpoint_during: bool | None = None, - debug: bool | None = None, - subgraphs: bool = False, - ) -> AsyncIterator[dict[str, Any] | Any]: - """Asynchronously stream graph steps for a single input. - - Args: - input: The input to the graph. - config: The configuration to use for the run. - stream_mode: The mode to stream output, defaults to `self.stream_mode`. - Options are: - - - `"values"`: Emit all values in the state after each step, including interrupts. - When used with functional API, values are emitted once at the end of the workflow. - - `"updates"`: Emit only the node or task names and updates returned by the nodes or tasks after each step. - If multiple updates are made in the same step (e.g. multiple nodes are run) then those updates are emitted separately. - - `"custom"`: Emit custom data from inside nodes or tasks using `StreamWriter`. - - `"messages"`: Emit LLM messages token-by-token together with metadata for any LLM invocations inside nodes or tasks. - Will be emitted as 2-tuples `(LLM token, metadata)`. - - `"debug"`: Emit debug events with as much information as possible for each step. - - You can pass a list as the `stream_mode` parameter to stream multiple modes at once. - The streamed outputs will be tuples of `(mode, data)`. - - See [LangGraph streaming guide](https://langchain-ai.github.io/langgraph/how-tos/streaming/) for more details. - print_mode: Accepts the same values as `stream_mode`, but only prints the output to the console, for debugging purposes. Does not affect the output of the graph in any way. - output_keys: The keys to stream, defaults to all non-context channels. - interrupt_before: Nodes to interrupt before, defaults to all nodes in the graph. - interrupt_after: Nodes to interrupt after, defaults to all nodes in the graph. - checkpoint_during: Whether to checkpoint intermediate steps, defaults to False. If False, only the final checkpoint is saved. - subgraphs: Whether to stream events from inside subgraphs, defaults to False. - If True, the events will be emitted as tuples `(namespace, data)`, - or `(namespace, mode, data)` if `stream_mode` is a list, - where `namespace` is a tuple with the path to the node where a subgraph is invoked, - e.g. `("parent_node:", "child_node:")`. - - See [LangGraph streaming guide](https://langchain-ai.github.io/langgraph/how-tos/streaming/) for more details. - - Yields: - The output of each step in the graph. The output shape depends on the stream_mode. - """ - - if stream_mode is None: - # if being called as a node in another graph, default to values mode - # but don't overwrite stream_mode arg if provided - stream_mode = ( - "values" - if config is not None and CONFIG_KEY_TASK_ID in config.get(CONF, {}) - else self.stream_mode - ) - if debug or self.debug: - print_mode = ["updates", "values"] - - stream = AsyncQueue() - aioloop = asyncio.get_running_loop() - stream_put = cast( - Callable[[StreamChunk], None], - partial(aioloop.call_soon_threadsafe, stream.put_nowait), - ) - - config = ensure_config(self.config, config) - callback_manager = get_async_callback_manager_for_config(config) - run_manager = await callback_manager.on_chain_start( - None, - input, - name=config.get("run_name", self.get_name()), - run_id=config.get("run_id"), - ) - # if running from astream_log() run each proc with streaming - do_stream = ( - next( - ( - True - for h in run_manager.handlers - if isinstance(h, _StreamingCallbackHandler) - and not isinstance(h, StreamMessagesHandler) - ), - False, - ) - if _StreamingCallbackHandler is not None - else False - ) - try: - # assign defaults - ( - stream_modes, - output_keys, - interrupt_before_, - interrupt_after_, - checkpointer, - store, - cache, - ) = self._defaults( - config, - stream_mode=stream_mode, - print_mode=print_mode, - output_keys=output_keys, - interrupt_before=interrupt_before, - interrupt_after=interrupt_after, - ) - # set up subgraph checkpointing - if self.checkpointer is True: - ns = cast(str, config[CONF][CONFIG_KEY_CHECKPOINT_NS]) - config[CONF][CONFIG_KEY_CHECKPOINT_NS] = recast_checkpoint_ns(ns) - # set up messages stream mode - if "messages" in stream_modes: - run_manager.inheritable_handlers.append( - StreamMessagesHandler(stream_put, subgraphs) - ) - # set up custom stream mode - if "custom" in stream_modes: - config[CONF][CONFIG_KEY_STREAM_WRITER] = ( - lambda c: aioloop.call_soon_threadsafe( - stream.put_nowait, - ( - tuple( - get_config()[CONF][CONFIG_KEY_CHECKPOINT_NS].split( - NS_SEP - )[:-1] - ), - "custom", - c, - ), - ) - ) - elif ( - CONFIG_KEY_STREAM not in config[CONF] - and CONFIG_KEY_STREAM_WRITER in config[CONF] - ): - # remove parent graph stream writer if subgraph streaming not requested - del config[CONF][CONFIG_KEY_STREAM_WRITER] - # set checkpointing mode for subgraphs - if checkpoint_during is not None: - config[CONF][CONFIG_KEY_CHECKPOINT_DURING] = checkpoint_during - async with AsyncPregelLoop( - input, - stream=StreamProtocol(stream.put_nowait, stream_modes), - config=config, - store=store, - cache=cache, - checkpointer=checkpointer, - nodes=self.nodes, - specs=self.channels, - output_keys=output_keys, - input_keys=self.input_channels, - stream_keys=self.stream_channels_asis, - interrupt_before=interrupt_before_, - interrupt_after=interrupt_after_, - manager=run_manager, - checkpoint_during=checkpoint_during - if checkpoint_during is not None - else config[CONF].get(CONFIG_KEY_CHECKPOINT_DURING, True), - trigger_to_nodes=self.trigger_to_nodes, - migrate_checkpoint=self._migrate_checkpoint, - retry_policy=self.retry_policy, - cache_policy=self.cache_policy, - ) as loop: - # create runner - runner = PregelRunner( - submit=config[CONF].get( - CONFIG_KEY_RUNNER_SUBMIT, weakref.WeakMethod(loop.submit) - ), - put_writes=weakref.WeakMethod(loop.put_writes), - use_astream=do_stream, - node_finished=config[CONF].get(CONFIG_KEY_NODE_FINISHED), - ) - # enable subgraph streaming - if subgraphs: - loop.config[CONF][CONFIG_KEY_STREAM] = StreamProtocol( - stream_put, stream_modes - ) - # enable concurrent streaming - if ( - self.stream_eager - or subgraphs - or "messages" in stream_modes - or "custom" in stream_modes - ): - - def get_waiter() -> asyncio.Task[None]: - return aioloop.create_task(stream.wait()) - - else: - get_waiter = None # type: ignore[assignment] - # Similarly to Bulk Synchronous Parallel / Pregel model - # computation proceeds in steps, while there are channel updates - # channel updates from step N are only visible in step N+1 - # channels are guaranteed to be immutable for the duration of the step, - # with channel updates applied only at the transition between steps - while loop.tick(): - for task in await loop.amatch_cached_writes(): - loop.output_writes(task.id, task.writes, cached=True) - async for _ in runner.atick( - [t for t in loop.tasks.values() if not t.writes], - timeout=self.step_timeout, - get_waiter=get_waiter, - schedule_task=loop.aaccept_push, - ): - # emit output - for o in _output( - stream_mode, - print_mode, - subgraphs, - stream.get_nowait, - asyncio.QueueEmpty, - ): - yield o - loop.after_tick() - # emit output - for o in _output( - stream_mode, - print_mode, - subgraphs, - stream.get_nowait, - asyncio.QueueEmpty, - ): - yield o - # handle exit - if loop.status == "out_of_steps": - msg = create_error_message( - message=( - f"Recursion limit of {config['recursion_limit']} reached " - "without hitting a stop condition. You can increase the " - "limit by setting the `recursion_limit` config key." - ), - error_code=ErrorCode.GRAPH_RECURSION_LIMIT, - ) - raise GraphRecursionError(msg) - # set final channel values as run output - await run_manager.on_chain_end(loop.output) - except BaseException as e: - await asyncio.shield(run_manager.on_chain_error(e)) - raise - - def invoke( - self, - input: InputT, - config: RunnableConfig | None = None, - *, - stream_mode: StreamMode = "values", - print_mode: StreamMode | Sequence[StreamMode] = (), - output_keys: str | Sequence[str] | None = None, - interrupt_before: All | Sequence[str] | None = None, - interrupt_after: All | Sequence[str] | None = None, - **kwargs: Any, - ) -> dict[str, Any] | Any: - """Run the graph with a single input and config. - - Args: - input: The input data for the graph. It can be a dictionary or any other type. - config: Optional. The configuration for the graph run. - stream_mode: Optional[str]. The stream mode for the graph run. Default is "values". - print_mode: Accepts the same values as `stream_mode`, but only prints the output to the console, for debugging purposes. Does not affect the output of the graph in any way. - output_keys: Optional. The output keys to retrieve from the graph run. - interrupt_before: Optional. The nodes to interrupt the graph run before. - interrupt_after: Optional. The nodes to interrupt the graph run after. - **kwargs: Additional keyword arguments to pass to the graph run. - - Returns: - The output of the graph run. If stream_mode is "values", it returns the latest output. - If stream_mode is not "values", it returns a list of output chunks. - """ - output_keys = output_keys if output_keys is not None else self.output_channels - - latest: dict[str, Any] | Any = None - chunks: list[dict[str, Any] | Any] = [] - interrupts: list[Interrupt] = [] - - for chunk in self.stream( - input, - config, - stream_mode=["updates", "values"] - if stream_mode == "values" - else stream_mode, - print_mode=print_mode, - output_keys=output_keys, - interrupt_before=interrupt_before, - interrupt_after=interrupt_after, - **kwargs, - ): - if stream_mode == "values": - if len(chunk) == 2: - mode, payload = cast(tuple[StreamMode, Any], chunk) - else: - _, mode, payload = cast( - tuple[tuple[str, ...], StreamMode, Any], chunk - ) - if ( - mode == "updates" - and isinstance(payload, dict) - and (ints := payload.get(INTERRUPT)) is not None - ): - interrupts.extend(ints) - elif mode == "values": - latest = payload - else: - chunks.append(chunk) - - if stream_mode == "values": - if interrupts: - return ( - {**latest, INTERRUPT: interrupts} - if isinstance(latest, dict) - else {INTERRUPT: interrupts} - ) - return latest - else: - return chunks - - async def ainvoke( - self, - input: InputT, - config: RunnableConfig | None = None, - *, - stream_mode: StreamMode = "values", - print_mode: StreamMode | Sequence[StreamMode] = (), - output_keys: str | Sequence[str] | None = None, - interrupt_before: All | Sequence[str] | None = None, - interrupt_after: All | Sequence[str] | None = None, - **kwargs: Any, - ) -> dict[str, Any] | Any: - """Asynchronously invoke the graph on a single input. - - Args: - input: The input data for the computation. It can be a dictionary or any other type. - config: Optional. The configuration for the computation. - stream_mode: Optional. The stream mode for the computation. Default is "values". - print_mode: Accepts the same values as `stream_mode`, but only prints the output to the console, for debugging purposes. Does not affect the output of the graph in any way. - output_keys: Optional. The output keys to include in the result. Default is None. - interrupt_before: Optional. The nodes to interrupt before. Default is None. - interrupt_after: Optional. The nodes to interrupt after. Default is None. - **kwargs: Additional keyword arguments. - - Returns: - The result of the computation. If stream_mode is "values", it returns the latest value. - If stream_mode is "chunks", it returns a list of chunks. - """ - - output_keys = output_keys if output_keys is not None else self.output_channels - - latest: dict[str, Any] | Any = None - chunks: list[dict[str, Any] | Any] = [] - interrupts: list[Interrupt] = [] - - async for chunk in self.astream( - input, - config, - stream_mode=["updates", "values"] - if stream_mode == "values" - else stream_mode, - print_mode=print_mode, - output_keys=output_keys, - interrupt_before=interrupt_before, - interrupt_after=interrupt_after, - **kwargs, - ): - if stream_mode == "values": - if len(chunk) == 2: - mode, payload = cast(tuple[StreamMode, Any], chunk) - else: - _, mode, payload = cast( - tuple[tuple[str, ...], StreamMode, Any], chunk - ) - if ( - mode == "updates" - and isinstance(payload, dict) - and (ints := payload.get(INTERRUPT)) is not None - ): - interrupts.extend(ints) - elif mode == "values": - latest = payload - else: - chunks.append(chunk) - - if stream_mode == "values": - if interrupts: - return ( - {**latest, INTERRUPT: interrupts} - if isinstance(latest, dict) - else {INTERRUPT: interrupts} - ) - return latest - else: - return chunks - - def clear_cache(self, nodes: Sequence[str] | None = None) -> None: - """Clear the cache for the given nodes.""" - if not self.cache: - raise ValueError("No cache is set for this graph. Cannot clear cache.") - nodes = nodes or self.nodes.keys() - # collect namespaces to clear - namespaces: list[tuple[str, ...]] = [] - for node in nodes: - if node in self.nodes: - namespaces.append( - ( - CACHE_NS_WRITES, - (identifier(self.nodes[node]) or "__dynamic__"), - node, - ), - ) - # clear cache - self.cache.clear(namespaces) - - async def aclear_cache(self, nodes: Sequence[str] | None = None) -> None: - """Asynchronously clear the cache for the given nodes.""" - if not self.cache: - raise ValueError("No cache is set for this graph. Cannot clear cache.") - nodes = nodes or self.nodes.keys() - # collect namespaces to clear - namespaces: list[tuple[str, ...]] = [] - for node in nodes: - if node in self.nodes: - namespaces.append( - ( - CACHE_NS_WRITES, - (identifier(self.nodes[node]) or "__dynamic__"), - node, - ), - ) - # clear cache - await self.cache.aclear(namespaces) - - -def _trigger_to_nodes(nodes: dict[str, PregelNode]) -> Mapping[str, Sequence[str]]: - """Index from a trigger to nodes that depend on it.""" - trigger_to_nodes: defaultdict[str, list[str]] = defaultdict(list) - for name, node in nodes.items(): - for trigger in node.triggers: - trigger_to_nodes[trigger].append(name) - return dict(trigger_to_nodes) - - -def _output( - stream_mode: StreamMode | Sequence[StreamMode], - print_mode: StreamMode | Sequence[StreamMode], - stream_subgraphs: bool, - getter: Callable[[], tuple[tuple[str, ...], str, Any]], - empty_exc: type[Exception], -) -> Iterator: - while True: - try: - ns, mode, payload = getter() - except empty_exc: - break - if mode in print_mode: - if stream_subgraphs and ns: - print( - " ".join( - ( - get_bolded_text(f"[{mode}]"), - get_colored_text(f"[graph={ns}]", color="yellow"), - repr(payload), - ) - ) - ) - else: - print( - " ".join( - ( - get_bolded_text(f"[{mode}]"), - repr(payload), - ) - ) - ) - if mode in stream_mode: - if stream_subgraphs and isinstance(stream_mode, list): - yield (ns, mode, payload) - elif isinstance(stream_mode, list): - yield (mode, payload) - elif stream_subgraphs: - yield (ns, payload) - else: - yield payload +__all__ = ("Pregel", "NodeBuilder") diff --git a/libs/langgraph/langgraph/pregel/algo.py b/libs/langgraph/langgraph/pregel/_algo.py similarity index 99% rename from libs/langgraph/langgraph/pregel/algo.py rename to libs/langgraph/langgraph/pregel/_algo.py index 9d15aff3c..270dfebdb 100644 --- a/libs/langgraph/langgraph/pregel/algo.py +++ b/libs/langgraph/langgraph/pregel/_algo.py @@ -25,6 +25,7 @@ from langchain_core.callbacks.manager import AsyncParentRunManager, ParentRunMan from langchain_core.runnables.config import RunnableConfig from xxhash import xxh3_128_hexdigest +from langgraph._internal._config import merge_configs, patch_config from langgraph.channels.base import BaseChannel from langgraph.channels.topic import Topic from langgraph.checkpoint.base import ( @@ -64,24 +65,23 @@ from langgraph.constants import ( RETURN, TAG_HIDDEN, TASKS, - Send, ) from langgraph.managed.base import ManagedValueMapping -from langgraph.pregel.call import get_runnable_for_task, identifier -from langgraph.pregel.io import read_channels -from langgraph.pregel.log import logger -from langgraph.pregel.read import INPUT_CACHE_KEY_TYPE, PregelNode +from langgraph.pregel._call import get_runnable_for_task, identifier +from langgraph.pregel._io import read_channels +from langgraph.pregel._log import logger +from langgraph.pregel._read import INPUT_CACHE_KEY_TYPE, PregelNode +from langgraph.pregel._scratchpad import PregelScratchpad from langgraph.store.base import BaseStore from langgraph.types import ( All, CacheKey, CachePolicy, PregelExecutableTask, - PregelScratchpad, PregelTask, RetryPolicy, + Send, ) -from langgraph.utils.config import merge_configs, patch_config GetNextVersion = Callable[[Optional[V], None], V] SUPPORTS_EXC_NOTES = sys.version_info >= (3, 11) diff --git a/libs/langgraph/langgraph/pregel/call.py b/libs/langgraph/langgraph/pregel/_call.py similarity index 96% rename from libs/langgraph/langgraph/pregel/call.py rename to libs/langgraph/langgraph/pregel/_call.py index df1e158cb..6bcd93f05 100644 --- a/libs/langgraph/langgraph/pregel/call.py +++ b/libs/langgraph/langgraph/pregel/_call.py @@ -13,16 +13,16 @@ from typing import Any, Callable, Generic, TypeVar, cast from langchain_core.runnables import Runnable from typing_extensions import ParamSpec -from langgraph.constants import CONF, CONFIG_KEY_CALL, RETURN -from langgraph.pregel.write import ChannelWrite, ChannelWriteEntry -from langgraph.types import CachePolicy, RetryPolicy -from langgraph.utils.config import get_config -from langgraph.utils.runnable import ( +from langgraph._internal._runnable import ( RunnableCallable, RunnableSeq, is_async_callable, run_in_executor, ) +from langgraph.config import get_config +from langgraph.constants import CONF, CONFIG_KEY_CALL, RETURN +from langgraph.pregel._write import ChannelWrite, ChannelWriteEntry +from langgraph.types import CachePolicy, RetryPolicy ## # Utilities borrowed from cloudpickle. @@ -78,8 +78,8 @@ def _whichmodule(obj: Any, name: str) -> str | None: def identifier(obj: Any, name: str | None = None) -> str | None: """Return the module and name of an object.""" - from langgraph.pregel.read import PregelNode - from langgraph.utils.runnable import RunnableCallable, RunnableSeq + from langgraph._internal._runnable import RunnableCallable, RunnableSeq + from langgraph.pregel._read import PregelNode if isinstance(obj, PregelNode): obj = obj.bound diff --git a/libs/langgraph/langgraph/pregel/checkpoint.py b/libs/langgraph/langgraph/pregel/_checkpoint.py similarity index 100% rename from libs/langgraph/langgraph/pregel/checkpoint.py rename to libs/langgraph/langgraph/pregel/_checkpoint.py diff --git a/libs/langgraph/langgraph/pregel/draw.py b/libs/langgraph/langgraph/pregel/_draw.py similarity index 97% rename from libs/langgraph/langgraph/pregel/draw.py rename to libs/langgraph/langgraph/pregel/_draw.py index 091e92be3..9720f10e7 100644 --- a/libs/langgraph/langgraph/pregel/draw.py +++ b/libs/langgraph/langgraph/pregel/_draw.py @@ -11,16 +11,16 @@ from langgraph.channels.base import BaseChannel from langgraph.checkpoint.base import BaseCheckpointSaver from langgraph.constants import CONF, CONFIG_KEY_SEND, END, INPUT, START from langgraph.managed.base import ManagedValueSpec -from langgraph.pregel.algo import ( +from langgraph.pregel._algo import ( PregelTaskWrites, apply_writes, increment, prepare_next_tasks, ) -from langgraph.pregel.checkpoint import channels_from_checkpoint, empty_checkpoint -from langgraph.pregel.io import map_input -from langgraph.pregel.read import PregelNode -from langgraph.pregel.write import ChannelWrite +from langgraph.pregel._checkpoint import channels_from_checkpoint, empty_checkpoint +from langgraph.pregel._io import map_input +from langgraph.pregel._read import PregelNode +from langgraph.pregel._write import ChannelWrite from langgraph.types import All, Checkpointer diff --git a/libs/langgraph/langgraph/pregel/executor.py b/libs/langgraph/langgraph/pregel/_executor.py similarity index 98% rename from libs/langgraph/langgraph/pregel/executor.py rename to libs/langgraph/langgraph/pregel/_executor.py index 62df4b19f..db37135c0 100644 --- a/libs/langgraph/langgraph/pregel/executor.py +++ b/libs/langgraph/langgraph/pregel/_executor.py @@ -18,8 +18,8 @@ from langchain_core.runnables import RunnableConfig from langchain_core.runnables.config import get_executor_for_config from typing_extensions import ParamSpec +from langgraph._internal._future import CONTEXT_NOT_SUPPORTED, run_coroutine_threadsafe from langgraph.errors import GraphBubbleUp -from langgraph.utils.future import CONTEXT_NOT_SUPPORTED, run_coroutine_threadsafe P = ParamSpec("P") T = TypeVar("T") diff --git a/libs/langgraph/langgraph/pregel/io.py b/libs/langgraph/langgraph/pregel/_io.py similarity index 99% rename from libs/langgraph/langgraph/pregel/io.py rename to libs/langgraph/langgraph/pregel/_io.py index 48268af76..3eff58c8a 100644 --- a/libs/langgraph/langgraph/pregel/io.py +++ b/libs/langgraph/langgraph/pregel/_io.py @@ -18,7 +18,7 @@ from langgraph.constants import ( TASKS, ) from langgraph.errors import InvalidUpdateError -from langgraph.pregel.log import logger +from langgraph.pregel._log import logger from langgraph.types import Command, PregelExecutableTask, Send diff --git a/libs/langgraph/langgraph/pregel/log.py b/libs/langgraph/langgraph/pregel/_log.py similarity index 100% rename from libs/langgraph/langgraph/pregel/log.py rename to libs/langgraph/langgraph/pregel/_log.py diff --git a/libs/langgraph/langgraph/pregel/loop.py b/libs/langgraph/langgraph/pregel/_loop.py similarity index 98% rename from libs/langgraph/langgraph/pregel/loop.py rename to libs/langgraph/langgraph/pregel/_loop.py index e5d4e1534..7dc2ec5dc 100644 --- a/libs/langgraph/langgraph/pregel/loop.py +++ b/libs/langgraph/langgraph/pregel/_loop.py @@ -27,6 +27,7 @@ from langchain_core.callbacks import AsyncParentRunManager, ParentRunManager from langchain_core.runnables import RunnableConfig from typing_extensions import ParamSpec, Self +from langgraph._internal._config import patch_configurable from langgraph.cache.base import BaseCache from langgraph.channels.base import BaseChannel from langgraph.checkpoint.base import ( @@ -69,7 +70,7 @@ from langgraph.managed.base import ( ManagedValueMapping, ManagedValueSpec, ) -from langgraph.pregel.algo import ( +from langgraph.pregel._algo import ( Call, GetNextVersion, PregelTaskWrites, @@ -81,44 +82,42 @@ from langgraph.pregel.algo import ( should_interrupt, task_path_str, ) -from langgraph.pregel.checkpoint import ( +from langgraph.pregel._checkpoint import ( channels_from_checkpoint, copy_checkpoint, create_checkpoint, empty_checkpoint, ) -from langgraph.pregel.debug import ( - map_debug_checkpoint, - map_debug_task_results, - map_debug_tasks, -) -from langgraph.pregel.executor import ( +from langgraph.pregel._executor import ( AsyncBackgroundExecutor, BackgroundExecutor, Submit, ) -from langgraph.pregel.io import ( +from langgraph.pregel._io import ( map_command, map_input, map_output_updates, map_output_values, read_channels, ) -from langgraph.pregel.read import PregelNode -from langgraph.pregel.utils import get_new_channel_versions, is_xxh3_128_hexdigest +from langgraph.pregel._read import PregelNode +from langgraph.pregel._scratchpad import PregelScratchpad +from langgraph.pregel._utils import get_new_channel_versions, is_xxh3_128_hexdigest +from langgraph.pregel.debug import ( + map_debug_checkpoint, + map_debug_task_results, + map_debug_tasks, +) +from langgraph.pregel.protocol import StreamChunk, StreamProtocol from langgraph.store.base import BaseStore from langgraph.types import ( All, CachePolicy, Command, PregelExecutableTask, - PregelScratchpad, RetryPolicy, - StreamChunk, StreamMode, - StreamProtocol, ) -from langgraph.utils.config import patch_configurable V = TypeVar("V") P = ParamSpec("P") diff --git a/libs/langgraph/langgraph/pregel/messages.py b/libs/langgraph/langgraph/pregel/_messages.py similarity index 97% rename from libs/langgraph/langgraph/pregel/messages.py rename to libs/langgraph/langgraph/pregel/_messages.py index 9a9210aa6..b06991ba3 100644 --- a/libs/langgraph/langgraph/pregel/messages.py +++ b/libs/langgraph/langgraph/pregel/_messages.py @@ -13,8 +13,9 @@ from langchain_core.callbacks import BaseCallbackHandler from langchain_core.messages import BaseMessage from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, LLMResult -from langgraph.constants import NS_SEP, TAG_HIDDEN, TAG_NOSTREAM, TAG_NOSTREAM_ALT -from langgraph.types import Command, StreamChunk +from langgraph.constants import NS_SEP, TAG_HIDDEN, TAG_NOSTREAM +from langgraph.pregel.protocol import StreamChunk +from langgraph.types import Command try: from langchain_core.tracers._streaming import _StreamingCallbackHandler @@ -94,9 +95,7 @@ class StreamMessagesHandler(BaseCallbackHandler, _StreamingCallbackHandler): metadata: dict[str, Any] | None = None, **kwargs: Any, ) -> Any: - if metadata and ( - not tags or (TAG_NOSTREAM not in tags and TAG_NOSTREAM_ALT not in tags) - ): + if metadata and (not tags or (TAG_NOSTREAM not in tags)): ns = tuple(cast(str, metadata["langgraph_checkpoint_ns"]).split(NS_SEP))[ :-1 ] diff --git a/libs/langgraph/langgraph/pregel/read.py b/libs/langgraph/langgraph/pregel/_read.py similarity index 97% rename from libs/langgraph/langgraph/pregel/read.py rename to libs/langgraph/langgraph/pregel/_read.py index 30a9af652..ea73d4d63 100644 --- a/libs/langgraph/langgraph/pregel/read.py +++ b/libs/langgraph/langgraph/pregel/_read.py @@ -10,13 +10,13 @@ from typing import ( from langchain_core.runnables import Runnable, RunnableConfig +from langgraph._internal._config import merge_configs +from langgraph._internal._runnable import RunnableCallable, RunnableSeq from langgraph.constants import CONF, CONFIG_KEY_READ +from langgraph.pregel._utils import find_subgraph_pregel +from langgraph.pregel._write import ChannelWrite from langgraph.pregel.protocol import PregelProtocol -from langgraph.pregel.utils import find_subgraph_pregel -from langgraph.pregel.write import ChannelWrite from langgraph.types import CachePolicy, RetryPolicy -from langgraph.utils.config import merge_configs -from langgraph.utils.runnable import RunnableCallable, RunnableSeq READ_TYPE = Callable[[Union[str, Sequence[str]], bool], Union[Any, dict[str, Any]]] INPUT_CACHE_KEY_TYPE = tuple[Callable[..., Any], tuple[str, ...]] diff --git a/libs/langgraph/langgraph/pregel/retry.py b/libs/langgraph/langgraph/pregel/_retry.py similarity index 99% rename from libs/langgraph/langgraph/pregel/retry.py rename to libs/langgraph/langgraph/pregel/_retry.py index a91edb9c4..4873e6824 100644 --- a/libs/langgraph/langgraph/pregel/retry.py +++ b/libs/langgraph/langgraph/pregel/_retry.py @@ -9,6 +9,7 @@ from collections.abc import Awaitable, Sequence from dataclasses import replace from typing import Any, Callable +from langgraph._internal._config import patch_configurable from langgraph.constants import ( CONF, CONFIG_KEY_CHECKPOINT_NS, @@ -17,7 +18,6 @@ from langgraph.constants import ( ) from langgraph.errors import GraphBubbleUp, ParentCommand from langgraph.types import Command, PregelExecutableTask, RetryPolicy -from langgraph.utils.config import patch_configurable logger = logging.getLogger(__name__) SUPPORTS_EXC_NOTES = sys.version_info >= (3, 11) diff --git a/libs/langgraph/langgraph/pregel/runner.py b/libs/langgraph/langgraph/pregel/_runner.py similarity index 98% rename from libs/langgraph/langgraph/pregel/runner.py rename to libs/langgraph/langgraph/pregel/_runner.py index ab6c7a17d..9c29eabca 100644 --- a/libs/langgraph/langgraph/pregel/runner.py +++ b/libs/langgraph/langgraph/pregel/_runner.py @@ -19,6 +19,7 @@ from typing import ( from langchain_core.callbacks import Callbacks +from langgraph._internal._future import chain_future, run_coroutine_threadsafe from langgraph.constants import ( CONF, CONFIG_KEY_CALL, @@ -32,16 +33,15 @@ from langgraph.constants import ( TAG_HIDDEN, ) from langgraph.errors import GraphBubbleUp, GraphInterrupt -from langgraph.pregel.algo import Call -from langgraph.pregel.executor import Submit -from langgraph.pregel.retry import arun_with_retry, run_with_retry +from langgraph.pregel._algo import Call +from langgraph.pregel._executor import Submit +from langgraph.pregel._retry import arun_with_retry, run_with_retry +from langgraph.pregel._scratchpad import PregelScratchpad from langgraph.types import ( CachePolicy, PregelExecutableTask, - PregelScratchpad, RetryPolicy, ) -from langgraph.utils.future import chain_future, run_coroutine_threadsafe F = TypeVar("F", concurrent.futures.Future, asyncio.Future) E = TypeVar("E", threading.Event, asyncio.Event) diff --git a/libs/langgraph/langgraph/pregel/_scratchpad.py b/libs/langgraph/langgraph/pregel/_scratchpad.py new file mode 100644 index 000000000..1e8eb8a8b --- /dev/null +++ b/libs/langgraph/langgraph/pregel/_scratchpad.py @@ -0,0 +1,18 @@ +import dataclasses +from typing import Any, Callable + +from langgraph.types import _DC_KWARGS + + +@dataclasses.dataclass(**_DC_KWARGS) +class PregelScratchpad: + step: int + stop: int + # call + call_counter: Callable[[], int] + # interrupt + interrupt_counter: Callable[[], int] + get_null_resume: Callable[[bool], Any] + resume: list[Any] + # subgraph + subgraph_counter: Callable[[], int] diff --git a/libs/langgraph/langgraph/pregel/utils.py b/libs/langgraph/langgraph/pregel/_utils.py similarity index 97% rename from libs/langgraph/langgraph/pregel/utils.py rename to libs/langgraph/langgraph/pregel/_utils.py index a37228c05..87c026ae4 100644 --- a/libs/langgraph/langgraph/pregel/utils.py +++ b/libs/langgraph/langgraph/pregel/_utils.py @@ -6,12 +6,12 @@ import re import textwrap from typing import Any, Callable -from langchain_core.runnables import RunnableLambda, RunnableSequence +from langchain_core.runnables import Runnable, RunnableLambda, RunnableSequence from typing_extensions import override +from langgraph._internal._runnable import RunnableCallable, RunnableSeq from langgraph.checkpoint.base import ChannelVersions from langgraph.pregel.protocol import PregelProtocol -from langgraph.utils.runnable import Runnable, RunnableCallable, RunnableSeq def get_new_channel_versions( diff --git a/libs/langgraph/langgraph/pregel/validate.py b/libs/langgraph/langgraph/pregel/_validate.py similarity index 98% rename from libs/langgraph/langgraph/pregel/validate.py rename to libs/langgraph/langgraph/pregel/_validate.py index 00da8b4b1..9a8910703 100644 --- a/libs/langgraph/langgraph/pregel/validate.py +++ b/libs/langgraph/langgraph/pregel/_validate.py @@ -6,7 +6,7 @@ from typing import Any from langgraph.channels.base import BaseChannel from langgraph.constants import RESERVED from langgraph.managed.base import ManagedValueMapping -from langgraph.pregel.read import PregelNode +from langgraph.pregel._read import PregelNode from langgraph.types import All diff --git a/libs/langgraph/langgraph/pregel/write.py b/libs/langgraph/langgraph/pregel/_write.py similarity index 98% rename from libs/langgraph/langgraph/pregel/write.py rename to libs/langgraph/langgraph/pregel/_write.py index 7f1fdc73f..56dceb9d4 100644 --- a/libs/langgraph/langgraph/pregel/write.py +++ b/libs/langgraph/langgraph/pregel/_write.py @@ -13,9 +13,10 @@ from typing import ( from langchain_core.runnables import Runnable, RunnableConfig -from langgraph.constants import CONF, CONFIG_KEY_SEND, MISSING, TASKS, Send +from langgraph._internal._runnable import RunnableCallable +from langgraph.constants import CONF, CONFIG_KEY_SEND, MISSING, TASKS from langgraph.errors import InvalidUpdateError -from langgraph.utils.runnable import RunnableCallable +from langgraph.types import Send TYPE_SEND = Callable[[Sequence[tuple[str, Any]]], None] R = TypeVar("R", bound=Runnable) diff --git a/libs/langgraph/langgraph/pregel/debug.py b/libs/langgraph/langgraph/pregel/debug.py index d8d3bbdae..0ccc168d0 100644 --- a/libs/langgraph/langgraph/pregel/debug.py +++ b/libs/langgraph/langgraph/pregel/debug.py @@ -5,8 +5,10 @@ from dataclasses import asdict from typing import Any from uuid import UUID +from langchain_core.runnables import RunnableConfig from typing_extensions import TypedDict +from langgraph._internal._config import patch_checkpoint_map from langgraph.channels.base import BaseChannel from langgraph.checkpoint.base import CheckpointMetadata, PendingWrite from langgraph.constants import ( @@ -20,9 +22,10 @@ from langgraph.constants import ( RETURN, TAG_HIDDEN, ) -from langgraph.pregel.io import read_channels +from langgraph.pregel._io import read_channels from langgraph.types import PregelExecutableTask, PregelTask, StateSnapshot -from langgraph.utils.config import RunnableConfig, patch_checkpoint_map + +__all__ = ("TaskPayload", "TaskResultPayload", "CheckpointTask", "CheckpointPayload") class TaskPayload(TypedDict): diff --git a/libs/langgraph/langgraph/pregel/main.py b/libs/langgraph/langgraph/pregel/main.py new file mode 100644 index 000000000..eded49e24 --- /dev/null +++ b/libs/langgraph/langgraph/pregel/main.py @@ -0,0 +1,3061 @@ +from __future__ import annotations + +import asyncio +import concurrent +import concurrent.futures +import queue +import weakref +from collections import defaultdict, deque +from collections.abc import AsyncIterator, Iterator, Mapping, Sequence +from functools import partial +from typing import Any, Callable, Generic, Union, cast, get_type_hints +from uuid import UUID, uuid5 + +from langchain_core.globals import get_debug +from langchain_core.runnables import ( + RunnableSequence, +) +from langchain_core.runnables.base import Input, Output +from langchain_core.runnables.config import ( + RunnableConfig, + get_async_callback_manager_for_config, + get_callback_manager_for_config, +) +from langchain_core.runnables.graph import Graph +from pydantic import BaseModel +from typing_extensions import Self + +from langgraph._internal._config import ( + ensure_config, + merge_configs, + patch_checkpoint_map, + patch_config, + patch_configurable, + recast_checkpoint_ns, +) +from langgraph._internal._pydantic import create_model +from langgraph._internal._queue import ( # type: ignore[attr-defined] + AsyncQueue, + SyncQueue, +) +from langgraph._internal._runnable import ( + Runnable, + RunnableLike, + RunnableSeq, + coerce_to_runnable, +) +from langgraph.cache.base import BaseCache +from langgraph.channels.base import BaseChannel +from langgraph.channels.topic import Topic +from langgraph.checkpoint.base import ( + BaseCheckpointSaver, + Checkpoint, + CheckpointTuple, +) +from langgraph.config import get_config +from langgraph.constants import ( + CACHE_NS_WRITES, + CONF, + CONFIG_KEY_CACHE, + CONFIG_KEY_CHECKPOINT_DURING, + CONFIG_KEY_CHECKPOINT_ID, + CONFIG_KEY_CHECKPOINT_NS, + CONFIG_KEY_CHECKPOINTER, + CONFIG_KEY_NODE_FINISHED, + CONFIG_KEY_READ, + CONFIG_KEY_RUNNER_SUBMIT, + CONFIG_KEY_SEND, + CONFIG_KEY_STORE, + CONFIG_KEY_STREAM, + CONFIG_KEY_STREAM_WRITER, + CONFIG_KEY_TASK_ID, + CONFIG_KEY_THREAD_ID, + END, + ERROR, + INPUT, + INTERRUPT, + NS_END, + NS_SEP, + NULL_TASK_ID, + PUSH, + TASKS, +) +from langgraph.errors import ( + ErrorCode, + GraphRecursionError, + InvalidUpdateError, + create_error_message, +) +from langgraph.managed.base import ManagedValueSpec +from langgraph.pregel._algo import ( + PregelTaskWrites, + _scratchpad, + apply_writes, + local_read, + prepare_next_tasks, +) +from langgraph.pregel._call import identifier +from langgraph.pregel._checkpoint import ( + channels_from_checkpoint, + copy_checkpoint, + create_checkpoint, + empty_checkpoint, +) +from langgraph.pregel._draw import draw_graph +from langgraph.pregel._io import map_input, read_channels +from langgraph.pregel._loop import AsyncPregelLoop, SyncPregelLoop +from langgraph.pregel._messages import StreamMessagesHandler +from langgraph.pregel._read import DEFAULT_BOUND, PregelNode +from langgraph.pregel._retry import RetryPolicy +from langgraph.pregel._runner import PregelRunner +from langgraph.pregel._utils import get_new_channel_versions +from langgraph.pregel._validate import validate_graph, validate_keys +from langgraph.pregel._write import ChannelWrite, ChannelWriteEntry +from langgraph.pregel.debug import get_bolded_text, get_colored_text, tasks_w_writes +from langgraph.pregel.protocol import PregelProtocol, StreamChunk, StreamProtocol +from langgraph.store.base import BaseStore +from langgraph.types import ( + All, + CachePolicy, + Checkpointer, + Interrupt, + Send, + StateSnapshot, + StateUpdate, + StreamMode, +) +from langgraph.typing import InputT, OutputT, StateT + +try: + from langchain_core.tracers._streaming import _StreamingCallbackHandler +except ImportError: + _StreamingCallbackHandler = None # type: ignore + +__all__ = ("NodeBuilder", "Pregel") + +_WriteValue = Union[Callable[[Input], Output], Any] + + +class NodeBuilder: + __slots__ = ( + "_channels", + "_triggers", + "_tags", + "_metadata", + "_writes", + "_bound", + "_retry_policy", + "_cache_policy", + ) + + _channels: str | list[str] + _triggers: list[str] + _tags: list[str] + _metadata: dict[str, Any] + _writes: list[ChannelWriteEntry] + _bound: Runnable + _retry_policy: list[RetryPolicy] + _cache_policy: CachePolicy | None + + def __init__( + self, + ) -> None: + self._channels = [] + self._triggers = [] + self._tags = [] + self._metadata = {} + self._writes = [] + self._bound = DEFAULT_BOUND + self._retry_policy = [] + self._cache_policy = None + + def subscribe_only( + self, + channel: str, + ) -> Self: + """Subscribe to a single channel.""" + if not self._channels: + self._channels = channel + else: + raise ValueError( + "Cannot subscribe to single channels when other channels are already subscribed to" + ) + + self._triggers.append(channel) + + return self + + def subscribe_to( + self, + *channels: str, + read: bool = True, + ) -> Self: + """Add channels to subscribe to. Node will be invoked when any of these + channels are updated, with a dict of the channel values as input. + + Args: + channels: Channel name(s) to subscribe to + read: If True, the channels will be included in the input to the node. + Otherwise, they will trigger the node without being sent in input. + + Returns: + Self for chaining + """ + if isinstance(self._channels, str): + raise ValueError( + "Cannot subscribe to channels when subscribed to a single channel" + ) + if read: + if not self._channels: + self._channels = list(channels) + else: + self._channels.extend(channels) + + if isinstance(channels, str): + self._triggers.append(channels) + else: + self._triggers.extend(channels) + + return self + + def read_from( + self, + *channels: str, + ) -> Self: + """Adds the specified channels to read from, without subscribing to them.""" + assert isinstance(self._channels, list), ( + "Cannot read additional channels when subscribed to single channels" + ) + self._channels.extend(channels) + return self + + def do( + self, + node: RunnableLike, + ) -> Self: + """Adds the specified node.""" + if self._bound is not DEFAULT_BOUND: + self._bound = RunnableSeq( + self._bound, coerce_to_runnable(node, name=None, trace=True) + ) + else: + self._bound = coerce_to_runnable(node, name=None, trace=True) + return self + + def write_to( + self, + *channels: str | ChannelWriteEntry, + **kwargs: _WriteValue, + ) -> Self: + """Add channel writes. + + Args: + *channels: Channel names to write to + **kwargs: Channel name and value mappings + + Returns: + Self for chaining + """ + self._writes.extend( + ChannelWriteEntry(c) if isinstance(c, str) else c for c in channels + ) + self._writes.extend( + ChannelWriteEntry(k, mapper=v) + if callable(v) + else ChannelWriteEntry(k, value=v) + for k, v in kwargs.items() + ) + + return self + + def meta(self, *tags: str, **metadata: Any) -> Self: + """Add tags or metadata to the node.""" + self._tags.extend(tags) + self._metadata.update(metadata) + return self + + def add_retry_policies(self, *policies: RetryPolicy) -> Self: + """Adds retry policies to the node.""" + self._retry_policy.extend(policies) + return self + + def add_cache_policy(self, policy: CachePolicy) -> Self: + """Adds cache policies to the node.""" + self._cache_policy = policy + return self + + def build(self) -> PregelNode: + """Builds the node.""" + return PregelNode( + channels=self._channels, + triggers=self._triggers, + tags=self._tags, + metadata=self._metadata, + writers=[ChannelWrite(self._writes)], + bound=self._bound, + retry_policy=self._retry_policy, + cache_policy=self._cache_policy, + ) + + +class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, OutputT]): + """Pregel manages the runtime behavior for LangGraph applications. + + ## Overview + + Pregel combines [**actors**](https://en.wikipedia.org/wiki/Actor_model) + and **channels** into a single application. + **Actors** read data from channels and write data to channels. + Pregel organizes the execution of the application into multiple steps, + following the **Pregel Algorithm**/**Bulk Synchronous Parallel** model. + + Each step consists of three phases: + + - **Plan**: Determine which **actors** to execute in this step. For example, + in the first step, select the **actors** that subscribe to the special + **input** channels; in subsequent steps, + select the **actors** that subscribe to channels updated in the previous step. + - **Execution**: Execute all selected **actors** in parallel, + until all complete, or one fails, or a timeout is reached. During this + phase, channel updates are invisible to actors until the next step. + - **Update**: Update the channels with the values written by the **actors** + in this step. + + Repeat until no **actors** are selected for execution, or a maximum number of + steps is reached. + + ## Actors + + An **actor** is a `PregelNode`. + It subscribes to channels, reads data from them, and writes data to them. + It can be thought of as an **actor** in the Pregel algorithm. + `PregelNodes` implement LangChain's + Runnable interface. + + ## Channels + + Channels are used to communicate between actors (`PregelNodes`). + Each channel has a value type, an update type, and an update function – which + takes a sequence of updates and + modifies the stored value. Channels can be used to send data from one chain to + another, or to send data from a chain to itself in a future step. LangGraph + provides a number of built-in channels: + + ### Basic channels: LastValue and Topic + + - `LastValue`: The default channel, stores the last value sent to the channel, + useful for input and output values, or for sending data from one step to the next + - `Topic`: A configurable PubSub Topic, useful for sending multiple values + between *actors*, or for accumulating output. Can be configured to deduplicate + values, and/or to accumulate values over the course of multiple steps. + + ### Advanced channels: Context and BinaryOperatorAggregate + + - `Context`: exposes the value of a context manager, managing its lifecycle. + Useful for accessing external resources that require setup and/or teardown. eg. + `client = Context(httpx.Client)` + - `BinaryOperatorAggregate`: stores a persistent value, updated by applying + a binary operator to the current value and each update + sent to the channel, useful for computing aggregates over multiple steps. eg. + `total = BinaryOperatorAggregate(int, operator.add)` + + ## Examples + + Most users will interact with Pregel via a + [StateGraph (Graph API)][langgraph.graph.StateGraph] or via an + [entrypoint (Functional API)][langgraph.func.entrypoint]. + + However, for **advanced** use cases, Pregel can be used directly. If you're + not sure whether you need to use Pregel directly, then the answer is probably no + – you should use the Graph API or Functional API instead. These are higher-level + interfaces that will compile down to Pregel under the hood. + + Here are some examples to give you a sense of how it works: + + Example: Single node application + ```python + from langgraph.channels import EphemeralValue + from langgraph.pregel import Pregel, NodeBuilder + + node1 = ( + NodeBuilder().subscribe_only("a") + .do(lambda x: x + x) + .write_to("b") + ) + + app = Pregel( + nodes={"node1": node1}, + channels={ + "a": EphemeralValue(str), + "b": EphemeralValue(str), + }, + input_channels=["a"], + output_channels=["b"], + ) + + app.invoke({"a": "foo"}) + ``` + + ```con + {'b': 'foofoo'} + ``` + + Example: Using multiple nodes and multiple output channels + ```python + from langgraph.channels import LastValue, EphemeralValue + from langgraph.pregel import Pregel, NodeBuilder + + node1 = ( + NodeBuilder().subscribe_only("a") + .do(lambda x: x + x) + .write_to("b") + ) + + node2 = ( + NodeBuilder().subscribe_to("b") + .do(lambda x: x["b"] + x["b"]) + .write_to("c") + ) + + + app = Pregel( + nodes={"node1": node1, "node2": node2}, + channels={ + "a": EphemeralValue(str), + "b": LastValue(str), + "c": EphemeralValue(str), + }, + input_channels=["a"], + output_channels=["b", "c"], + ) + + app.invoke({"a": "foo"}) + ``` + + ```con + {'b': 'foofoo', 'c': 'foofoofoofoo'} + ``` + + Example: Using a Topic channel + ```python + from langgraph.channels import LastValue, EphemeralValue, Topic + from langgraph.pregel import Pregel, NodeBuilder + + node1 = ( + NodeBuilder().subscribe_only("a") + .do(lambda x: x + x) + .write_to("b", "c") + ) + + node2 = ( + NodeBuilder().subscribe_only("b") + .do(lambda x: x + x) + .write_to("c") + ) + + + app = Pregel( + nodes={"node1": node1, "node2": node2}, + channels={ + "a": EphemeralValue(str), + "b": EphemeralValue(str), + "c": Topic(str, accumulate=True), + }, + input_channels=["a"], + output_channels=["c"], + ) + + app.invoke({"a": "foo"}) + ``` + + ```pycon + {'c': ['foofoo', 'foofoofoofoo']} + ``` + + Example: Using a BinaryOperatorAggregate channel + ```python + from langgraph.channels import EphemeralValue, BinaryOperatorAggregate + from langgraph.pregel import Pregel, NodeBuilder + + + node1 = ( + NodeBuilder().subscribe_only("a") + .do(lambda x: x + x) + .write_to("b", "c") + ) + + node2 = ( + NodeBuilder().subscribe_only("b") + .do(lambda x: x + x) + .write_to("c") + ) + + + def reducer(current, update): + if current: + return current + " | " + update + else: + return update + + app = Pregel( + nodes={"node1": node1, "node2": node2}, + channels={ + "a": EphemeralValue(str), + "b": EphemeralValue(str), + "c": BinaryOperatorAggregate(str, operator=reducer), + }, + input_channels=["a"], + output_channels=["c"] + ) + + app.invoke({"a": "foo"}) + ``` + + ```con + {'c': 'foofoo | foofoofoofoo'} + ``` + + Example: Introducing a cycle + This example demonstrates how to introduce a cycle in the graph, by having + a chain write to a channel it subscribes to. Execution will continue + until a None value is written to the channel. + + ```python + from langgraph.channels import EphemeralValue + from langgraph.pregel import Pregel, NodeBuilder, ChannelWriteEntry + + example_node = ( + NodeBuilder().subscribe_only("value") + .do(lambda x: x + x if len(x) < 10 else None) + .write_to(ChannelWriteEntry(channel="value", skip_none=True)) + ) + + app = Pregel( + nodes={"example_node": example_node}, + channels={ + "value": EphemeralValue(str), + }, + input_channels=["value"], + output_channels=["value"] + ) + + app.invoke({"value": "a"}) + ``` + + ```con + {'value': 'aaaaaaaaaaaaaaaa'} + ``` + """ + + nodes: dict[str, PregelNode] + + channels: dict[str, BaseChannel | ManagedValueSpec] + + stream_mode: StreamMode = "values" + """Mode to stream output, defaults to 'values'.""" + + stream_eager: bool = False + """Whether to force emitting stream events eagerly, automatically turned on + for stream_mode "messages" and "custom".""" + + output_channels: str | Sequence[str] + + stream_channels: str | Sequence[str] | None = None + """Channels to stream, defaults to all channels not in reserved channels""" + + interrupt_after_nodes: All | Sequence[str] + + interrupt_before_nodes: All | Sequence[str] + + input_channels: str | Sequence[str] + + step_timeout: float | None = None + """Maximum time to wait for a step to complete, in seconds. Defaults to None.""" + + debug: bool + """Whether to print debug information during execution. Defaults to False.""" + + checkpointer: Checkpointer = None + """Checkpointer used to save and load graph state. Defaults to None.""" + + store: BaseStore | None = None + """Memory store to use for SharedValues. Defaults to None.""" + + cache: BaseCache | None = None + """Cache to use for storing node results. Defaults to None.""" + + retry_policy: Sequence[RetryPolicy] = () + """Retry policies to use when running tasks. Empty set disables retries.""" + + cache_policy: CachePolicy | None = None + """Cache policy to use for all nodes. Can be overridden by individual nodes. + Defaults to None.""" + + config_type: type[Any] | None = None + + config: RunnableConfig | None = None + + name: str = "LangGraph" + + trigger_to_nodes: Mapping[str, Sequence[str]] + + def __init__( + self, + *, + nodes: dict[str, PregelNode | NodeBuilder], + channels: dict[str, BaseChannel | ManagedValueSpec] | None, + auto_validate: bool = True, + stream_mode: StreamMode = "values", + stream_eager: bool = False, + output_channels: str | Sequence[str], + stream_channels: str | Sequence[str] | None = None, + interrupt_after_nodes: All | Sequence[str] = (), + interrupt_before_nodes: All | Sequence[str] = (), + input_channels: str | Sequence[str], + step_timeout: float | None = None, + debug: bool | None = None, + checkpointer: BaseCheckpointSaver | None = None, + store: BaseStore | None = None, + cache: BaseCache | None = None, + retry_policy: RetryPolicy | Sequence[RetryPolicy] = (), + cache_policy: CachePolicy | None = None, + config_type: type[Any] | None = None, + config: RunnableConfig | None = None, + trigger_to_nodes: Mapping[str, Sequence[str]] | None = None, + name: str = "LangGraph", + ) -> None: + self.nodes = { + k: v.build() if isinstance(v, NodeBuilder) else v for k, v in nodes.items() + } + self.channels = channels or {} + if TASKS in self.channels and not isinstance(self.channels[TASKS], Topic): + raise ValueError( + f"Channel '{TASKS}' is reserved and cannot be used in the graph." + ) + else: + self.channels[TASKS] = Topic(Send, accumulate=False) + self.stream_mode = stream_mode + self.stream_eager = stream_eager + self.output_channels = output_channels + self.stream_channels = stream_channels + self.interrupt_after_nodes = interrupt_after_nodes + self.interrupt_before_nodes = interrupt_before_nodes + self.input_channels = input_channels + self.step_timeout = step_timeout + self.debug = debug if debug is not None else get_debug() + self.checkpointer = checkpointer + self.store = store + self.cache = cache + self.retry_policy = ( + (retry_policy,) if isinstance(retry_policy, RetryPolicy) else retry_policy + ) + self.cache_policy = cache_policy + self.config_type = config_type + self.config = config + self.trigger_to_nodes = trigger_to_nodes or {} + self.name = name + if auto_validate: + self.validate() + + def get_graph( + self, config: RunnableConfig | None = None, *, xray: int | bool = False + ) -> Graph: + """Return a drawable representation of the computation graph.""" + # gather subgraphs + if xray: + subgraphs = { + k: v.get_graph( + config, + xray=xray if isinstance(xray, bool) or xray <= 0 else xray - 1, + ) + for k, v in self.get_subgraphs() + } + else: + subgraphs = {} + + return draw_graph( + merge_configs(self.config, config), + nodes=self.nodes, + specs=self.channels, + input_channels=self.input_channels, + interrupt_after_nodes=self.interrupt_after_nodes, + interrupt_before_nodes=self.interrupt_before_nodes, + trigger_to_nodes=self.trigger_to_nodes, + checkpointer=self.checkpointer, + subgraphs=subgraphs, + ) + + async def aget_graph( + self, config: RunnableConfig | None = None, *, xray: int | bool = False + ) -> Graph: + """Return a drawable representation of the computation graph.""" + + # gather subgraphs + if xray: + subpregels: dict[str, PregelProtocol] = { + k: v async for k, v in self.aget_subgraphs() + } + subgraphs = { + k: v + for k, v in zip( + subpregels, + await asyncio.gather( + *( + p.aget_graph( + config, + xray=xray + if isinstance(xray, bool) or xray <= 0 + else xray - 1, + ) + for p in subpregels.values() + ) + ), + ) + } + else: + subgraphs = {} + + return draw_graph( + merge_configs(self.config, config), + nodes=self.nodes, + specs=self.channels, + input_channels=self.input_channels, + interrupt_after_nodes=self.interrupt_after_nodes, + interrupt_before_nodes=self.interrupt_before_nodes, + trigger_to_nodes=self.trigger_to_nodes, + checkpointer=self.checkpointer, + subgraphs=subgraphs, + ) + + def _repr_mimebundle_(self, **kwargs: Any) -> dict[str, Any]: + """Mime bundle used by Jupyter to display the graph""" + return { + "text/plain": repr(self), + "image/png": self.get_graph().draw_mermaid_png(), + } + + def copy(self, update: dict[str, Any] | None = None) -> Self: + attrs = {k: v for k, v in self.__dict__.items() if k != "__orig_class__"} + attrs.update(update or {}) + return self.__class__(**attrs) + + def with_config(self, config: RunnableConfig | None = None, **kwargs: Any) -> Self: + """Create a copy of the Pregel object with an updated config.""" + return self.copy( + {"config": merge_configs(self.config, config, cast(RunnableConfig, kwargs))} + ) + + def validate(self) -> Self: + validate_graph( + self.nodes, + {k: v for k, v in self.channels.items() if isinstance(v, BaseChannel)}, + {k: v for k, v in self.channels.items() if not isinstance(v, BaseChannel)}, + self.input_channels, + self.output_channels, + self.stream_channels, + self.interrupt_after_nodes, + self.interrupt_before_nodes, + ) + self.trigger_to_nodes = _trigger_to_nodes(self.nodes) + return self + + def config_schema(self, *, include: Sequence[str] | None = None) -> type[BaseModel]: + include = include or [] + fields = { + **({"configurable": (self.config_type, None)} if self.config_type else {}), + **{ + field_name: (field_type, None) + for field_name, field_type in get_type_hints(RunnableConfig).items() + if field_name in [i for i in include if i != "configurable"] + }, + } + return create_model(self.get_name("Config"), field_definitions=fields) + + def get_config_jsonschema( + self, *, include: Sequence[str] | None = None + ) -> dict[str, Any]: + schema = self.config_schema(include=include) + if hasattr(schema, "model_json_schema"): + return schema.model_json_schema() + else: + return schema.schema() + + @property + def InputType(self) -> Any: + if isinstance(self.input_channels, str): + channel = self.channels[self.input_channels] + if isinstance(channel, BaseChannel): + return channel.UpdateType + + def get_input_schema(self, config: RunnableConfig | None = None) -> type[BaseModel]: + config = merge_configs(self.config, config) + if isinstance(self.input_channels, str): + return super().get_input_schema(config) + else: + return create_model( + self.get_name("Input"), + field_definitions={ + k: (c.UpdateType, None) + for k in self.input_channels or self.channels.keys() + if (c := self.channels[k]) and isinstance(c, BaseChannel) + }, + ) + + def get_input_jsonschema( + self, config: RunnableConfig | None = None + ) -> dict[str, Any]: + schema = self.get_input_schema(config) + if hasattr(schema, "model_json_schema"): + return schema.model_json_schema() + else: + return schema.schema() + + @property + def OutputType(self) -> Any: + if isinstance(self.output_channels, str): + channel = self.channels[self.output_channels] + if isinstance(channel, BaseChannel): + return channel.ValueType + + def get_output_schema( + self, config: RunnableConfig | None = None + ) -> type[BaseModel]: + config = merge_configs(self.config, config) + if isinstance(self.output_channels, str): + return super().get_output_schema(config) + else: + return create_model( + self.get_name("Output"), + field_definitions={ + k: (c.ValueType, None) + for k in self.output_channels + if (c := self.channels[k]) and isinstance(c, BaseChannel) + }, + ) + + def get_output_jsonschema( + self, config: RunnableConfig | None = None + ) -> dict[str, Any]: + schema = self.get_output_schema(config) + if hasattr(schema, "model_json_schema"): + return schema.model_json_schema() + else: + return schema.schema() + + @property + def stream_channels_list(self) -> Sequence[str]: + stream_channels = self.stream_channels_asis + return ( + [stream_channels] if isinstance(stream_channels, str) else stream_channels + ) + + @property + def stream_channels_asis(self) -> str | Sequence[str]: + return self.stream_channels or [ + k for k in self.channels if isinstance(self.channels[k], BaseChannel) + ] + + def get_subgraphs( + self, *, namespace: str | None = None, recurse: bool = False + ) -> Iterator[tuple[str, PregelProtocol]]: + """Get the subgraphs of the graph. + + Args: + namespace: The namespace to filter the subgraphs by. + recurse: Whether to recurse into the subgraphs. + If False, only the immediate subgraphs will be returned. + + Returns: + Iterator[tuple[str, PregelProtocol]]: An iterator of the (namespace, subgraph) pairs. + """ + for name, node in self.nodes.items(): + # filter by prefix + if namespace is not None: + if not namespace.startswith(name): + continue + + # find the subgraph, if any + graph = node.subgraphs[0] if node.subgraphs else None + + # if found, yield recursively + if graph: + if name == namespace: + yield name, graph + return # we found it, stop searching + if namespace is None: + yield name, graph + if recurse and isinstance(graph, Pregel): + if namespace is not None: + namespace = namespace[len(name) + 1 :] + yield from ( + (f"{name}{NS_SEP}{n}", s) + for n, s in graph.get_subgraphs( + namespace=namespace, recurse=recurse + ) + ) + + async def aget_subgraphs( + self, *, namespace: str | None = None, recurse: bool = False + ) -> AsyncIterator[tuple[str, PregelProtocol]]: + """Get the subgraphs of the graph. + + Args: + namespace: The namespace to filter the subgraphs by. + recurse: Whether to recurse into the subgraphs. + If False, only the immediate subgraphs will be returned. + + Returns: + AsyncIterator[tuple[str, PregelProtocol]]: An iterator of the (namespace, subgraph) pairs. + """ + for name, node in self.get_subgraphs(namespace=namespace, recurse=recurse): + yield name, node + + def _migrate_checkpoint(self, checkpoint: Checkpoint) -> None: + """Migrate a saved checkpoint to new channel layout.""" + if checkpoint["v"] < 4 and checkpoint.get("pending_sends"): + pending_sends: list[Send] = checkpoint.pop("pending_sends") + checkpoint["channel_values"][TASKS] = pending_sends + checkpoint["channel_versions"][TASKS] = max( + checkpoint["channel_versions"].values() + ) + + def _prepare_state_snapshot( + self, + config: RunnableConfig, + saved: CheckpointTuple | None, + recurse: BaseCheckpointSaver | None = None, + apply_pending_writes: bool = False, + ) -> StateSnapshot: + if not saved: + return StateSnapshot( + values={}, + next=(), + config=config, + metadata=None, + created_at=None, + parent_config=None, + tasks=(), + interrupts=(), + ) + + # migrate checkpoint if needed + self._migrate_checkpoint(saved.checkpoint) + + step = saved.metadata.get("step", -1) + 1 + stop = step + 2 + channels, managed = channels_from_checkpoint( + self.channels, + saved.checkpoint, + ) + # tasks for this checkpoint + next_tasks = prepare_next_tasks( + saved.checkpoint, + saved.pending_writes or [], + self.nodes, + channels, + managed, + saved.config, + step, + stop, + for_execution=True, + store=self.store, + checkpointer=( + self.checkpointer + if isinstance(self.checkpointer, BaseCheckpointSaver) + else None + ), + manager=None, + ) + # get the subgraphs + subgraphs = dict(self.get_subgraphs()) + parent_ns = saved.config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") + task_states: dict[str, RunnableConfig | StateSnapshot] = {} + for task in next_tasks.values(): + if task.name not in subgraphs: + continue + # assemble checkpoint_ns for this task + task_ns = f"{task.name}{NS_END}{task.id}" + if parent_ns: + task_ns = f"{parent_ns}{NS_SEP}{task_ns}" + if not recurse: + # set config as signal that subgraph checkpoints exist + config = { + CONF: { + "thread_id": saved.config[CONF]["thread_id"], + CONFIG_KEY_CHECKPOINT_NS: task_ns, + } + } + task_states[task.id] = config + else: + # get the state of the subgraph + config = { + CONF: { + CONFIG_KEY_CHECKPOINTER: recurse, + "thread_id": saved.config[CONF]["thread_id"], + CONFIG_KEY_CHECKPOINT_NS: task_ns, + } + } + task_states[task.id] = subgraphs[task.name].get_state( + config, subgraphs=True + ) + # apply pending writes + if null_writes := [ + w[1:] for w in saved.pending_writes or [] if w[0] == NULL_TASK_ID + ]: + apply_writes( + saved.checkpoint, + channels, + [PregelTaskWrites((), INPUT, null_writes, [])], + None, + self.trigger_to_nodes, + ) + if apply_pending_writes and saved.pending_writes: + for tid, k, v in saved.pending_writes: + if k in (ERROR, INTERRUPT): + continue + if tid not in next_tasks: + continue + next_tasks[tid].writes.append((k, v)) + if tasks := [t for t in next_tasks.values() if t.writes]: + apply_writes( + saved.checkpoint, channels, tasks, None, self.trigger_to_nodes + ) + tasks_with_writes = tasks_w_writes( + next_tasks.values(), + saved.pending_writes, + task_states, + self.stream_channels_asis, + ) + # assemble the state snapshot + return StateSnapshot( + read_channels(channels, self.stream_channels_asis), + tuple(t.name for t in next_tasks.values() if not t.writes), + patch_checkpoint_map(saved.config, saved.metadata), + saved.metadata, + saved.checkpoint["ts"], + patch_checkpoint_map(saved.parent_config, saved.metadata), + tasks_with_writes, + tuple([i for task in tasks_with_writes for i in task.interrupts]), + ) + + async def _aprepare_state_snapshot( + self, + config: RunnableConfig, + saved: CheckpointTuple | None, + recurse: BaseCheckpointSaver | None = None, + apply_pending_writes: bool = False, + ) -> StateSnapshot: + if not saved: + return StateSnapshot( + values={}, + next=(), + config=config, + metadata=None, + created_at=None, + parent_config=None, + tasks=(), + interrupts=(), + ) + + # migrate checkpoint if needed + self._migrate_checkpoint(saved.checkpoint) + + step = saved.metadata.get("step", -1) + 1 + stop = step + 2 + channels, managed = channels_from_checkpoint( + self.channels, + saved.checkpoint, + ) + # tasks for this checkpoint + next_tasks = prepare_next_tasks( + saved.checkpoint, + saved.pending_writes or [], + self.nodes, + channels, + managed, + saved.config, + step, + stop, + for_execution=True, + store=self.store, + checkpointer=( + self.checkpointer + if isinstance(self.checkpointer, BaseCheckpointSaver) + else None + ), + manager=None, + ) + # get the subgraphs + subgraphs = {n: g async for n, g in self.aget_subgraphs()} + parent_ns = saved.config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") + task_states: dict[str, RunnableConfig | StateSnapshot] = {} + for task in next_tasks.values(): + if task.name not in subgraphs: + continue + # assemble checkpoint_ns for this task + task_ns = f"{task.name}{NS_END}{task.id}" + if parent_ns: + task_ns = f"{parent_ns}{NS_SEP}{task_ns}" + if not recurse: + # set config as signal that subgraph checkpoints exist + config = { + CONF: { + "thread_id": saved.config[CONF]["thread_id"], + CONFIG_KEY_CHECKPOINT_NS: task_ns, + } + } + task_states[task.id] = config + else: + # get the state of the subgraph + config = { + CONF: { + CONFIG_KEY_CHECKPOINTER: recurse, + "thread_id": saved.config[CONF]["thread_id"], + CONFIG_KEY_CHECKPOINT_NS: task_ns, + } + } + task_states[task.id] = await subgraphs[task.name].aget_state( + config, subgraphs=True + ) + # apply pending writes + if null_writes := [ + w[1:] for w in saved.pending_writes or [] if w[0] == NULL_TASK_ID + ]: + apply_writes( + saved.checkpoint, + channels, + [PregelTaskWrites((), INPUT, null_writes, [])], + None, + self.trigger_to_nodes, + ) + if apply_pending_writes and saved.pending_writes: + for tid, k, v in saved.pending_writes: + if k in (ERROR, INTERRUPT): + continue + if tid not in next_tasks: + continue + next_tasks[tid].writes.append((k, v)) + if tasks := [t for t in next_tasks.values() if t.writes]: + apply_writes( + saved.checkpoint, channels, tasks, None, self.trigger_to_nodes + ) + + tasks_with_writes = tasks_w_writes( + next_tasks.values(), + saved.pending_writes, + task_states, + self.stream_channels_asis, + ) + # assemble the state snapshot + return StateSnapshot( + read_channels(channels, self.stream_channels_asis), + tuple(t.name for t in next_tasks.values() if not t.writes), + patch_checkpoint_map(saved.config, saved.metadata), + saved.metadata, + saved.checkpoint["ts"], + patch_checkpoint_map(saved.parent_config, saved.metadata), + tasks_with_writes, + tuple([i for task in tasks_with_writes for i in task.interrupts]), + ) + + def get_state( + self, config: RunnableConfig, *, subgraphs: bool = False + ) -> StateSnapshot: + """Get the current state of the graph.""" + checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( + CONFIG_KEY_CHECKPOINTER, self.checkpointer + ) + if not checkpointer: + raise ValueError("No checkpointer set") + + if ( + checkpoint_ns := config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") + ) and CONFIG_KEY_CHECKPOINTER not in config[CONF]: + # remove task_ids from checkpoint_ns + recast = recast_checkpoint_ns(checkpoint_ns) + # find the subgraph with the matching name + for _, pregel in self.get_subgraphs(namespace=recast, recurse=True): + return pregel.get_state( + patch_configurable(config, {CONFIG_KEY_CHECKPOINTER: checkpointer}), + subgraphs=subgraphs, + ) + else: + raise ValueError(f"Subgraph {recast} not found") + + config = merge_configs(self.config, config) if self.config else config + if self.checkpointer is True: + ns = cast(str, config[CONF][CONFIG_KEY_CHECKPOINT_NS]) + config = merge_configs( + config, {CONF: {CONFIG_KEY_CHECKPOINT_NS: recast_checkpoint_ns(ns)}} + ) + thread_id = config[CONF][CONFIG_KEY_THREAD_ID] + if not isinstance(thread_id, str): + config[CONF][CONFIG_KEY_THREAD_ID] = str(thread_id) + + saved = checkpointer.get_tuple(config) + return self._prepare_state_snapshot( + config, + saved, + recurse=checkpointer if subgraphs else None, + apply_pending_writes=CONFIG_KEY_CHECKPOINT_ID not in config[CONF], + ) + + async def aget_state( + self, config: RunnableConfig, *, subgraphs: bool = False + ) -> StateSnapshot: + """Get the current state of the graph.""" + checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( + CONFIG_KEY_CHECKPOINTER, self.checkpointer + ) + if not checkpointer: + raise ValueError("No checkpointer set") + + if ( + checkpoint_ns := config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") + ) and CONFIG_KEY_CHECKPOINTER not in config[CONF]: + # remove task_ids from checkpoint_ns + recast = recast_checkpoint_ns(checkpoint_ns) + # find the subgraph with the matching name + async for _, pregel in self.aget_subgraphs(namespace=recast, recurse=True): + return await pregel.aget_state( + patch_configurable(config, {CONFIG_KEY_CHECKPOINTER: checkpointer}), + subgraphs=subgraphs, + ) + else: + raise ValueError(f"Subgraph {recast} not found") + + config = merge_configs(self.config, config) if self.config else config + if self.checkpointer is True: + ns = cast(str, config[CONF][CONFIG_KEY_CHECKPOINT_NS]) + config = merge_configs( + config, {CONF: {CONFIG_KEY_CHECKPOINT_NS: recast_checkpoint_ns(ns)}} + ) + thread_id = config[CONF][CONFIG_KEY_THREAD_ID] + if not isinstance(thread_id, str): + config[CONF][CONFIG_KEY_THREAD_ID] = str(thread_id) + + saved = await checkpointer.aget_tuple(config) + return await self._aprepare_state_snapshot( + config, + saved, + recurse=checkpointer if subgraphs else None, + apply_pending_writes=CONFIG_KEY_CHECKPOINT_ID not in config[CONF], + ) + + def get_state_history( + self, + config: RunnableConfig, + *, + filter: dict[str, Any] | None = None, + before: RunnableConfig | None = None, + limit: int | None = None, + ) -> Iterator[StateSnapshot]: + """Get the history of the state of the graph.""" + config = ensure_config(config) + checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( + CONFIG_KEY_CHECKPOINTER, self.checkpointer + ) + if not checkpointer: + raise ValueError("No checkpointer set") + + if ( + checkpoint_ns := config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") + ) and CONFIG_KEY_CHECKPOINTER not in config[CONF]: + # remove task_ids from checkpoint_ns + recast = recast_checkpoint_ns(checkpoint_ns) + # find the subgraph with the matching name + for _, pregel in self.get_subgraphs(namespace=recast, recurse=True): + yield from pregel.get_state_history( + patch_configurable(config, {CONFIG_KEY_CHECKPOINTER: checkpointer}), + filter=filter, + before=before, + limit=limit, + ) + return + else: + raise ValueError(f"Subgraph {recast} not found") + + config = merge_configs( + self.config, + config, + { + CONF: { + CONFIG_KEY_CHECKPOINT_NS: checkpoint_ns, + CONFIG_KEY_THREAD_ID: str(config[CONF][CONFIG_KEY_THREAD_ID]), + } + }, + ) + # eagerly consume list() to avoid holding up the db cursor + for checkpoint_tuple in list( + checkpointer.list(config, before=before, limit=limit, filter=filter) + ): + yield self._prepare_state_snapshot( + checkpoint_tuple.config, checkpoint_tuple + ) + + async def aget_state_history( + self, + config: RunnableConfig, + *, + filter: dict[str, Any] | None = None, + before: RunnableConfig | None = None, + limit: int | None = None, + ) -> AsyncIterator[StateSnapshot]: + """Asynchronously get the history of the state of the graph.""" + config = ensure_config(config) + checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( + CONFIG_KEY_CHECKPOINTER, self.checkpointer + ) + if not checkpointer: + raise ValueError("No checkpointer set") + + if ( + checkpoint_ns := config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") + ) and CONFIG_KEY_CHECKPOINTER not in config[CONF]: + # remove task_ids from checkpoint_ns + recast = recast_checkpoint_ns(checkpoint_ns) + # find the subgraph with the matching name + async for _, pregel in self.aget_subgraphs(namespace=recast, recurse=True): + async for state in pregel.aget_state_history( + patch_configurable(config, {CONFIG_KEY_CHECKPOINTER: checkpointer}), + filter=filter, + before=before, + limit=limit, + ): + yield state + return + else: + raise ValueError(f"Subgraph {recast} not found") + + config = merge_configs( + self.config, + config, + { + CONF: { + CONFIG_KEY_CHECKPOINT_NS: checkpoint_ns, + CONFIG_KEY_THREAD_ID: str(config[CONF][CONFIG_KEY_THREAD_ID]), + } + }, + ) + # eagerly consume list() to avoid holding up the db cursor + for checkpoint_tuple in [ + c + async for c in checkpointer.alist( + config, before=before, limit=limit, filter=filter + ) + ]: + yield await self._aprepare_state_snapshot( + checkpoint_tuple.config, checkpoint_tuple + ) + + def bulk_update_state( + self, + config: RunnableConfig, + supersteps: Sequence[Sequence[StateUpdate]], + ) -> RunnableConfig: + """Apply updates to the graph state in bulk. Requires a checkpointer to be set. + + Args: + config: The config to apply the updates to. + supersteps: A list of supersteps, each including a list of updates to apply sequentially to a graph state. + Each update is a tuple of the form `(values, as_node, task_id)` where task_id is optional. + + Raises: + ValueError: If no checkpointer is set or no updates are provided. + InvalidUpdateError: If an invalid update is provided. + + Returns: + RunnableConfig: The updated config. + """ + + checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( + CONFIG_KEY_CHECKPOINTER, self.checkpointer + ) + if not checkpointer: + raise ValueError("No checkpointer set") + + if len(supersteps) == 0: + raise ValueError("No supersteps provided") + + if any(len(u) == 0 for u in supersteps): + raise ValueError("No updates provided") + + # delegate to subgraph + if ( + checkpoint_ns := config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") + ) and CONFIG_KEY_CHECKPOINTER not in config[CONF]: + # remove task_ids from checkpoint_ns + recast = recast_checkpoint_ns(checkpoint_ns) + # find the subgraph with the matching name + for _, pregel in self.get_subgraphs(namespace=recast, recurse=True): + return pregel.bulk_update_state( + patch_configurable(config, {CONFIG_KEY_CHECKPOINTER: checkpointer}), + supersteps, + ) + else: + raise ValueError(f"Subgraph {recast} not found") + + def perform_superstep( + input_config: RunnableConfig, updates: Sequence[StateUpdate] + ) -> RunnableConfig: + # get last checkpoint + config = ensure_config(self.config, input_config) + saved = checkpointer.get_tuple(config) + if saved is not None: + self._migrate_checkpoint(saved.checkpoint) + checkpoint = ( + copy_checkpoint(saved.checkpoint) if saved else empty_checkpoint() + ) + checkpoint_previous_versions = ( + saved.checkpoint["channel_versions"].copy() if saved else {} + ) + step = saved.metadata.get("step", -1) if saved else -1 + # merge configurable fields with previous checkpoint config + checkpoint_config = patch_configurable( + config, + { + CONFIG_KEY_CHECKPOINT_NS: config[CONF].get( + CONFIG_KEY_CHECKPOINT_NS, "" + ) + }, + ) + if saved: + checkpoint_config = patch_configurable(config, saved.config[CONF]) + channels, managed = channels_from_checkpoint( + self.channels, + checkpoint, + ) + values, as_node = updates[0][:2] + + # no values as END, just clear all tasks + if values is None and as_node == END: + if len(updates) > 1: + raise InvalidUpdateError( + "Cannot apply multiple updates when clearing state" + ) + + if saved is not None: + # tasks for this checkpoint + next_tasks = prepare_next_tasks( + checkpoint, + saved.pending_writes or [], + self.nodes, + channels, + managed, + saved.config, + step + 1, + step + 3, + for_execution=True, + store=self.store, + checkpointer=checkpointer, + manager=None, + ) + # apply null writes + if null_writes := [ + w[1:] + for w in saved.pending_writes or [] + if w[0] == NULL_TASK_ID + ]: + apply_writes( + checkpoint, + channels, + [PregelTaskWrites((), INPUT, null_writes, [])], + checkpointer.get_next_version, + self.trigger_to_nodes, + ) + # apply writes from tasks that already ran + for tid, k, v in saved.pending_writes or []: + if k in (ERROR, INTERRUPT): + continue + if tid not in next_tasks: + continue + next_tasks[tid].writes.append((k, v)) + # clear all current tasks + apply_writes( + checkpoint, + channels, + next_tasks.values(), + checkpointer.get_next_version, + self.trigger_to_nodes, + ) + # save checkpoint + next_config = checkpointer.put( + checkpoint_config, + create_checkpoint(checkpoint, channels, step), + { + "source": "update", + "step": step + 1, + "parents": saved.metadata.get("parents", {}) if saved else {}, + }, + get_new_channel_versions( + checkpoint_previous_versions, + checkpoint["channel_versions"], + ), + ) + return patch_checkpoint_map( + next_config, saved.metadata if saved else None + ) + + # act as an input + if as_node == INPUT: + if len(updates) > 1: + raise InvalidUpdateError( + "Cannot apply multiple updates when updating as input" + ) + + if input_writes := deque(map_input(self.input_channels, values)): + apply_writes( + checkpoint, + channels, + [PregelTaskWrites((), INPUT, input_writes, [])], + checkpointer.get_next_version, + self.trigger_to_nodes, + ) + + # apply input write to channels + next_step = ( + step + 1 + if saved and saved.metadata.get("step") is not None + else -1 + ) + next_config = checkpointer.put( + checkpoint_config, + create_checkpoint(checkpoint, channels, next_step), + { + "source": "input", + "step": next_step, + "parents": saved.metadata.get("parents", {}) + if saved + else {}, + }, + get_new_channel_versions( + checkpoint_previous_versions, + checkpoint["channel_versions"], + ), + ) + + # store the writes + checkpointer.put_writes( + next_config, + input_writes, + str(uuid5(UUID(checkpoint["id"]), INPUT)), + ) + + return patch_checkpoint_map( + next_config, saved.metadata if saved else None + ) + else: + raise InvalidUpdateError( + f"Received no input writes for {self.input_channels}" + ) + + # copy checkpoint + if as_node == "__copy__": + if len(updates) > 1: + raise InvalidUpdateError( + "Cannot copy checkpoint with multiple updates" + ) + + if saved is None: + raise InvalidUpdateError("Cannot copy a non-existent checkpoint") + + next_checkpoint = create_checkpoint(checkpoint, None, step) + + # copy checkpoint + next_config = checkpointer.put( + saved.parent_config + or patch_configurable( + saved.config, {CONFIG_KEY_CHECKPOINT_ID: None} + ), + next_checkpoint, + { + "source": "fork", + "step": step + 1, + "parents": saved.metadata.get("parents", {}), + }, + {}, + ) + + # we want to both clone a checkpoint and update state in one go. + # reuse the same task ID if possible. + if isinstance(values, list) and len(values) > 0: + # figure out the task IDs for the next update checkpoint + next_tasks = prepare_next_tasks( + next_checkpoint, + saved.pending_writes or [], + self.nodes, + channels, + managed, + next_config, + step + 2, + step + 4, + for_execution=True, + store=self.store, + checkpointer=checkpointer, + manager=None, + ) + + tasks_group_by = defaultdict(list) + user_group_by: dict[str, list[StateUpdate]] = defaultdict(list) + + for task in next_tasks.values(): + tasks_group_by[task.name].append(task.id) + + for item in values: + if not isinstance(item, Sequence): + raise InvalidUpdateError( + f"Invalid update item: {item} when copying checkpoint" + ) + + values, as_node = item[:2] + + user_group = user_group_by[as_node] + tasks_group = tasks_group_by[as_node] + + target_idx = len(user_group) + task_id = ( + tasks_group[target_idx] + if target_idx < len(tasks_group) + else None + ) + + user_group_by[as_node].append( + StateUpdate(values=values, as_node=as_node, task_id=task_id) + ) + + return perform_superstep( + patch_checkpoint_map(next_config, saved.metadata), + [item for lst in user_group_by.values() for item in lst], + ) + + return patch_checkpoint_map(next_config, saved.metadata) + + # apply pending writes, if not on specific checkpoint + if ( + CONFIG_KEY_CHECKPOINT_ID not in config[CONF] + and saved is not None + and saved.pending_writes + ): + # tasks for this checkpoint + next_tasks = prepare_next_tasks( + checkpoint, + saved.pending_writes, + self.nodes, + channels, + managed, + saved.config, + step + 1, + step + 3, + for_execution=True, + store=self.store, + checkpointer=checkpointer, + manager=None, + ) + # apply null writes + if null_writes := [ + w[1:] for w in saved.pending_writes or [] if w[0] == NULL_TASK_ID + ]: + apply_writes( + checkpoint, + channels, + [PregelTaskWrites((), INPUT, null_writes, [])], + checkpointer.get_next_version, + self.trigger_to_nodes, + ) + # apply writes + for tid, k, v in saved.pending_writes: + if k in (ERROR, INTERRUPT): + continue + if tid not in next_tasks: + continue + next_tasks[tid].writes.append((k, v)) + if tasks := [t for t in next_tasks.values() if t.writes]: + apply_writes( + checkpoint, + channels, + tasks, + checkpointer.get_next_version, + self.trigger_to_nodes, + ) + valid_updates: list[tuple[str, dict[str, Any] | None, str | None]] = [] + if len(updates) == 1: + values, as_node, task_id = updates[0] + # find last node that updated the state, if not provided + if as_node is None and len(self.nodes) == 1: + as_node = tuple(self.nodes)[0] + elif as_node is None and not any( + v + for vv in checkpoint["versions_seen"].values() + for v in vv.values() + ): + if ( + isinstance(self.input_channels, str) + and self.input_channels in self.nodes + ): + as_node = self.input_channels + elif as_node is None: + last_seen_by_node = sorted( + (v, n) + for n, seen in checkpoint["versions_seen"].items() + if n in self.nodes + for v in seen.values() + ) + # if two nodes updated the state at the same time, it's ambiguous + if last_seen_by_node: + if len(last_seen_by_node) == 1: + as_node = last_seen_by_node[0][1] + elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]: + as_node = last_seen_by_node[-1][1] + if as_node is None: + raise InvalidUpdateError("Ambiguous update, specify as_node") + if as_node not in self.nodes: + raise InvalidUpdateError(f"Node {as_node} does not exist") + valid_updates.append((as_node, values, task_id)) + else: + for values, as_node, task_id in updates: + if as_node is None: + raise InvalidUpdateError( + "as_node is required when applying multiple updates" + ) + if as_node not in self.nodes: + raise InvalidUpdateError(f"Node {as_node} does not exist") + + valid_updates.append((as_node, values, task_id)) + + run_tasks: list[PregelTaskWrites] = [] + run_task_ids: list[str] = [] + + for as_node, values, provided_task_id in valid_updates: + # create task to run all writers of the chosen node + writers = self.nodes[as_node].flat_writers + if not writers: + raise InvalidUpdateError(f"Node {as_node} has no writers") + writes: deque[tuple[str, Any]] = deque() + task = PregelTaskWrites((), as_node, writes, [INTERRUPT]) + task_id = provided_task_id or str( + uuid5(UUID(checkpoint["id"]), INTERRUPT) + ) + run_tasks.append(task) + run_task_ids.append(task_id) + run = RunnableSequence(*writers) if len(writers) > 1 else writers[0] + # execute task + run.invoke( + values, + patch_config( + config, + run_name=self.name + "UpdateState", + configurable={ + # deque.extend is thread-safe + CONFIG_KEY_SEND: writes.extend, + CONFIG_KEY_TASK_ID: task_id, + CONFIG_KEY_READ: partial( + local_read, + _scratchpad( + None, + [], + task_id, + "", + None, + step, + step + 2, + ), + channels, + managed, + task, + ), + }, + ), + ) + # save task writes + for task_id, task in zip(run_task_ids, run_tasks): + # channel writes are saved to current checkpoint + channel_writes = [w for w in task.writes if w[0] != PUSH] + if saved and channel_writes: + checkpointer.put_writes(checkpoint_config, channel_writes, task_id) + # apply to checkpoint and save + apply_writes( + checkpoint, + channels, + run_tasks, + checkpointer.get_next_version, + self.trigger_to_nodes, + ) + checkpoint = create_checkpoint(checkpoint, channels, step + 1) + next_config = checkpointer.put( + checkpoint_config, + checkpoint, + { + "source": "update", + "step": step + 1, + "parents": saved.metadata.get("parents", {}) if saved else {}, + }, + get_new_channel_versions( + checkpoint_previous_versions, checkpoint["channel_versions"] + ), + ) + for task_id, task in zip(run_task_ids, run_tasks): + # save push writes + if push_writes := [w for w in task.writes if w[0] == PUSH]: + checkpointer.put_writes(next_config, push_writes, task_id) + + return patch_checkpoint_map(next_config, saved.metadata if saved else None) + + current_config = patch_configurable( + config, {CONFIG_KEY_THREAD_ID: str(config[CONF][CONFIG_KEY_THREAD_ID])} + ) + for superstep in supersteps: + current_config = perform_superstep(current_config, superstep) + return current_config + + async def abulk_update_state( + self, + config: RunnableConfig, + supersteps: Sequence[Sequence[StateUpdate]], + ) -> RunnableConfig: + """Asynchronously apply updates to the graph state in bulk. Requires a checkpointer to be set. + + Args: + config: The config to apply the updates to. + supersteps: A list of supersteps, each including a list of updates to apply sequentially to a graph state. + Each update is a tuple of the form `(values, as_node, task_id)` where task_id is optional. + + Raises: + ValueError: If no checkpointer is set or no updates are provided. + InvalidUpdateError: If an invalid update is provided. + + Returns: + RunnableConfig: The updated config. + """ + + checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( + CONFIG_KEY_CHECKPOINTER, self.checkpointer + ) + if not checkpointer: + raise ValueError("No checkpointer set") + + if len(supersteps) == 0: + raise ValueError("No supersteps provided") + + if any(len(u) == 0 for u in supersteps): + raise ValueError("No updates provided") + + # delegate to subgraph + if ( + checkpoint_ns := config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") + ) and CONFIG_KEY_CHECKPOINTER not in config[CONF]: + # remove task_ids from checkpoint_ns + recast = recast_checkpoint_ns(checkpoint_ns) + # find the subgraph with the matching name + async for _, pregel in self.aget_subgraphs(namespace=recast, recurse=True): + return await pregel.abulk_update_state( + patch_configurable(config, {CONFIG_KEY_CHECKPOINTER: checkpointer}), + supersteps, + ) + else: + raise ValueError(f"Subgraph {recast} not found") + + async def aperform_superstep( + input_config: RunnableConfig, updates: Sequence[StateUpdate] + ) -> RunnableConfig: + # get last checkpoint + config = ensure_config(self.config, input_config) + saved = await checkpointer.aget_tuple(config) + if saved is not None: + self._migrate_checkpoint(saved.checkpoint) + checkpoint = ( + copy_checkpoint(saved.checkpoint) if saved else empty_checkpoint() + ) + checkpoint_previous_versions = ( + saved.checkpoint["channel_versions"].copy() if saved else {} + ) + step = saved.metadata.get("step", -1) if saved else -1 + # merge configurable fields with previous checkpoint config + checkpoint_config = patch_configurable( + config, + { + CONFIG_KEY_CHECKPOINT_NS: config[CONF].get( + CONFIG_KEY_CHECKPOINT_NS, "" + ) + }, + ) + if saved: + checkpoint_config = patch_configurable(config, saved.config[CONF]) + channels, managed = channels_from_checkpoint( + self.channels, + checkpoint, + ) + values, as_node = updates[0][:2] + # no values, just clear all tasks + if values is None and as_node == END: + if len(updates) > 1: + raise InvalidUpdateError( + "Cannot apply multiple updates when clearing state" + ) + if saved is not None: + # tasks for this checkpoint + next_tasks = prepare_next_tasks( + checkpoint, + saved.pending_writes or [], + self.nodes, + channels, + managed, + saved.config, + step + 1, + step + 3, + for_execution=True, + store=self.store, + checkpointer=checkpointer, + manager=None, + ) + # apply null writes + if null_writes := [ + w[1:] + for w in saved.pending_writes or [] + if w[0] == NULL_TASK_ID + ]: + apply_writes( + checkpoint, + channels, + [PregelTaskWrites((), INPUT, null_writes, [])], + checkpointer.get_next_version, + self.trigger_to_nodes, + ) + # apply writes from tasks that already ran + for tid, k, v in saved.pending_writes or []: + if k in (ERROR, INTERRUPT): + continue + if tid not in next_tasks: + continue + next_tasks[tid].writes.append((k, v)) + # clear all current tasks + apply_writes( + checkpoint, + channels, + next_tasks.values(), + checkpointer.get_next_version, + self.trigger_to_nodes, + ) + # save checkpoint + next_config = await checkpointer.aput( + checkpoint_config, + create_checkpoint(checkpoint, channels, step), + { + "source": "update", + "step": step + 1, + "parents": saved.metadata.get("parents", {}) if saved else {}, + }, + get_new_channel_versions( + checkpoint_previous_versions, checkpoint["channel_versions"] + ), + ) + return patch_checkpoint_map( + next_config, saved.metadata if saved else None + ) + + # act as an input + if as_node == INPUT: + if len(updates) > 1: + raise InvalidUpdateError( + "Cannot apply multiple updates when updating as input" + ) + + if input_writes := deque(map_input(self.input_channels, values)): + apply_writes( + checkpoint, + channels, + [PregelTaskWrites((), INPUT, input_writes, [])], + checkpointer.get_next_version, + self.trigger_to_nodes, + ) + + # apply input write to channels + next_step = ( + step + 1 + if saved and saved.metadata.get("step") is not None + else -1 + ) + next_config = await checkpointer.aput( + checkpoint_config, + create_checkpoint(checkpoint, channels, next_step), + { + "source": "input", + "step": next_step, + "parents": saved.metadata.get("parents", {}) + if saved + else {}, + }, + get_new_channel_versions( + checkpoint_previous_versions, + checkpoint["channel_versions"], + ), + ) + + # store the writes + await checkpointer.aput_writes( + next_config, + input_writes, + str(uuid5(UUID(checkpoint["id"]), INPUT)), + ) + + return patch_checkpoint_map( + next_config, saved.metadata if saved else None + ) + else: + raise InvalidUpdateError( + f"Received no input writes for {self.input_channels}" + ) + + # no values, copy checkpoint + if as_node == "__copy__": + if len(updates) > 1: + raise InvalidUpdateError( + "Cannot copy checkpoint with multiple updates" + ) + + if saved is None: + raise InvalidUpdateError("Cannot copy a non-existent checkpoint") + + next_checkpoint = create_checkpoint(checkpoint, None, step) + + # copy checkpoint + next_config = await checkpointer.aput( + saved.parent_config + or patch_configurable( + saved.config, {CONFIG_KEY_CHECKPOINT_ID: None} + ), + next_checkpoint, + { + "source": "fork", + "step": step + 1, + "parents": saved.metadata.get("parents", {}), + }, + {}, + ) + + # we want to both clone a checkpoint and update state in one go. + # reuse the same task ID if possible. + if isinstance(values, list) and len(values) > 0: + # figure out the task IDs for the next update checkpoint + next_tasks = prepare_next_tasks( + next_checkpoint, + saved.pending_writes or [], + self.nodes, + channels, + managed, + next_config, + step + 2, + step + 4, + for_execution=True, + store=self.store, + checkpointer=checkpointer, + manager=None, + ) + + tasks_group_by = defaultdict(list) + user_group_by: dict[str, list[StateUpdate]] = defaultdict(list) + + for task in next_tasks.values(): + tasks_group_by[task.name].append(task.id) + + for item in values: + if not isinstance(item, Sequence): + raise InvalidUpdateError( + f"Invalid update item: {item} when copying checkpoint" + ) + + values, as_node = item[:2] + user_group = user_group_by[as_node] + tasks_group = tasks_group_by[as_node] + + target_idx = len(user_group) + task_id = ( + tasks_group[target_idx] + if target_idx < len(tasks_group) + else None + ) + + user_group_by[as_node].append( + StateUpdate(values=values, as_node=as_node, task_id=task_id) + ) + + return await aperform_superstep( + patch_checkpoint_map(next_config, saved.metadata), + [item for lst in user_group_by.values() for item in lst], + ) + + return patch_checkpoint_map( + next_config, saved.metadata if saved else None + ) + # apply pending writes, if not on specific checkpoint + if ( + CONFIG_KEY_CHECKPOINT_ID not in config[CONF] + and saved is not None + and saved.pending_writes + ): + # tasks for this checkpoint + next_tasks = prepare_next_tasks( + checkpoint, + saved.pending_writes, + self.nodes, + channels, + managed, + saved.config, + step + 1, + step + 3, + for_execution=True, + store=self.store, + checkpointer=checkpointer, + manager=None, + ) + # apply null writes + if null_writes := [ + w[1:] for w in saved.pending_writes or [] if w[0] == NULL_TASK_ID + ]: + apply_writes( + checkpoint, + channels, + [PregelTaskWrites((), INPUT, null_writes, [])], + checkpointer.get_next_version, + self.trigger_to_nodes, + ) + for tid, k, v in saved.pending_writes: + if k in (ERROR, INTERRUPT): + continue + if tid not in next_tasks: + continue + next_tasks[tid].writes.append((k, v)) + if tasks := [t for t in next_tasks.values() if t.writes]: + apply_writes( + checkpoint, + channels, + tasks, + checkpointer.get_next_version, + self.trigger_to_nodes, + ) + valid_updates: list[tuple[str, dict[str, Any] | None, str | None]] = [] + if len(updates) == 1: + values, as_node, task_id = updates[0] + # find last node that updated the state, if not provided + if as_node is None and len(self.nodes) == 1: + as_node = tuple(self.nodes)[0] + elif as_node is None and not saved: + if ( + isinstance(self.input_channels, str) + and self.input_channels in self.nodes + ): + as_node = self.input_channels + elif as_node is None: + last_seen_by_node = sorted( + (v, n) + for n, seen in checkpoint["versions_seen"].items() + if n in self.nodes + for v in seen.values() + ) + # if two nodes updated the state at the same time, it's ambiguous + if last_seen_by_node: + if len(last_seen_by_node) == 1: + as_node = last_seen_by_node[0][1] + elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]: + as_node = last_seen_by_node[-1][1] + if as_node is None: + raise InvalidUpdateError("Ambiguous update, specify as_node") + if as_node not in self.nodes: + raise InvalidUpdateError(f"Node {as_node} does not exist") + valid_updates.append((as_node, values, task_id)) + else: + for values, as_node, task_id in updates: + if as_node is None: + raise InvalidUpdateError( + "as_node is required when applying multiple updates" + ) + if as_node not in self.nodes: + raise InvalidUpdateError(f"Node {as_node} does not exist") + + valid_updates.append((as_node, values, task_id)) + + run_tasks: list[PregelTaskWrites] = [] + run_task_ids: list[str] = [] + + for as_node, values, provided_task_id in valid_updates: + # create task to run all writers of the chosen node + writers = self.nodes[as_node].flat_writers + if not writers: + raise InvalidUpdateError(f"Node {as_node} has no writers") + writes: deque[tuple[str, Any]] = deque() + task = PregelTaskWrites((), as_node, writes, [INTERRUPT]) + task_id = provided_task_id or str( + uuid5(UUID(checkpoint["id"]), INTERRUPT) + ) + run_tasks.append(task) + run_task_ids.append(task_id) + run = RunnableSequence(*writers) if len(writers) > 1 else writers[0] + # execute task + await run.ainvoke( + values, + patch_config( + config, + run_name=self.name + "UpdateState", + configurable={ + # deque.extend is thread-safe + CONFIG_KEY_SEND: writes.extend, + CONFIG_KEY_TASK_ID: task_id, + CONFIG_KEY_READ: partial( + local_read, + _scratchpad( + None, + [], + task_id, + "", + None, + step, + step + 2, + ), + channels, + managed, + task, + ), + }, + ), + ) + # save task writes + for task_id, task in zip(run_task_ids, run_tasks): + # channel writes are saved to current checkpoint + channel_writes = [w for w in task.writes if w[0] != PUSH] + if saved and channel_writes: + await checkpointer.aput_writes( + checkpoint_config, channel_writes, task_id + ) + # apply to checkpoint and save + apply_writes( + checkpoint, + channels, + run_tasks, + checkpointer.get_next_version, + self.trigger_to_nodes, + ) + checkpoint = create_checkpoint(checkpoint, channels, step + 1) + # save checkpoint, after applying writes + next_config = await checkpointer.aput( + checkpoint_config, + checkpoint, + { + "source": "update", + "step": step + 1, + "parents": saved.metadata.get("parents", {}) if saved else {}, + }, + get_new_channel_versions( + checkpoint_previous_versions, checkpoint["channel_versions"] + ), + ) + for task_id, task in zip(run_task_ids, run_tasks): + # save push writes + if push_writes := [w for w in task.writes if w[0] == PUSH]: + await checkpointer.aput_writes(next_config, push_writes, task_id) + return patch_checkpoint_map(next_config, saved.metadata if saved else None) + + current_config = patch_configurable( + config, {CONFIG_KEY_THREAD_ID: str(config[CONF][CONFIG_KEY_THREAD_ID])} + ) + for superstep in supersteps: + current_config = await aperform_superstep(current_config, superstep) + return current_config + + def update_state( + self, + config: RunnableConfig, + values: dict[str, Any] | Any | None, + as_node: str | None = None, + task_id: str | None = None, + ) -> RunnableConfig: + """Update the state of the graph with the given values, as if they came from + node `as_node`. If `as_node` is not provided, it will be set to the last node + that updated the state, if not ambiguous. + """ + return self.bulk_update_state(config, [[StateUpdate(values, as_node, task_id)]]) + + async def aupdate_state( + self, + config: RunnableConfig, + values: dict[str, Any] | Any, + as_node: str | None = None, + task_id: str | None = None, + ) -> RunnableConfig: + """Asynchronously update the state of the graph with the given values, as if they came from + node `as_node`. If `as_node` is not provided, it will be set to the last node + that updated the state, if not ambiguous. + """ + return await self.abulk_update_state( + config, [[StateUpdate(values, as_node, task_id)]] + ) + + def _defaults( + self, + config: RunnableConfig, + *, + stream_mode: StreamMode | Sequence[StreamMode], + print_mode: StreamMode | Sequence[StreamMode], + output_keys: str | Sequence[str] | None, + interrupt_before: All | Sequence[str] | None, + interrupt_after: All | Sequence[str] | None, + ) -> tuple[ + set[StreamMode], + str | Sequence[str], + All | Sequence[str], + All | Sequence[str], + BaseCheckpointSaver | None, + BaseStore | None, + BaseCache | None, + ]: + if config["recursion_limit"] < 1: + raise ValueError("recursion_limit must be at least 1") + if output_keys is None: + output_keys = self.stream_channels_asis + else: + validate_keys(output_keys, self.channels) + interrupt_before = interrupt_before or self.interrupt_before_nodes + interrupt_after = interrupt_after or self.interrupt_after_nodes + if not isinstance(stream_mode, list): + stream_modes = {stream_mode} + else: + stream_modes = set(stream_mode) + if isinstance(print_mode, str): + stream_modes.add(print_mode) + else: + stream_modes.update(print_mode) + if self.checkpointer is False: + checkpointer: BaseCheckpointSaver | None = None + elif CONFIG_KEY_CHECKPOINTER in config.get(CONF, {}): + checkpointer = config[CONF][CONFIG_KEY_CHECKPOINTER] + elif self.checkpointer is True: + raise RuntimeError("checkpointer=True cannot be used for root graphs.") + else: + checkpointer = self.checkpointer + if checkpointer and not config.get(CONF): + raise ValueError( + "Checkpointer requires one or more of the following 'configurable' " + "keys: thread_id, checkpoint_ns, checkpoint_id" + ) + if CONFIG_KEY_STORE in config.get(CONF, {}): + store: BaseStore | None = config[CONF][CONFIG_KEY_STORE] + else: + store = self.store + if CONFIG_KEY_CACHE in config.get(CONF, {}): + cache: BaseCache | None = config[CONF][CONFIG_KEY_CACHE] + else: + cache = self.cache + return ( + stream_modes, + output_keys, + interrupt_before, + interrupt_after, + checkpointer, + store, + cache, + ) + + def stream( + self, + input: InputT, + config: RunnableConfig | None = None, + *, + stream_mode: StreamMode | Sequence[StreamMode] | None = None, + print_mode: StreamMode | Sequence[StreamMode] = (), + output_keys: str | Sequence[str] | None = None, + interrupt_before: All | Sequence[str] | None = None, + interrupt_after: All | Sequence[str] | None = None, + checkpoint_during: bool | None = None, + debug: bool | None = None, + subgraphs: bool = False, + ) -> Iterator[dict[str, Any] | Any]: + """Stream graph steps for a single input. + + Args: + input: The input to the graph. + config: The configuration to use for the run. + stream_mode: The mode to stream output, defaults to `self.stream_mode`. + Options are: + + - `"values"`: Emit all values in the state after each step, including interrupts. + When used with functional API, values are emitted once at the end of the workflow. + - `"updates"`: Emit only the node or task names and updates returned by the nodes or tasks after each step. + If multiple updates are made in the same step (e.g. multiple nodes are run) then those updates are emitted separately. + - `"custom"`: Emit custom data from inside nodes or tasks using `StreamWriter`. + - `"messages"`: Emit LLM messages token-by-token together with metadata for any LLM invocations inside nodes or tasks. + Will be emitted as 2-tuples `(LLM token, metadata)`. + - `"checkpoints"`: Emit an event when a checkpoint is created, in the same format as returned by get_state(). + - `"tasks"`: Emit events when tasks start and finish, including their results and errors. + + You can pass a list as the `stream_mode` parameter to stream multiple modes at once. + The streamed outputs will be tuples of `(mode, data)`. + + See [LangGraph streaming guide](https://langchain-ai.github.io/langgraph/how-tos/streaming/) for more details. + print_mode: Accepts the same values as `stream_mode`, but only prints the output to the console, for debugging purposes. Does not affect the output of the graph in any way. + output_keys: The keys to stream, defaults to all non-context channels. + interrupt_before: Nodes to interrupt before, defaults to all nodes in the graph. + interrupt_after: Nodes to interrupt after, defaults to all nodes in the graph. + checkpoint_during: Whether to checkpoint intermediate steps, defaults to False. If False, only the final checkpoint is saved. + subgraphs: Whether to stream events from inside subgraphs, defaults to False. + If True, the events will be emitted as tuples `(namespace, data)`, + or `(namespace, mode, data)` if `stream_mode` is a list, + where `namespace` is a tuple with the path to the node where a subgraph is invoked, + e.g. `("parent_node:", "child_node:")`. + + See [LangGraph streaming guide](https://langchain-ai.github.io/langgraph/how-tos/streaming/) for more details. + + Yields: + The output of each step in the graph. The output shape depends on the stream_mode. + """ + + if stream_mode is None: + # if being called as a node in another graph, default to values mode + # but don't overwrite stream_mode arg if provided + stream_mode = ( + "values" + if config is not None and CONFIG_KEY_TASK_ID in config.get(CONF, {}) + else self.stream_mode + ) + if debug or self.debug: + print_mode = ["updates", "values"] + + stream = SyncQueue() + + config = ensure_config(self.config, config) + callback_manager = get_callback_manager_for_config(config) + run_manager = callback_manager.on_chain_start( + None, + input, + name=config.get("run_name", self.get_name()), + run_id=config.get("run_id"), + ) + try: + # assign defaults + ( + stream_modes, + output_keys, + interrupt_before_, + interrupt_after_, + checkpointer, + store, + cache, + ) = self._defaults( + config, + stream_mode=stream_mode, + print_mode=print_mode, + output_keys=output_keys, + interrupt_before=interrupt_before, + interrupt_after=interrupt_after, + ) + # set up subgraph checkpointing + if self.checkpointer is True: + ns = cast(str, config[CONF][CONFIG_KEY_CHECKPOINT_NS]) + config[CONF][CONFIG_KEY_CHECKPOINT_NS] = recast_checkpoint_ns(ns) + # set up messages stream mode + if "messages" in stream_modes: + run_manager.inheritable_handlers.append( + StreamMessagesHandler(stream.put, subgraphs) + ) + # set up custom stream mode + if "custom" in stream_modes: + config[CONF][CONFIG_KEY_STREAM_WRITER] = lambda c: stream.put( + ( + tuple( + get_config()[CONF][CONFIG_KEY_CHECKPOINT_NS].split(NS_SEP)[ + :-1 + ] + ), + "custom", + c, + ) + ) + elif ( + CONFIG_KEY_STREAM not in config[CONF] + and CONFIG_KEY_STREAM_WRITER in config[CONF] + ): + # remove parent graph stream writer if subgraph streaming not requested + del config[CONF][CONFIG_KEY_STREAM_WRITER] + # set checkpointing mode for subgraphs + if checkpoint_during is not None: + config[CONF][CONFIG_KEY_CHECKPOINT_DURING] = checkpoint_during + with SyncPregelLoop( + input, + stream=StreamProtocol(stream.put, stream_modes), + config=config, + store=store, + cache=cache, + checkpointer=checkpointer, + nodes=self.nodes, + specs=self.channels, + output_keys=output_keys, + input_keys=self.input_channels, + stream_keys=self.stream_channels_asis, + interrupt_before=interrupt_before_, + interrupt_after=interrupt_after_, + manager=run_manager, + checkpoint_during=checkpoint_during + if checkpoint_during is not None + else config[CONF].get(CONFIG_KEY_CHECKPOINT_DURING, True), + trigger_to_nodes=self.trigger_to_nodes, + migrate_checkpoint=self._migrate_checkpoint, + retry_policy=self.retry_policy, + cache_policy=self.cache_policy, + ) as loop: + # create runner + runner = PregelRunner( + submit=config[CONF].get( + CONFIG_KEY_RUNNER_SUBMIT, weakref.WeakMethod(loop.submit) + ), + put_writes=weakref.WeakMethod(loop.put_writes), + node_finished=config[CONF].get(CONFIG_KEY_NODE_FINISHED), + ) + # enable subgraph streaming + if subgraphs: + loop.config[CONF][CONFIG_KEY_STREAM] = loop.stream + # enable concurrent streaming + if ( + self.stream_eager + or subgraphs + or "messages" in stream_modes + or "custom" in stream_modes + ): + # we are careful to have a single waiter live at any one time + # because on exit we increment semaphore count by exactly 1 + waiter: concurrent.futures.Future | None = None + # because sync futures cannot be cancelled, we instead + # release the stream semaphore on exit, which will cause + # a pending waiter to return immediately + loop.stack.callback(stream._count.release) + + def get_waiter() -> concurrent.futures.Future[None]: + nonlocal waiter + if waiter is None or waiter.done(): + waiter = loop.submit(stream.wait) + return waiter + else: + return waiter + + else: + get_waiter = None # type: ignore[assignment] + # Similarly to Bulk Synchronous Parallel / Pregel model + # computation proceeds in steps, while there are channel updates. + # Channel updates from step N are only visible in step N+1 + # channels are guaranteed to be immutable for the duration of the step, + # with channel updates applied only at the transition between steps. + while loop.tick(): + for task in loop.match_cached_writes(): + loop.output_writes(task.id, task.writes, cached=True) + for _ in runner.tick( + [t for t in loop.tasks.values() if not t.writes], + timeout=self.step_timeout, + get_waiter=get_waiter, + schedule_task=loop.accept_push, + ): + # emit output + yield from _output( + stream_mode, print_mode, subgraphs, stream.get, queue.Empty + ) + loop.after_tick() + # emit output + yield from _output( + stream_mode, print_mode, subgraphs, stream.get, queue.Empty + ) + # handle exit + if loop.status == "out_of_steps": + msg = create_error_message( + message=( + f"Recursion limit of {config['recursion_limit']} reached " + "without hitting a stop condition. You can increase the " + "limit by setting the `recursion_limit` config key." + ), + error_code=ErrorCode.GRAPH_RECURSION_LIMIT, + ) + raise GraphRecursionError(msg) + # set final channel values as run output + run_manager.on_chain_end(loop.output) + except BaseException as e: + run_manager.on_chain_error(e) + raise + + async def astream( + self, + input: InputT, + config: RunnableConfig | None = None, + *, + stream_mode: StreamMode | Sequence[StreamMode] | None = None, + print_mode: StreamMode | Sequence[StreamMode] = (), + output_keys: str | Sequence[str] | None = None, + interrupt_before: All | Sequence[str] | None = None, + interrupt_after: All | Sequence[str] | None = None, + checkpoint_during: bool | None = None, + debug: bool | None = None, + subgraphs: bool = False, + ) -> AsyncIterator[dict[str, Any] | Any]: + """Asynchronously stream graph steps for a single input. + + Args: + input: The input to the graph. + config: The configuration to use for the run. + stream_mode: The mode to stream output, defaults to `self.stream_mode`. + Options are: + + - `"values"`: Emit all values in the state after each step, including interrupts. + When used with functional API, values are emitted once at the end of the workflow. + - `"updates"`: Emit only the node or task names and updates returned by the nodes or tasks after each step. + If multiple updates are made in the same step (e.g. multiple nodes are run) then those updates are emitted separately. + - `"custom"`: Emit custom data from inside nodes or tasks using `StreamWriter`. + - `"messages"`: Emit LLM messages token-by-token together with metadata for any LLM invocations inside nodes or tasks. + Will be emitted as 2-tuples `(LLM token, metadata)`. + - `"debug"`: Emit debug events with as much information as possible for each step. + + You can pass a list as the `stream_mode` parameter to stream multiple modes at once. + The streamed outputs will be tuples of `(mode, data)`. + + See [LangGraph streaming guide](https://langchain-ai.github.io/langgraph/how-tos/streaming/) for more details. + print_mode: Accepts the same values as `stream_mode`, but only prints the output to the console, for debugging purposes. Does not affect the output of the graph in any way. + output_keys: The keys to stream, defaults to all non-context channels. + interrupt_before: Nodes to interrupt before, defaults to all nodes in the graph. + interrupt_after: Nodes to interrupt after, defaults to all nodes in the graph. + checkpoint_during: Whether to checkpoint intermediate steps, defaults to False. If False, only the final checkpoint is saved. + subgraphs: Whether to stream events from inside subgraphs, defaults to False. + If True, the events will be emitted as tuples `(namespace, data)`, + or `(namespace, mode, data)` if `stream_mode` is a list, + where `namespace` is a tuple with the path to the node where a subgraph is invoked, + e.g. `("parent_node:", "child_node:")`. + + See [LangGraph streaming guide](https://langchain-ai.github.io/langgraph/how-tos/streaming/) for more details. + + Yields: + The output of each step in the graph. The output shape depends on the stream_mode. + """ + + if stream_mode is None: + # if being called as a node in another graph, default to values mode + # but don't overwrite stream_mode arg if provided + stream_mode = ( + "values" + if config is not None and CONFIG_KEY_TASK_ID in config.get(CONF, {}) + else self.stream_mode + ) + if debug or self.debug: + print_mode = ["updates", "values"] + + stream = AsyncQueue() + aioloop = asyncio.get_running_loop() + stream_put = cast( + Callable[[StreamChunk], None], + partial(aioloop.call_soon_threadsafe, stream.put_nowait), + ) + + config = ensure_config(self.config, config) + callback_manager = get_async_callback_manager_for_config(config) + run_manager = await callback_manager.on_chain_start( + None, + input, + name=config.get("run_name", self.get_name()), + run_id=config.get("run_id"), + ) + # if running from astream_log() run each proc with streaming + do_stream = ( + next( + ( + True + for h in run_manager.handlers + if isinstance(h, _StreamingCallbackHandler) + and not isinstance(h, StreamMessagesHandler) + ), + False, + ) + if _StreamingCallbackHandler is not None + else False + ) + try: + # assign defaults + ( + stream_modes, + output_keys, + interrupt_before_, + interrupt_after_, + checkpointer, + store, + cache, + ) = self._defaults( + config, + stream_mode=stream_mode, + print_mode=print_mode, + output_keys=output_keys, + interrupt_before=interrupt_before, + interrupt_after=interrupt_after, + ) + # set up subgraph checkpointing + if self.checkpointer is True: + ns = cast(str, config[CONF][CONFIG_KEY_CHECKPOINT_NS]) + config[CONF][CONFIG_KEY_CHECKPOINT_NS] = recast_checkpoint_ns(ns) + # set up messages stream mode + if "messages" in stream_modes: + run_manager.inheritable_handlers.append( + StreamMessagesHandler(stream_put, subgraphs) + ) + # set up custom stream mode + if "custom" in stream_modes: + config[CONF][CONFIG_KEY_STREAM_WRITER] = ( + lambda c: aioloop.call_soon_threadsafe( + stream.put_nowait, + ( + tuple( + get_config()[CONF][CONFIG_KEY_CHECKPOINT_NS].split( + NS_SEP + )[:-1] + ), + "custom", + c, + ), + ) + ) + elif ( + CONFIG_KEY_STREAM not in config[CONF] + and CONFIG_KEY_STREAM_WRITER in config[CONF] + ): + # remove parent graph stream writer if subgraph streaming not requested + del config[CONF][CONFIG_KEY_STREAM_WRITER] + # set checkpointing mode for subgraphs + if checkpoint_during is not None: + config[CONF][CONFIG_KEY_CHECKPOINT_DURING] = checkpoint_during + async with AsyncPregelLoop( + input, + stream=StreamProtocol(stream.put_nowait, stream_modes), + config=config, + store=store, + cache=cache, + checkpointer=checkpointer, + nodes=self.nodes, + specs=self.channels, + output_keys=output_keys, + input_keys=self.input_channels, + stream_keys=self.stream_channels_asis, + interrupt_before=interrupt_before_, + interrupt_after=interrupt_after_, + manager=run_manager, + checkpoint_during=checkpoint_during + if checkpoint_during is not None + else config[CONF].get(CONFIG_KEY_CHECKPOINT_DURING, True), + trigger_to_nodes=self.trigger_to_nodes, + migrate_checkpoint=self._migrate_checkpoint, + retry_policy=self.retry_policy, + cache_policy=self.cache_policy, + ) as loop: + # create runner + runner = PregelRunner( + submit=config[CONF].get( + CONFIG_KEY_RUNNER_SUBMIT, weakref.WeakMethod(loop.submit) + ), + put_writes=weakref.WeakMethod(loop.put_writes), + use_astream=do_stream, + node_finished=config[CONF].get(CONFIG_KEY_NODE_FINISHED), + ) + # enable subgraph streaming + if subgraphs: + loop.config[CONF][CONFIG_KEY_STREAM] = StreamProtocol( + stream_put, stream_modes + ) + # enable concurrent streaming + if ( + self.stream_eager + or subgraphs + or "messages" in stream_modes + or "custom" in stream_modes + ): + + def get_waiter() -> asyncio.Task[None]: + return aioloop.create_task(stream.wait()) + + else: + get_waiter = None # type: ignore[assignment] + # Similarly to Bulk Synchronous Parallel / Pregel model + # computation proceeds in steps, while there are channel updates + # channel updates from step N are only visible in step N+1 + # channels are guaranteed to be immutable for the duration of the step, + # with channel updates applied only at the transition between steps + while loop.tick(): + for task in await loop.amatch_cached_writes(): + loop.output_writes(task.id, task.writes, cached=True) + async for _ in runner.atick( + [t for t in loop.tasks.values() if not t.writes], + timeout=self.step_timeout, + get_waiter=get_waiter, + schedule_task=loop.aaccept_push, + ): + # emit output + for o in _output( + stream_mode, + print_mode, + subgraphs, + stream.get_nowait, + asyncio.QueueEmpty, + ): + yield o + loop.after_tick() + # emit output + for o in _output( + stream_mode, + print_mode, + subgraphs, + stream.get_nowait, + asyncio.QueueEmpty, + ): + yield o + # handle exit + if loop.status == "out_of_steps": + msg = create_error_message( + message=( + f"Recursion limit of {config['recursion_limit']} reached " + "without hitting a stop condition. You can increase the " + "limit by setting the `recursion_limit` config key." + ), + error_code=ErrorCode.GRAPH_RECURSION_LIMIT, + ) + raise GraphRecursionError(msg) + # set final channel values as run output + await run_manager.on_chain_end(loop.output) + except BaseException as e: + await asyncio.shield(run_manager.on_chain_error(e)) + raise + + def invoke( + self, + input: InputT, + config: RunnableConfig | None = None, + *, + stream_mode: StreamMode = "values", + print_mode: StreamMode | Sequence[StreamMode] = (), + output_keys: str | Sequence[str] | None = None, + interrupt_before: All | Sequence[str] | None = None, + interrupt_after: All | Sequence[str] | None = None, + **kwargs: Any, + ) -> dict[str, Any] | Any: + """Run the graph with a single input and config. + + Args: + input: The input data for the graph. It can be a dictionary or any other type. + config: Optional. The configuration for the graph run. + stream_mode: Optional[str]. The stream mode for the graph run. Default is "values". + print_mode: Accepts the same values as `stream_mode`, but only prints the output to the console, for debugging purposes. Does not affect the output of the graph in any way. + output_keys: Optional. The output keys to retrieve from the graph run. + interrupt_before: Optional. The nodes to interrupt the graph run before. + interrupt_after: Optional. The nodes to interrupt the graph run after. + **kwargs: Additional keyword arguments to pass to the graph run. + + Returns: + The output of the graph run. If stream_mode is "values", it returns the latest output. + If stream_mode is not "values", it returns a list of output chunks. + """ + output_keys = output_keys if output_keys is not None else self.output_channels + + latest: dict[str, Any] | Any = None + chunks: list[dict[str, Any] | Any] = [] + interrupts: list[Interrupt] = [] + + for chunk in self.stream( + input, + config, + stream_mode=["updates", "values"] + if stream_mode == "values" + else stream_mode, + print_mode=print_mode, + output_keys=output_keys, + interrupt_before=interrupt_before, + interrupt_after=interrupt_after, + **kwargs, + ): + if stream_mode == "values": + if len(chunk) == 2: + mode, payload = cast(tuple[StreamMode, Any], chunk) + else: + _, mode, payload = cast( + tuple[tuple[str, ...], StreamMode, Any], chunk + ) + if ( + mode == "updates" + and isinstance(payload, dict) + and (ints := payload.get(INTERRUPT)) is not None + ): + interrupts.extend(ints) + elif mode == "values": + latest = payload + else: + chunks.append(chunk) + + if stream_mode == "values": + if interrupts: + return ( + {**latest, INTERRUPT: interrupts} + if isinstance(latest, dict) + else {INTERRUPT: interrupts} + ) + return latest + else: + return chunks + + async def ainvoke( + self, + input: InputT, + config: RunnableConfig | None = None, + *, + stream_mode: StreamMode = "values", + print_mode: StreamMode | Sequence[StreamMode] = (), + output_keys: str | Sequence[str] | None = None, + interrupt_before: All | Sequence[str] | None = None, + interrupt_after: All | Sequence[str] | None = None, + **kwargs: Any, + ) -> dict[str, Any] | Any: + """Asynchronously invoke the graph on a single input. + + Args: + input: The input data for the computation. It can be a dictionary or any other type. + config: Optional. The configuration for the computation. + stream_mode: Optional. The stream mode for the computation. Default is "values". + print_mode: Accepts the same values as `stream_mode`, but only prints the output to the console, for debugging purposes. Does not affect the output of the graph in any way. + output_keys: Optional. The output keys to include in the result. Default is None. + interrupt_before: Optional. The nodes to interrupt before. Default is None. + interrupt_after: Optional. The nodes to interrupt after. Default is None. + **kwargs: Additional keyword arguments. + + Returns: + The result of the computation. If stream_mode is "values", it returns the latest value. + If stream_mode is "chunks", it returns a list of chunks. + """ + + output_keys = output_keys if output_keys is not None else self.output_channels + + latest: dict[str, Any] | Any = None + chunks: list[dict[str, Any] | Any] = [] + interrupts: list[Interrupt] = [] + + async for chunk in self.astream( + input, + config, + stream_mode=["updates", "values"] + if stream_mode == "values" + else stream_mode, + print_mode=print_mode, + output_keys=output_keys, + interrupt_before=interrupt_before, + interrupt_after=interrupt_after, + **kwargs, + ): + if stream_mode == "values": + if len(chunk) == 2: + mode, payload = cast(tuple[StreamMode, Any], chunk) + else: + _, mode, payload = cast( + tuple[tuple[str, ...], StreamMode, Any], chunk + ) + if ( + mode == "updates" + and isinstance(payload, dict) + and (ints := payload.get(INTERRUPT)) is not None + ): + interrupts.extend(ints) + elif mode == "values": + latest = payload + else: + chunks.append(chunk) + + if stream_mode == "values": + if interrupts: + return ( + {**latest, INTERRUPT: interrupts} + if isinstance(latest, dict) + else {INTERRUPT: interrupts} + ) + return latest + else: + return chunks + + def clear_cache(self, nodes: Sequence[str] | None = None) -> None: + """Clear the cache for the given nodes.""" + if not self.cache: + raise ValueError("No cache is set for this graph. Cannot clear cache.") + nodes = nodes or self.nodes.keys() + # collect namespaces to clear + namespaces: list[tuple[str, ...]] = [] + for node in nodes: + if node in self.nodes: + namespaces.append( + ( + CACHE_NS_WRITES, + (identifier(self.nodes[node]) or "__dynamic__"), + node, + ), + ) + # clear cache + self.cache.clear(namespaces) + + async def aclear_cache(self, nodes: Sequence[str] | None = None) -> None: + """Asynchronously clear the cache for the given nodes.""" + if not self.cache: + raise ValueError("No cache is set for this graph. Cannot clear cache.") + nodes = nodes or self.nodes.keys() + # collect namespaces to clear + namespaces: list[tuple[str, ...]] = [] + for node in nodes: + if node in self.nodes: + namespaces.append( + ( + CACHE_NS_WRITES, + (identifier(self.nodes[node]) or "__dynamic__"), + node, + ), + ) + # clear cache + await self.cache.aclear(namespaces) + + +def _trigger_to_nodes(nodes: dict[str, PregelNode]) -> Mapping[str, Sequence[str]]: + """Index from a trigger to nodes that depend on it.""" + trigger_to_nodes: defaultdict[str, list[str]] = defaultdict(list) + for name, node in nodes.items(): + for trigger in node.triggers: + trigger_to_nodes[trigger].append(name) + return dict(trigger_to_nodes) + + +def _output( + stream_mode: StreamMode | Sequence[StreamMode], + print_mode: StreamMode | Sequence[StreamMode], + stream_subgraphs: bool, + getter: Callable[[], tuple[tuple[str, ...], str, Any]], + empty_exc: type[Exception], +) -> Iterator: + while True: + try: + ns, mode, payload = getter() + except empty_exc: + break + if mode in print_mode: + if stream_subgraphs and ns: + print( + " ".join( + ( + get_bolded_text(f"[{mode}]"), + get_colored_text(f"[graph={ns}]", color="yellow"), + repr(payload), + ) + ) + ) + else: + print( + " ".join( + ( + get_bolded_text(f"[{mode}]"), + repr(payload), + ) + ) + ) + if mode in stream_mode: + if stream_subgraphs and isinstance(stream_mode, list): + yield (ns, mode, payload) + elif isinstance(stream_mode, list): + yield (mode, payload) + elif stream_subgraphs: + yield (ns, payload) + else: + yield payload diff --git a/libs/langgraph/langgraph/pregel/protocol.py b/libs/langgraph/langgraph/pregel/protocol.py index e55be3cbc..504cb1760 100644 --- a/libs/langgraph/langgraph/pregel/protocol.py +++ b/libs/langgraph/langgraph/pregel/protocol.py @@ -2,17 +2,18 @@ from __future__ import annotations from abc import ABC, abstractmethod from collections.abc import AsyncIterator, Iterator, Sequence -from typing import Any, Generic +from typing import Any, Callable, Generic, cast from langchain_core.runnables import Runnable, RunnableConfig from langchain_core.runnables.graph import Graph as DrawableGraph from typing_extensions import Self -from langgraph.pregel.types import All, StateSnapshot, StateUpdate, StreamMode +from langgraph.types import All, StateSnapshot, StateUpdate, StreamMode from langgraph.typing import InputT, OutputT, StateT +__all__ = ("PregelProtocol", "StreamProtocol") + -# TODO: remove Runnable inheritance here! class PregelProtocol(Runnable[InputT, Any], Generic[StateT, InputT, OutputT], ABC): @abstractmethod def with_config( @@ -138,3 +139,22 @@ class PregelProtocol(Runnable[InputT, Any], Generic[StateT, InputT, OutputT], AB interrupt_before: All | Sequence[str] | None = None, interrupt_after: All | Sequence[str] | None = None, ) -> dict[str, Any] | Any: ... + + +StreamChunk = tuple[tuple[str, ...], str, Any] + + +class StreamProtocol: + __slots__ = ("modes", "__call__") + + modes: set[StreamMode] + + __call__: Callable[[Self, StreamChunk], None] + + def __init__( + self, + __call__: Callable[[StreamChunk], None], + modes: set[StreamMode], + ) -> None: + self.__call__ = cast(Callable[[Self, StreamChunk], None], __call__) + self.modes = modes diff --git a/libs/langgraph/langgraph/pregel/py.typed b/libs/langgraph/langgraph/pregel/py.typed deleted file mode 100644 index e69de29bb..000000000 diff --git a/libs/langgraph/langgraph/pregel/remote.py b/libs/langgraph/langgraph/pregel/remote.py index 38b334e46..145dd548f 100644 --- a/libs/langgraph/langgraph/pregel/remote.py +++ b/libs/langgraph/langgraph/pregel/remote.py @@ -29,6 +29,7 @@ from langgraph_sdk.schema import Command as CommandSDK from langgraph_sdk.schema import StreamMode as StreamModeSDK from typing_extensions import Self +from langgraph._internal._config import merge_configs from langgraph.checkpoint.base import CheckpointMetadata from langgraph.constants import ( CONF, @@ -41,12 +42,19 @@ from langgraph.constants import ( NS_SEP, ) from langgraph.errors import GraphInterrupt -from langgraph.pregel.protocol import PregelProtocol -from langgraph.pregel.types import All, PregelTask, StateSnapshot, StreamMode -from langgraph.types import Command, Interrupt, StreamProtocol -from langgraph.utils.config import merge_configs +from langgraph.pregel.protocol import PregelProtocol, StreamProtocol +from langgraph.types import ( + All, + Command, + Interrupt, + PregelTask, + StateSnapshot, + StreamMode, +) -CONF_DROPLIST = frozenset( +__all__ = ("RemoteGraph", "RemoteException") + +_CONF_DROPLIST = frozenset( ( CONFIG_KEY_CHECKPOINT_MAP, CONFIG_KEY_CHECKPOINT_ID, @@ -56,7 +64,7 @@ CONF_DROPLIST = frozenset( ) -def sanitize_config_value(v: Any) -> Any: +def _sanitize_config_value(v: Any) -> Any: """Recursively sanitize a config value to ensure it contains only primitives.""" if isinstance(v, (str, int, float, bool)): return v @@ -64,14 +72,14 @@ def sanitize_config_value(v: Any) -> Any: sanitized_dict = {} for k, val in v.items(): if isinstance(k, str): - sanitized_value = sanitize_config_value(val) + sanitized_value = _sanitize_config_value(val) if sanitized_value is not None: sanitized_dict[k] = sanitized_value return sanitized_dict elif isinstance(v, (list, tuple)): sanitized_list = [] for item in v: - sanitized_item = sanitize_config_value(item) + sanitized_item = _sanitize_config_value(item) if sanitized_item is not None: sanitized_list.append(sanitized_item) return sanitized_list @@ -347,7 +355,7 @@ class RemoteGraph(PregelProtocol): for k, v in config["metadata"].items(): if ( isinstance(k, str) - and (sanitized_value := sanitize_config_value(v)) is not None + and (sanitized_value := _sanitize_config_value(v)) is not None ): sanitized["metadata"][k] = sanitized_value @@ -356,8 +364,8 @@ class RemoteGraph(PregelProtocol): for k, v in config["configurable"].items(): if ( isinstance(k, str) - and k not in CONF_DROPLIST - and (sanitized_value := sanitize_config_value(v)) is not None + and k not in _CONF_DROPLIST + and (sanitized_value := _sanitize_config_value(v)) is not None ): sanitized["configurable"][k] = sanitized_value diff --git a/libs/langgraph/langgraph/pregel/types.py b/libs/langgraph/langgraph/pregel/types.py index 212c0c4af..39a36df68 100644 --- a/libs/langgraph/langgraph/pregel/types.py +++ b/libs/langgraph/langgraph/pregel/types.py @@ -25,3 +25,14 @@ __all__ = [ "StreamWriter", "default_retry_on", ] + +from warnings import warn + +from langgraph.warnings import LangGraphDeprecatedSinceV10 + +warn( + "Importing from langgraph.pregel.types is deprecated. " + "Please use 'from langgraph.types import ...' instead.", + LangGraphDeprecatedSinceV10, + stacklevel=2, +) diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py index d95c4fd51..0813270cf 100644 --- a/libs/langgraph/langgraph/types.py +++ b/libs/langgraph/langgraph/types.py @@ -18,12 +18,12 @@ from typing import ( ) from langchain_core.runnables import Runnable, RunnableConfig -from typing_extensions import Self from xxhash import xxh3_128_hexdigest +from langgraph._internal._cache import default_cache_key +from langgraph._internal._fields import get_cached_annotated_keys, get_update_as_tuples +from langgraph._internal._retry import default_retry_on from langgraph.checkpoint.base import BaseCheckpointSaver, CheckpointMetadata -from langgraph.utils.cache import default_cache_key -from langgraph.utils.fields import get_cached_annotated_keys, get_update_as_tuples if TYPE_CHECKING: from langgraph.pregel.protocol import PregelProtocol @@ -37,6 +37,24 @@ except ImportError: pass +__all__ = ( + "All", + "Checkpointer", + "StreamMode", + "StreamWriter", + "RetryPolicy", + "CachePolicy", + "Interrupt", + "StateUpdate", + "PregelTask", + "PregelExecutableTask", + "StateSnapshot", + "Send", + "Command", + "interrupt", +) + + All = Literal["*"] """Special value to indicate that graph should interrupt on all nodes.""" @@ -73,37 +91,6 @@ else: _DC_KWARGS = {"frozen": True} -def default_retry_on(exc: Exception) -> bool: - import httpx - import requests - - if isinstance(exc, ConnectionError): - return True - if isinstance(exc, httpx.HTTPStatusError): - return 500 <= exc.response.status_code < 600 - if isinstance(exc, requests.HTTPError): - return 500 <= exc.response.status_code < 600 if exc.response else True - if isinstance( - exc, - ( - ValueError, - TypeError, - ArithmeticError, - ImportError, - LookupError, - NameError, - SyntaxError, - RuntimeError, - ReferenceError, - StopIteration, - StopAsyncIteration, - OSError, - ), - ): - return False - return True - - class RetryPolicy(NamedTuple): """Configuration for retrying nodes. @@ -364,39 +351,6 @@ class Command(Generic[N], ToolOutputMixin): PARENT: ClassVar[Literal["__parent__"]] = "__parent__" -StreamChunk = tuple[tuple[str, ...], str, Any] - - -class StreamProtocol: - __slots__ = ("modes", "__call__") - - modes: set[StreamMode] - - __call__: Callable[[Self, StreamChunk], None] - - def __init__( - self, - __call__: Callable[[StreamChunk], None], - modes: set[StreamMode], - ) -> None: - self.__call__ = cast(Callable[[Self, StreamChunk], None], __call__) - self.modes = modes - - -@dataclasses.dataclass(**_DC_KWARGS) -class PregelScratchpad: - step: int - stop: int - # call - call_counter: Callable[[], int] - # interrupt - interrupt_counter: Callable[[], int] - get_null_resume: Callable[[bool], Any] - resume: list[Any] - # subgraph - subgraph_counter: Callable[[], int] - - def interrupt(value: Any) -> Any: """Interrupt the graph with a resumable exception from within a node. @@ -504,7 +458,7 @@ def interrupt(value: Any) -> Any: conf = get_config()["configurable"] # track interrupt index - scratchpad: PregelScratchpad = conf[CONFIG_KEY_SCRATCHPAD] + scratchpad = conf[CONFIG_KEY_SCRATCHPAD] idx = scratchpad.interrupt_counter() # find previous resume values if scratchpad.resume: diff --git a/libs/langgraph/langgraph/typing.py b/libs/langgraph/langgraph/typing.py index 01ea27adf..d7a82e9fd 100644 --- a/libs/langgraph/langgraph/typing.py +++ b/libs/langgraph/langgraph/typing.py @@ -4,7 +4,15 @@ from typing import Union from typing_extensions import TypeVar -from langgraph._typing import StateLike +from langgraph._internal._typing import StateLike + +__all__ = ( + "StateT", + "StateT_co", + "StateT_contra", + "InputT", + "OutputT", +) StateT = TypeVar("StateT", bound=StateLike) """Type variable used to represent the state in a graph.""" @@ -19,12 +27,6 @@ InputT = TypeVar("InputT", bound=StateLike, default=StateT) Defaults to `StateT`. """ -ResolvedInputT = TypeVar("ResolvedInputT", bound=StateLike) -"""Type variable used to represent the resolved input to a state graph. - -No default. -""" - OutputT = TypeVar("OutputT", bound=Union[StateLike, None], default=StateT) """Type variable used to represent the output of a state graph.""" diff --git a/libs/langgraph/langgraph/utils/py.typed b/libs/langgraph/langgraph/utils/py.typed deleted file mode 100644 index e69de29bb..000000000 diff --git a/libs/langgraph/langgraph/version.py b/libs/langgraph/langgraph/version.py index f5cb757f5..a81f647c7 100644 --- a/libs/langgraph/langgraph/version.py +++ b/libs/langgraph/langgraph/version.py @@ -2,6 +2,8 @@ from importlib import metadata +__all__ = ("__version__",) + try: __version__ = metadata.version(__package__) except metadata.PackageNotFoundError: diff --git a/libs/langgraph/langgraph/warnings.py b/libs/langgraph/langgraph/warnings.py index e8fd59d88..638f247e3 100644 --- a/libs/langgraph/langgraph/warnings.py +++ b/libs/langgraph/langgraph/warnings.py @@ -2,6 +2,12 @@ from __future__ import annotations +__all__ = ( + "LangGraphDeprecationWarning", + "LangGraphDeprecatedSinceV05", + "LangGraphDeprecatedSinceV10", +) + class LangGraphDeprecationWarning(DeprecationWarning): """A LangGraph specific deprecation warning. @@ -46,3 +52,10 @@ class LangGraphDeprecatedSinceV05(LangGraphDeprecationWarning): def __init__(self, message: str, *args: object) -> None: super().__init__(message, *args, since=(0, 5), expected_removal=(2, 0)) + + +class LangGraphDeprecatedSinceV10(LangGraphDeprecationWarning): + """A specific `LangGraphDeprecationWarning` subclass defining functionality deprecated since LangGraph v1.0.0""" + + def __init__(self, message: str, *args: object) -> None: + super().__init__(message, *args, since=(1, 0), expected_removal=(2, 0)) diff --git a/libs/langgraph/tests/__snapshots__/test_large_cases.ambr b/libs/langgraph/tests/__snapshots__/test_large_cases.ambr index 9f4d08cf9..2a6c8dd25 100644 --- a/libs/langgraph/tests/__snapshots__/test_large_cases.ambr +++ b/libs/langgraph/tests/__snapshots__/test_large_cases.ambr @@ -1,105 +1,4 @@ # serializer version: 1 -# name: test_conditional_graph[memory] - ''' - { - "nodes": [ - { - "id": "agent", - "type": "runnable", - "data": { - "id": [ - "langchain", - "schema", - "runnable", - "RunnableAssign" - ], - "name": "agent" - } - }, - { - "id": "tools", - "type": "runnable", - "data": { - "id": [ - "langgraph", - "utils", - "runnable", - "RunnableCallable" - ], - "name": "tools" - }, - "metadata": { - "parents": {}, - "version": 2, - "variant": "b" - } - }, - { - "id": "__start__" - }, - { - "id": "__end__" - } - ], - "edges": [ - { - "source": "__start__", - "target": "agent" - }, - { - "source": "agent", - "target": "__end__", - "data": "exit", - "conditional": true - }, - { - "source": "agent", - "target": "tools", - "data": "continue", - "conditional": true - }, - { - "source": "tools", - "target": "agent" - } - ] - } - ''' -# --- -# name: test_conditional_graph[memory].1 - ''' - graph TD; - __start__ --> agent; - agent -.  exit  .-> __end__; - agent -.  continue  .-> tools; - tools --> agent; - - ''' -# --- -# name: test_conditional_graph[memory].2 - ''' - --- - config: - flowchart: - curve: linear - --- - graph TD; - agent(agent) - tools(tools
parents = {} - version = 2 - variant = b) - __start__([

__start__

]):::first - __end__([

__end__

]):::last - __start__ --> agent; - agent -.  exit  .-> __end__; - agent -.  continue  .-> tools; - tools --> agent; - classDef default fill:#f2f0ff,line-height:1.2 - classDef first fill-opacity:0 - classDef last fill:#bfb6fc - - ''' -# --- # name: test_conditional_state_graph[memory] '{"$defs": {"AgentAction": {"description": "Represents a request to execute an action by an agent.\\n\\nThe action consists of the name of the tool to execute and the input to pass\\nto the tool. The log is used to pass along extra information about the action.", "properties": {"tool": {"title": "Tool", "type": "string"}, "tool_input": {"anyOf": [{"type": "string"}, {"type": "object"}], "title": "Tool Input"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentAction", "default": "AgentAction", "enum": ["AgentAction"], "title": "Type", "type": "string"}}, "required": ["tool", "tool_input", "log"], "title": "AgentAction", "type": "object"}, "AgentFinish": {"description": "Final return value of an ActionAgent.\\n\\nAgents return an AgentFinish when they have reached a stopping condition.", "properties": {"return_values": {"title": "Return Values", "type": "object"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentFinish", "default": "AgentFinish", "enum": ["AgentFinish"], "title": "Type", "type": "string"}}, "required": ["return_values", "log"], "title": "AgentFinish", "type": "object"}}, "properties": {"input": {"default": null, "title": "Input", "type": "string"}, "agent_outcome": {"anyOf": [{"$ref": "#/$defs/AgentAction"}, {"$ref": "#/$defs/AgentFinish"}, {"type": "null"}], "default": null, "title": "Agent Outcome"}, "intermediate_steps": {"default": null, "items": {"maxItems": 2, "minItems": 2, "prefixItems": [{"$ref": "#/$defs/AgentAction"}, {"type": "string"}], "type": "array"}, "title": "Intermediate Steps", "type": "array"}}, "title": "LangGraphInput", "type": "object"}' # --- @@ -116,8 +15,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "__start__" @@ -142,8 +41,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "tools" @@ -204,8 +103,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "__start__" @@ -291,8 +190,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "__start__" @@ -304,8 +203,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "agent" diff --git a/libs/langgraph/tests/__snapshots__/test_pregel.ambr b/libs/langgraph/tests/__snapshots__/test_pregel.ambr index a47c52178..b8e226348 100644 --- a/libs/langgraph/tests/__snapshots__/test_pregel.ambr +++ b/libs/langgraph/tests/__snapshots__/test_pregel.ambr @@ -1,93 +1,4 @@ # serializer version: 1 -# name: test_conditional_entrypoint_graph - '{"title": "LangGraphInput"}' -# --- -# name: test_conditional_entrypoint_graph.1 - '{"title": "LangGraphOutput"}' -# --- -# name: test_conditional_entrypoint_graph.2 - ''' - { - "nodes": [ - { - "id": "left", - "type": "runnable", - "data": { - "id": [ - "langgraph", - "utils", - "runnable", - "RunnableCallable" - ], - "name": "left" - } - }, - { - "id": "right", - "type": "runnable", - "data": { - "id": [ - "langgraph", - "utils", - "runnable", - "RunnableCallable" - ], - "name": "right" - } - }, - { - "id": "__start__", - "type": "runnable", - "data": { - "id": [ - "langgraph", - "utils", - "runnable", - "RunnableCallable" - ], - "name": "__start__" - } - }, - { - "id": "__end__" - } - ], - "edges": [ - { - "source": "__start__", - "target": "left", - "data": "go-left", - "conditional": true - }, - { - "source": "__start__", - "target": "right", - "data": "go-right", - "conditional": true - }, - { - "source": "left", - "target": "__end__", - "conditional": true - }, - { - "source": "right", - "target": "__end__" - } - ] - } - ''' -# --- -# name: test_conditional_entrypoint_graph.3 - ''' - graph TD; - __start__ -.  go-left  .-> left; - __start__ -.  go-right  .-> right; - left -.-> __end__; - right --> __end__; - - ''' -# --- # name: test_conditional_entrypoint_graph_state '{"properties": {"input": {"default": null, "title": "Input", "type": "string"}, "output": {"default": null, "title": "Output", "type": "string"}, "steps": {"default": null, "items": {"type": "string"}, "title": "Steps", "type": "array"}}, "title": "LangGraphInput", "type": "object"}' # --- @@ -104,8 +15,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "__start__" @@ -117,8 +28,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "left" @@ -130,8 +41,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "right" @@ -193,8 +104,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "__start__" @@ -206,8 +117,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "get_weather" @@ -249,8 +160,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "__start__" @@ -262,8 +173,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "A" @@ -275,8 +186,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "B" @@ -327,8 +238,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "__start__" @@ -340,8 +251,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "human" @@ -353,8 +264,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "agent" @@ -406,8 +317,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "__start__" @@ -461,8 +372,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "__start__" @@ -474,8 +385,8 @@ "data": { "id": [ "langgraph", - "utils", - "runnable", + "_internal", + "_runnable", "RunnableCallable" ], "name": "worker_node" @@ -767,8 +678,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': '__start__', @@ -780,8 +691,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': 'tool_one', @@ -793,8 +704,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': 'tool_three', @@ -809,8 +720,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': 'tool_two:__start__', @@ -822,8 +733,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': 'tool_two:tool_two_slow', @@ -835,8 +746,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': 'tool_two:tool_two_fast', @@ -1020,8 +931,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': '__start__', @@ -1033,8 +944,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': 'ask_question', @@ -1046,8 +957,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': 'answer_question', @@ -1087,8 +998,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': '__start__', @@ -1100,8 +1011,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': 'generate_analysts', @@ -1126,8 +1037,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': 'generate_sections', @@ -1185,8 +1096,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': '__start__', @@ -1198,8 +1109,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': 'generate_analysts', @@ -1211,8 +1122,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': 'generate_sections', @@ -1227,8 +1138,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': 'conduct_interview:__start__', @@ -1240,8 +1151,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': 'conduct_interview:ask_question', @@ -1253,8 +1164,8 @@ 'data': dict({ 'id': list([ 'langgraph', - 'utils', - 'runnable', + '_internal', + '_runnable', 'RunnableCallable', ]), 'name': 'conduct_interview:answer_question', diff --git a/libs/langgraph/tests/test_algo.py b/libs/langgraph/tests/test_algo.py index 4dbdffeee..f32fa4334 100644 --- a/libs/langgraph/tests/test_algo.py +++ b/libs/langgraph/tests/test_algo.py @@ -1,6 +1,6 @@ from langgraph.constants import PULL, PUSH -from langgraph.pregel.algo import prepare_next_tasks, task_path_str -from langgraph.pregel.checkpoint import channels_from_checkpoint, empty_checkpoint +from langgraph.pregel._algo import prepare_next_tasks, task_path_str +from langgraph.pregel._checkpoint import channels_from_checkpoint, empty_checkpoint def test_prepare_next_tasks() -> None: diff --git a/libs/langgraph/tests/test_checkpoint_migration.py b/libs/langgraph/tests/test_checkpoint_migration.py index 1f3b47c13..d1dd65e61 100644 --- a/libs/langgraph/tests/test_checkpoint_migration.py +++ b/libs/langgraph/tests/test_checkpoint_migration.py @@ -7,11 +7,11 @@ from typing import Annotated, Literal, Optional, Union import pytest from typing_extensions import TypedDict +from langgraph._internal._config import patch_configurable from langgraph.checkpoint.base import BaseCheckpointSaver, CheckpointTuple from langgraph.graph.state import StateGraph -from langgraph.pregel.checkpoint import copy_checkpoint +from langgraph.pregel._checkpoint import copy_checkpoint from langgraph.types import Command, Interrupt, PregelTask, StateSnapshot, interrupt -from langgraph.utils.config import patch_configurable from tests.any_int import AnyInt from tests.any_str import AnyDict, AnyObject, AnyStr diff --git a/libs/langgraph/tests/test_config_async.py b/libs/langgraph/tests/test_config_async.py index e57433a28..a65a5f2af 100644 --- a/libs/langgraph/tests/test_config_async.py +++ b/libs/langgraph/tests/test_config_async.py @@ -1,7 +1,7 @@ import pytest from langchain_core.callbacks import AsyncCallbackManager -from langgraph.utils.config import get_async_callback_manager_for_config +from langgraph._internal._config import get_async_callback_manager_for_config pytestmark = pytest.mark.anyio diff --git a/libs/langgraph/tests/test_deprecation.py b/libs/langgraph/tests/test_deprecation.py index 2a217edae..0edb00b6f 100644 --- a/libs/langgraph/tests/test_deprecation.py +++ b/libs/langgraph/tests/test_deprecation.py @@ -4,7 +4,7 @@ from typing_extensions import TypedDict from langgraph.func import entrypoint, task from langgraph.graph import StateGraph from langgraph.types import RetryPolicy -from langgraph.warnings import LangGraphDeprecatedSinceV05 +from langgraph.warnings import LangGraphDeprecatedSinceV05, LangGraphDeprecatedSinceV10 class PlainState(TypedDict): ... @@ -66,3 +66,25 @@ def test_add_node_input_schema() -> None: match="`input` is deprecated and will be removed. Please use `input_schema` instead.", ): builder.add_node("test_node", lambda state: state, input=PlainState) # type: ignore[arg-type] + + +def test_constants_deprecation() -> None: + with pytest.warns( + LangGraphDeprecatedSinceV10, + match="Importing Send from langgraph.constants is deprecated. Please use 'from langgraph.types import Send' instead.", + ): + from langgraph.constants import Send # noqa: F401 + + with pytest.warns( + LangGraphDeprecatedSinceV10, + match="Importing Interrupt from langgraph.constants is deprecated. Please use 'from langgraph.types import Interrupt' instead.", + ): + from langgraph.constants import Interrupt # noqa: F401 + + +def test_pregel_deprecation() -> None: + with pytest.warns( + LangGraphDeprecatedSinceV10, + match="Importing from langgraph.pregel.types is deprecated. Please use 'from langgraph.types import ...' instead.", + ): + from langgraph.pregel.types import StateSnapshot # noqa: F401 diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index dc732da91..2fabec768 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -43,27 +43,26 @@ from langgraph.checkpoint.base import ( from langgraph.checkpoint.memory import InMemorySaver from langgraph.config import get_stream_writer from langgraph.constants import CONFIG_KEY_NODE_FINISHED, ERROR, PULL, START -from langgraph.errors import InvalidUpdateError, ParentCommand +from langgraph.errors import GraphRecursionError, InvalidUpdateError, ParentCommand from langgraph.func import entrypoint, task from langgraph.graph import END, StateGraph from langgraph.graph.message import MessageGraph, MessagesState, add_messages from langgraph.prebuilt.tool_node import ToolNode from langgraph.pregel import ( - GraphRecursionError, NodeBuilder, Pregel, - StateSnapshot, ) -from langgraph.pregel.loop import SyncPregelLoop -from langgraph.pregel.retry import RetryPolicy -from langgraph.pregel.runner import PregelRunner +from langgraph.pregel._loop import SyncPregelLoop +from langgraph.pregel._runner import PregelRunner from langgraph.store.base import BaseStore from langgraph.types import ( CachePolicy, Command, Interrupt, PregelTask, + RetryPolicy, Send, + StateSnapshot, StateUpdate, StreamWriter, interrupt, diff --git a/libs/langgraph/tests/test_pregel_async.py b/libs/langgraph/tests/test_pregel_async.py index 61d1ab5d7..6569a1d8d 100644 --- a/libs/langgraph/tests/test_pregel_async.py +++ b/libs/langgraph/tests/test_pregel_async.py @@ -42,22 +42,28 @@ from langgraph.checkpoint.base import ( from langgraph.checkpoint.memory import InMemorySaver from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer from langgraph.constants import CONFIG_KEY_NODE_FINISHED, ERROR, PULL, START -from langgraph.errors import InvalidUpdateError, NodeInterrupt, ParentCommand +from langgraph.errors import ( + GraphRecursionError, + InvalidUpdateError, + NodeInterrupt, + ParentCommand, +) from langgraph.func import entrypoint, task from langgraph.graph import END, StateGraph from langgraph.graph.message import MessagesState, add_messages from langgraph.prebuilt.tool_node import ToolNode -from langgraph.pregel import GraphRecursionError, NodeBuilder, Pregel, StateSnapshot -from langgraph.pregel.loop import AsyncPregelLoop -from langgraph.pregel.retry import RetryPolicy -from langgraph.pregel.runner import PregelRunner +from langgraph.pregel import NodeBuilder, Pregel +from langgraph.pregel._loop import AsyncPregelLoop +from langgraph.pregel._runner import PregelRunner from langgraph.store.base import BaseStore from langgraph.types import ( CachePolicy, Command, Interrupt, PregelTask, + RetryPolicy, Send, + StateSnapshot, StateUpdate, StreamWriter, interrupt, @@ -8381,7 +8387,7 @@ async def test_draw_invalid(): "id": "__start__", "type": "runnable", "data": { - "id": ["langgraph", "utils", "runnable", "RunnableCallable"], + "id": ["langgraph", "_internal", "_runnable", "RunnableCallable"], "name": "__start__", }, }, @@ -8389,7 +8395,7 @@ async def test_draw_invalid(): "id": "agent", "type": "runnable", "data": { - "id": ["langgraph", "utils", "runnable", "RunnableCallable"], + "id": ["langgraph", "_internal", "_runnable", "RunnableCallable"], "name": "agent", }, }, @@ -8397,7 +8403,7 @@ async def test_draw_invalid(): "id": "tool", "type": "runnable", "data": { - "id": ["langgraph", "utils", "runnable", "RunnableCallable"], + "id": ["langgraph", "_internal", "_runnable", "RunnableCallable"], "name": "tool", }, }, @@ -8405,7 +8411,7 @@ async def test_draw_invalid(): "id": "nothing", "type": "runnable", "data": { - "id": ["langgraph", "utils", "runnable", "RunnableCallable"], + "id": ["langgraph", "_internal", "_runnable", "RunnableCallable"], "name": "nothing", }, }, diff --git a/libs/langgraph/tests/test_pydantic.py b/libs/langgraph/tests/test_pydantic.py index 8298970ba..e3340f3ec 100644 --- a/libs/langgraph/tests/test_pydantic.py +++ b/libs/langgraph/tests/test_pydantic.py @@ -21,9 +21,9 @@ from pydantic import ( model_validator, ) +from langgraph._internal._pydantic import is_supported_by_pydantic from langgraph.constants import END, START from langgraph.graph.state import StateGraph -from langgraph.utils.pydantic import is_supported_by_pydantic def test_is_supported_by_pydantic() -> None: diff --git a/libs/langgraph/tests/test_remote_graph.py b/libs/langgraph/tests/test_remote_graph.py index 5fa61db3f..262109673 100644 --- a/libs/langgraph/tests/test_remote_graph.py +++ b/libs/langgraph/tests/test_remote_graph.py @@ -15,8 +15,7 @@ from langgraph.errors import GraphInterrupt from langgraph.graph import StateGraph, add_messages from langgraph.pregel import Pregel from langgraph.pregel.remote import RemoteGraph -from langgraph.pregel.types import StateSnapshot -from langgraph.types import Interrupt +from langgraph.types import Interrupt, StateSnapshot from tests.conftest import NO_DOCKER from tests.example_app.example_graph import app diff --git a/libs/langgraph/tests/test_retry.py b/libs/langgraph/tests/test_retry.py index 6eae7ee5c..ac37bea91 100644 --- a/libs/langgraph/tests/test_retry.py +++ b/libs/langgraph/tests/test_retry.py @@ -4,7 +4,7 @@ import pytest from typing_extensions import TypedDict from langgraph.graph import START, StateGraph -from langgraph.pregel.retry import _should_retry_on +from langgraph.pregel._retry import _should_retry_on from langgraph.types import RetryPolicy diff --git a/libs/langgraph/tests/test_runnable.py b/libs/langgraph/tests/test_runnable.py index 1dd8e35cc..5fb390919 100644 --- a/libs/langgraph/tests/test_runnable.py +++ b/libs/langgraph/tests/test_runnable.py @@ -4,9 +4,9 @@ from typing import Any, Optional import pytest +from langgraph._internal._runnable import RunnableCallable from langgraph.store.base import BaseStore from langgraph.types import StreamWriter -from langgraph.utils.runnable import RunnableCallable pytestmark = pytest.mark.anyio @@ -85,7 +85,7 @@ def test_runnable_callable_injectable_arguments() -> None: """ # Test Optional[BaseStore] annotation. - def func_optional_store(inputs: Any, store: Optional[BaseStore]) -> str: # noqa: UP007 + def func_optional_store(inputs: Any, store: Optional[BaseStore]) -> str: # noqa: UP045 """Test function that accepts an optional store parameter.""" assert store is None return "success" @@ -159,7 +159,7 @@ async def test_runnable_callable_injectable_arguments_async() -> None: """ # Test Optional[BaseStore] annotation. - def func_optional_store(inputs: Any, store: Optional[BaseStore]) -> str: # noqa: UP007 + def func_optional_store(inputs: Any, store: Optional[BaseStore]) -> str: # noqa: UP045 """Test function that accepts an optional store parameter.""" assert store is None return "success" diff --git a/libs/langgraph/tests/test_utils.py b/libs/langgraph/tests/test_utils.py index 455032afa..fabac0595 100644 --- a/libs/langgraph/tests/test_utils.py +++ b/libs/langgraph/tests/test_utils.py @@ -17,18 +17,18 @@ import langsmith import pytest from typing_extensions import NotRequired, Required, TypedDict -from langgraph.graph import END, StateGraph -from langgraph.graph.state import CompiledStateGraph -from langgraph.utils.config import _is_not_empty -from langgraph.utils.fields import ( +from langgraph._internal._config import _is_not_empty +from langgraph._internal._fields import ( _is_optional_type, get_enhanced_type_hints, get_field_default, ) -from langgraph.utils.runnable import ( +from langgraph._internal._runnable import ( is_async_callable, is_async_generator, ) +from langgraph.graph import END, StateGraph +from langgraph.graph.state import CompiledStateGraph pytestmark = pytest.mark.anyio diff --git a/libs/langgraph/langgraph/utils/__init__.py b/libs/langgraph/utils/__init__.py similarity index 100% rename from libs/langgraph/langgraph/utils/__init__.py rename to libs/langgraph/utils/__init__.py diff --git a/libs/langgraph/utils/runnable.py b/libs/langgraph/utils/runnable.py new file mode 100644 index 000000000..0c1a94ed4 --- /dev/null +++ b/libs/langgraph/utils/runnable.py @@ -0,0 +1,2 @@ +# import for backwards compatibility +from langgraph._internal._runnable import RunnableCallable, RunnableSeq # noqa: F401 diff --git a/libs/langgraph/uv.lock b/libs/langgraph/uv.lock index db2d48375..a4f5a8600 100644 --- a/libs/langgraph/uv.lock +++ b/libs/langgraph/uv.lock @@ -2,10 +2,8 @@ version = 1 revision = 2 requires-python = ">=3.9" resolution-markers = [ - "python_full_version >= '3.13' and python_full_version < '4.0'", - "python_full_version >= '3.12.4' and python_full_version < '3.13'", - "python_full_version >= '3.11' and python_full_version < '3.12.4'", - "python_full_version >= '4.0'", + "python_full_version >= '3.13'", + "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", "python_full_version < '3.10'", ] @@ -57,14 +55,14 @@ wheels = [ [[package]] name = "argon2-cffi" -version = "23.1.0" +version = "25.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argon2-cffi-bindings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/fa/57ec2c6d16ecd2ba0cf15f3c7d1c3c2e7b5fcb83555ff56d7ab10888ec8f/argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", size = 42798, upload-time = "2023-08-15T14:13:12.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea", size = 15124, upload-time = "2023-08-15T14:13:10.752Z" }, + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, ] [[package]] @@ -175,7 +173,7 @@ name = "blockbuster" version = "1.5.24" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "forbiddenfruit", marker = "python_full_version >= '3.11' and python_full_version < '4.0' and implementation_name == 'cpython'" }, + { name = "forbiddenfruit", marker = "python_full_version >= '3.11' and implementation_name == 'cpython'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/35/c8/1e456a043179f2aef10bcaafea79f6d06c0ac45cc994767a54f680509f3b/blockbuster-1.5.24.tar.gz", hash = "sha256:97645775761a5d425666ec0bc99629b65c7eccdc2f770d2439850682567af4ec", size = 51245, upload-time = "2025-03-18T10:12:06.398Z" } wheels = [ @@ -184,11 +182,11 @@ wheels = [ [[package]] name = "certifi" -version = "2025.4.26" +version = "2025.6.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753, upload-time = "2025-06-15T02:45:51.329Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, + { url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650, upload-time = "2025-06-15T02:45:49.977Z" }, ] [[package]] @@ -354,10 +352,8 @@ name = "click" version = "8.2.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13' and python_full_version < '4.0'", - "python_full_version >= '3.12.4' and python_full_version < '3.13'", - "python_full_version >= '3.11' and python_full_version < '3.12.4'", - "python_full_version >= '4.0'", + "python_full_version >= '3.13'", + "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", ] dependencies = [ @@ -400,76 +396,76 @@ wheels = [ [[package]] name = "coverage" -version = "7.8.2" +version = "7.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/07/998afa4a0ecdf9b1981ae05415dad2d4e7716e1b1f00abbd91691ac09ac9/coverage-7.8.2.tar.gz", hash = "sha256:a886d531373a1f6ff9fad2a2ba4a045b68467b779ae729ee0b3b10ac20033b27", size = 812759, upload-time = "2025-05-23T11:39:57.856Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/e0/98670a80884f64578f0c22cd70c5e81a6e07b08167721c7487b4d70a7ca0/coverage-7.9.1.tar.gz", hash = "sha256:6cf43c78c4282708a28e466316935ec7489a9c487518a77fa68f716c67909cec", size = 813650, upload-time = "2025-06-13T13:02:28.627Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/6b/7dd06399a5c0b81007e3a6af0395cd60e6a30f959f8d407d3ee04642e896/coverage-7.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd8ec21e1443fd7a447881332f7ce9d35b8fbd2849e761bb290b584535636b0a", size = 211573, upload-time = "2025-05-23T11:37:47.207Z" }, - { url = "https://files.pythonhosted.org/packages/f0/df/2b24090820a0bac1412955fb1a4dade6bc3b8dcef7b899c277ffaf16916d/coverage-7.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c26c2396674816deaeae7ded0e2b42c26537280f8fe313335858ffff35019be", size = 212006, upload-time = "2025-05-23T11:37:50.289Z" }, - { url = "https://files.pythonhosted.org/packages/c5/c4/e4e3b998e116625562a872a342419652fa6ca73f464d9faf9f52f1aff427/coverage-7.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1aec326ed237e5880bfe69ad41616d333712c7937bcefc1343145e972938f9b3", size = 241128, upload-time = "2025-05-23T11:37:52.229Z" }, - { url = "https://files.pythonhosted.org/packages/b1/67/b28904afea3e87a895da850ba587439a61699bf4b73d04d0dfd99bbd33b4/coverage-7.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e818796f71702d7a13e50c70de2a1924f729228580bcba1607cccf32eea46e6", size = 239026, upload-time = "2025-05-23T11:37:53.846Z" }, - { url = "https://files.pythonhosted.org/packages/8c/0f/47bf7c5630d81bc2cd52b9e13043685dbb7c79372a7f5857279cc442b37c/coverage-7.8.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:546e537d9e24efc765c9c891328f30f826e3e4808e31f5d0f87c4ba12bbd1622", size = 240172, upload-time = "2025-05-23T11:37:55.711Z" }, - { url = "https://files.pythonhosted.org/packages/ba/38/af3eb9d36d85abc881f5aaecf8209383dbe0fa4cac2d804c55d05c51cb04/coverage-7.8.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab9b09a2349f58e73f8ebc06fac546dd623e23b063e5398343c5270072e3201c", size = 240086, upload-time = "2025-05-23T11:37:57.724Z" }, - { url = "https://files.pythonhosted.org/packages/9e/64/c40c27c2573adeba0fe16faf39a8aa57368a1f2148865d6bb24c67eadb41/coverage-7.8.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fd51355ab8a372d89fb0e6a31719e825cf8df8b6724bee942fb5b92c3f016ba3", size = 238792, upload-time = "2025-05-23T11:37:59.737Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ab/b7c85146f15457671c1412afca7c25a5696d7625e7158002aa017e2d7e3c/coverage-7.8.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0774df1e093acb6c9e4d58bce7f86656aeed6c132a16e2337692c12786b32404", size = 239096, upload-time = "2025-05-23T11:38:01.693Z" }, - { url = "https://files.pythonhosted.org/packages/d3/50/9446dad1310905fb1dc284d60d4320a5b25d4e3e33f9ea08b8d36e244e23/coverage-7.8.2-cp310-cp310-win32.whl", hash = "sha256:00f2e2f2e37f47e5f54423aeefd6c32a7dbcedc033fcd3928a4f4948e8b96af7", size = 214144, upload-time = "2025-05-23T11:38:03.68Z" }, - { url = "https://files.pythonhosted.org/packages/23/ed/792e66ad7b8b0df757db8d47af0c23659cdb5a65ef7ace8b111cacdbee89/coverage-7.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:145b07bea229821d51811bf15eeab346c236d523838eda395ea969d120d13347", size = 215043, upload-time = "2025-05-23T11:38:05.217Z" }, - { url = "https://files.pythonhosted.org/packages/6a/4d/1ff618ee9f134d0de5cc1661582c21a65e06823f41caf801aadf18811a8e/coverage-7.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b99058eef42e6a8dcd135afb068b3d53aff3921ce699e127602efff9956457a9", size = 211692, upload-time = "2025-05-23T11:38:08.485Z" }, - { url = "https://files.pythonhosted.org/packages/96/fa/c3c1b476de96f2bc7a8ca01a9f1fcb51c01c6b60a9d2c3e66194b2bdb4af/coverage-7.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5feb7f2c3e6ea94d3b877def0270dff0947b8d8c04cfa34a17be0a4dc1836879", size = 212115, upload-time = "2025-05-23T11:38:09.989Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c2/5414c5a1b286c0f3881ae5adb49be1854ac5b7e99011501f81c8c1453065/coverage-7.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:670a13249b957bb9050fab12d86acef7bf8f6a879b9d1a883799276e0d4c674a", size = 244740, upload-time = "2025-05-23T11:38:11.947Z" }, - { url = "https://files.pythonhosted.org/packages/cd/46/1ae01912dfb06a642ef3dd9cf38ed4996fda8fe884dab8952da616f81a2b/coverage-7.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bdc8bf760459a4a4187b452213e04d039990211f98644c7292adf1e471162b5", size = 242429, upload-time = "2025-05-23T11:38:13.955Z" }, - { url = "https://files.pythonhosted.org/packages/06/58/38c676aec594bfe2a87c7683942e5a30224791d8df99bcc8439fde140377/coverage-7.8.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07a989c867986c2a75f158f03fdb413128aad29aca9d4dbce5fc755672d96f11", size = 244218, upload-time = "2025-05-23T11:38:15.631Z" }, - { url = "https://files.pythonhosted.org/packages/80/0c/95b1023e881ce45006d9abc250f76c6cdab7134a1c182d9713878dfefcb2/coverage-7.8.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2db10dedeb619a771ef0e2949ccba7b75e33905de959c2643a4607bef2f3fb3a", size = 243865, upload-time = "2025-05-23T11:38:17.622Z" }, - { url = "https://files.pythonhosted.org/packages/57/37/0ae95989285a39e0839c959fe854a3ae46c06610439350d1ab860bf020ac/coverage-7.8.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e6ea7dba4e92926b7b5f0990634b78ea02f208d04af520c73a7c876d5a8d36cb", size = 242038, upload-time = "2025-05-23T11:38:19.966Z" }, - { url = "https://files.pythonhosted.org/packages/4d/82/40e55f7c0eb5e97cc62cbd9d0746fd24e8caf57be5a408b87529416e0c70/coverage-7.8.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ef2f22795a7aca99fc3c84393a55a53dd18ab8c93fb431004e4d8f0774150f54", size = 242567, upload-time = "2025-05-23T11:38:21.912Z" }, - { url = "https://files.pythonhosted.org/packages/f9/35/66a51adc273433a253989f0d9cc7aa6bcdb4855382cf0858200afe578861/coverage-7.8.2-cp311-cp311-win32.whl", hash = "sha256:641988828bc18a6368fe72355df5f1703e44411adbe49bba5644b941ce6f2e3a", size = 214194, upload-time = "2025-05-23T11:38:23.571Z" }, - { url = "https://files.pythonhosted.org/packages/f6/8f/a543121f9f5f150eae092b08428cb4e6b6d2d134152c3357b77659d2a605/coverage-7.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:8ab4a51cb39dc1933ba627e0875046d150e88478dbe22ce145a68393e9652975", size = 215109, upload-time = "2025-05-23T11:38:25.137Z" }, - { url = "https://files.pythonhosted.org/packages/77/65/6cc84b68d4f35186463cd7ab1da1169e9abb59870c0f6a57ea6aba95f861/coverage-7.8.2-cp311-cp311-win_arm64.whl", hash = "sha256:8966a821e2083c74d88cca5b7dcccc0a3a888a596a04c0b9668a891de3a0cc53", size = 213521, upload-time = "2025-05-23T11:38:27.123Z" }, - { url = "https://files.pythonhosted.org/packages/8d/2a/1da1ada2e3044fcd4a3254fb3576e160b8fe5b36d705c8a31f793423f763/coverage-7.8.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e2f6fe3654468d061942591aef56686131335b7a8325684eda85dacdf311356c", size = 211876, upload-time = "2025-05-23T11:38:29.01Z" }, - { url = "https://files.pythonhosted.org/packages/70/e9/3d715ffd5b6b17a8be80cd14a8917a002530a99943cc1939ad5bb2aa74b9/coverage-7.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76090fab50610798cc05241bf83b603477c40ee87acd358b66196ab0ca44ffa1", size = 212130, upload-time = "2025-05-23T11:38:30.675Z" }, - { url = "https://files.pythonhosted.org/packages/a0/02/fdce62bb3c21649abfd91fbdcf041fb99be0d728ff00f3f9d54d97ed683e/coverage-7.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd0a0a5054be160777a7920b731a0570284db5142abaaf81bcbb282b8d99279", size = 246176, upload-time = "2025-05-23T11:38:32.395Z" }, - { url = "https://files.pythonhosted.org/packages/a7/52/decbbed61e03b6ffe85cd0fea360a5e04a5a98a7423f292aae62423b8557/coverage-7.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da23ce9a3d356d0affe9c7036030b5c8f14556bd970c9b224f9c8205505e3b99", size = 243068, upload-time = "2025-05-23T11:38:33.989Z" }, - { url = "https://files.pythonhosted.org/packages/38/6c/d0e9c0cce18faef79a52778219a3c6ee8e336437da8eddd4ab3dbd8fadff/coverage-7.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9392773cffeb8d7e042a7b15b82a414011e9d2b5fdbbd3f7e6a6b17d5e21b20", size = 245328, upload-time = "2025-05-23T11:38:35.568Z" }, - { url = "https://files.pythonhosted.org/packages/f0/70/f703b553a2f6b6c70568c7e398ed0789d47f953d67fbba36a327714a7bca/coverage-7.8.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:876cbfd0b09ce09d81585d266c07a32657beb3eaec896f39484b631555be0fe2", size = 245099, upload-time = "2025-05-23T11:38:37.627Z" }, - { url = "https://files.pythonhosted.org/packages/ec/fb/4cbb370dedae78460c3aacbdad9d249e853f3bc4ce5ff0e02b1983d03044/coverage-7.8.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3da9b771c98977a13fbc3830f6caa85cae6c9c83911d24cb2d218e9394259c57", size = 243314, upload-time = "2025-05-23T11:38:39.238Z" }, - { url = "https://files.pythonhosted.org/packages/39/9f/1afbb2cb9c8699b8bc38afdce00a3b4644904e6a38c7bf9005386c9305ec/coverage-7.8.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a990f6510b3292686713bfef26d0049cd63b9c7bb17e0864f133cbfd2e6167f", size = 244489, upload-time = "2025-05-23T11:38:40.845Z" }, - { url = "https://files.pythonhosted.org/packages/79/fa/f3e7ec7d220bff14aba7a4786ae47043770cbdceeea1803083059c878837/coverage-7.8.2-cp312-cp312-win32.whl", hash = "sha256:bf8111cddd0f2b54d34e96613e7fbdd59a673f0cf5574b61134ae75b6f5a33b8", size = 214366, upload-time = "2025-05-23T11:38:43.551Z" }, - { url = "https://files.pythonhosted.org/packages/54/aa/9cbeade19b7e8e853e7ffc261df885d66bf3a782c71cba06c17df271f9e6/coverage-7.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:86a323a275e9e44cdf228af9b71c5030861d4d2610886ab920d9945672a81223", size = 215165, upload-time = "2025-05-23T11:38:45.148Z" }, - { url = "https://files.pythonhosted.org/packages/c4/73/e2528bf1237d2448f882bbebaec5c3500ef07301816c5c63464b9da4d88a/coverage-7.8.2-cp312-cp312-win_arm64.whl", hash = "sha256:820157de3a589e992689ffcda8639fbabb313b323d26388d02e154164c57b07f", size = 213548, upload-time = "2025-05-23T11:38:46.74Z" }, - { url = "https://files.pythonhosted.org/packages/1a/93/eb6400a745ad3b265bac36e8077fdffcf0268bdbbb6c02b7220b624c9b31/coverage-7.8.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ea561010914ec1c26ab4188aef8b1567272ef6de096312716f90e5baa79ef8ca", size = 211898, upload-time = "2025-05-23T11:38:49.066Z" }, - { url = "https://files.pythonhosted.org/packages/1b/7c/bdbf113f92683024406a1cd226a199e4200a2001fc85d6a6e7e299e60253/coverage-7.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cb86337a4fcdd0e598ff2caeb513ac604d2f3da6d53df2c8e368e07ee38e277d", size = 212171, upload-time = "2025-05-23T11:38:51.207Z" }, - { url = "https://files.pythonhosted.org/packages/91/22/594513f9541a6b88eb0dba4d5da7d71596dadef6b17a12dc2c0e859818a9/coverage-7.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a4636ddb666971345541b59899e969f3b301143dd86b0ddbb570bd591f1e85", size = 245564, upload-time = "2025-05-23T11:38:52.857Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f4/2860fd6abeebd9f2efcfe0fd376226938f22afc80c1943f363cd3c28421f/coverage-7.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5040536cf9b13fb033f76bcb5e1e5cb3b57c4807fef37db9e0ed129c6a094257", size = 242719, upload-time = "2025-05-23T11:38:54.529Z" }, - { url = "https://files.pythonhosted.org/packages/89/60/f5f50f61b6332451520e6cdc2401700c48310c64bc2dd34027a47d6ab4ca/coverage-7.8.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc67994df9bcd7e0150a47ef41278b9e0a0ea187caba72414b71dc590b99a108", size = 244634, upload-time = "2025-05-23T11:38:57.326Z" }, - { url = "https://files.pythonhosted.org/packages/3b/70/7f4e919039ab7d944276c446b603eea84da29ebcf20984fb1fdf6e602028/coverage-7.8.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e6c86888fd076d9e0fe848af0a2142bf606044dc5ceee0aa9eddb56e26895a0", size = 244824, upload-time = "2025-05-23T11:38:59.421Z" }, - { url = "https://files.pythonhosted.org/packages/26/45/36297a4c0cea4de2b2c442fe32f60c3991056c59cdc3cdd5346fbb995c97/coverage-7.8.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:684ca9f58119b8e26bef860db33524ae0365601492e86ba0b71d513f525e7050", size = 242872, upload-time = "2025-05-23T11:39:01.049Z" }, - { url = "https://files.pythonhosted.org/packages/a4/71/e041f1b9420f7b786b1367fa2a375703889ef376e0d48de9f5723fb35f11/coverage-7.8.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8165584ddedb49204c4e18da083913bdf6a982bfb558632a79bdaadcdafd0d48", size = 244179, upload-time = "2025-05-23T11:39:02.709Z" }, - { url = "https://files.pythonhosted.org/packages/bd/db/3c2bf49bdc9de76acf2491fc03130c4ffc51469ce2f6889d2640eb563d77/coverage-7.8.2-cp313-cp313-win32.whl", hash = "sha256:34759ee2c65362163699cc917bdb2a54114dd06d19bab860725f94ef45a3d9b7", size = 214393, upload-time = "2025-05-23T11:39:05.457Z" }, - { url = "https://files.pythonhosted.org/packages/c6/dc/947e75d47ebbb4b02d8babb1fad4ad381410d5bc9da7cfca80b7565ef401/coverage-7.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:2f9bc608fbafaee40eb60a9a53dbfb90f53cc66d3d32c2849dc27cf5638a21e3", size = 215194, upload-time = "2025-05-23T11:39:07.171Z" }, - { url = "https://files.pythonhosted.org/packages/90/31/a980f7df8a37eaf0dc60f932507fda9656b3a03f0abf188474a0ea188d6d/coverage-7.8.2-cp313-cp313-win_arm64.whl", hash = "sha256:9fe449ee461a3b0c7105690419d0b0aba1232f4ff6d120a9e241e58a556733f7", size = 213580, upload-time = "2025-05-23T11:39:08.862Z" }, - { url = "https://files.pythonhosted.org/packages/8a/6a/25a37dd90f6c95f59355629417ebcb74e1c34e38bb1eddf6ca9b38b0fc53/coverage-7.8.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8369a7c8ef66bded2b6484053749ff220dbf83cba84f3398c84c51a6f748a008", size = 212734, upload-time = "2025-05-23T11:39:11.109Z" }, - { url = "https://files.pythonhosted.org/packages/36/8b/3a728b3118988725f40950931abb09cd7f43b3c740f4640a59f1db60e372/coverage-7.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:159b81df53a5fcbc7d45dae3adad554fdbde9829a994e15227b3f9d816d00b36", size = 212959, upload-time = "2025-05-23T11:39:12.751Z" }, - { url = "https://files.pythonhosted.org/packages/53/3c/212d94e6add3a3c3f412d664aee452045ca17a066def8b9421673e9482c4/coverage-7.8.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6fcbbd35a96192d042c691c9e0c49ef54bd7ed865846a3c9d624c30bb67ce46", size = 257024, upload-time = "2025-05-23T11:39:15.569Z" }, - { url = "https://files.pythonhosted.org/packages/a4/40/afc03f0883b1e51bbe804707aae62e29c4e8c8bbc365c75e3e4ddeee9ead/coverage-7.8.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05364b9cc82f138cc86128dc4e2e1251c2981a2218bfcd556fe6b0fbaa3501be", size = 252867, upload-time = "2025-05-23T11:39:17.64Z" }, - { url = "https://files.pythonhosted.org/packages/18/a2/3699190e927b9439c6ded4998941a3c1d6fa99e14cb28d8536729537e307/coverage-7.8.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46d532db4e5ff3979ce47d18e2fe8ecad283eeb7367726da0e5ef88e4fe64740", size = 255096, upload-time = "2025-05-23T11:39:19.328Z" }, - { url = "https://files.pythonhosted.org/packages/b4/06/16e3598b9466456b718eb3e789457d1a5b8bfb22e23b6e8bbc307df5daf0/coverage-7.8.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4000a31c34932e7e4fa0381a3d6deb43dc0c8f458e3e7ea6502e6238e10be625", size = 256276, upload-time = "2025-05-23T11:39:21.077Z" }, - { url = "https://files.pythonhosted.org/packages/a7/d5/4b5a120d5d0223050a53d2783c049c311eea1709fa9de12d1c358e18b707/coverage-7.8.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:43ff5033d657cd51f83015c3b7a443287250dc14e69910577c3e03bd2e06f27b", size = 254478, upload-time = "2025-05-23T11:39:22.838Z" }, - { url = "https://files.pythonhosted.org/packages/ba/85/f9ecdb910ecdb282b121bfcaa32fa8ee8cbd7699f83330ee13ff9bbf1a85/coverage-7.8.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94316e13f0981cbbba132c1f9f365cac1d26716aaac130866ca812006f662199", size = 255255, upload-time = "2025-05-23T11:39:24.644Z" }, - { url = "https://files.pythonhosted.org/packages/50/63/2d624ac7d7ccd4ebbd3c6a9eba9d7fc4491a1226071360d59dd84928ccb2/coverage-7.8.2-cp313-cp313t-win32.whl", hash = "sha256:3f5673888d3676d0a745c3d0e16da338c5eea300cb1f4ada9c872981265e76d8", size = 215109, upload-time = "2025-05-23T11:39:26.722Z" }, - { url = "https://files.pythonhosted.org/packages/22/5e/7053b71462e970e869111c1853afd642212568a350eba796deefdfbd0770/coverage-7.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:2c08b05ee8d7861e45dc5a2cc4195c8c66dca5ac613144eb6ebeaff2d502e73d", size = 216268, upload-time = "2025-05-23T11:39:28.429Z" }, - { url = "https://files.pythonhosted.org/packages/07/69/afa41aa34147655543dbe96994f8a246daf94b361ccf5edfd5df62ce066a/coverage-7.8.2-cp313-cp313t-win_arm64.whl", hash = "sha256:1e1448bb72b387755e1ff3ef1268a06617afd94188164960dba8d0245a46004b", size = 214071, upload-time = "2025-05-23T11:39:30.55Z" }, - { url = "https://files.pythonhosted.org/packages/71/1e/388267ad9c6aa126438acc1ceafede3bb746afa9872e3ec5f0691b7d5efa/coverage-7.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:496948261eaac5ac9cf43f5d0a9f6eb7a6d4cb3bedb2c5d294138142f5c18f2a", size = 211566, upload-time = "2025-05-23T11:39:32.333Z" }, - { url = "https://files.pythonhosted.org/packages/8f/a5/acc03e5cf0bba6357f5e7c676343de40fbf431bb1e115fbebf24b2f7f65e/coverage-7.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eacd2de0d30871eff893bab0b67840a96445edcb3c8fd915e6b11ac4b2f3fa6d", size = 211996, upload-time = "2025-05-23T11:39:34.512Z" }, - { url = "https://files.pythonhosted.org/packages/5b/a2/0fc0a9f6b7c24fa4f1d7210d782c38cb0d5e692666c36eaeae9a441b6755/coverage-7.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b039ffddc99ad65d5078ef300e0c7eed08c270dc26570440e3ef18beb816c1ca", size = 240741, upload-time = "2025-05-23T11:39:36.252Z" }, - { url = "https://files.pythonhosted.org/packages/e6/da/1c6ba2cf259710eed8916d4fd201dccc6be7380ad2b3b9f63ece3285d809/coverage-7.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e49824808d4375ede9dd84e9961a59c47f9113039f1a525e6be170aa4f5c34d", size = 238672, upload-time = "2025-05-23T11:39:38.03Z" }, - { url = "https://files.pythonhosted.org/packages/ac/51/c8fae0dc3ca421e6e2509503696f910ff333258db672800c3bdef256265a/coverage-7.8.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b069938961dfad881dc2f8d02b47645cd2f455d3809ba92a8a687bf513839787", size = 239769, upload-time = "2025-05-23T11:39:40.24Z" }, - { url = "https://files.pythonhosted.org/packages/59/8e/b97042ae92c59f40be0c989df090027377ba53f2d6cef73c9ca7685c26a6/coverage-7.8.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:de77c3ba8bb686d1c411e78ee1b97e6e0b963fb98b1637658dd9ad2c875cf9d7", size = 239555, upload-time = "2025-05-23T11:39:42.3Z" }, - { url = "https://files.pythonhosted.org/packages/47/35/b8893e682d6e96b1db2af5997fc13ef62219426fb17259d6844c693c5e00/coverage-7.8.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1676628065a498943bd3f64f099bb573e08cf1bc6088bbe33cf4424e0876f4b3", size = 237768, upload-time = "2025-05-23T11:39:44.069Z" }, - { url = "https://files.pythonhosted.org/packages/03/6c/023b0b9a764cb52d6243a4591dcb53c4caf4d7340445113a1f452bb80591/coverage-7.8.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8e1a26e7e50076e35f7afafde570ca2b4d7900a491174ca357d29dece5aacee7", size = 238757, upload-time = "2025-05-23T11:39:46.195Z" }, - { url = "https://files.pythonhosted.org/packages/03/ed/3af7e4d721bd61a8df7de6de9e8a4271e67f3d9e086454558fd9f48eb4f6/coverage-7.8.2-cp39-cp39-win32.whl", hash = "sha256:6782a12bf76fa61ad9350d5a6ef5f3f020b57f5e6305cbc663803f2ebd0f270a", size = 214166, upload-time = "2025-05-23T11:39:47.934Z" }, - { url = "https://files.pythonhosted.org/packages/9d/30/ee774b626773750dc6128354884652507df3c59d6aa8431526107e595227/coverage-7.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:1efa4166ba75ccefd647f2d78b64f53f14fb82622bc94c5a5cb0a622f50f1c9e", size = 215050, upload-time = "2025-05-23T11:39:50.252Z" }, - { url = "https://files.pythonhosted.org/packages/69/2f/572b29496d8234e4a7773200dd835a0d32d9e171f2d974f3fe04a9dbc271/coverage-7.8.2-pp39.pp310.pp311-none-any.whl", hash = "sha256:ec455eedf3ba0bbdf8f5a570012617eb305c63cb9f03428d39bf544cb2b94837", size = 203636, upload-time = "2025-05-23T11:39:52.002Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1a/0b9c32220ad694d66062f571cc5cedfa9997b64a591e8a500bb63de1bd40/coverage-7.8.2-py3-none-any.whl", hash = "sha256:726f32ee3713f7359696331a18daf0c3b3a70bb0ae71141b9d3c52be7c595e32", size = 203623, upload-time = "2025-05-23T11:39:53.846Z" }, + { url = "https://files.pythonhosted.org/packages/c1/78/1c1c5ec58f16817c09cbacb39783c3655d54a221b6552f47ff5ac9297603/coverage-7.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc94d7c5e8423920787c33d811c0be67b7be83c705f001f7180c7b186dcf10ca", size = 212028, upload-time = "2025-06-13T13:00:29.293Z" }, + { url = "https://files.pythonhosted.org/packages/98/db/e91b9076f3a888e3b4ad7972ea3842297a52cc52e73fd1e529856e473510/coverage-7.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16aa0830d0c08a2c40c264cef801db8bc4fc0e1892782e45bcacbd5889270509", size = 212420, upload-time = "2025-06-13T13:00:34.027Z" }, + { url = "https://files.pythonhosted.org/packages/0e/d0/2b3733412954576b0aea0a16c3b6b8fbe95eb975d8bfa10b07359ead4252/coverage-7.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf95981b126f23db63e9dbe4cf65bd71f9a6305696fa5e2262693bc4e2183f5b", size = 241529, upload-time = "2025-06-13T13:00:35.786Z" }, + { url = "https://files.pythonhosted.org/packages/b3/00/5e2e5ae2e750a872226a68e984d4d3f3563cb01d1afb449a17aa819bc2c4/coverage-7.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f05031cf21699785cd47cb7485f67df619e7bcdae38e0fde40d23d3d0210d3c3", size = 239403, upload-time = "2025-06-13T13:00:37.399Z" }, + { url = "https://files.pythonhosted.org/packages/37/3b/a2c27736035156b0a7c20683afe7df498480c0dfdf503b8c878a21b6d7fb/coverage-7.9.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb4fbcab8764dc072cb651a4bcda4d11fb5658a1d8d68842a862a6610bd8cfa3", size = 240548, upload-time = "2025-06-13T13:00:39.647Z" }, + { url = "https://files.pythonhosted.org/packages/98/f5/13d5fc074c3c0e0dc80422d9535814abf190f1254d7c3451590dc4f8b18c/coverage-7.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0f16649a7330ec307942ed27d06ee7e7a38417144620bb3d6e9a18ded8a2d3e5", size = 240459, upload-time = "2025-06-13T13:00:40.934Z" }, + { url = "https://files.pythonhosted.org/packages/36/24/24b9676ea06102df824c4a56ffd13dc9da7904478db519efa877d16527d5/coverage-7.9.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cea0a27a89e6432705fffc178064503508e3c0184b4f061700e771a09de58187", size = 239128, upload-time = "2025-06-13T13:00:42.343Z" }, + { url = "https://files.pythonhosted.org/packages/be/05/242b7a7d491b369ac5fee7908a6e5ba42b3030450f3ad62c645b40c23e0e/coverage-7.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e980b53a959fa53b6f05343afbd1e6f44a23ed6c23c4b4c56c6662bbb40c82ce", size = 239402, upload-time = "2025-06-13T13:00:43.634Z" }, + { url = "https://files.pythonhosted.org/packages/73/e0/4de7f87192fa65c9c8fbaeb75507e124f82396b71de1797da5602898be32/coverage-7.9.1-cp310-cp310-win32.whl", hash = "sha256:70760b4c5560be6ca70d11f8988ee6542b003f982b32f83d5ac0b72476607b70", size = 214518, upload-time = "2025-06-13T13:00:45.622Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ab/5e4e2fe458907d2a65fab62c773671cfc5ac704f1e7a9ddd91996f66e3c2/coverage-7.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a66e8f628b71f78c0e0342003d53b53101ba4e00ea8dabb799d9dba0abbbcebe", size = 215436, upload-time = "2025-06-13T13:00:47.245Z" }, + { url = "https://files.pythonhosted.org/packages/60/34/fa69372a07d0903a78ac103422ad34db72281c9fc625eba94ac1185da66f/coverage-7.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95c765060e65c692da2d2f51a9499c5e9f5cf5453aeaf1420e3fc847cc060582", size = 212146, upload-time = "2025-06-13T13:00:48.496Z" }, + { url = "https://files.pythonhosted.org/packages/27/f0/da1894915d2767f093f081c42afeba18e760f12fdd7a2f4acbe00564d767/coverage-7.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba383dc6afd5ec5b7a0d0c23d38895db0e15bcba7fb0fa8901f245267ac30d86", size = 212536, upload-time = "2025-06-13T13:00:51.535Z" }, + { url = "https://files.pythonhosted.org/packages/10/d5/3fc33b06e41e390f88eef111226a24e4504d216ab8e5d1a7089aa5a3c87a/coverage-7.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37ae0383f13cbdcf1e5e7014489b0d71cc0106458878ccde52e8a12ced4298ed", size = 245092, upload-time = "2025-06-13T13:00:52.883Z" }, + { url = "https://files.pythonhosted.org/packages/0a/39/7aa901c14977aba637b78e95800edf77f29f5a380d29768c5b66f258305b/coverage-7.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69aa417a030bf11ec46149636314c24c8d60fadb12fc0ee8f10fda0d918c879d", size = 242806, upload-time = "2025-06-13T13:00:54.571Z" }, + { url = "https://files.pythonhosted.org/packages/43/fc/30e5cfeaf560b1fc1989227adedc11019ce4bb7cce59d65db34fe0c2d963/coverage-7.9.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a4be2a28656afe279b34d4f91c3e26eccf2f85500d4a4ff0b1f8b54bf807338", size = 244610, upload-time = "2025-06-13T13:00:56.932Z" }, + { url = "https://files.pythonhosted.org/packages/bf/15/cca62b13f39650bc87b2b92bb03bce7f0e79dd0bf2c7529e9fc7393e4d60/coverage-7.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:382e7ddd5289f140259b610e5f5c58f713d025cb2f66d0eb17e68d0a94278875", size = 244257, upload-time = "2025-06-13T13:00:58.545Z" }, + { url = "https://files.pythonhosted.org/packages/cd/1a/c0f2abe92c29e1464dbd0ff9d56cb6c88ae2b9e21becdb38bea31fcb2f6c/coverage-7.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e5532482344186c543c37bfad0ee6069e8ae4fc38d073b8bc836fc8f03c9e250", size = 242309, upload-time = "2025-06-13T13:00:59.836Z" }, + { url = "https://files.pythonhosted.org/packages/57/8d/c6fd70848bd9bf88fa90df2af5636589a8126d2170f3aade21ed53f2b67a/coverage-7.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a39d18b3f50cc121d0ce3838d32d58bd1d15dab89c910358ebefc3665712256c", size = 242898, upload-time = "2025-06-13T13:01:02.506Z" }, + { url = "https://files.pythonhosted.org/packages/c2/9e/6ca46c7bff4675f09a66fe2797cd1ad6a24f14c9c7c3b3ebe0470a6e30b8/coverage-7.9.1-cp311-cp311-win32.whl", hash = "sha256:dd24bd8d77c98557880def750782df77ab2b6885a18483dc8588792247174b32", size = 214561, upload-time = "2025-06-13T13:01:04.012Z" }, + { url = "https://files.pythonhosted.org/packages/a1/30/166978c6302010742dabcdc425fa0f938fa5a800908e39aff37a7a876a13/coverage-7.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b55ad10a35a21b8015eabddc9ba31eb590f54adc9cd39bcf09ff5349fd52125", size = 215493, upload-time = "2025-06-13T13:01:05.702Z" }, + { url = "https://files.pythonhosted.org/packages/60/07/a6d2342cd80a5be9f0eeab115bc5ebb3917b4a64c2953534273cf9bc7ae6/coverage-7.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ad935f0016be24c0e97fc8c40c465f9c4b85cbbe6eac48934c0dc4d2568321e", size = 213869, upload-time = "2025-06-13T13:01:09.345Z" }, + { url = "https://files.pythonhosted.org/packages/68/d9/7f66eb0a8f2fce222de7bdc2046ec41cb31fe33fb55a330037833fb88afc/coverage-7.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8de12b4b87c20de895f10567639c0797b621b22897b0af3ce4b4e204a743626", size = 212336, upload-time = "2025-06-13T13:01:10.909Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/e07cb920ef3addf20f052ee3d54906e57407b6aeee3227a9c91eea38a665/coverage-7.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5add197315a054e92cee1b5f686a2bcba60c4c3e66ee3de77ace6c867bdee7cb", size = 212571, upload-time = "2025-06-13T13:01:12.518Z" }, + { url = "https://files.pythonhosted.org/packages/78/f8/96f155de7e9e248ca9c8ff1a40a521d944ba48bec65352da9be2463745bf/coverage-7.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600a1d4106fe66f41e5d0136dfbc68fe7200a5cbe85610ddf094f8f22e1b0300", size = 246377, upload-time = "2025-06-13T13:01:14.87Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cf/1d783bd05b7bca5c10ded5f946068909372e94615a4416afadfe3f63492d/coverage-7.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a876e4c3e5a2a1715a6608906aa5a2e0475b9c0f68343c2ada98110512ab1d8", size = 243394, upload-time = "2025-06-13T13:01:16.23Z" }, + { url = "https://files.pythonhosted.org/packages/02/dd/e7b20afd35b0a1abea09fb3998e1abc9f9bd953bee548f235aebd2b11401/coverage-7.9.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f34346dd63010453922c8e628a52ea2d2ccd73cb2487f7700ac531b247c8a5", size = 245586, upload-time = "2025-06-13T13:01:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/4e/38/b30b0006fea9d617d1cb8e43b1bc9a96af11eff42b87eb8c716cf4d37469/coverage-7.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:888f8eee13f2377ce86d44f338968eedec3291876b0b8a7289247ba52cb984cd", size = 245396, upload-time = "2025-06-13T13:01:19.164Z" }, + { url = "https://files.pythonhosted.org/packages/31/e4/4d8ec1dc826e16791f3daf1b50943e8e7e1eb70e8efa7abb03936ff48418/coverage-7.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9969ef1e69b8c8e1e70d591f91bbc37fc9a3621e447525d1602801a24ceda898", size = 243577, upload-time = "2025-06-13T13:01:22.433Z" }, + { url = "https://files.pythonhosted.org/packages/25/f4/b0e96c5c38e6e40ef465c4bc7f138863e2909c00e54a331da335faf0d81a/coverage-7.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:60c458224331ee3f1a5b472773e4a085cc27a86a0b48205409d364272d67140d", size = 244809, upload-time = "2025-06-13T13:01:24.143Z" }, + { url = "https://files.pythonhosted.org/packages/8a/65/27e0a1fa5e2e5079bdca4521be2f5dabf516f94e29a0defed35ac2382eb2/coverage-7.9.1-cp312-cp312-win32.whl", hash = "sha256:5f646a99a8c2b3ff4c6a6e081f78fad0dde275cd59f8f49dc4eab2e394332e74", size = 214724, upload-time = "2025-06-13T13:01:25.435Z" }, + { url = "https://files.pythonhosted.org/packages/9b/a8/d5b128633fd1a5e0401a4160d02fa15986209a9e47717174f99dc2f7166d/coverage-7.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:30f445f85c353090b83e552dcbbdad3ec84c7967e108c3ae54556ca69955563e", size = 215535, upload-time = "2025-06-13T13:01:27.861Z" }, + { url = "https://files.pythonhosted.org/packages/a3/37/84bba9d2afabc3611f3e4325ee2c6a47cd449b580d4a606b240ce5a6f9bf/coverage-7.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:af41da5dca398d3474129c58cb2b106a5d93bbb196be0d307ac82311ca234342", size = 213904, upload-time = "2025-06-13T13:01:29.202Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a7/a027970c991ca90f24e968999f7d509332daf6b8c3533d68633930aaebac/coverage-7.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:31324f18d5969feef7344a932c32428a2d1a3e50b15a6404e97cba1cc9b2c631", size = 212358, upload-time = "2025-06-13T13:01:30.909Z" }, + { url = "https://files.pythonhosted.org/packages/f2/48/6aaed3651ae83b231556750280682528fea8ac7f1232834573472d83e459/coverage-7.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0c804506d624e8a20fb3108764c52e0eef664e29d21692afa375e0dd98dc384f", size = 212620, upload-time = "2025-06-13T13:01:32.256Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2a/f4b613f3b44d8b9f144847c89151992b2b6b79cbc506dee89ad0c35f209d/coverage-7.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef64c27bc40189f36fcc50c3fb8f16ccda73b6a0b80d9bd6e6ce4cffcd810bbd", size = 245788, upload-time = "2025-06-13T13:01:33.948Z" }, + { url = "https://files.pythonhosted.org/packages/04/d2/de4fdc03af5e4e035ef420ed26a703c6ad3d7a07aff2e959eb84e3b19ca8/coverage-7.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4fe2348cc6ec372e25adec0219ee2334a68d2f5222e0cba9c0d613394e12d86", size = 243001, upload-time = "2025-06-13T13:01:35.285Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e8/eed18aa5583b0423ab7f04e34659e51101135c41cd1dcb33ac1d7013a6d6/coverage-7.9.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34ed2186fe52fcc24d4561041979a0dec69adae7bce2ae8d1c49eace13e55c43", size = 244985, upload-time = "2025-06-13T13:01:36.712Z" }, + { url = "https://files.pythonhosted.org/packages/17/f8/ae9e5cce8885728c934eaa58ebfa8281d488ef2afa81c3dbc8ee9e6d80db/coverage-7.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:25308bd3d00d5eedd5ae7d4357161f4df743e3c0240fa773ee1b0f75e6c7c0f1", size = 245152, upload-time = "2025-06-13T13:01:39.303Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c8/272c01ae792bb3af9b30fac14d71d63371db227980682836ec388e2c57c0/coverage-7.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73e9439310f65d55a5a1e0564b48e34f5369bee943d72c88378f2d576f5a5751", size = 243123, upload-time = "2025-06-13T13:01:40.727Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d0/2819a1e3086143c094ab446e3bdf07138527a7b88cb235c488e78150ba7a/coverage-7.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37ab6be0859141b53aa89412a82454b482c81cf750de4f29223d52268a86de67", size = 244506, upload-time = "2025-06-13T13:01:42.184Z" }, + { url = "https://files.pythonhosted.org/packages/8b/4e/9f6117b89152df7b6112f65c7a4ed1f2f5ec8e60c4be8f351d91e7acc848/coverage-7.9.1-cp313-cp313-win32.whl", hash = "sha256:64bdd969456e2d02a8b08aa047a92d269c7ac1f47e0c977675d550c9a0863643", size = 214766, upload-time = "2025-06-13T13:01:44.482Z" }, + { url = "https://files.pythonhosted.org/packages/27/0f/4b59f7c93b52c2c4ce7387c5a4e135e49891bb3b7408dcc98fe44033bbe0/coverage-7.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:be9e3f68ca9edb897c2184ad0eee815c635565dbe7a0e7e814dc1f7cbab92c0a", size = 215568, upload-time = "2025-06-13T13:01:45.772Z" }, + { url = "https://files.pythonhosted.org/packages/09/1e/9679826336f8c67b9c39a359352882b24a8a7aee48d4c9cad08d38d7510f/coverage-7.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:1c503289ffef1d5105d91bbb4d62cbe4b14bec4d13ca225f9c73cde9bb46207d", size = 213939, upload-time = "2025-06-13T13:01:47.087Z" }, + { url = "https://files.pythonhosted.org/packages/bb/5b/5c6b4e7a407359a2e3b27bf9c8a7b658127975def62077d441b93a30dbe8/coverage-7.9.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0b3496922cb5f4215bf5caaef4cf12364a26b0be82e9ed6d050f3352cf2d7ef0", size = 213079, upload-time = "2025-06-13T13:01:48.554Z" }, + { url = "https://files.pythonhosted.org/packages/a2/22/1e2e07279fd2fd97ae26c01cc2186e2258850e9ec125ae87184225662e89/coverage-7.9.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9565c3ab1c93310569ec0d86b017f128f027cab0b622b7af288696d7ed43a16d", size = 213299, upload-time = "2025-06-13T13:01:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/14/c0/4c5125a4b69d66b8c85986d3321520f628756cf524af810baab0790c7647/coverage-7.9.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2241ad5dbf79ae1d9c08fe52b36d03ca122fb9ac6bca0f34439e99f8327ac89f", size = 256535, upload-time = "2025-06-13T13:01:51.314Z" }, + { url = "https://files.pythonhosted.org/packages/81/8b/e36a04889dda9960be4263e95e777e7b46f1bb4fc32202612c130a20c4da/coverage-7.9.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bb5838701ca68b10ebc0937dbd0eb81974bac54447c55cd58dea5bca8451029", size = 252756, upload-time = "2025-06-13T13:01:54.403Z" }, + { url = "https://files.pythonhosted.org/packages/98/82/be04eff8083a09a4622ecd0e1f31a2c563dbea3ed848069e7b0445043a70/coverage-7.9.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a25f814591a8c0c5372c11ac8967f669b97444c47fd794926e175c4047ece", size = 254912, upload-time = "2025-06-13T13:01:56.769Z" }, + { url = "https://files.pythonhosted.org/packages/0f/25/c26610a2c7f018508a5ab958e5b3202d900422cf7cdca7670b6b8ca4e8df/coverage-7.9.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2d04b16a6062516df97969f1ae7efd0de9c31eb6ebdceaa0d213b21c0ca1a683", size = 256144, upload-time = "2025-06-13T13:01:58.19Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8b/fb9425c4684066c79e863f1e6e7ecebb49e3a64d9f7f7860ef1688c56f4a/coverage-7.9.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7931b9e249edefb07cd6ae10c702788546341d5fe44db5b6108a25da4dca513f", size = 254257, upload-time = "2025-06-13T13:01:59.645Z" }, + { url = "https://files.pythonhosted.org/packages/93/df/27b882f54157fc1131e0e215b0da3b8d608d9b8ef79a045280118a8f98fe/coverage-7.9.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52e92b01041151bf607ee858e5a56c62d4b70f4dac85b8c8cb7fb8a351ab2c10", size = 255094, upload-time = "2025-06-13T13:02:01.37Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/cad1c3dbed8b3ee9e16fa832afe365b4e3eeab1fb6edb65ebbf745eabc92/coverage-7.9.1-cp313-cp313t-win32.whl", hash = "sha256:684e2110ed84fd1ca5f40e89aa44adf1729dc85444004111aa01866507adf363", size = 215437, upload-time = "2025-06-13T13:02:02.905Z" }, + { url = "https://files.pythonhosted.org/packages/99/4d/fad293bf081c0e43331ca745ff63673badc20afea2104b431cdd8c278b4c/coverage-7.9.1-cp313-cp313t-win_amd64.whl", hash = "sha256:437c576979e4db840539674e68c84b3cda82bc824dd138d56bead1435f1cb5d7", size = 216605, upload-time = "2025-06-13T13:02:05.638Z" }, + { url = "https://files.pythonhosted.org/packages/1f/56/4ee027d5965fc7fc126d7ec1187529cc30cc7d740846e1ecb5e92d31b224/coverage-7.9.1-cp313-cp313t-win_arm64.whl", hash = "sha256:18a0912944d70aaf5f399e350445738a1a20b50fbea788f640751c2ed9208b6c", size = 214392, upload-time = "2025-06-13T13:02:07.642Z" }, + { url = "https://files.pythonhosted.org/packages/a5/d6/c41dd9b02bf16ec001aaf1cbef665537606899a3db1094e78f5ae17540ca/coverage-7.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f424507f57878e424d9a95dc4ead3fbdd72fd201e404e861e465f28ea469951", size = 212029, upload-time = "2025-06-13T13:02:09.058Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c0/40420d81d731f84c3916dcdf0506b3e6c6570817bff2576b83f780914ae6/coverage-7.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:535fde4001b2783ac80865d90e7cc7798b6b126f4cd8a8c54acfe76804e54e58", size = 212407, upload-time = "2025-06-13T13:02:11.151Z" }, + { url = "https://files.pythonhosted.org/packages/9b/87/f0db7d62d0e09f14d6d2f6ae8c7274a2f09edf74895a34b412a0601e375a/coverage-7.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02532fd3290bb8fa6bec876520842428e2a6ed6c27014eca81b031c2d30e3f71", size = 241160, upload-time = "2025-06-13T13:02:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/a9/b7/3337c064f058a5d7696c4867159651a5b5fb01a5202bcf37362f0c51400e/coverage-7.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56f5eb308b17bca3bbff810f55ee26d51926d9f89ba92707ee41d3c061257e55", size = 239027, upload-time = "2025-06-13T13:02:14.294Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/5898a283f66d1bd413c32c2e0e05408196fd4f37e206e2b06c6e0c626e0e/coverage-7.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfa447506c1a52271f1b0de3f42ea0fa14676052549095e378d5bff1c505ff7b", size = 240145, upload-time = "2025-06-13T13:02:15.745Z" }, + { url = "https://files.pythonhosted.org/packages/e0/33/d96e3350078a3c423c549cb5b2ba970de24c5257954d3e4066e2b2152d30/coverage-7.9.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9ca8e220006966b4a7b68e8984a6aee645a0384b0769e829ba60281fe61ec4f7", size = 239871, upload-time = "2025-06-13T13:02:17.344Z" }, + { url = "https://files.pythonhosted.org/packages/1d/6e/6fb946072455f71a820cac144d49d11747a0f1a21038060a68d2d0200499/coverage-7.9.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:49f1d0788ba5b7ba65933f3a18864117c6506619f5ca80326b478f72acf3f385", size = 238122, upload-time = "2025-06-13T13:02:18.849Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5c/bc43f25c8586840ce25a796a8111acf6a2b5f0909ba89a10d41ccff3920d/coverage-7.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:68cd53aec6f45b8e4724c0950ce86eacb775c6be01ce6e3669fe4f3a21e768ed", size = 239058, upload-time = "2025-06-13T13:02:21.423Z" }, + { url = "https://files.pythonhosted.org/packages/11/d8/ce2007418dd7fd00ff8c8b898bb150bb4bac2d6a86df05d7b88a07ff595f/coverage-7.9.1-cp39-cp39-win32.whl", hash = "sha256:95335095b6c7b1cc14c3f3f17d5452ce677e8490d101698562b2ffcacc304c8d", size = 214532, upload-time = "2025-06-13T13:02:22.857Z" }, + { url = "https://files.pythonhosted.org/packages/20/21/334e76fa246e92e6d69cab217f7c8a70ae0cc8f01438bd0544103f29528e/coverage-7.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:e1b5191d1648acc439b24721caab2fd0c86679d8549ed2c84d5a7ec1bedcc244", size = 215439, upload-time = "2025-06-13T13:02:24.268Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e5/c723545c3fd3204ebde3b4cc4b927dce709d3b6dc577754bb57f63ca4a4a/coverage-7.9.1-pp39.pp310.pp311-none-any.whl", hash = "sha256:db0f04118d1db74db6c9e1cb1898532c7dcc220f1d2718f058601f7c3f499514", size = 204009, upload-time = "2025-06-13T13:02:25.787Z" }, + { url = "https://files.pythonhosted.org/packages/08/b8/7ddd1e8ba9701dea08ce22029917140e6f66a859427406579fd8d0ca7274/coverage-7.9.1-py3-none-any.whl", hash = "sha256:66b974b145aa189516b6bf2d8423e888b742517d37872f6ee4c5be0073bd9a3c", size = 204000, upload-time = "2025-06-13T13:02:27.173Z" }, ] [package.optional-dependencies] @@ -482,7 +478,7 @@ name = "cryptography" version = "44.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi", marker = "python_full_version >= '3.11' and python_full_version < '4.0' and platform_python_implementation != 'PyPy'" }, + { name = "cffi", marker = "python_full_version >= '3.11' and platform_python_implementation != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/53/d6/1411ab4d6108ab167d06254c5be517681f1e331f90edf1379895bcb87020/cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053", size = 711096, upload-time = "2025-05-02T19:36:04.667Z" } wheels = [ @@ -701,8 +697,8 @@ dependencies = [ { name = "comm" }, { name = "debugpy" }, { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "ipython", version = "8.36.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "ipython", version = "9.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "ipython", version = "9.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "jupyter-client" }, { name = "jupyter-core" }, { name = "matplotlib-inline" }, @@ -745,7 +741,7 @@ wheels = [ [[package]] name = "ipython" -version = "8.36.0" +version = "8.37.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.10.*'", @@ -763,20 +759,18 @@ dependencies = [ { name = "traitlets", marker = "python_full_version == '3.10.*'" }, { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/9f/d9a73710df947b7804bd9d93509463fb3a89e0ddc99c9fcc67279cddbeb6/ipython-8.36.0.tar.gz", hash = "sha256:24658e9fe5c5c819455043235ba59cfffded4a35936eefceceab6b192f7092ff", size = 5604997, upload-time = "2025-04-25T18:03:38.031Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/31/10ac88f3357fc276dc8a64e8880c82e80e7459326ae1d0a211b40abf6665/ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216", size = 5606088, upload-time = "2025-05-31T16:39:09.613Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/d7/c1c9f371790b3a181e343c4815a361e5a0cc7d90ef6642d64ba5d05de289/ipython-8.36.0-py3-none-any.whl", hash = "sha256:12b913914d010dcffa2711505ec8be4bf0180742d97f1e5175e51f22086428c1", size = 831074, upload-time = "2025-04-25T18:03:34.951Z" }, + { url = "https://files.pythonhosted.org/packages/91/d0/274fbf7b0b12643cbbc001ce13e6a5b1607ac4929d1b11c72460152c9fc3/ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2", size = 831864, upload-time = "2025-05-31T16:39:06.38Z" }, ] [[package]] name = "ipython" -version = "9.2.0" +version = "9.4.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13' and python_full_version < '4.0'", - "python_full_version >= '3.12.4' and python_full_version < '3.13'", - "python_full_version >= '3.11' and python_full_version < '3.12.4'", - "python_full_version >= '4.0'", + "python_full_version >= '3.13'", + "python_full_version >= '3.11' and python_full_version < '3.13'", ] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, @@ -791,9 +785,9 @@ dependencies = [ { name = "traitlets", marker = "python_full_version >= '3.11'" }, { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9d/02/63a84444a7409b3c0acd1de9ffe524660e0e5d82ee473e78b45e5bfb64a4/ipython-9.2.0.tar.gz", hash = "sha256:62a9373dbc12f28f9feaf4700d052195bf89806279fc8ca11f3f54017d04751b", size = 4424394, upload-time = "2025-04-25T17:55:40.498Z" } +sdist = { url = "https://files.pythonhosted.org/packages/54/80/406f9e3bde1c1fd9bf5a0be9d090f8ae623e401b7670d8f6fdf2ab679891/ipython-9.4.0.tar.gz", hash = "sha256:c033c6d4e7914c3d9768aabe76bbe87ba1dc66a92a05db6bfa1125d81f2ee270", size = 4385338, upload-time = "2025-07-01T11:11:30.606Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/ce/5e897ee51b7d26ab4e47e5105e7368d40ce6cfae2367acdf3165396d50be/ipython-9.2.0-py3-none-any.whl", hash = "sha256:fef5e33c4a1ae0759e0bba5917c9db4eb8c53fee917b6a526bd973e1ca5159f6", size = 604277, upload-time = "2025-04-25T17:55:37.625Z" }, + { url = "https://files.pythonhosted.org/packages/63/f8/0031ee2b906a15a33d6bfc12dd09c3dfa966b3cb5b284ecfb7549e6ac3c4/ipython-9.4.0-py3-none-any.whl", hash = "sha256:25850f025a446d9b359e8d296ba175a36aedd32e83ca9b5060430fe16801f066", size = 611021, upload-time = "2025-07-01T11:11:27.85Z" }, ] [[package]] @@ -815,8 +809,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "comm" }, { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "ipython", version = "8.36.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "ipython", version = "9.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "ipython", version = "9.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "jupyterlab-widgets" }, { name = "traitlets" }, { name = "widgetsnbextension" }, @@ -1015,8 +1009,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ipykernel" }, { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "ipython", version = "8.36.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "ipython", version = "9.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "ipython", version = "9.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "jupyter-client" }, { name = "jupyter-core" }, { name = "prompt-toolkit" }, @@ -1120,7 +1114,7 @@ wheels = [ [[package]] name = "jupyterlab" -version = "4.4.3" +version = "4.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-lru" }, @@ -1139,9 +1133,9 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/2d/d1678dcf2db66cb4a38a80d9e5fcf48c349f3ac12f2d38882993353ae768/jupyterlab-4.4.3.tar.gz", hash = "sha256:a94c32fd7f8b93e82a49dc70a6ec45a5c18281ca2a7228d12765e4e210e5bca2", size = 23032376, upload-time = "2025-05-26T11:18:00.996Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/4d/7ca5b46ea56742880d71a768a9e6fb8f8482228427eb89492d55c5d0bb7d/jupyterlab-4.4.4.tar.gz", hash = "sha256:163fee1ef702e0a057f75d2eed3ed1da8a986d59eb002cbeb6f0c2779e6cd153", size = 23044296, upload-time = "2025-06-28T13:07:20.708Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/4d/7dd5c2ffbb960930452a031dc8410746183c924580f2ab4e68ceb5b3043f/jupyterlab-4.4.3-py3-none-any.whl", hash = "sha256:164302f6d4b6c44773dfc38d585665a4db401a16e5296c37df5cba63904fbdea", size = 12295480, upload-time = "2025-05-26T11:17:56.607Z" }, + { url = "https://files.pythonhosted.org/packages/f8/82/66910ce0995dbfdb33609f41c99fe32ce483b9624a3e7d672af14ff63b9f/jupyterlab-4.4.4-py3-none-any.whl", hash = "sha256:711611e4f59851152eb93316c3547c3ec6291f16bb455f1f4fa380d25637e0dd", size = 12296310, upload-time = "2025-06-28T13:07:15.676Z" }, ] [[package]] @@ -1280,32 +1274,32 @@ dev = [ [[package]] name = "langgraph-api" -version = "0.2.36" +version = "0.2.75" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cloudpickle", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "cryptography", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "httpx", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "jsonschema-rs", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "langchain-core", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "langgraph", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "langgraph-checkpoint", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "langgraph-runtime-inmem", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "langgraph-sdk", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "langsmith", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "orjson", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "pyjwt", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "sse-starlette", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "starlette", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "structlog", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "tenacity", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "truststore", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "uvicorn", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "watchfiles", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, + { name = "cloudpickle", marker = "python_full_version >= '3.11'" }, + { name = "cryptography", marker = "python_full_version >= '3.11'" }, + { name = "httpx", marker = "python_full_version >= '3.11'" }, + { name = "jsonschema-rs", marker = "python_full_version >= '3.11'" }, + { name = "langchain-core", marker = "python_full_version >= '3.11'" }, + { name = "langgraph", marker = "python_full_version >= '3.11'" }, + { name = "langgraph-checkpoint", marker = "python_full_version >= '3.11'" }, + { name = "langgraph-runtime-inmem", marker = "python_full_version >= '3.11'" }, + { name = "langgraph-sdk", marker = "python_full_version >= '3.11'" }, + { name = "langsmith", marker = "python_full_version >= '3.11'" }, + { name = "orjson", marker = "python_full_version >= '3.11'" }, + { name = "pyjwt", marker = "python_full_version >= '3.11'" }, + { name = "sse-starlette", marker = "python_full_version >= '3.11'" }, + { name = "starlette", marker = "python_full_version >= '3.11'" }, + { name = "structlog", marker = "python_full_version >= '3.11'" }, + { name = "tenacity", marker = "python_full_version >= '3.11'" }, + { name = "truststore", marker = "python_full_version >= '3.11'" }, + { name = "uvicorn", marker = "python_full_version >= '3.11'" }, + { name = "watchfiles", marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0c/57/ea1a163198909f0e8713584d49858ef8884f2d1f14273fd3c40344480606/langgraph_api-0.2.36.tar.gz", hash = "sha256:5d45dce8733b954e61b142abc28d0962a37b03ffc578e20f2ccbc98d48579ece", size = 213623, upload-time = "2025-05-29T20:55:34.628Z" } +sdist = { url = "https://files.pythonhosted.org/packages/91/96/ddd4f965b66122ccf46924f9135b719e15140ef4f379d6133092fb2173a9/langgraph_api-0.2.75.tar.gz", hash = "sha256:96afc3bafe34d13f4a2acb3ab256b930888cd74facf394e4f5d4c23a3843e971", size = 231565, upload-time = "2025-06-30T23:10:43.479Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/b7/0d05bbcd42a9070aa200e6001b466ef29d274c4715b654d4f6741a7ed8f5/langgraph_api-0.2.36-py3-none-any.whl", hash = "sha256:e01579d4322c9ce65d0eb8361092a301bcd12d375adb2e806274c7692fe3d8da", size = 177962, upload-time = "2025-05-29T20:55:33.446Z" }, + { url = "https://files.pythonhosted.org/packages/29/18/17b77fa7facfebc6642f2d5dc8d2a10723c2c551adc3090e608e349b4e58/langgraph_api-0.2.75-py3-none-any.whl", hash = "sha256:87fd6352916ad54a851b8add4378ef2ac30a5b1fde6917b8383b9964e84676b9", size = 188024, upload-time = "2025-06-30T23:10:42.083Z" }, ] [[package]] @@ -1403,21 +1397,22 @@ dev = [ [[package]] name = "langgraph-cli" -version = "0.2.10" +version = "0.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "langgraph-sdk", marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/5e/b12bc8140cd4f797ad7f596bf90558994fd6891df8974bc3fc4747eabdc7/langgraph_cli-0.2.10.tar.gz", hash = "sha256:0c215b364daeaf10de681e4960ecaafc7c9cd2a4100b41052d78d95cababf422", size = 31690, upload-time = "2025-05-09T20:18:27.542Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/c2/53aaae208a3a08f727ef9d03edfd8f499403e017fc451c8ca5b52e95c930/langgraph_cli-0.3.3.tar.gz", hash = "sha256:120adc44064786bb11f1376a7b324b2125276a2e2c3a04bbfab7b8c1622ad4d7", size = 722842, upload-time = "2025-06-13T20:17:52.639Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/06/7151d7c8d6c2bccc0919ddb35a63caf3707b96c94561f47f14b08d73ef5e/langgraph_cli-0.2.10-py3-none-any.whl", hash = "sha256:4aaa8d828d8d3bf0f55d2b2a36b2d9944021d65a4b06ed708c6d5eea725f65a7", size = 34833, upload-time = "2025-05-09T20:18:26.173Z" }, + { url = "https://files.pythonhosted.org/packages/50/aa/f52742987a30f9f2d80c00e1caccb310185dd511d07b56c3e611fe31adb5/langgraph_cli-0.3.3-py3-none-any.whl", hash = "sha256:a0c6b04d6a37191431ede17f7a358f1944c6e5ac9e1b7d2004b9f973fe940094", size = 35880, upload-time = "2025-06-13T20:17:51.437Z" }, ] [package.optional-dependencies] inmem = [ - { name = "langgraph-api", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "langgraph-runtime-inmem", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, + { name = "langgraph-api", marker = "python_full_version >= '3.11'" }, + { name = "langgraph-runtime-inmem", marker = "python_full_version >= '3.11'" }, { name = "python-dotenv" }, ] @@ -1454,19 +1449,19 @@ dev = [ [[package]] name = "langgraph-runtime-inmem" -version = "0.2.0" +version = "0.3.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "blockbuster", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "langgraph", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "langgraph-checkpoint", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "sse-starlette", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "starlette", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "structlog", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, + { name = "blockbuster", marker = "python_full_version >= '3.11'" }, + { name = "langgraph", marker = "python_full_version >= '3.11'" }, + { name = "langgraph-checkpoint", marker = "python_full_version >= '3.11'" }, + { name = "sse-starlette", marker = "python_full_version >= '3.11'" }, + { name = "starlette", marker = "python_full_version >= '3.11'" }, + { name = "structlog", marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/8b/54452b0336674afe0ddbba0d5f52ddb323d9fa838e5bab694e1947697323/langgraph_runtime_inmem-0.2.0.tar.gz", hash = "sha256:3eed0bd7e241fbad6c7954e8494ddb133af7587f110c7eb4e3be15df5731a05a", size = 72666, upload-time = "2025-05-21T21:22:12.022Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/17/7ff669ff44a53ab342903c2996fff75a77494af9fe56abcfbca64fe2342b/langgraph_runtime_inmem-0.3.4.tar.gz", hash = "sha256:eda7828f3ea07126e5265024b74a3fa9bf611633ad83ba3296ab9f51d89b7c0c", size = 77424, upload-time = "2025-07-01T14:45:07.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/1f/5676d5237b0682257957338f5f7ee1f063f6d33fa158f878ad926800e95d/langgraph_runtime_inmem-0.2.0-py3-none-any.whl", hash = "sha256:6ad30c493d5963afbeac3136560303fba205b3850c5181f57f1794d8ecb8a461", size = 28406, upload-time = "2025-05-21T21:22:10.674Z" }, + { url = "https://files.pythonhosted.org/packages/95/0e/39c13ca7229a9425a0e5744a1d3817f80d38dd5ca7703494fb9cf836ba45/langgraph_runtime_inmem-0.3.4-py3-none-any.whl", hash = "sha256:dcb9ac68ac90b3fb1ddaf666d14a367ab70e69d5bb5589b77a72c318e29104ae", size = 29139, upload-time = "2025-07-01T14:45:06.472Z" }, ] [[package]] @@ -1607,7 +1602,7 @@ wheels = [ [[package]] name = "mypy" -version = "1.16.0" +version = "1.16.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, @@ -1615,39 +1610,39 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d4/38/13c2f1abae94d5ea0354e146b95a1be9b2137a0d506728e0da037c4276f6/mypy-1.16.0.tar.gz", hash = "sha256:84b94283f817e2aa6350a14b4a8fb2a35a53c286f97c9d30f53b63620e7af8ab", size = 3323139, upload-time = "2025-05-29T13:46:12.532Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/69/92c7fa98112e4d9eb075a239caa4ef4649ad7d441545ccffbd5e34607cbb/mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab", size = 3324747, upload-time = "2025-06-16T16:51:35.145Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/5e/a0485f0608a3d67029d3d73cec209278b025e3493a3acfda3ef3a88540fd/mypy-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7909541fef256527e5ee9c0a7e2aeed78b6cda72ba44298d1334fe7881b05c5c", size = 10967416, upload-time = "2025-05-29T13:34:17.783Z" }, - { url = "https://files.pythonhosted.org/packages/4b/53/5837c221f74c0d53a4bfc3003296f8179c3a2a7f336d7de7bbafbe96b688/mypy-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e71d6f0090c2256c713ed3d52711d01859c82608b5d68d4fa01a3fe30df95571", size = 10087654, upload-time = "2025-05-29T13:32:37.878Z" }, - { url = "https://files.pythonhosted.org/packages/29/59/5fd2400352c3093bed4c09017fe671d26bc5bb7e6ef2d4bf85f2a2488104/mypy-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:936ccfdd749af4766be824268bfe22d1db9eb2f34a3ea1d00ffbe5b5265f5491", size = 11875192, upload-time = "2025-05-29T13:34:54.281Z" }, - { url = "https://files.pythonhosted.org/packages/ad/3e/4bfec74663a64c2012f3e278dbc29ffe82b121bc551758590d1b6449ec0c/mypy-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4086883a73166631307fdd330c4a9080ce24913d4f4c5ec596c601b3a4bdd777", size = 12612939, upload-time = "2025-05-29T13:33:14.766Z" }, - { url = "https://files.pythonhosted.org/packages/88/1f/fecbe3dcba4bf2ca34c26ca016383a9676711907f8db4da8354925cbb08f/mypy-1.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:feec38097f71797da0231997e0de3a58108c51845399669ebc532c815f93866b", size = 12874719, upload-time = "2025-05-29T13:21:52.09Z" }, - { url = "https://files.pythonhosted.org/packages/f3/51/c2d280601cd816c43dfa512a759270d5a5ef638d7ac9bea9134c8305a12f/mypy-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:09a8da6a0ee9a9770b8ff61b39c0bb07971cda90e7297f4213741b48a0cc8d93", size = 9487053, upload-time = "2025-05-29T13:33:29.797Z" }, - { url = "https://files.pythonhosted.org/packages/24/c4/ff2f79db7075c274fe85b5fff8797d29c6b61b8854c39e3b7feb556aa377/mypy-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9f826aaa7ff8443bac6a494cf743f591488ea940dd360e7dd330e30dd772a5ab", size = 10884498, upload-time = "2025-05-29T13:18:54.066Z" }, - { url = "https://files.pythonhosted.org/packages/02/07/12198e83006235f10f6a7808917376b5d6240a2fd5dce740fe5d2ebf3247/mypy-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82d056e6faa508501af333a6af192c700b33e15865bda49611e3d7d8358ebea2", size = 10011755, upload-time = "2025-05-29T13:34:00.851Z" }, - { url = "https://files.pythonhosted.org/packages/f1/9b/5fd5801a72b5d6fb6ec0105ea1d0e01ab2d4971893076e558d4b6d6b5f80/mypy-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089bedc02307c2548eb51f426e085546db1fa7dd87fbb7c9fa561575cf6eb1ff", size = 11800138, upload-time = "2025-05-29T13:32:55.082Z" }, - { url = "https://files.pythonhosted.org/packages/2e/81/a117441ea5dfc3746431e51d78a4aca569c677aa225bca2cc05a7c239b61/mypy-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a2322896003ba66bbd1318c10d3afdfe24e78ef12ea10e2acd985e9d684a666", size = 12533156, upload-time = "2025-05-29T13:19:12.963Z" }, - { url = "https://files.pythonhosted.org/packages/3f/38/88ec57c6c86014d3f06251e00f397b5a7daa6888884d0abf187e4f5f587f/mypy-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:021a68568082c5b36e977d54e8f1de978baf401a33884ffcea09bd8e88a98f4c", size = 12742426, upload-time = "2025-05-29T13:20:22.72Z" }, - { url = "https://files.pythonhosted.org/packages/bd/53/7e9d528433d56e6f6f77ccf24af6ce570986c2d98a5839e4c2009ef47283/mypy-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:54066fed302d83bf5128632d05b4ec68412e1f03ef2c300434057d66866cea4b", size = 9478319, upload-time = "2025-05-29T13:21:17.582Z" }, - { url = "https://files.pythonhosted.org/packages/70/cf/158e5055e60ca2be23aec54a3010f89dcffd788732634b344fc9cb1e85a0/mypy-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5436d11e89a3ad16ce8afe752f0f373ae9620841c50883dc96f8b8805620b13", size = 11062927, upload-time = "2025-05-29T13:35:52.328Z" }, - { url = "https://files.pythonhosted.org/packages/94/34/cfff7a56be1609f5d10ef386342ce3494158e4d506516890142007e6472c/mypy-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f2622af30bf01d8fc36466231bdd203d120d7a599a6d88fb22bdcb9dbff84090", size = 10083082, upload-time = "2025-05-29T13:35:33.378Z" }, - { url = "https://files.pythonhosted.org/packages/b3/7f/7242062ec6288c33d8ad89574df87c3903d394870e5e6ba1699317a65075/mypy-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d045d33c284e10a038f5e29faca055b90eee87da3fc63b8889085744ebabb5a1", size = 11828306, upload-time = "2025-05-29T13:21:02.164Z" }, - { url = "https://files.pythonhosted.org/packages/6f/5f/b392f7b4f659f5b619ce5994c5c43caab3d80df2296ae54fa888b3d17f5a/mypy-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4968f14f44c62e2ec4a038c8797a87315be8df7740dc3ee8d3bfe1c6bf5dba8", size = 12702764, upload-time = "2025-05-29T13:20:42.826Z" }, - { url = "https://files.pythonhosted.org/packages/9b/c0/7646ef3a00fa39ac9bc0938626d9ff29d19d733011be929cfea59d82d136/mypy-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb14a4a871bb8efb1e4a50360d4e3c8d6c601e7a31028a2c79f9bb659b63d730", size = 12896233, upload-time = "2025-05-29T13:18:37.446Z" }, - { url = "https://files.pythonhosted.org/packages/6d/38/52f4b808b3fef7f0ef840ee8ff6ce5b5d77381e65425758d515cdd4f5bb5/mypy-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:bd4e1ebe126152a7bbaa4daedd781c90c8f9643c79b9748caa270ad542f12bec", size = 9565547, upload-time = "2025-05-29T13:20:02.836Z" }, - { url = "https://files.pythonhosted.org/packages/97/9c/ca03bdbefbaa03b264b9318a98950a9c683e06472226b55472f96ebbc53d/mypy-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a9e056237c89f1587a3be1a3a70a06a698d25e2479b9a2f57325ddaaffc3567b", size = 11059753, upload-time = "2025-05-29T13:18:18.167Z" }, - { url = "https://files.pythonhosted.org/packages/36/92/79a969b8302cfe316027c88f7dc6fee70129490a370b3f6eb11d777749d0/mypy-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b07e107affb9ee6ce1f342c07f51552d126c32cd62955f59a7db94a51ad12c0", size = 10073338, upload-time = "2025-05-29T13:19:48.079Z" }, - { url = "https://files.pythonhosted.org/packages/14/9b/a943f09319167da0552d5cd722104096a9c99270719b1afeea60d11610aa/mypy-1.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6fb60cbd85dc65d4d63d37cb5c86f4e3a301ec605f606ae3a9173e5cf34997b", size = 11827764, upload-time = "2025-05-29T13:46:04.47Z" }, - { url = "https://files.pythonhosted.org/packages/ec/64/ff75e71c65a0cb6ee737287c7913ea155845a556c64144c65b811afdb9c7/mypy-1.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7e32297a437cc915599e0578fa6bc68ae6a8dc059c9e009c628e1c47f91495d", size = 12701356, upload-time = "2025-05-29T13:35:13.553Z" }, - { url = "https://files.pythonhosted.org/packages/0a/ad/0e93c18987a1182c350f7a5fab70550852f9fabe30ecb63bfbe51b602074/mypy-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:afe420c9380ccec31e744e8baff0d406c846683681025db3531b32db56962d52", size = 12900745, upload-time = "2025-05-29T13:17:24.409Z" }, - { url = "https://files.pythonhosted.org/packages/28/5d/036c278d7a013e97e33f08c047fe5583ab4f1fc47c9a49f985f1cdd2a2d7/mypy-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:55f9076c6ce55dd3f8cd0c6fff26a008ca8e5131b89d5ba6d86bd3f47e736eeb", size = 9572200, upload-time = "2025-05-29T13:33:44.92Z" }, - { url = "https://files.pythonhosted.org/packages/bd/eb/c0759617fe2159aee7a653f13cceafbf7f0b6323b4197403f2e587ca947d/mypy-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f56236114c425620875c7cf71700e3d60004858da856c6fc78998ffe767b73d3", size = 10956081, upload-time = "2025-05-29T13:19:32.264Z" }, - { url = "https://files.pythonhosted.org/packages/70/35/df3c74a2967bdf86edea58b265feeec181d693432faed1c3b688b7c231e3/mypy-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:15486beea80be24ff067d7d0ede673b001d0d684d0095803b3e6e17a886a2a92", size = 10084422, upload-time = "2025-05-29T13:18:01.437Z" }, - { url = "https://files.pythonhosted.org/packages/b3/07/145ffe29f4b577219943b7b1dc0a71df7ead3c5bed4898686bd87c5b5cc2/mypy-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2ed0e0847a80655afa2c121835b848ed101cc7b8d8d6ecc5205aedc732b1436", size = 11879670, upload-time = "2025-05-29T13:17:45.971Z" }, - { url = "https://files.pythonhosted.org/packages/c6/94/0421562d6b046e22986758c9ae31865d10ea0ba607ae99b32c9d18b16f66/mypy-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eb5fbc8063cb4fde7787e4c0406aa63094a34a2daf4673f359a1fb64050e9cb2", size = 12610528, upload-time = "2025-05-29T13:34:36.983Z" }, - { url = "https://files.pythonhosted.org/packages/1a/f1/39a22985b78c766a594ae1e0bbb6f8bdf5f31ea8d0c52291a3c211fd3cd5/mypy-1.16.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a5fcfdb7318c6a8dd127b14b1052743b83e97a970f0edb6c913211507a255e20", size = 12871923, upload-time = "2025-05-29T13:32:21.823Z" }, - { url = "https://files.pythonhosted.org/packages/f3/8e/84db4fb0d01f43d2c82fa9072ca72a42c49e52d58f44307bbd747c977bc2/mypy-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:2e7e0ad35275e02797323a5aa1be0b14a4d03ffdb2e5f2b0489fa07b89c67b21", size = 9482931, upload-time = "2025-05-29T13:21:32.326Z" }, - { url = "https://files.pythonhosted.org/packages/99/a3/6ed10530dec8e0fdc890d81361260c9ef1f5e5c217ad8c9b21ecb2b8366b/mypy-1.16.0-py3-none-any.whl", hash = "sha256:29e1499864a3888bca5c1542f2d7232c6e586295183320caa95758fc84034031", size = 2265773, upload-time = "2025-05-29T13:35:18.762Z" }, + { url = "https://files.pythonhosted.org/packages/8e/12/2bf23a80fcef5edb75de9a1e295d778e0f46ea89eb8b115818b663eff42b/mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a", size = 10958644, upload-time = "2025-06-16T16:51:11.649Z" }, + { url = "https://files.pythonhosted.org/packages/08/50/bfe47b3b278eacf348291742fd5e6613bbc4b3434b72ce9361896417cfe5/mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72", size = 10087033, upload-time = "2025-06-16T16:35:30.089Z" }, + { url = "https://files.pythonhosted.org/packages/21/de/40307c12fe25675a0776aaa2cdd2879cf30d99eec91b898de00228dc3ab5/mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea", size = 11875645, upload-time = "2025-06-16T16:35:48.49Z" }, + { url = "https://files.pythonhosted.org/packages/a6/d8/85bdb59e4a98b7a31495bd8f1a4445d8ffc86cde4ab1f8c11d247c11aedc/mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574", size = 12616986, upload-time = "2025-06-16T16:48:39.526Z" }, + { url = "https://files.pythonhosted.org/packages/0e/d0/bb25731158fa8f8ee9e068d3e94fcceb4971fedf1424248496292512afe9/mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d", size = 12878632, upload-time = "2025-06-16T16:36:08.195Z" }, + { url = "https://files.pythonhosted.org/packages/2d/11/822a9beb7a2b825c0cb06132ca0a5183f8327a5e23ef89717c9474ba0bc6/mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6", size = 9484391, upload-time = "2025-06-16T16:37:56.151Z" }, + { url = "https://files.pythonhosted.org/packages/9a/61/ec1245aa1c325cb7a6c0f8570a2eee3bfc40fa90d19b1267f8e50b5c8645/mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc", size = 10890557, upload-time = "2025-06-16T16:37:21.421Z" }, + { url = "https://files.pythonhosted.org/packages/6b/bb/6eccc0ba0aa0c7a87df24e73f0ad34170514abd8162eb0c75fd7128171fb/mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782", size = 10012921, upload-time = "2025-06-16T16:51:28.659Z" }, + { url = "https://files.pythonhosted.org/packages/5f/80/b337a12e2006715f99f529e732c5f6a8c143bb58c92bb142d5ab380963a5/mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507", size = 11802887, upload-time = "2025-06-16T16:50:53.627Z" }, + { url = "https://files.pythonhosted.org/packages/d9/59/f7af072d09793d581a745a25737c7c0a945760036b16aeb620f658a017af/mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca", size = 12531658, upload-time = "2025-06-16T16:33:55.002Z" }, + { url = "https://files.pythonhosted.org/packages/82/c4/607672f2d6c0254b94a646cfc45ad589dd71b04aa1f3d642b840f7cce06c/mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4", size = 12732486, upload-time = "2025-06-16T16:37:03.301Z" }, + { url = "https://files.pythonhosted.org/packages/b6/5e/136555ec1d80df877a707cebf9081bd3a9f397dedc1ab9750518d87489ec/mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6", size = 9479482, upload-time = "2025-06-16T16:47:37.48Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d6/39482e5fcc724c15bf6280ff5806548c7185e0c090712a3736ed4d07e8b7/mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d", size = 11066493, upload-time = "2025-06-16T16:47:01.683Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/26c347890efc6b757f4d5bb83f4a0cf5958b8cf49c938ac99b8b72b420a6/mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9", size = 10081687, upload-time = "2025-06-16T16:48:19.367Z" }, + { url = "https://files.pythonhosted.org/packages/44/c7/b5cb264c97b86914487d6a24bd8688c0172e37ec0f43e93b9691cae9468b/mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79", size = 11839723, upload-time = "2025-06-16T16:49:20.912Z" }, + { url = "https://files.pythonhosted.org/packages/15/f8/491997a9b8a554204f834ed4816bda813aefda31cf873bb099deee3c9a99/mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15", size = 12722980, upload-time = "2025-06-16T16:37:40.929Z" }, + { url = "https://files.pythonhosted.org/packages/df/f0/2bd41e174b5fd93bc9de9a28e4fb673113633b8a7f3a607fa4a73595e468/mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd", size = 12903328, upload-time = "2025-06-16T16:34:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/61/81/5572108a7bec2c46b8aff7e9b524f371fe6ab5efb534d38d6b37b5490da8/mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b", size = 9562321, upload-time = "2025-06-16T16:48:58.823Z" }, + { url = "https://files.pythonhosted.org/packages/28/e3/96964af4a75a949e67df4b95318fe2b7427ac8189bbc3ef28f92a1c5bc56/mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438", size = 11063480, upload-time = "2025-06-16T16:47:56.205Z" }, + { url = "https://files.pythonhosted.org/packages/f5/4d/cd1a42b8e5be278fab7010fb289d9307a63e07153f0ae1510a3d7b703193/mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536", size = 10090538, upload-time = "2025-06-16T16:46:43.92Z" }, + { url = "https://files.pythonhosted.org/packages/c9/4f/c3c6b4b66374b5f68bab07c8cabd63a049ff69796b844bc759a0ca99bb2a/mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f", size = 11836839, upload-time = "2025-06-16T16:36:28.039Z" }, + { url = "https://files.pythonhosted.org/packages/b4/7e/81ca3b074021ad9775e5cb97ebe0089c0f13684b066a750b7dc208438403/mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359", size = 12715634, upload-time = "2025-06-16T16:50:34.441Z" }, + { url = "https://files.pythonhosted.org/packages/e9/95/bdd40c8be346fa4c70edb4081d727a54d0a05382d84966869738cfa8a497/mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be", size = 12895584, upload-time = "2025-06-16T16:34:54.857Z" }, + { url = "https://files.pythonhosted.org/packages/5a/fd/d486a0827a1c597b3b48b1bdef47228a6e9ee8102ab8c28f944cb83b65dc/mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee", size = 9573886, upload-time = "2025-06-16T16:36:43.589Z" }, + { url = "https://files.pythonhosted.org/packages/49/5e/ed1e6a7344005df11dfd58b0fdd59ce939a0ba9f7ed37754bf20670b74db/mypy-1.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069", size = 10959511, upload-time = "2025-06-16T16:47:21.945Z" }, + { url = "https://files.pythonhosted.org/packages/30/88/a7cbc2541e91fe04f43d9e4577264b260fecedb9bccb64ffb1a34b7e6c22/mypy-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da", size = 10075555, upload-time = "2025-06-16T16:50:14.084Z" }, + { url = "https://files.pythonhosted.org/packages/93/f7/c62b1e31a32fbd1546cca5e0a2e5f181be5761265ad1f2e94f2a306fa906/mypy-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c", size = 11874169, upload-time = "2025-06-16T16:49:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/c8/15/db580a28034657fb6cb87af2f8996435a5b19d429ea4dcd6e1c73d418e60/mypy-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383", size = 12610060, upload-time = "2025-06-16T16:34:15.215Z" }, + { url = "https://files.pythonhosted.org/packages/ec/78/c17f48f6843048fa92d1489d3095e99324f2a8c420f831a04ccc454e2e51/mypy-1.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40", size = 12875199, upload-time = "2025-06-16T16:35:14.448Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d6/ed42167d0a42680381653fd251d877382351e1bd2c6dd8a818764be3beb1/mypy-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b", size = 9487033, upload-time = "2025-06-16T16:49:57.907Z" }, + { url = "https://files.pythonhosted.org/packages/cf/d3/53e684e78e07c1a2bf7105715e5edd09ce951fc3f47cf9ed095ec1b7a037/mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37", size = 2265923, upload-time = "2025-06-16T16:48:02.366Z" }, ] [[package]] @@ -1726,7 +1721,7 @@ wheels = [ [[package]] name = "notebook" -version = "7.4.3" +version = "7.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-server" }, @@ -1735,9 +1730,9 @@ dependencies = [ { name = "notebook-shim" }, { name = "tornado" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dc/21/4f83b15e483da4f4f63928edd0cb08b6e7d33f8a15c23b116a90c44c6235/notebook-7.4.3.tar.gz", hash = "sha256:a1567481cd3853f2610ee0ecf5dfa12bb508e878ee8f92152c134ef7f0568a76", size = 13881668, upload-time = "2025-05-26T14:27:21.656Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/4e/a40b5a94eb01fc51746db7854296d88b84905ab18ee0fcef853a60d708a3/notebook-7.4.4.tar.gz", hash = "sha256:392fd501e266f2fb3466c6fcd3331163a2184968cb5c5accf90292e01dfe528c", size = 13883628, upload-time = "2025-06-30T13:04:18.099Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/1b/16c809d799e3ddd7a97c8b43734f79624b74ddef9707e7d92275a13777bc/notebook-7.4.3-py3-none-any.whl", hash = "sha256:9cdeee954e04101cadb195d90e2ab62b7c9286c1d4f858bf3bb54e40df16c0c3", size = 14286402, upload-time = "2025-05-26T14:27:17.339Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c0/e64d2047fd752249b0b69f6aee2a7049eb94e7273e5baabc8b8ad05cc068/notebook-7.4.4-py3-none-any.whl", hash = "sha256:32840f7f777b6bff79bb101159336e9b332bdbfba1495b8739e34d1d65cbc1c0", size = 14288000, upload-time = "2025-06-30T13:04:14.584Z" }, ] [[package]] @@ -1956,11 +1951,11 @@ wheels = [ [[package]] name = "prometheus-client" -version = "0.22.0" +version = "0.22.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5b/5a/3fa1fa7e91a203759aaf316be394f70f2ef598d589b9785a8611b6094c00/prometheus_client-0.22.0.tar.gz", hash = "sha256:18da1d2241ac2d10c8d2110f13eedcd5c7c0c8af18c926e8731f04fc10cd575c", size = 74443, upload-time = "2025-05-16T20:50:18.333Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/cf/40dde0a2be27cc1eb41e333d1a674a74ce8b8b0457269cc640fd42b07cf7/prometheus_client-0.22.1.tar.gz", hash = "sha256:190f1331e783cf21eb60bca559354e0a4d4378facecf78f5428c39b675d20d28", size = 69746, upload-time = "2025-06-02T14:29:01.152Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/c7/cee159ba3d7192e84a4c166ec1752f44a5fa859ac0eeda2d73a1da65ab47/prometheus_client-0.22.0-py3-none-any.whl", hash = "sha256:c8951bbe64e62b96cd8e8f5d917279d1b9b91ab766793f33d4dce6c228558713", size = 62658, upload-time = "2025-05-16T20:50:16.978Z" }, + { url = "https://files.pythonhosted.org/packages/32/ae/ec06af4fe3ee72d16973474f122541746196aaa16cea6f66d18b963c6177/prometheus_client-0.22.1-py3-none-any.whl", hash = "sha256:cca895342e308174341b2cbf99a56bef291fbc0ef7b9e5412a0f26d653ba7094", size = 58694, upload-time = "2025-06-02T14:29:00.068Z" }, ] [[package]] @@ -2267,11 +2262,11 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.1" +version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] @@ -2297,7 +2292,7 @@ wheels = [ [[package]] name = "pytest" -version = "8.3.5" +version = "8.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -2305,24 +2300,26 @@ dependencies = [ { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, + { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] [[package]] name = "pytest-cov" -version = "6.1.1" +version = "6.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload-time = "2025-04-05T14:07:51.592Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload-time = "2025-04-05T14:07:49.641Z" }, + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, ] [[package]] @@ -2377,15 +2374,15 @@ wheels = [ [[package]] name = "pytest-xdist" -version = "3.7.0" +version = "3.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "execnet" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/dc/865845cfe987b21658e871d16e0a24e871e00884c545f246dd8f6f69edda/pytest_xdist-3.7.0.tar.gz", hash = "sha256:f9248c99a7c15b7d2f90715df93610353a485827bc06eefb6566d23f6400f126", size = 87550, upload-time = "2025-05-26T21:18:20.251Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/b2/0e802fde6f1c5b2f7ae7e9ad42b83fd4ecebac18a8a8c2f2f14e39dce6e1/pytest_xdist-3.7.0-py3-none-any.whl", hash = "sha256:7d3fbd255998265052435eb9daa4e99b62e6fb9cfb6efd1f858d4d8c0c7f0ca0", size = 46142, upload-time = "2025-05-26T21:18:18.759Z" }, + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, ] [package.optional-dependencies] @@ -2407,11 +2404,11 @@ wheels = [ [[package]] name = "python-dotenv" -version = "1.1.0" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload-time = "2025-03-25T10:14:56.835Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, ] [[package]] @@ -2516,91 +2513,77 @@ wheels = [ [[package]] name = "pyzmq" -version = "26.4.0" +version = "27.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "implementation_name == 'pypy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/11/b9213d25230ac18a71b39b3723494e57adebe36e066397b961657b3b41c1/pyzmq-26.4.0.tar.gz", hash = "sha256:4bd13f85f80962f91a651a7356fe0472791a5f7a92f227822b5acf44795c626d", size = 278293, upload-time = "2025-04-04T12:05:44.049Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/06/50a4e9648b3e8b992bef8eb632e457307553a89d294103213cfd47b3da69/pyzmq-27.0.0.tar.gz", hash = "sha256:b1f08eeb9ce1510e6939b6e5dcd46a17765e2333daae78ecf4606808442e52cf", size = 280478, upload-time = "2025-06-13T14:09:07.087Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/b8/af1d814ffc3ff9730f9a970cbf216b6f078e5d251a25ef5201d7bc32a37c/pyzmq-26.4.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:0329bdf83e170ac133f44a233fc651f6ed66ef8e66693b5af7d54f45d1ef5918", size = 1339238, upload-time = "2025-04-04T12:03:07.022Z" }, - { url = "https://files.pythonhosted.org/packages/ee/e4/5aafed4886c264f2ea6064601ad39c5fc4e9b6539c6ebe598a859832eeee/pyzmq-26.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:398a825d2dea96227cf6460ce0a174cf7657d6f6827807d4d1ae9d0f9ae64315", size = 672848, upload-time = "2025-04-04T12:03:08.591Z" }, - { url = "https://files.pythonhosted.org/packages/79/39/026bf49c721cb42f1ef3ae0ee3d348212a7621d2adb739ba97599b6e4d50/pyzmq-26.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d52d62edc96787f5c1dfa6c6ccff9b581cfae5a70d94ec4c8da157656c73b5b", size = 911299, upload-time = "2025-04-04T12:03:10Z" }, - { url = "https://files.pythonhosted.org/packages/03/23/b41f936a9403b8f92325c823c0f264c6102a0687a99c820f1aaeb99c1def/pyzmq-26.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1410c3a3705db68d11eb2424d75894d41cff2f64d948ffe245dd97a9debfebf4", size = 867920, upload-time = "2025-04-04T12:03:11.311Z" }, - { url = "https://files.pythonhosted.org/packages/c1/3e/2de5928cdadc2105e7c8f890cc5f404136b41ce5b6eae5902167f1d5641c/pyzmq-26.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:7dacb06a9c83b007cc01e8e5277f94c95c453c5851aac5e83efe93e72226353f", size = 862514, upload-time = "2025-04-04T12:03:13.013Z" }, - { url = "https://files.pythonhosted.org/packages/ce/57/109569514dd32e05a61d4382bc88980c95bfd2f02e58fea47ec0ccd96de1/pyzmq-26.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6bab961c8c9b3a4dc94d26e9b2cdf84de9918931d01d6ff38c721a83ab3c0ef5", size = 1204494, upload-time = "2025-04-04T12:03:14.795Z" }, - { url = "https://files.pythonhosted.org/packages/aa/02/dc51068ff2ca70350d1151833643a598625feac7b632372d229ceb4de3e1/pyzmq-26.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7a5c09413b924d96af2aa8b57e76b9b0058284d60e2fc3730ce0f979031d162a", size = 1514525, upload-time = "2025-04-04T12:03:16.246Z" }, - { url = "https://files.pythonhosted.org/packages/48/2a/a7d81873fff0645eb60afaec2b7c78a85a377af8f1d911aff045d8955bc7/pyzmq-26.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7d489ac234d38e57f458fdbd12a996bfe990ac028feaf6f3c1e81ff766513d3b", size = 1414659, upload-time = "2025-04-04T12:03:17.652Z" }, - { url = "https://files.pythonhosted.org/packages/ef/ea/813af9c42ae21845c1ccfe495bd29c067622a621e85d7cda6bc437de8101/pyzmq-26.4.0-cp310-cp310-win32.whl", hash = "sha256:dea1c8db78fb1b4b7dc9f8e213d0af3fc8ecd2c51a1d5a3ca1cde1bda034a980", size = 580348, upload-time = "2025-04-04T12:03:19.384Z" }, - { url = "https://files.pythonhosted.org/packages/20/68/318666a89a565252c81d3fed7f3b4c54bd80fd55c6095988dfa2cd04a62b/pyzmq-26.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:fa59e1f5a224b5e04dc6c101d7186058efa68288c2d714aa12d27603ae93318b", size = 643838, upload-time = "2025-04-04T12:03:20.795Z" }, - { url = "https://files.pythonhosted.org/packages/91/f8/fb1a15b5f4ecd3e588bfde40c17d32ed84b735195b5c7d1d7ce88301a16f/pyzmq-26.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:a651fe2f447672f4a815e22e74630b6b1ec3a1ab670c95e5e5e28dcd4e69bbb5", size = 559565, upload-time = "2025-04-04T12:03:22.676Z" }, - { url = "https://files.pythonhosted.org/packages/32/6d/234e3b0aa82fd0290b1896e9992f56bdddf1f97266110be54d0177a9d2d9/pyzmq-26.4.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:bfcf82644c9b45ddd7cd2a041f3ff8dce4a0904429b74d73a439e8cab1bd9e54", size = 1339723, upload-time = "2025-04-04T12:03:24.358Z" }, - { url = "https://files.pythonhosted.org/packages/4f/11/6d561efe29ad83f7149a7cd48e498e539ed09019c6cd7ecc73f4cc725028/pyzmq-26.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9bcae3979b2654d5289d3490742378b2f3ce804b0b5fd42036074e2bf35b030", size = 672645, upload-time = "2025-04-04T12:03:25.693Z" }, - { url = "https://files.pythonhosted.org/packages/19/fd/81bfe3e23f418644660bad1a90f0d22f0b3eebe33dd65a79385530bceb3d/pyzmq-26.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccdff8ac4246b6fb60dcf3982dfaeeff5dd04f36051fe0632748fc0aa0679c01", size = 910133, upload-time = "2025-04-04T12:03:27.625Z" }, - { url = "https://files.pythonhosted.org/packages/97/68/321b9c775595ea3df832a9516252b653fe32818db66fdc8fa31c9b9fce37/pyzmq-26.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4550af385b442dc2d55ab7717837812799d3674cb12f9a3aa897611839c18e9e", size = 867428, upload-time = "2025-04-04T12:03:29.004Z" }, - { url = "https://files.pythonhosted.org/packages/4e/6e/159cbf2055ef36aa2aa297e01b24523176e5b48ead283c23a94179fb2ba2/pyzmq-26.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2f9f7ffe9db1187a253fca95191854b3fda24696f086e8789d1d449308a34b88", size = 862409, upload-time = "2025-04-04T12:03:31.032Z" }, - { url = "https://files.pythonhosted.org/packages/05/1c/45fb8db7be5a7d0cadea1070a9cbded5199a2d578de2208197e592f219bd/pyzmq-26.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3709c9ff7ba61589b7372923fd82b99a81932b592a5c7f1a24147c91da9a68d6", size = 1205007, upload-time = "2025-04-04T12:03:32.687Z" }, - { url = "https://files.pythonhosted.org/packages/f8/fa/658c7f583af6498b463f2fa600f34e298e1b330886f82f1feba0dc2dd6c3/pyzmq-26.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f8f3c30fb2d26ae5ce36b59768ba60fb72507ea9efc72f8f69fa088450cff1df", size = 1514599, upload-time = "2025-04-04T12:03:34.084Z" }, - { url = "https://files.pythonhosted.org/packages/4d/d7/44d641522353ce0a2bbd150379cb5ec32f7120944e6bfba4846586945658/pyzmq-26.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:382a4a48c8080e273427fc692037e3f7d2851959ffe40864f2db32646eeb3cef", size = 1414546, upload-time = "2025-04-04T12:03:35.478Z" }, - { url = "https://files.pythonhosted.org/packages/72/76/c8ed7263218b3d1e9bce07b9058502024188bd52cc0b0a267a9513b431fc/pyzmq-26.4.0-cp311-cp311-win32.whl", hash = "sha256:d56aad0517d4c09e3b4f15adebba8f6372c5102c27742a5bdbfc74a7dceb8fca", size = 579247, upload-time = "2025-04-04T12:03:36.846Z" }, - { url = "https://files.pythonhosted.org/packages/c3/d0/2d9abfa2571a0b1a67c0ada79a8aa1ba1cce57992d80f771abcdf99bb32c/pyzmq-26.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:963977ac8baed7058c1e126014f3fe58b3773f45c78cce7af5c26c09b6823896", size = 644727, upload-time = "2025-04-04T12:03:38.578Z" }, - { url = "https://files.pythonhosted.org/packages/0d/d1/c8ad82393be6ccedfc3c9f3adb07f8f3976e3c4802640fe3f71441941e70/pyzmq-26.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0c8e8cadc81e44cc5088fcd53b9b3b4ce9344815f6c4a03aec653509296fae3", size = 559942, upload-time = "2025-04-04T12:03:40.143Z" }, - { url = "https://files.pythonhosted.org/packages/10/44/a778555ebfdf6c7fc00816aad12d185d10a74d975800341b1bc36bad1187/pyzmq-26.4.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:5227cb8da4b6f68acfd48d20c588197fd67745c278827d5238c707daf579227b", size = 1341586, upload-time = "2025-04-04T12:03:41.954Z" }, - { url = "https://files.pythonhosted.org/packages/9c/4f/f3a58dc69ac757e5103be3bd41fb78721a5e17da7cc617ddb56d973a365c/pyzmq-26.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1c07a7fa7f7ba86554a2b1bef198c9fed570c08ee062fd2fd6a4dcacd45f905", size = 665880, upload-time = "2025-04-04T12:03:43.45Z" }, - { url = "https://files.pythonhosted.org/packages/fe/45/50230bcfb3ae5cb98bee683b6edeba1919f2565d7cc1851d3c38e2260795/pyzmq-26.4.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae775fa83f52f52de73183f7ef5395186f7105d5ed65b1ae65ba27cb1260de2b", size = 902216, upload-time = "2025-04-04T12:03:45.572Z" }, - { url = "https://files.pythonhosted.org/packages/41/59/56bbdc5689be5e13727491ad2ba5efd7cd564365750514f9bc8f212eef82/pyzmq-26.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c760d0226ebd52f1e6b644a9e839b5db1e107a23f2fcd46ec0569a4fdd4e63", size = 859814, upload-time = "2025-04-04T12:03:47.188Z" }, - { url = "https://files.pythonhosted.org/packages/81/b1/57db58cfc8af592ce94f40649bd1804369c05b2190e4cbc0a2dad572baeb/pyzmq-26.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ef8c6ecc1d520debc147173eaa3765d53f06cd8dbe7bd377064cdbc53ab456f5", size = 855889, upload-time = "2025-04-04T12:03:49.223Z" }, - { url = "https://files.pythonhosted.org/packages/e8/92/47542e629cbac8f221c230a6d0f38dd3d9cff9f6f589ed45fdf572ffd726/pyzmq-26.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3150ef4084e163dec29ae667b10d96aad309b668fac6810c9e8c27cf543d6e0b", size = 1197153, upload-time = "2025-04-04T12:03:50.591Z" }, - { url = "https://files.pythonhosted.org/packages/07/e5/b10a979d1d565d54410afc87499b16c96b4a181af46e7645ab4831b1088c/pyzmq-26.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4448c9e55bf8329fa1dcedd32f661bf611214fa70c8e02fee4347bc589d39a84", size = 1507352, upload-time = "2025-04-04T12:03:52.473Z" }, - { url = "https://files.pythonhosted.org/packages/ab/58/5a23db84507ab9c01c04b1232a7a763be66e992aa2e66498521bbbc72a71/pyzmq-26.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e07dde3647afb084d985310d067a3efa6efad0621ee10826f2cb2f9a31b89d2f", size = 1406834, upload-time = "2025-04-04T12:03:54Z" }, - { url = "https://files.pythonhosted.org/packages/22/74/aaa837b331580c13b79ac39396601fb361454ee184ca85e8861914769b99/pyzmq-26.4.0-cp312-cp312-win32.whl", hash = "sha256:ba034a32ecf9af72adfa5ee383ad0fd4f4e38cdb62b13624278ef768fe5b5b44", size = 577992, upload-time = "2025-04-04T12:03:55.815Z" }, - { url = "https://files.pythonhosted.org/packages/30/0f/55f8c02c182856743b82dde46b2dc3e314edda7f1098c12a8227eeda0833/pyzmq-26.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:056a97aab4064f526ecb32f4343917a4022a5d9efb6b9df990ff72e1879e40be", size = 640466, upload-time = "2025-04-04T12:03:57.231Z" }, - { url = "https://files.pythonhosted.org/packages/e4/29/073779afc3ef6f830b8de95026ef20b2d1ec22d0324d767748d806e57379/pyzmq-26.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f23c750e485ce1eb639dbd576d27d168595908aa2d60b149e2d9e34c9df40e0", size = 556342, upload-time = "2025-04-04T12:03:59.218Z" }, - { url = "https://files.pythonhosted.org/packages/d7/20/fb2c92542488db70f833b92893769a569458311a76474bda89dc4264bd18/pyzmq-26.4.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:c43fac689880f5174d6fc864857d1247fe5cfa22b09ed058a344ca92bf5301e3", size = 1339484, upload-time = "2025-04-04T12:04:00.671Z" }, - { url = "https://files.pythonhosted.org/packages/58/29/2f06b9cabda3a6ea2c10f43e67ded3e47fc25c54822e2506dfb8325155d4/pyzmq-26.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:902aca7eba477657c5fb81c808318460328758e8367ecdd1964b6330c73cae43", size = 666106, upload-time = "2025-04-04T12:04:02.366Z" }, - { url = "https://files.pythonhosted.org/packages/77/e4/dcf62bd29e5e190bd21bfccaa4f3386e01bf40d948c239239c2f1e726729/pyzmq-26.4.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5e48a830bfd152fe17fbdeaf99ac5271aa4122521bf0d275b6b24e52ef35eb6", size = 902056, upload-time = "2025-04-04T12:04:03.919Z" }, - { url = "https://files.pythonhosted.org/packages/1a/cf/b36b3d7aea236087d20189bec1a87eeb2b66009731d7055e5c65f845cdba/pyzmq-26.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31be2b6de98c824c06f5574331f805707c667dc8f60cb18580b7de078479891e", size = 860148, upload-time = "2025-04-04T12:04:05.581Z" }, - { url = "https://files.pythonhosted.org/packages/18/a6/f048826bc87528c208e90604c3bf573801e54bd91e390cbd2dfa860e82dc/pyzmq-26.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6332452034be001bbf3206ac59c0d2a7713de5f25bb38b06519fc6967b7cf771", size = 855983, upload-time = "2025-04-04T12:04:07.096Z" }, - { url = "https://files.pythonhosted.org/packages/0a/27/454d34ab6a1d9772a36add22f17f6b85baf7c16e14325fa29e7202ca8ee8/pyzmq-26.4.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:da8c0f5dd352136853e6a09b1b986ee5278dfddfebd30515e16eae425c872b30", size = 1197274, upload-time = "2025-04-04T12:04:08.523Z" }, - { url = "https://files.pythonhosted.org/packages/f4/3d/7abfeab6b83ad38aa34cbd57c6fc29752c391e3954fd12848bd8d2ec0df6/pyzmq-26.4.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f4ccc1a0a2c9806dda2a2dd118a3b7b681e448f3bb354056cad44a65169f6d86", size = 1507120, upload-time = "2025-04-04T12:04:10.58Z" }, - { url = "https://files.pythonhosted.org/packages/13/ff/bc8d21dbb9bc8705126e875438a1969c4f77e03fc8565d6901c7933a3d01/pyzmq-26.4.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1c0b5fceadbab461578daf8d1dcc918ebe7ddd2952f748cf30c7cf2de5d51101", size = 1406738, upload-time = "2025-04-04T12:04:12.509Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5d/d4cd85b24de71d84d81229e3bbb13392b2698432cf8fdcea5afda253d587/pyzmq-26.4.0-cp313-cp313-win32.whl", hash = "sha256:28e2b0ff5ba4b3dd11062d905682bad33385cfa3cc03e81abd7f0822263e6637", size = 577826, upload-time = "2025-04-04T12:04:14.289Z" }, - { url = "https://files.pythonhosted.org/packages/c6/6c/f289c1789d7bb6e5a3b3bef7b2a55089b8561d17132be7d960d3ff33b14e/pyzmq-26.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:23ecc9d241004c10e8b4f49d12ac064cd7000e1643343944a10df98e57bc544b", size = 640406, upload-time = "2025-04-04T12:04:15.757Z" }, - { url = "https://files.pythonhosted.org/packages/b3/99/676b8851cb955eb5236a0c1e9ec679ea5ede092bf8bf2c8a68d7e965cac3/pyzmq-26.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:1edb0385c7f025045d6e0f759d4d3afe43c17a3d898914ec6582e6f464203c08", size = 556216, upload-time = "2025-04-04T12:04:17.212Z" }, - { url = "https://files.pythonhosted.org/packages/65/c2/1fac340de9d7df71efc59d9c50fc7a635a77b103392d1842898dd023afcb/pyzmq-26.4.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:93a29e882b2ba1db86ba5dd5e88e18e0ac6b627026c5cfbec9983422011b82d4", size = 1333769, upload-time = "2025-04-04T12:04:18.665Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c7/6c03637e8d742c3b00bec4f5e4cd9d1c01b2f3694c6f140742e93ca637ed/pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45684f276f57110bb89e4300c00f1233ca631f08f5f42528a5c408a79efc4a", size = 658826, upload-time = "2025-04-04T12:04:20.405Z" }, - { url = "https://files.pythonhosted.org/packages/a5/97/a8dca65913c0f78e0545af2bb5078aebfc142ca7d91cdaffa1fbc73e5dbd/pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f72073e75260cb301aad4258ad6150fa7f57c719b3f498cb91e31df16784d89b", size = 891650, upload-time = "2025-04-04T12:04:22.413Z" }, - { url = "https://files.pythonhosted.org/packages/7d/7e/f63af1031eb060bf02d033732b910fe48548dcfdbe9c785e9f74a6cc6ae4/pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be37e24b13026cfedd233bcbbccd8c0bcd2fdd186216094d095f60076201538d", size = 849776, upload-time = "2025-04-04T12:04:23.959Z" }, - { url = "https://files.pythonhosted.org/packages/f6/fa/1a009ce582802a895c0d5fe9413f029c940a0a8ee828657a3bb0acffd88b/pyzmq-26.4.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:237b283044934d26f1eeff4075f751b05d2f3ed42a257fc44386d00df6a270cf", size = 842516, upload-time = "2025-04-04T12:04:25.449Z" }, - { url = "https://files.pythonhosted.org/packages/6e/bc/f88b0bad0f7a7f500547d71e99f10336f2314e525d4ebf576a1ea4a1d903/pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:b30f862f6768b17040929a68432c8a8be77780317f45a353cb17e423127d250c", size = 1189183, upload-time = "2025-04-04T12:04:27.035Z" }, - { url = "https://files.pythonhosted.org/packages/d9/8c/db446a3dd9cf894406dec2e61eeffaa3c07c3abb783deaebb9812c4af6a5/pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:c80fcd3504232f13617c6ab501124d373e4895424e65de8b72042333316f64a8", size = 1495501, upload-time = "2025-04-04T12:04:28.833Z" }, - { url = "https://files.pythonhosted.org/packages/05/4c/bf3cad0d64c3214ac881299c4562b815f05d503bccc513e3fd4fdc6f67e4/pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:26a2a7451606b87f67cdeca2c2789d86f605da08b4bd616b1a9981605ca3a364", size = 1395540, upload-time = "2025-04-04T12:04:30.562Z" }, - { url = "https://files.pythonhosted.org/packages/06/91/21d3af57bc77e86e9d1e5384f256fd25cdb4c8eed4c45c8119da8120915f/pyzmq-26.4.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:a88643de8abd000ce99ca72056a1a2ae15881ee365ecb24dd1d9111e43d57842", size = 1340634, upload-time = "2025-04-04T12:04:47.661Z" }, - { url = "https://files.pythonhosted.org/packages/54/e6/58cd825023e998a0e49db7322b3211e6cf93f0796710b77d1496304c10d1/pyzmq-26.4.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a744ce209ecb557406fb928f3c8c55ce79b16c3eeb682da38ef5059a9af0848", size = 907880, upload-time = "2025-04-04T12:04:49.294Z" }, - { url = "https://files.pythonhosted.org/packages/72/83/619e44a766ef738cb7e8ed8e5a54565627801bdb027ca6dfb70762385617/pyzmq-26.4.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9434540f333332224ecb02ee6278b6c6f11ea1266b48526e73c903119b2f420f", size = 863003, upload-time = "2025-04-04T12:04:51Z" }, - { url = "https://files.pythonhosted.org/packages/b6/6a/a59af31320598bdc63d2c5a3181d14a89673c2c794540678285482e8a342/pyzmq-26.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6c6f0a23e55cd38d27d4c89add963294ea091ebcb104d7fdab0f093bc5abb1c", size = 673432, upload-time = "2025-04-04T12:04:52.611Z" }, - { url = "https://files.pythonhosted.org/packages/29/ae/64dd6c18b08ce2cb009c60f11cf01c87f323acd80344d8b059c0304a7370/pyzmq-26.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6145df55dc2309f6ef72d70576dcd5aabb0fd373311613fe85a5e547c722b780", size = 1205221, upload-time = "2025-04-04T12:04:54.31Z" }, - { url = "https://files.pythonhosted.org/packages/d0/0b/c583ab750957b025244a66948831bc9ca486d11c820da4626caf6480ee1a/pyzmq-26.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2ea81823840ef8c56e5d2f9918e4d571236294fea4d1842b302aebffb9e40997", size = 1515299, upload-time = "2025-04-04T12:04:56.063Z" }, - { url = "https://files.pythonhosted.org/packages/22/ba/95ba76292c49dd9c6dff1f127b4867033020b708d101cba6e4fc5a3d166d/pyzmq-26.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cc2abc385dc37835445abe206524fbc0c9e3fce87631dfaa90918a1ba8f425eb", size = 1415366, upload-time = "2025-04-04T12:04:58.241Z" }, - { url = "https://files.pythonhosted.org/packages/6e/65/51abe36169effda26ac7400ffac96f463e09dff40d344cdc2629d9a59162/pyzmq-26.4.0-cp39-cp39-win32.whl", hash = "sha256:41a2508fe7bed4c76b4cf55aacfb8733926f59d440d9ae2b81ee8220633b4d12", size = 580773, upload-time = "2025-04-04T12:04:59.786Z" }, - { url = "https://files.pythonhosted.org/packages/89/68/d9ac94086c63a0ed8d73e9e8aec54b39f481696698a5a939a7207629fb30/pyzmq-26.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:d4000e8255d6cbce38982e5622ebb90823f3409b7ffe8aeae4337ef7d6d2612a", size = 644340, upload-time = "2025-04-04T12:05:01.389Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8f/66c261d657c1b0791ee5b372c90b1646b453adb581fcdc1dc5c94e5b03e3/pyzmq-26.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f6919d9c120488246bdc2a2f96662fa80d67b35bd6d66218f457e722b3ff64", size = 560075, upload-time = "2025-04-04T12:05:02.975Z" }, - { url = "https://files.pythonhosted.org/packages/47/03/96004704a84095f493be8d2b476641f5c967b269390173f85488a53c1c13/pyzmq-26.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:98d948288ce893a2edc5ec3c438fe8de2daa5bbbd6e2e865ec5f966e237084ba", size = 834408, upload-time = "2025-04-04T12:05:04.569Z" }, - { url = "https://files.pythonhosted.org/packages/e4/7f/68d8f3034a20505db7551cb2260248be28ca66d537a1ac9a257913d778e4/pyzmq-26.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9f34f5c9e0203ece706a1003f1492a56c06c0632d86cb77bcfe77b56aacf27b", size = 569580, upload-time = "2025-04-04T12:05:06.283Z" }, - { url = "https://files.pythonhosted.org/packages/9b/a6/2b0d6801ec33f2b2a19dd8d02e0a1e8701000fec72926e6787363567d30c/pyzmq-26.4.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80c9b48aef586ff8b698359ce22f9508937c799cc1d2c9c2f7c95996f2300c94", size = 798250, upload-time = "2025-04-04T12:05:07.88Z" }, - { url = "https://files.pythonhosted.org/packages/96/2a/0322b3437de977dcac8a755d6d7ce6ec5238de78e2e2d9353730b297cf12/pyzmq-26.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3f2a5b74009fd50b53b26f65daff23e9853e79aa86e0aa08a53a7628d92d44a", size = 756758, upload-time = "2025-04-04T12:05:09.483Z" }, - { url = "https://files.pythonhosted.org/packages/c2/33/43704f066369416d65549ccee366cc19153911bec0154da7c6b41fca7e78/pyzmq-26.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:61c5f93d7622d84cb3092d7f6398ffc77654c346545313a3737e266fc11a3beb", size = 555371, upload-time = "2025-04-04T12:05:11.062Z" }, - { url = "https://files.pythonhosted.org/packages/04/52/a70fcd5592715702248306d8e1729c10742c2eac44529984413b05c68658/pyzmq-26.4.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4478b14cb54a805088299c25a79f27eaf530564a7a4f72bf432a040042b554eb", size = 834405, upload-time = "2025-04-04T12:05:13.3Z" }, - { url = "https://files.pythonhosted.org/packages/25/f9/1a03f1accff16b3af1a6fa22cbf7ced074776abbf688b2e9cb4629700c62/pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a28ac29c60e4ba84b5f58605ace8ad495414a724fe7aceb7cf06cd0598d04e1", size = 569578, upload-time = "2025-04-04T12:05:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/76/0c/3a633acd762aa6655fcb71fa841907eae0ab1e8582ff494b137266de341d/pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43b03c1ceea27c6520124f4fb2ba9c647409b9abdf9a62388117148a90419494", size = 798248, upload-time = "2025-04-04T12:05:17.376Z" }, - { url = "https://files.pythonhosted.org/packages/cd/cc/6c99c84aa60ac1cc56747bed6be8ce6305b9b861d7475772e7a25ce019d3/pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7731abd23a782851426d4e37deb2057bf9410848a4459b5ede4fe89342e687a9", size = 756757, upload-time = "2025-04-04T12:05:19.19Z" }, - { url = "https://files.pythonhosted.org/packages/13/9c/d8073bd898eb896e94c679abe82e47506e2b750eb261cf6010ced869797c/pyzmq-26.4.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a222ad02fbe80166b0526c038776e8042cd4e5f0dec1489a006a1df47e9040e0", size = 555371, upload-time = "2025-04-04T12:05:20.702Z" }, - { url = "https://files.pythonhosted.org/packages/af/b2/71a644b629e1a93ccae9e22a45aec9d23065dfcc24c399cb837f81cd08c2/pyzmq-26.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:552b0d2e39987733e1e9e948a0ced6ff75e0ea39ab1a1db2fc36eb60fd8760db", size = 834397, upload-time = "2025-04-04T12:05:31.217Z" }, - { url = "https://files.pythonhosted.org/packages/a9/dd/052a25651eaaff8f5fd652fb40a3abb400e71207db2d605cf6faf0eac598/pyzmq-26.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd670a8aa843f2ee637039bbd412e0d7294a5e588e1ecc9ad98b0cdc050259a4", size = 569571, upload-time = "2025-04-04T12:05:32.877Z" }, - { url = "https://files.pythonhosted.org/packages/a5/5d/201ca10b5d12ab187a418352c06d70c3e2087310af038b11056aba1359be/pyzmq-26.4.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d367b7b775a0e1e54a59a2ba3ed4d5e0a31566af97cc9154e34262777dab95ed", size = 798243, upload-time = "2025-04-04T12:05:34.91Z" }, - { url = "https://files.pythonhosted.org/packages/bd/d4/2c64e54749536ad1633400f28d71e71e19375d00ce1fe9bb1123364dc927/pyzmq-26.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112af16c406e4a93df2caef49f884f4c2bb2b558b0b5577ef0b2465d15c1abc", size = 756751, upload-time = "2025-04-04T12:05:37.12Z" }, - { url = "https://files.pythonhosted.org/packages/08/e6/34d119af43d06a8dcd88bf7a62dac69597eaba52b49ecce76ff06b40f1fd/pyzmq-26.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c76c298683f82669cab0b6da59071f55238c039738297c69f187a542c6d40099", size = 745400, upload-time = "2025-04-04T12:05:40.694Z" }, - { url = "https://files.pythonhosted.org/packages/f8/49/b5e471d74a63318e51f30d329b17d2550bdededaab55baed2e2499de7ce4/pyzmq-26.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:49b6ca2e625b46f499fb081aaf7819a177f41eeb555acb05758aa97f4f95d147", size = 555367, upload-time = "2025-04-04T12:05:42.356Z" }, + { url = "https://files.pythonhosted.org/packages/9c/09/1681d4b047626d352c083770618ac29655ab1f5c20eee31dc94c000b9b7b/pyzmq-27.0.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:b973ee650e8f442ce482c1d99ca7ab537c69098d53a3d046676a484fd710c87a", size = 1329291, upload-time = "2025-06-13T14:06:57.945Z" }, + { url = "https://files.pythonhosted.org/packages/9d/b2/9c9385225fdd54db9506ed8accbb9ea63ca813ba59d43d7f282a6a16a30b/pyzmq-27.0.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:661942bc7cd0223d569d808f2e5696d9cc120acc73bf3e88a1f1be7ab648a7e4", size = 905952, upload-time = "2025-06-13T14:07:03.232Z" }, + { url = "https://files.pythonhosted.org/packages/41/73/333c72c7ec182cdffe25649e3da1c3b9f3cf1cede63cfdc23d1384d4a601/pyzmq-27.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50360fb2a056ffd16e5f4177eee67f1dd1017332ea53fb095fe7b5bf29c70246", size = 666165, upload-time = "2025-06-13T14:07:04.667Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fe/fc7b9c1a50981928e25635a926653cb755364316db59ccd6e79cfb9a0b4f/pyzmq-27.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf209a6dc4b420ed32a7093642843cbf8703ed0a7d86c16c0b98af46762ebefb", size = 853755, upload-time = "2025-06-13T14:07:06.93Z" }, + { url = "https://files.pythonhosted.org/packages/8c/4c/740ed4b6e8fa160cd19dc5abec8db68f440564b2d5b79c1d697d9862a2f7/pyzmq-27.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c2dace4a7041cca2fba5357a2d7c97c5effdf52f63a1ef252cfa496875a3762d", size = 1654868, upload-time = "2025-06-13T14:07:08.224Z" }, + { url = "https://files.pythonhosted.org/packages/97/00/875b2ecfcfc78ab962a59bd384995186818524ea957dc8ad3144611fae12/pyzmq-27.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:63af72b2955fc77caf0a77444baa2431fcabb4370219da38e1a9f8d12aaebe28", size = 2033443, upload-time = "2025-06-13T14:07:09.653Z" }, + { url = "https://files.pythonhosted.org/packages/60/55/6dd9c470c42d713297c5f2a56f7903dc1ebdb4ab2edda996445c21651900/pyzmq-27.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e8c4adce8e37e75c4215297d7745551b8dcfa5f728f23ce09bf4e678a9399413", size = 1891288, upload-time = "2025-06-13T14:07:11.099Z" }, + { url = "https://files.pythonhosted.org/packages/28/5d/54b0ef50d40d7c65a627f4a4b4127024ba9820f2af8acd933a4d30ae192e/pyzmq-27.0.0-cp310-cp310-win32.whl", hash = "sha256:5d5ef4718ecab24f785794e0e7536436698b459bfbc19a1650ef55280119d93b", size = 567936, upload-time = "2025-06-13T14:07:12.468Z" }, + { url = "https://files.pythonhosted.org/packages/18/ea/dedca4321de748ca48d3bcdb72274d4d54e8d84ea49088d3de174bd45d88/pyzmq-27.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:e40609380480b3d12c30f841323f42451c755b8fece84235236f5fe5ffca8c1c", size = 628686, upload-time = "2025-06-13T14:07:14.051Z" }, + { url = "https://files.pythonhosted.org/packages/d4/a7/fcdeedc306e71e94ac262cba2d02337d885f5cdb7e8efced8e5ffe327808/pyzmq-27.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:6b0397b0be277b46762956f576e04dc06ced265759e8c2ff41a0ee1aa0064198", size = 559039, upload-time = "2025-06-13T14:07:15.289Z" }, + { url = "https://files.pythonhosted.org/packages/44/df/84c630654106d9bd9339cdb564aa941ed41b023a0264251d6743766bb50e/pyzmq-27.0.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:21457825249b2a53834fa969c69713f8b5a79583689387a5e7aed880963ac564", size = 1332718, upload-time = "2025-06-13T14:07:16.555Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8e/f6a5461a07654d9840d256476434ae0ff08340bba562a455f231969772cb/pyzmq-27.0.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1958947983fef513e6e98eff9cb487b60bf14f588dc0e6bf35fa13751d2c8251", size = 908248, upload-time = "2025-06-13T14:07:18.033Z" }, + { url = "https://files.pythonhosted.org/packages/7c/93/82863e8d695a9a3ae424b63662733ae204a295a2627d52af2f62c2cd8af9/pyzmq-27.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0dc628b5493f9a8cd9844b8bee9732ef587ab00002157c9329e4fc0ef4d3afa", size = 668647, upload-time = "2025-06-13T14:07:19.378Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/15278769b348121eacdbfcbd8c4d40f1102f32fa6af5be1ffc032ed684be/pyzmq-27.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7bbe9e1ed2c8d3da736a15694d87c12493e54cc9dc9790796f0321794bbc91f", size = 856600, upload-time = "2025-06-13T14:07:20.906Z" }, + { url = "https://files.pythonhosted.org/packages/d4/af/1c469b3d479bd095edb28e27f12eee10b8f00b356acbefa6aeb14dd295d1/pyzmq-27.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc1091f59143b471d19eb64f54bae4f54bcf2a466ffb66fe45d94d8d734eb495", size = 1657748, upload-time = "2025-06-13T14:07:22.549Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f4/17f965d0ee6380b1d6326da842a50e4b8b9699745161207945f3745e8cb5/pyzmq-27.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7011ade88c8e535cf140f8d1a59428676fbbce7c6e54fefce58bf117aefb6667", size = 2034311, upload-time = "2025-06-13T14:07:23.966Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6e/7c391d81fa3149fd759de45d298003de6cfab343fb03e92c099821c448db/pyzmq-27.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c386339d7e3f064213aede5d03d054b237937fbca6dd2197ac8cf3b25a6b14e", size = 1893630, upload-time = "2025-06-13T14:07:25.899Z" }, + { url = "https://files.pythonhosted.org/packages/0e/e0/eaffe7a86f60e556399e224229e7769b717f72fec0706b70ab2c03aa04cb/pyzmq-27.0.0-cp311-cp311-win32.whl", hash = "sha256:0546a720c1f407b2172cb04b6b094a78773491497e3644863cf5c96c42df8cff", size = 567706, upload-time = "2025-06-13T14:07:27.595Z" }, + { url = "https://files.pythonhosted.org/packages/c9/05/89354a8cffdcce6e547d48adaaf7be17007fc75572123ff4ca90a4ca04fc/pyzmq-27.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:15f39d50bd6c9091c67315ceb878a4f531957b121d2a05ebd077eb35ddc5efed", size = 630322, upload-time = "2025-06-13T14:07:28.938Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/4ab976d5e1e63976719389cc4f3bfd248a7f5f2bb2ebe727542363c61b5f/pyzmq-27.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c5817641eebb391a2268c27fecd4162448e03538387093cdbd8bf3510c316b38", size = 558435, upload-time = "2025-06-13T14:07:30.256Z" }, + { url = "https://files.pythonhosted.org/packages/93/a7/9ad68f55b8834ede477842214feba6a4c786d936c022a67625497aacf61d/pyzmq-27.0.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:cbabc59dcfaac66655c040dfcb8118f133fb5dde185e5fc152628354c1598e52", size = 1305438, upload-time = "2025-06-13T14:07:31.676Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ee/26aa0f98665a22bc90ebe12dced1de5f3eaca05363b717f6fb229b3421b3/pyzmq-27.0.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:cb0ac5179cba4b2f94f1aa208fbb77b62c4c9bf24dd446278b8b602cf85fcda3", size = 895095, upload-time = "2025-06-13T14:07:33.104Z" }, + { url = "https://files.pythonhosted.org/packages/cf/85/c57e7ab216ecd8aa4cc7e3b83b06cc4e9cf45c87b0afc095f10cd5ce87c1/pyzmq-27.0.0-cp312-abi3-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53a48f0228eab6cbf69fde3aa3c03cbe04e50e623ef92ae395fce47ef8a76152", size = 651826, upload-time = "2025-06-13T14:07:34.831Z" }, + { url = "https://files.pythonhosted.org/packages/69/9a/9ea7e230feda9400fb0ae0d61d7d6ddda635e718d941c44eeab22a179d34/pyzmq-27.0.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:111db5f395e09f7e775f759d598f43cb815fc58e0147623c4816486e1a39dc22", size = 839750, upload-time = "2025-06-13T14:07:36.553Z" }, + { url = "https://files.pythonhosted.org/packages/08/66/4cebfbe71f3dfbd417011daca267539f62ed0fbc68105357b68bbb1a25b7/pyzmq-27.0.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c8878011653dcdc27cc2c57e04ff96f0471e797f5c19ac3d7813a245bcb24371", size = 1641357, upload-time = "2025-06-13T14:07:38.21Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f6/b0f62578c08d2471c791287149cb8c2aaea414ae98c6e995c7dbe008adfb/pyzmq-27.0.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:c0ed2c1f335ba55b5fdc964622254917d6b782311c50e138863eda409fbb3b6d", size = 2020281, upload-time = "2025-06-13T14:07:39.599Z" }, + { url = "https://files.pythonhosted.org/packages/37/b9/4f670b15c7498495da9159edc374ec09c88a86d9cd5a47d892f69df23450/pyzmq-27.0.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e918d70862d4cfd4b1c187310015646a14e1f5917922ab45b29f28f345eeb6be", size = 1877110, upload-time = "2025-06-13T14:07:41.027Z" }, + { url = "https://files.pythonhosted.org/packages/66/31/9dee25c226295b740609f0d46db2fe972b23b6f5cf786360980524a3ba92/pyzmq-27.0.0-cp312-abi3-win32.whl", hash = "sha256:88b4e43cab04c3c0f0d55df3b1eef62df2b629a1a369b5289a58f6fa8b07c4f4", size = 559297, upload-time = "2025-06-13T14:07:42.533Z" }, + { url = "https://files.pythonhosted.org/packages/9b/12/52da5509800f7ff2d287b2f2b4e636e7ea0f001181cba6964ff6c1537778/pyzmq-27.0.0-cp312-abi3-win_amd64.whl", hash = "sha256:dce4199bf5f648a902ce37e7b3afa286f305cd2ef7a8b6ec907470ccb6c8b371", size = 619203, upload-time = "2025-06-13T14:07:43.843Z" }, + { url = "https://files.pythonhosted.org/packages/93/6d/7f2e53b19d1edb1eb4f09ec7c3a1f945ca0aac272099eab757d15699202b/pyzmq-27.0.0-cp312-abi3-win_arm64.whl", hash = "sha256:56e46bbb85d52c1072b3f809cc1ce77251d560bc036d3a312b96db1afe76db2e", size = 551927, upload-time = "2025-06-13T14:07:45.51Z" }, + { url = "https://files.pythonhosted.org/packages/19/62/876b27c4ff777db4ceba1c69ea90d3c825bb4f8d5e7cd987ce5802e33c55/pyzmq-27.0.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c36ad534c0c29b4afa088dc53543c525b23c0797e01b69fef59b1a9c0e38b688", size = 1340826, upload-time = "2025-06-13T14:07:46.881Z" }, + { url = "https://files.pythonhosted.org/packages/43/69/58ef8f4f59d3bcd505260c73bee87b008850f45edca40ddaba54273c35f4/pyzmq-27.0.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:67855c14173aec36395d7777aaba3cc527b393821f30143fd20b98e1ff31fd38", size = 897283, upload-time = "2025-06-13T14:07:49.562Z" }, + { url = "https://files.pythonhosted.org/packages/43/15/93a0d0396700a60475ad3c5d42c5f1c308d3570bc94626b86c71ef9953e0/pyzmq-27.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8617c7d43cd8ccdb62aebe984bfed77ca8f036e6c3e46dd3dddda64b10f0ab7a", size = 660567, upload-time = "2025-06-13T14:07:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b3/fe055513e498ca32f64509abae19b9c9eb4d7c829e02bd8997dd51b029eb/pyzmq-27.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:67bfbcbd0a04c575e8103a6061d03e393d9f80ffdb9beb3189261e9e9bc5d5e9", size = 847681, upload-time = "2025-06-13T14:07:52.77Z" }, + { url = "https://files.pythonhosted.org/packages/b6/4f/ff15300b00b5b602191f3df06bbc8dd4164e805fdd65bb77ffbb9c5facdc/pyzmq-27.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5cd11d46d7b7e5958121b3eaf4cd8638eff3a720ec527692132f05a57f14341d", size = 1650148, upload-time = "2025-06-13T14:07:54.178Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6f/84bdfff2a224a6f26a24249a342e5906993c50b0761e311e81b39aef52a7/pyzmq-27.0.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:b801c2e40c5aa6072c2f4876de8dccd100af6d9918d4d0d7aa54a1d982fd4f44", size = 2023768, upload-time = "2025-06-13T14:07:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/64/39/dc2db178c26a42228c5ac94a9cc595030458aa64c8d796a7727947afbf55/pyzmq-27.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20d5cb29e8c5f76a127c75b6e7a77e846bc4b655c373baa098c26a61b7ecd0ef", size = 1885199, upload-time = "2025-06-13T14:07:57.166Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/dae7b06a1f8cdee5d8e7a63d99c5d129c401acc40410bef2cbf42025e26f/pyzmq-27.0.0-cp313-cp313t-win32.whl", hash = "sha256:a20528da85c7ac7a19b7384e8c3f8fa707841fd85afc4ed56eda59d93e3d98ad", size = 575439, upload-time = "2025-06-13T14:07:58.959Z" }, + { url = "https://files.pythonhosted.org/packages/eb/bc/1709dc55f0970cf4cb8259e435e6773f9946f41a045c2cb90e870b7072da/pyzmq-27.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d8229f2efece6a660ee211d74d91dbc2a76b95544d46c74c615e491900dc107f", size = 639933, upload-time = "2025-06-13T14:08:00.777Z" }, + { url = "https://files.pythonhosted.org/packages/19/dc/95210fe17e5d7dba89bd663e1d88f50a8003f296284731b09f1d95309a42/pyzmq-27.0.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:100f6e5052ba42b2533011d34a018a5ace34f8cac67cb03cfa37c8bdae0ca617", size = 1330656, upload-time = "2025-06-13T14:08:17.414Z" }, + { url = "https://files.pythonhosted.org/packages/d3/7e/63f742b578316258e03ecb393d35c0964348d80834bdec8a100ed7bb9c91/pyzmq-27.0.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:bf6c6b061efd00404b9750e2cfbd9507492c8d4b3721ded76cb03786131be2ed", size = 906522, upload-time = "2025-06-13T14:08:18.945Z" }, + { url = "https://files.pythonhosted.org/packages/1f/bf/f0b2b67f5a9bfe0fbd0e978a2becd901f802306aa8e29161cb0963094352/pyzmq-27.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee05728c0b0b2484a9fc20466fa776fffb65d95f7317a3419985b8c908563861", size = 863545, upload-time = "2025-06-13T14:08:20.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/0e/7d90ccd2ef577c8bae7f926acd2011a6d960eea8a068c5fd52b419206960/pyzmq-27.0.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7cdf07fe0a557b131366f80727ec8ccc4b70d89f1e3f920d94a594d598d754f0", size = 666796, upload-time = "2025-06-13T14:08:21.836Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6d/ca8007a313baa73361778773aef210f4902e68f468d1f93b6c8b908fabbd/pyzmq-27.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:90252fa2ff3a104219db1f5ced7032a7b5fc82d7c8d2fec2b9a3e6fd4e25576b", size = 1655599, upload-time = "2025-06-13T14:08:23.343Z" }, + { url = "https://files.pythonhosted.org/packages/46/de/5cb4f99d6c0dd8f33d729c9ebd49af279586e5ab127e93aa6ef0ecd08c4c/pyzmq-27.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ea6d441c513bf18c578c73c323acf7b4184507fc244762193aa3a871333c9045", size = 2034119, upload-time = "2025-06-13T14:08:26.369Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8d/57cc90c8b5f30a97a7e86ec91a3b9822ec7859d477e9c30f531fb78f4a97/pyzmq-27.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ae2b34bcfaae20c064948a4113bf8709eee89fd08317eb293ae4ebd69b4d9740", size = 1891955, upload-time = "2025-06-13T14:08:28.39Z" }, + { url = "https://files.pythonhosted.org/packages/24/f5/a7012022573188903802ab75b5314b00e5c629228f3a36fadb421a42ebff/pyzmq-27.0.0-cp39-cp39-win32.whl", hash = "sha256:5b10bd6f008937705cf6e7bf8b6ece5ca055991e3eb130bca8023e20b86aa9a3", size = 568497, upload-time = "2025-06-13T14:08:30.089Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f3/2a4b2798275a574801221d94d599ed3e26d19f6378a7364cdfa3bee53944/pyzmq-27.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:00387d12a8af4b24883895f7e6b9495dc20a66027b696536edac35cb988c38f3", size = 629315, upload-time = "2025-06-13T14:08:31.877Z" }, + { url = "https://files.pythonhosted.org/packages/da/eb/386a70314f305816142d6e8537f5557e5fd9614c03698d6c88cbd6c41190/pyzmq-27.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:4c19d39c04c29a6619adfeb19e3735c421b3bfee082f320662f52e59c47202ba", size = 559596, upload-time = "2025-06-13T14:08:33.357Z" }, + { url = "https://files.pythonhosted.org/packages/09/6f/be6523a7f3821c0b5370912ef02822c028611360e0d206dd945bdbf9eaef/pyzmq-27.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:656c1866505a5735d0660b7da6d7147174bbf59d4975fc2b7f09f43c9bc25745", size = 835950, upload-time = "2025-06-13T14:08:35Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1e/a50fdd5c15018de07ab82a61bc460841be967ee7bbe7abee3b714d66f7ac/pyzmq-27.0.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74175b9e12779382432dd1d1f5960ebe7465d36649b98a06c6b26be24d173fab", size = 799876, upload-time = "2025-06-13T14:08:36.849Z" }, + { url = "https://files.pythonhosted.org/packages/88/a1/89eb5b71f5a504f8f887aceb8e1eb3626e00c00aa8085381cdff475440dc/pyzmq-27.0.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8c6de908465697a8708e4d6843a1e884f567962fc61eb1706856545141d0cbb", size = 567400, upload-time = "2025-06-13T14:08:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/56/aa/4571dbcff56cfb034bac73fde8294e123c975ce3eea89aff31bf6dc6382b/pyzmq-27.0.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c644aaacc01d0df5c7072826df45e67301f191c55f68d7b2916d83a9ddc1b551", size = 747031, upload-time = "2025-06-13T14:08:40.413Z" }, + { url = "https://files.pythonhosted.org/packages/46/e0/d25f30fe0991293c5b2f5ef3b070d35fa6d57c0c7428898c3ab4913d0297/pyzmq-27.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:10f70c1d9a446a85013a36871a296007f6fe4232b530aa254baf9da3f8328bc0", size = 544726, upload-time = "2025-06-13T14:08:41.997Z" }, + { url = "https://files.pythonhosted.org/packages/98/a6/92394373b8dbc1edc9d53c951e8d3989d518185174ee54492ec27711779d/pyzmq-27.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd1dc59763effd1576f8368047c9c31468fce0af89d76b5067641137506792ae", size = 835948, upload-time = "2025-06-13T14:08:43.516Z" }, + { url = "https://files.pythonhosted.org/packages/56/f3/4dc38d75d9995bfc18773df3e41f2a2ca9b740b06f1a15dbf404077e7588/pyzmq-27.0.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:60e8cc82d968174650c1860d7b716366caab9973787a1c060cf8043130f7d0f7", size = 799874, upload-time = "2025-06-13T14:08:45.017Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ba/64af397e0f421453dc68e31d5e0784d554bf39013a2de0872056e96e58af/pyzmq-27.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14fe7aaac86e4e93ea779a821967360c781d7ac5115b3f1a171ced77065a0174", size = 567400, upload-time = "2025-06-13T14:08:46.855Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/ec956cbe98809270b59a22891d5758edae147a258e658bf3024a8254c855/pyzmq-27.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6ad0562d4e6abb785be3e4dd68599c41be821b521da38c402bc9ab2a8e7ebc7e", size = 747031, upload-time = "2025-06-13T14:08:48.419Z" }, + { url = "https://files.pythonhosted.org/packages/be/8a/4a3764a68abc02e2fbb0668d225b6fda5cd39586dd099cee8b2ed6ab0452/pyzmq-27.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:9df43a2459cd3a3563404c1456b2c4c69564daa7dbaf15724c09821a3329ce46", size = 544726, upload-time = "2025-06-13T14:08:49.903Z" }, + { url = "https://files.pythonhosted.org/packages/03/f6/11b2a6c8cd13275c31cddc3f89981a1b799a3c41dec55289fa18dede96b5/pyzmq-27.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:39ddd3ba0a641f01d8f13a3cfd4c4924eb58e660d8afe87e9061d6e8ca6f7ac3", size = 835944, upload-time = "2025-06-13T14:08:59.189Z" }, + { url = "https://files.pythonhosted.org/packages/73/34/aa39076f4e07ae1912fa4b966fe24e831e01d736d4c1c7e8a3aa28a555b5/pyzmq-27.0.0-pp39-pypy39_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:8ca7e6a0388dd9e1180b14728051068f4efe83e0d2de058b5ff92c63f399a73f", size = 799869, upload-time = "2025-06-13T14:09:00.758Z" }, + { url = "https://files.pythonhosted.org/packages/65/f3/81ed6b3dd242408ee79c0d8a88734644acf208baee8666ecd7e52664cf55/pyzmq-27.0.0-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2524c40891be6a3106885a3935d58452dd83eb7a5742a33cc780a1ad4c49dec0", size = 758371, upload-time = "2025-06-13T14:09:02.461Z" }, + { url = "https://files.pythonhosted.org/packages/e1/04/dac4ca674764281caf744e8adefd88f7e325e1605aba0f9a322094b903fa/pyzmq-27.0.0-pp39-pypy39_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a56e3e5bd2d62a01744fd2f1ce21d760c7c65f030e9522738d75932a14ab62a", size = 567393, upload-time = "2025-06-13T14:09:04.037Z" }, + { url = "https://files.pythonhosted.org/packages/51/8b/619a9ee2fa4d3c724fbadde946427735ade64da03894b071bbdc3b789d83/pyzmq-27.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:096af9e133fec3a72108ddefba1e42985cb3639e9de52cfd336b6fc23aa083e9", size = 544715, upload-time = "2025-06-13T14:09:05.579Z" }, ] [[package]] @@ -2619,7 +2602,7 @@ wheels = [ [[package]] name = "requests" -version = "2.32.3" +version = "2.32.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -2627,9 +2610,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, ] [[package]] @@ -2791,27 +2774,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.11.12" +version = "0.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/0a/92416b159ec00cdf11e5882a9d80d29bf84bba3dbebc51c4898bfbca1da6/ruff-0.11.12.tar.gz", hash = "sha256:43cf7f69c7d7c7d7513b9d59c5d8cafd704e05944f978614aa9faff6ac202603", size = 4202289, upload-time = "2025-05-29T13:31:40.037Z" } +sdist = { url = "https://files.pythonhosted.org/packages/97/38/796a101608a90494440856ccfb52b1edae90de0b817e76bfade66b12d320/ruff-0.12.1.tar.gz", hash = "sha256:806bbc17f1104fd57451a98a58df35388ee3ab422e029e8f5cf30aa4af2c138c", size = 4413426, upload-time = "2025-06-26T20:34:14.784Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/cc/53eb79f012d15e136d40a8e8fc519ba8f55a057f60b29c2df34efd47c6e3/ruff-0.11.12-py3-none-linux_armv6l.whl", hash = "sha256:c7680aa2f0d4c4f43353d1e72123955c7a2159b8646cd43402de6d4a3a25d7cc", size = 10285597, upload-time = "2025-05-29T13:30:57.539Z" }, - { url = "https://files.pythonhosted.org/packages/e7/d7/73386e9fb0232b015a23f62fea7503f96e29c29e6c45461d4a73bac74df9/ruff-0.11.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2cad64843da9f134565c20bcc430642de897b8ea02e2e79e6e02a76b8dcad7c3", size = 11053154, upload-time = "2025-05-29T13:31:00.865Z" }, - { url = "https://files.pythonhosted.org/packages/4e/eb/3eae144c5114e92deb65a0cb2c72326c8469e14991e9bc3ec0349da1331c/ruff-0.11.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9b6886b524a1c659cee1758140138455d3c029783d1b9e643f3624a5ee0cb0aa", size = 10403048, upload-time = "2025-05-29T13:31:03.413Z" }, - { url = "https://files.pythonhosted.org/packages/29/64/20c54b20e58b1058db6689e94731f2a22e9f7abab74e1a758dfba058b6ca/ruff-0.11.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc3a3690aad6e86c1958d3ec3c38c4594b6ecec75c1f531e84160bd827b2012", size = 10597062, upload-time = "2025-05-29T13:31:05.539Z" }, - { url = "https://files.pythonhosted.org/packages/29/3a/79fa6a9a39422a400564ca7233a689a151f1039110f0bbbabcb38106883a/ruff-0.11.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f97fdbc2549f456c65b3b0048560d44ddd540db1f27c778a938371424b49fe4a", size = 10155152, upload-time = "2025-05-29T13:31:07.986Z" }, - { url = "https://files.pythonhosted.org/packages/e5/a4/22c2c97b2340aa968af3a39bc38045e78d36abd4ed3fa2bde91c31e712e3/ruff-0.11.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74adf84960236961090e2d1348c1a67d940fd12e811a33fb3d107df61eef8fc7", size = 11723067, upload-time = "2025-05-29T13:31:10.57Z" }, - { url = "https://files.pythonhosted.org/packages/bc/cf/3e452fbd9597bcd8058856ecd42b22751749d07935793a1856d988154151/ruff-0.11.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b56697e5b8bcf1d61293ccfe63873aba08fdbcbbba839fc046ec5926bdb25a3a", size = 12460807, upload-time = "2025-05-29T13:31:12.88Z" }, - { url = "https://files.pythonhosted.org/packages/2f/ec/8f170381a15e1eb7d93cb4feef8d17334d5a1eb33fee273aee5d1f8241a3/ruff-0.11.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d47afa45e7b0eaf5e5969c6b39cbd108be83910b5c74626247e366fd7a36a13", size = 12063261, upload-time = "2025-05-29T13:31:15.236Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bf/57208f8c0a8153a14652a85f4116c0002148e83770d7a41f2e90b52d2b4e/ruff-0.11.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bf9603fe1bf949de8b09a2da896f05c01ed7a187f4a386cdba6760e7f61be", size = 11329601, upload-time = "2025-05-29T13:31:18.68Z" }, - { url = "https://files.pythonhosted.org/packages/c3/56/edf942f7fdac5888094d9ffa303f12096f1a93eb46570bcf5f14c0c70880/ruff-0.11.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08033320e979df3b20dba567c62f69c45e01df708b0f9c83912d7abd3e0801cd", size = 11522186, upload-time = "2025-05-29T13:31:21.216Z" }, - { url = "https://files.pythonhosted.org/packages/ed/63/79ffef65246911ed7e2290aeece48739d9603b3a35f9529fec0fc6c26400/ruff-0.11.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:929b7706584f5bfd61d67d5070f399057d07c70585fa8c4491d78ada452d3bef", size = 10449032, upload-time = "2025-05-29T13:31:23.417Z" }, - { url = "https://files.pythonhosted.org/packages/88/19/8c9d4d8a1c2a3f5a1ea45a64b42593d50e28b8e038f1aafd65d6b43647f3/ruff-0.11.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7de4a73205dc5756b8e09ee3ed67c38312dce1aa28972b93150f5751199981b5", size = 10129370, upload-time = "2025-05-29T13:31:25.777Z" }, - { url = "https://files.pythonhosted.org/packages/bc/0f/2d15533eaa18f460530a857e1778900cd867ded67f16c85723569d54e410/ruff-0.11.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2635c2a90ac1b8ca9e93b70af59dfd1dd2026a40e2d6eebaa3efb0465dd9cf02", size = 11123529, upload-time = "2025-05-29T13:31:28.396Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e2/4c2ac669534bdded835356813f48ea33cfb3a947dc47f270038364587088/ruff-0.11.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d05d6a78a89166f03f03a198ecc9d18779076ad0eec476819467acb401028c0c", size = 11577642, upload-time = "2025-05-29T13:31:30.647Z" }, - { url = "https://files.pythonhosted.org/packages/a7/9b/c9ddf7f924d5617a1c94a93ba595f4b24cb5bc50e98b94433ab3f7ad27e5/ruff-0.11.12-py3-none-win32.whl", hash = "sha256:f5a07f49767c4be4772d161bfc049c1f242db0cfe1bd976e0f0886732a4765d6", size = 10475511, upload-time = "2025-05-29T13:31:32.917Z" }, - { url = "https://files.pythonhosted.org/packages/fd/d6/74fb6d3470c1aada019ffff33c0f9210af746cca0a4de19a1f10ce54968a/ruff-0.11.12-py3-none-win_amd64.whl", hash = "sha256:5a4d9f8030d8c3a45df201d7fb3ed38d0219bccd7955268e863ee4a115fa0832", size = 11523573, upload-time = "2025-05-29T13:31:35.782Z" }, - { url = "https://files.pythonhosted.org/packages/44/42/d58086ec20f52d2b0140752ae54b355ea2be2ed46f914231136dd1effcc7/ruff-0.11.12-py3-none-win_arm64.whl", hash = "sha256:65194e37853158d368e333ba282217941029a28ea90913c67e558c611d04daa5", size = 10697770, upload-time = "2025-05-29T13:31:38.009Z" }, + { url = "https://files.pythonhosted.org/packages/06/bf/3dba52c1d12ab5e78d75bd78ad52fb85a6a1f29cc447c2423037b82bed0d/ruff-0.12.1-py3-none-linux_armv6l.whl", hash = "sha256:6013a46d865111e2edb71ad692fbb8262e6c172587a57c0669332a449384a36b", size = 10305649, upload-time = "2025-06-26T20:33:39.242Z" }, + { url = "https://files.pythonhosted.org/packages/8c/65/dab1ba90269bc8c81ce1d499a6517e28fe6f87b2119ec449257d0983cceb/ruff-0.12.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b3f75a19e03a4b0757d1412edb7f27cffb0c700365e9d6b60bc1b68d35bc89e0", size = 11120201, upload-time = "2025-06-26T20:33:42.207Z" }, + { url = "https://files.pythonhosted.org/packages/3f/3e/2d819ffda01defe857fa2dd4cba4d19109713df4034cc36f06bbf582d62a/ruff-0.12.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9a256522893cb7e92bb1e1153283927f842dea2e48619c803243dccc8437b8be", size = 10466769, upload-time = "2025-06-26T20:33:44.102Z" }, + { url = "https://files.pythonhosted.org/packages/63/37/bde4cf84dbd7821c8de56ec4ccc2816bce8125684f7b9e22fe4ad92364de/ruff-0.12.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:069052605fe74c765a5b4272eb89880e0ff7a31e6c0dbf8767203c1fbd31c7ff", size = 10660902, upload-time = "2025-06-26T20:33:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/0e/3a/390782a9ed1358c95e78ccc745eed1a9d657a537e5c4c4812fce06c8d1a0/ruff-0.12.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a684f125a4fec2d5a6501a466be3841113ba6847827be4573fddf8308b83477d", size = 10167002, upload-time = "2025-06-26T20:33:47.81Z" }, + { url = "https://files.pythonhosted.org/packages/6d/05/f2d4c965009634830e97ffe733201ec59e4addc5b1c0efa035645baa9e5f/ruff-0.12.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdecdef753bf1e95797593007569d8e1697a54fca843d78f6862f7dc279e23bd", size = 11751522, upload-time = "2025-06-26T20:33:49.857Z" }, + { url = "https://files.pythonhosted.org/packages/35/4e/4bfc519b5fcd462233f82fc20ef8b1e5ecce476c283b355af92c0935d5d9/ruff-0.12.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:70d52a058c0e7b88b602f575d23596e89bd7d8196437a4148381a3f73fcd5010", size = 12520264, upload-time = "2025-06-26T20:33:52.199Z" }, + { url = "https://files.pythonhosted.org/packages/85/b2/7756a6925da236b3a31f234b4167397c3e5f91edb861028a631546bad719/ruff-0.12.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84d0a69d1e8d716dfeab22d8d5e7c786b73f2106429a933cee51d7b09f861d4e", size = 12133882, upload-time = "2025-06-26T20:33:54.231Z" }, + { url = "https://files.pythonhosted.org/packages/dd/00/40da9c66d4a4d51291e619be6757fa65c91b92456ff4f01101593f3a1170/ruff-0.12.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cc32e863adcf9e71690248607ccdf25252eeeab5193768e6873b901fd441fed", size = 11608941, upload-time = "2025-06-26T20:33:56.202Z" }, + { url = "https://files.pythonhosted.org/packages/91/e7/f898391cc026a77fbe68dfea5940f8213622474cb848eb30215538a2dadf/ruff-0.12.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fd49a4619f90d5afc65cf42e07b6ae98bb454fd5029d03b306bd9e2273d44cc", size = 11602887, upload-time = "2025-06-26T20:33:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/f6/02/0891872fc6aab8678084f4cf8826f85c5d2d24aa9114092139a38123f94b/ruff-0.12.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ed5af6aaaea20710e77698e2055b9ff9b3494891e1b24d26c07055459bb717e9", size = 10521742, upload-time = "2025-06-26T20:34:00.465Z" }, + { url = "https://files.pythonhosted.org/packages/2a/98/d6534322c74a7d47b0f33b036b2498ccac99d8d8c40edadb552c038cecf1/ruff-0.12.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:801d626de15e6bf988fbe7ce59b303a914ff9c616d5866f8c79eb5012720ae13", size = 10149909, upload-time = "2025-06-26T20:34:02.603Z" }, + { url = "https://files.pythonhosted.org/packages/34/5c/9b7ba8c19a31e2b6bd5e31aa1e65b533208a30512f118805371dbbbdf6a9/ruff-0.12.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2be9d32a147f98a1972c1e4df9a6956d612ca5f5578536814372113d09a27a6c", size = 11136005, upload-time = "2025-06-26T20:34:04.723Z" }, + { url = "https://files.pythonhosted.org/packages/dc/34/9bbefa4d0ff2c000e4e533f591499f6b834346025e11da97f4ded21cb23e/ruff-0.12.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:49b7ce354eed2a322fbaea80168c902de9504e6e174fd501e9447cad0232f9e6", size = 11648579, upload-time = "2025-06-26T20:34:06.766Z" }, + { url = "https://files.pythonhosted.org/packages/6f/1c/20cdb593783f8f411839ce749ec9ae9e4298c2b2079b40295c3e6e2089e1/ruff-0.12.1-py3-none-win32.whl", hash = "sha256:d973fa626d4c8267848755bd0414211a456e99e125dcab147f24daa9e991a245", size = 10519495, upload-time = "2025-06-26T20:34:08.718Z" }, + { url = "https://files.pythonhosted.org/packages/cf/56/7158bd8d3cf16394928f47c637d39a7d532268cd45220bdb6cd622985760/ruff-0.12.1-py3-none-win_amd64.whl", hash = "sha256:9e1123b1c033f77bd2590e4c1fe7e8ea72ef990a85d2484351d408224d603013", size = 11547485, upload-time = "2025-06-26T20:34:11.008Z" }, + { url = "https://files.pythonhosted.org/packages/91/d0/6902c0d017259439d6fd2fd9393cea1cfe30169940118b007d5e0ea7e954/ruff-0.12.1-py3-none-win_arm64.whl", hash = "sha256:78ad09a022c64c13cc6077707f036bab0fac8cd7088772dcd1e5be21c5002efc", size = 10691209, upload-time = "2025-06-26T20:34:12.928Z" }, ] [[package]] @@ -2876,9 +2859,9 @@ name = "sse-starlette" version = "2.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "starlette", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "uvicorn", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, + { name = "anyio", marker = "python_full_version >= '3.11'" }, + { name = "starlette", marker = "python_full_version >= '3.11'" }, + { name = "uvicorn", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678, upload-time = "2024-08-01T08:52:50.248Z" } wheels = [ @@ -2901,23 +2884,24 @@ wheels = [ [[package]] name = "starlette" -version = "0.47.0" +version = "0.47.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, + { name = "anyio", marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/d0/0332bd8a25779a0e2082b0e179805ad39afad642938b371ae0882e7f880d/starlette-0.47.0.tar.gz", hash = "sha256:1f64887e94a447fed5f23309fb6890ef23349b7e478faa7b24a851cd4eb844af", size = 2582856, upload-time = "2025-05-29T15:45:27.628Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/69/662169fdb92fb96ec3eaee218cf540a629d629c86d7993d9651226a6789b/starlette-0.47.1.tar.gz", hash = "sha256:aef012dd2b6be325ffa16698f9dc533614fb1cebd593a906b90dc1025529a79b", size = 2583072, upload-time = "2025-06-21T04:03:17.337Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/81/c60b35fe9674f63b38a8feafc414fca0da378a9dbd5fa1e0b8d23fcc7a9b/starlette-0.47.0-py3-none-any.whl", hash = "sha256:9d052d4933683af40ffd47c7465433570b4949dc937e20ad1d73b34e72f10c37", size = 72796, upload-time = "2025-05-29T15:45:26.305Z" }, + { url = "https://files.pythonhosted.org/packages/82/95/38ef0cd7fa11eaba6a99b3c4f5ac948d8bc6ff199aabd327a29cc000840c/starlette-0.47.1-py3-none-any.whl", hash = "sha256:5e11c9f5c7c3f24959edbf2dffdc01bba860228acf657129467d8a7468591527", size = 72747, upload-time = "2025-06-21T04:03:15.705Z" }, ] [[package]] name = "structlog" -version = "25.3.0" +version = "25.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ff/6a/b0b6d440e429d2267076c4819300d9929563b1da959cf1f68afbcd69fe45/structlog-25.3.0.tar.gz", hash = "sha256:8dab497e6f6ca962abad0c283c46744185e0c9ba900db52a423cb6db99f7abeb", size = 1367514, upload-time = "2025-04-25T16:00:39.167Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/b9/6e672db4fec07349e7a8a8172c1a6ae235c58679ca29c3f86a61b5e59ff3/structlog-25.4.0.tar.gz", hash = "sha256:186cd1b0a8ae762e29417095664adf1d6a31702160a46dacb7796ea82f7409e4", size = 1369138, upload-time = "2025-06-02T08:21:12.971Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/52/7a2c7a317b254af857464da3d60a0d3730c44f912f8c510c76a738a207fd/structlog-25.3.0-py3-none-any.whl", hash = "sha256:a341f5524004c158498c3127eecded091eb67d3a611e7a3093deca30db06e172", size = 68240, upload-time = "2025-04-25T16:00:37.295Z" }, + { url = "https://files.pythonhosted.org/packages/a0/4a/97ee6973e3a73c74c8120d59829c3861ea52210667ec3e7a16045c62b64d/structlog-25.4.0-py3-none-any.whl", hash = "sha256:fe809ff5c27e557d14e613f45ca441aabda051d119ee5a0102aaba6ce40eed2c", size = 68720, upload-time = "2025-06-02T08:21:11.43Z" }, ] [[package]] @@ -3054,23 +3038,23 @@ wheels = [ [[package]] name = "types-requests" -version = "2.32.0.20250515" +version = "2.32.4.20250611" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/c1/cdc4f9b8cfd9130fbe6276db574f114541f4231fcc6fb29648289e6e3390/types_requests-2.32.0.20250515.tar.gz", hash = "sha256:09c8b63c11318cb2460813871aaa48b671002e59fda67ca909e9883777787581", size = 23012, upload-time = "2025-05-15T03:04:31.817Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/7f/73b3a04a53b0fd2a911d4ec517940ecd6600630b559e4505cc7b68beb5a0/types_requests-2.32.4.20250611.tar.gz", hash = "sha256:741c8777ed6425830bf51e54d6abe245f79b4dcb9019f1622b773463946bf826", size = 23118, upload-time = "2025-06-11T03:11:41.272Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/0f/68a997c73a129287785f418c1ebb6004f81e46b53b3caba88c0e03fcd04a/types_requests-2.32.0.20250515-py3-none-any.whl", hash = "sha256:f8eba93b3a892beee32643ff836993f15a785816acca21ea0ffa006f05ef0fb2", size = 20635, upload-time = "2025-05-15T03:04:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ea/0be9258c5a4fa1ba2300111aa5a0767ee6d18eb3fd20e91616c12082284d/types_requests-2.32.4.20250611-py3-none-any.whl", hash = "sha256:ad2fe5d3b0cb3c2c902c8815a70e7fb2302c4b8c1f77bdcd738192cdb3878072", size = 20643, upload-time = "2025-06-11T03:11:40.186Z" }, ] [[package]] name = "typing-extensions" -version = "4.13.2" +version = "4.14.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, + { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" }, ] [[package]] @@ -3093,24 +3077,24 @@ wheels = [ [[package]] name = "urllib3" -version = "2.4.0" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] [[package]] name = "uvicorn" -version = "0.34.2" +version = "0.35.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, - { name = "h11", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, + { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "h11", marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/ae/9bbb19b9e1c450cf9ecaef06463e40234d98d95bf572fab11b4f19ae5ded/uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328", size = 76815, upload-time = "2025-04-19T06:02:50.101Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483, upload-time = "2025-04-19T06:02:48.42Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, ] [[package]] @@ -3190,83 +3174,118 @@ wheels = [ [[package]] name = "watchfiles" -version = "1.0.5" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio", marker = "python_full_version >= '3.11' and python_full_version < '4.0'" }, + { name = "anyio", marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/03/e2/8ed598c42057de7aa5d97c472254af4906ff0a59a66699d426fc9ef795d7/watchfiles-1.0.5.tar.gz", hash = "sha256:b7529b5dcc114679d43827d8c35a07c493ad6f083633d573d81c660abc5979e9", size = 94537, upload-time = "2025-04-08T10:36:26.722Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/9a/d451fcc97d029f5812e898fd30a53fd8c15c7bbd058fd75cfc6beb9bd761/watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575", size = 94406, upload-time = "2025-06-15T19:06:59.42Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/4d/d02e6ea147bb7fff5fd109c694a95109612f419abed46548a930e7f7afa3/watchfiles-1.0.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5c40fe7dd9e5f81e0847b1ea64e1f5dd79dd61afbedb57759df06767ac719b40", size = 405632, upload-time = "2025-04-08T10:34:41.832Z" }, - { url = "https://files.pythonhosted.org/packages/60/31/9ee50e29129d53a9a92ccf1d3992751dc56fc3c8f6ee721be1c7b9c81763/watchfiles-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c0db396e6003d99bb2d7232c957b5f0b5634bbd1b24e381a5afcc880f7373fb", size = 395734, upload-time = "2025-04-08T10:34:44.236Z" }, - { url = "https://files.pythonhosted.org/packages/ad/8c/759176c97195306f028024f878e7f1c776bda66ccc5c68fa51e699cf8f1d/watchfiles-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b551d4fb482fc57d852b4541f911ba28957d051c8776e79c3b4a51eb5e2a1b11", size = 455008, upload-time = "2025-04-08T10:34:45.617Z" }, - { url = "https://files.pythonhosted.org/packages/55/1a/5e977250c795ee79a0229e3b7f5e3a1b664e4e450756a22da84d2f4979fe/watchfiles-1.0.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:830aa432ba5c491d52a15b51526c29e4a4b92bf4f92253787f9726fe01519487", size = 459029, upload-time = "2025-04-08T10:34:46.814Z" }, - { url = "https://files.pythonhosted.org/packages/e6/17/884cf039333605c1d6e296cf5be35fad0836953c3dfd2adb71b72f9dbcd0/watchfiles-1.0.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a16512051a822a416b0d477d5f8c0e67b67c1a20d9acecb0aafa3aa4d6e7d256", size = 488916, upload-time = "2025-04-08T10:34:48.571Z" }, - { url = "https://files.pythonhosted.org/packages/ef/e0/bcb6e64b45837056c0a40f3a2db3ef51c2ced19fda38484fa7508e00632c/watchfiles-1.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe0cbc787770e52a96c6fda6726ace75be7f840cb327e1b08d7d54eadc3bc85", size = 523763, upload-time = "2025-04-08T10:34:50.268Z" }, - { url = "https://files.pythonhosted.org/packages/24/e9/f67e9199f3bb35c1837447ecf07e9830ec00ff5d35a61e08c2cd67217949/watchfiles-1.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d363152c5e16b29d66cbde8fa614f9e313e6f94a8204eaab268db52231fe5358", size = 502891, upload-time = "2025-04-08T10:34:51.419Z" }, - { url = "https://files.pythonhosted.org/packages/23/ed/a6cf815f215632f5c8065e9c41fe872025ffea35aa1f80499f86eae922db/watchfiles-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ee32c9a9bee4d0b7bd7cbeb53cb185cf0b622ac761efaa2eba84006c3b3a614", size = 454921, upload-time = "2025-04-08T10:34:52.67Z" }, - { url = "https://files.pythonhosted.org/packages/92/4c/e14978599b80cde8486ab5a77a821e8a982ae8e2fcb22af7b0886a033ec8/watchfiles-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29c7fd632ccaf5517c16a5188e36f6612d6472ccf55382db6c7fe3fcccb7f59f", size = 631422, upload-time = "2025-04-08T10:34:53.985Z" }, - { url = "https://files.pythonhosted.org/packages/b2/1a/9263e34c3458f7614b657f974f4ee61fd72f58adce8b436e16450e054efd/watchfiles-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e637810586e6fe380c8bc1b3910accd7f1d3a9a7262c8a78d4c8fb3ba6a2b3d", size = 625675, upload-time = "2025-04-08T10:34:55.173Z" }, - { url = "https://files.pythonhosted.org/packages/96/1f/1803a18bd6ab04a0766386a19bcfe64641381a04939efdaa95f0e3b0eb58/watchfiles-1.0.5-cp310-cp310-win32.whl", hash = "sha256:cd47d063fbeabd4c6cae1d4bcaa38f0902f8dc5ed168072874ea11d0c7afc1ff", size = 277921, upload-time = "2025-04-08T10:34:56.318Z" }, - { url = "https://files.pythonhosted.org/packages/c2/3b/29a89de074a7d6e8b4dc67c26e03d73313e4ecf0d6e97e942a65fa7c195e/watchfiles-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:86c0df05b47a79d80351cd179893f2f9c1b1cae49d96e8b3290c7f4bd0ca0a92", size = 291526, upload-time = "2025-04-08T10:34:57.95Z" }, - { url = "https://files.pythonhosted.org/packages/39/f4/41b591f59021786ef517e1cdc3b510383551846703e03f204827854a96f8/watchfiles-1.0.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:237f9be419e977a0f8f6b2e7b0475ababe78ff1ab06822df95d914a945eac827", size = 405336, upload-time = "2025-04-08T10:34:59.359Z" }, - { url = "https://files.pythonhosted.org/packages/ae/06/93789c135be4d6d0e4f63e96eea56dc54050b243eacc28439a26482b5235/watchfiles-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0da39ff917af8b27a4bdc5a97ac577552a38aac0d260a859c1517ea3dc1a7c4", size = 395977, upload-time = "2025-04-08T10:35:00.522Z" }, - { url = "https://files.pythonhosted.org/packages/d2/db/1cd89bd83728ca37054512d4d35ab69b5f12b8aa2ac9be3b0276b3bf06cc/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cfcb3952350e95603f232a7a15f6c5f86c5375e46f0bd4ae70d43e3e063c13d", size = 455232, upload-time = "2025-04-08T10:35:01.698Z" }, - { url = "https://files.pythonhosted.org/packages/40/90/d8a4d44ffe960517e487c9c04f77b06b8abf05eb680bed71c82b5f2cad62/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68b2dddba7a4e6151384e252a5632efcaa9bc5d1c4b567f3cb621306b2ca9f63", size = 459151, upload-time = "2025-04-08T10:35:03.358Z" }, - { url = "https://files.pythonhosted.org/packages/6c/da/267a1546f26465dead1719caaba3ce660657f83c9d9c052ba98fb8856e13/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95cf944fcfc394c5f9de794ce581914900f82ff1f855326f25ebcf24d5397418", size = 489054, upload-time = "2025-04-08T10:35:04.561Z" }, - { url = "https://files.pythonhosted.org/packages/b1/31/33850dfd5c6efb6f27d2465cc4c6b27c5a6f5ed53c6fa63b7263cf5f60f6/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf6cd9f83d7c023b1aba15d13f705ca7b7d38675c121f3cc4a6e25bd0857ee9", size = 523955, upload-time = "2025-04-08T10:35:05.786Z" }, - { url = "https://files.pythonhosted.org/packages/09/84/b7d7b67856efb183a421f1416b44ca975cb2ea6c4544827955dfb01f7dc2/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:852de68acd6212cd6d33edf21e6f9e56e5d98c6add46f48244bd479d97c967c6", size = 502234, upload-time = "2025-04-08T10:35:07.187Z" }, - { url = "https://files.pythonhosted.org/packages/71/87/6dc5ec6882a2254cfdd8b0718b684504e737273903b65d7338efaba08b52/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5730f3aa35e646103b53389d5bc77edfbf578ab6dab2e005142b5b80a35ef25", size = 454750, upload-time = "2025-04-08T10:35:08.859Z" }, - { url = "https://files.pythonhosted.org/packages/3d/6c/3786c50213451a0ad15170d091570d4a6554976cf0df19878002fc96075a/watchfiles-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:18b3bd29954bc4abeeb4e9d9cf0b30227f0f206c86657674f544cb032296acd5", size = 631591, upload-time = "2025-04-08T10:35:10.64Z" }, - { url = "https://files.pythonhosted.org/packages/1b/b3/1427425ade4e359a0deacce01a47a26024b2ccdb53098f9d64d497f6684c/watchfiles-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ba5552a1b07c8edbf197055bc9d518b8f0d98a1c6a73a293bc0726dce068ed01", size = 625370, upload-time = "2025-04-08T10:35:12.412Z" }, - { url = "https://files.pythonhosted.org/packages/15/ba/f60e053b0b5b8145d682672024aa91370a29c5c921a88977eb565de34086/watchfiles-1.0.5-cp311-cp311-win32.whl", hash = "sha256:2f1fefb2e90e89959447bc0420fddd1e76f625784340d64a2f7d5983ef9ad246", size = 277791, upload-time = "2025-04-08T10:35:13.719Z" }, - { url = "https://files.pythonhosted.org/packages/50/ed/7603c4e164225c12c0d4e8700b64bb00e01a6c4eeea372292a3856be33a4/watchfiles-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b6e76ceb1dd18c8e29c73f47d41866972e891fc4cc7ba014f487def72c1cf096", size = 291622, upload-time = "2025-04-08T10:35:15.071Z" }, - { url = "https://files.pythonhosted.org/packages/a2/c2/99bb7c96b4450e36877fde33690ded286ff555b5a5c1d925855d556968a1/watchfiles-1.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:266710eb6fddc1f5e51843c70e3bebfb0f5e77cf4f27129278c70554104d19ed", size = 283699, upload-time = "2025-04-08T10:35:16.732Z" }, - { url = "https://files.pythonhosted.org/packages/2a/8c/4f0b9bdb75a1bfbd9c78fad7d8854369283f74fe7cf03eb16be77054536d/watchfiles-1.0.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5eb568c2aa6018e26da9e6c86f3ec3fd958cee7f0311b35c2630fa4217d17f2", size = 401511, upload-time = "2025-04-08T10:35:17.956Z" }, - { url = "https://files.pythonhosted.org/packages/dc/4e/7e15825def77f8bd359b6d3f379f0c9dac4eb09dd4ddd58fd7d14127179c/watchfiles-1.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a04059f4923ce4e856b4b4e5e783a70f49d9663d22a4c3b3298165996d1377f", size = 392715, upload-time = "2025-04-08T10:35:19.202Z" }, - { url = "https://files.pythonhosted.org/packages/58/65/b72fb817518728e08de5840d5d38571466c1b4a3f724d190cec909ee6f3f/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e380c89983ce6e6fe2dd1e1921b9952fb4e6da882931abd1824c092ed495dec", size = 454138, upload-time = "2025-04-08T10:35:20.586Z" }, - { url = "https://files.pythonhosted.org/packages/3e/a4/86833fd2ea2e50ae28989f5950b5c3f91022d67092bfec08f8300d8b347b/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe43139b2c0fdc4a14d4f8d5b5d967f7a2777fd3d38ecf5b1ec669b0d7e43c21", size = 458592, upload-time = "2025-04-08T10:35:21.87Z" }, - { url = "https://files.pythonhosted.org/packages/38/7e/42cb8df8be9a37e50dd3a818816501cf7a20d635d76d6bd65aae3dbbff68/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee0822ce1b8a14fe5a066f93edd20aada932acfe348bede8aa2149f1a4489512", size = 487532, upload-time = "2025-04-08T10:35:23.143Z" }, - { url = "https://files.pythonhosted.org/packages/fc/fd/13d26721c85d7f3df6169d8b495fcac8ab0dc8f0945ebea8845de4681dab/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0dbcb1c2d8f2ab6e0a81c6699b236932bd264d4cef1ac475858d16c403de74d", size = 522865, upload-time = "2025-04-08T10:35:24.702Z" }, - { url = "https://files.pythonhosted.org/packages/a1/0d/7f9ae243c04e96c5455d111e21b09087d0eeaf9a1369e13a01c7d3d82478/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2014a2b18ad3ca53b1f6c23f8cd94a18ce930c1837bd891262c182640eb40a6", size = 499887, upload-time = "2025-04-08T10:35:25.969Z" }, - { url = "https://files.pythonhosted.org/packages/8e/0f/a257766998e26aca4b3acf2ae97dff04b57071e991a510857d3799247c67/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f6ae86d5cb647bf58f9f655fcf577f713915a5d69057a0371bc257e2553234", size = 454498, upload-time = "2025-04-08T10:35:27.353Z" }, - { url = "https://files.pythonhosted.org/packages/81/79/8bf142575a03e0af9c3d5f8bcae911ee6683ae93a625d349d4ecf4c8f7df/watchfiles-1.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1a7bac2bde1d661fb31f4d4e8e539e178774b76db3c2c17c4bb3e960a5de07a2", size = 630663, upload-time = "2025-04-08T10:35:28.685Z" }, - { url = "https://files.pythonhosted.org/packages/f1/80/abe2e79f610e45c63a70d271caea90c49bbf93eb00fa947fa9b803a1d51f/watchfiles-1.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ab626da2fc1ac277bbf752446470b367f84b50295264d2d313e28dc4405d663", size = 625410, upload-time = "2025-04-08T10:35:30.42Z" }, - { url = "https://files.pythonhosted.org/packages/91/6f/bc7fbecb84a41a9069c2c6eb6319f7f7df113adf113e358c57fc1aff7ff5/watchfiles-1.0.5-cp312-cp312-win32.whl", hash = "sha256:9f4571a783914feda92018ef3901dab8caf5b029325b5fe4558c074582815249", size = 277965, upload-time = "2025-04-08T10:35:32.023Z" }, - { url = "https://files.pythonhosted.org/packages/99/a5/bf1c297ea6649ec59e935ab311f63d8af5faa8f0b86993e3282b984263e3/watchfiles-1.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:360a398c3a19672cf93527f7e8d8b60d8275119c5d900f2e184d32483117a705", size = 291693, upload-time = "2025-04-08T10:35:33.225Z" }, - { url = "https://files.pythonhosted.org/packages/7f/7b/fd01087cc21db5c47e5beae507b87965db341cce8a86f9eb12bf5219d4e0/watchfiles-1.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:1a2902ede862969077b97523987c38db28abbe09fb19866e711485d9fbf0d417", size = 283287, upload-time = "2025-04-08T10:35:34.568Z" }, - { url = "https://files.pythonhosted.org/packages/c7/62/435766874b704f39b2fecd8395a29042db2b5ec4005bd34523415e9bd2e0/watchfiles-1.0.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0b289572c33a0deae62daa57e44a25b99b783e5f7aed81b314232b3d3c81a11d", size = 401531, upload-time = "2025-04-08T10:35:35.792Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a6/e52a02c05411b9cb02823e6797ef9bbba0bfaf1bb627da1634d44d8af833/watchfiles-1.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a056c2f692d65bf1e99c41045e3bdcaea3cb9e6b5a53dcaf60a5f3bd95fc9763", size = 392417, upload-time = "2025-04-08T10:35:37.048Z" }, - { url = "https://files.pythonhosted.org/packages/3f/53/c4af6819770455932144e0109d4854437769672d7ad897e76e8e1673435d/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9dca99744991fc9850d18015c4f0438865414e50069670f5f7eee08340d8b40", size = 453423, upload-time = "2025-04-08T10:35:38.357Z" }, - { url = "https://files.pythonhosted.org/packages/cb/d1/8e88df58bbbf819b8bc5cfbacd3c79e01b40261cad0fc84d1e1ebd778a07/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:894342d61d355446d02cd3988a7326af344143eb33a2fd5d38482a92072d9563", size = 458185, upload-time = "2025-04-08T10:35:39.708Z" }, - { url = "https://files.pythonhosted.org/packages/ff/70/fffaa11962dd5429e47e478a18736d4e42bec42404f5ee3b92ef1b87ad60/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab44e1580924d1ffd7b3938e02716d5ad190441965138b4aa1d1f31ea0877f04", size = 486696, upload-time = "2025-04-08T10:35:41.469Z" }, - { url = "https://files.pythonhosted.org/packages/39/db/723c0328e8b3692d53eb273797d9a08be6ffb1d16f1c0ba2bdbdc2a3852c/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6f9367b132078b2ceb8d066ff6c93a970a18c3029cea37bfd7b2d3dd2e5db8f", size = 522327, upload-time = "2025-04-08T10:35:43.289Z" }, - { url = "https://files.pythonhosted.org/packages/cd/05/9fccc43c50c39a76b68343484b9da7b12d42d0859c37c61aec018c967a32/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2e55a9b162e06e3f862fb61e399fe9f05d908d019d87bf5b496a04ef18a970a", size = 499741, upload-time = "2025-04-08T10:35:44.574Z" }, - { url = "https://files.pythonhosted.org/packages/23/14/499e90c37fa518976782b10a18b18db9f55ea73ca14641615056f8194bb3/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0125f91f70e0732a9f8ee01e49515c35d38ba48db507a50c5bdcad9503af5827", size = 453995, upload-time = "2025-04-08T10:35:46.336Z" }, - { url = "https://files.pythonhosted.org/packages/61/d9/f75d6840059320df5adecd2c687fbc18960a7f97b55c300d20f207d48aef/watchfiles-1.0.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:13bb21f8ba3248386337c9fa51c528868e6c34a707f729ab041c846d52a0c69a", size = 629693, upload-time = "2025-04-08T10:35:48.161Z" }, - { url = "https://files.pythonhosted.org/packages/fc/17/180ca383f5061b61406477218c55d66ec118e6c0c51f02d8142895fcf0a9/watchfiles-1.0.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:839ebd0df4a18c5b3c1b890145b5a3f5f64063c2a0d02b13c76d78fe5de34936", size = 624677, upload-time = "2025-04-08T10:35:49.65Z" }, - { url = "https://files.pythonhosted.org/packages/bf/15/714d6ef307f803f236d69ee9d421763707899d6298d9f3183e55e366d9af/watchfiles-1.0.5-cp313-cp313-win32.whl", hash = "sha256:4a8ec1e4e16e2d5bafc9ba82f7aaecfeec990ca7cd27e84fb6f191804ed2fcfc", size = 277804, upload-time = "2025-04-08T10:35:51.093Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b4/c57b99518fadf431f3ef47a610839e46e5f8abf9814f969859d1c65c02c7/watchfiles-1.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:f436601594f15bf406518af922a89dcaab416568edb6f65c4e5bbbad1ea45c11", size = 291087, upload-time = "2025-04-08T10:35:52.458Z" }, - { url = "https://files.pythonhosted.org/packages/c5/95/94f3dd15557f5553261e407551c5e4d340e50161c55aa30812c79da6cb04/watchfiles-1.0.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:2cfb371be97d4db374cba381b9f911dd35bb5f4c58faa7b8b7106c8853e5d225", size = 405686, upload-time = "2025-04-08T10:35:53.86Z" }, - { url = "https://files.pythonhosted.org/packages/f4/aa/b99e968153f8b70159ecca7b3daf46a6f46d97190bdaa3a449ad31b921d7/watchfiles-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a3904d88955fda461ea2531fcf6ef73584ca921415d5cfa44457a225f4a42bc1", size = 396047, upload-time = "2025-04-08T10:35:55.232Z" }, - { url = "https://files.pythonhosted.org/packages/23/cb/90d3d760ad4bc7290e313fb9236c7d60598627a25a5a72764e48d9652064/watchfiles-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b7a21715fb12274a71d335cff6c71fe7f676b293d322722fe708a9ec81d91f5", size = 456081, upload-time = "2025-04-08T10:35:57.102Z" }, - { url = "https://files.pythonhosted.org/packages/3e/65/79c6cebe5bcb695cdac145946ad5a09b9f66762549e82fb2d064ea960c95/watchfiles-1.0.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dfd6ae1c385ab481766b3c61c44aca2b3cd775f6f7c0fa93d979ddec853d29d5", size = 459838, upload-time = "2025-04-08T10:35:58.867Z" }, - { url = "https://files.pythonhosted.org/packages/3f/84/699f52632cdaa777f6df7f6f1cc02a23a75b41071b7e6765b9a412495f61/watchfiles-1.0.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b659576b950865fdad31fa491d31d37cf78b27113a7671d39f919828587b429b", size = 489753, upload-time = "2025-04-08T10:36:00.237Z" }, - { url = "https://files.pythonhosted.org/packages/25/68/3241f82ad414fd969de6bf3a93805682e5eb589aeab510322f2aa14462f8/watchfiles-1.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1909e0a9cd95251b15bff4261de5dd7550885bd172e3536824bf1cf6b121e200", size = 525015, upload-time = "2025-04-08T10:36:02.159Z" }, - { url = "https://files.pythonhosted.org/packages/85/c4/30d879e252f52b01660f545c193e6b81c48aac2e0eeec71263af3add905b/watchfiles-1.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:832ccc221927c860e7286c55c9b6ebcc0265d5e072f49c7f6456c7798d2b39aa", size = 503816, upload-time = "2025-04-08T10:36:03.869Z" }, - { url = "https://files.pythonhosted.org/packages/6b/7d/fa34750f6f4b1a70d96fa6b685fe2948d01e3936328ea528f182943eb373/watchfiles-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85fbb6102b3296926d0c62cfc9347f6237fb9400aecd0ba6bbda94cae15f2b3b", size = 456137, upload-time = "2025-04-08T10:36:05.226Z" }, - { url = "https://files.pythonhosted.org/packages/8f/0c/a1569709aaeccb1dd74b0dd304d0de29e3ea1fdf11e08c78f489628f9ebb/watchfiles-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:15ac96dd567ad6c71c71f7b2c658cb22b7734901546cd50a475128ab557593ca", size = 632673, upload-time = "2025-04-08T10:36:06.752Z" }, - { url = "https://files.pythonhosted.org/packages/90/b6/645eaaca11f3ac625cf3b6e008e543acf0bf2581f68b5e205a13b05618b6/watchfiles-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b6227351e11c57ae997d222e13f5b6f1f0700d84b8c52304e8675d33a808382", size = 626659, upload-time = "2025-04-08T10:36:08.18Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c4/e741d9b92b0a2c74b976ff78bbc9a1276b4d904c590878e8fe0ec9fecca5/watchfiles-1.0.5-cp39-cp39-win32.whl", hash = "sha256:974866e0db748ebf1eccab17862bc0f0303807ed9cda465d1324625b81293a18", size = 278471, upload-time = "2025-04-08T10:36:10.546Z" }, - { url = "https://files.pythonhosted.org/packages/50/1b/36b0cb6add99105f78931994b30bc1dd24118c0e659ab6a3ffe0dd8734d4/watchfiles-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:9848b21ae152fe79c10dd0197304ada8f7b586d3ebc3f27f43c506e5a52a863c", size = 292027, upload-time = "2025-04-08T10:36:11.901Z" }, - { url = "https://files.pythonhosted.org/packages/1a/03/81f9fcc3963b3fc415cd4b0b2b39ee8cc136c42fb10a36acf38745e9d283/watchfiles-1.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f59b870db1f1ae5a9ac28245707d955c8721dd6565e7f411024fa374b5362d1d", size = 405947, upload-time = "2025-04-08T10:36:13.721Z" }, - { url = "https://files.pythonhosted.org/packages/54/97/8c4213a852feb64807ec1d380f42d4fc8bfaef896bdbd94318f8fd7f3e4e/watchfiles-1.0.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9475b0093767e1475095f2aeb1d219fb9664081d403d1dff81342df8cd707034", size = 397276, upload-time = "2025-04-08T10:36:15.131Z" }, - { url = "https://files.pythonhosted.org/packages/78/12/d4464d19860cb9672efa45eec1b08f8472c478ed67dcd30647c51ada7aef/watchfiles-1.0.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc533aa50664ebd6c628b2f30591956519462f5d27f951ed03d6c82b2dfd9965", size = 455550, upload-time = "2025-04-08T10:36:16.635Z" }, - { url = "https://files.pythonhosted.org/packages/90/fb/b07bcdf1034d8edeaef4c22f3e9e3157d37c5071b5f9492ffdfa4ad4bed7/watchfiles-1.0.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed1cd825158dcaae36acce7b2db33dcbfd12b30c34317a88b8ed80f0541cc57", size = 455542, upload-time = "2025-04-08T10:36:18.655Z" }, - { url = "https://files.pythonhosted.org/packages/5b/84/7b69282c0df2bf2dff4e50be2c54669cddf219a5a5fb077891c00c00e5c8/watchfiles-1.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:554389562c29c2c182e3908b149095051f81d28c2fec79ad6c8997d7d63e0009", size = 405783, upload-time = "2025-04-08T10:36:20.553Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ae/03fca0545d99b7ea21df49bead7b51e7dca9ce3b45bb6d34530aa18c16a2/watchfiles-1.0.5-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a74add8d7727e6404d5dc4dcd7fac65d4d82f95928bbee0cf5414c900e86773e", size = 397133, upload-time = "2025-04-08T10:36:22.439Z" }, - { url = "https://files.pythonhosted.org/packages/1a/07/c2b6390003e933b2e187a3f7070c00bd87da8a58d6f2393e039b06a88c2e/watchfiles-1.0.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb1489f25b051a89fae574505cc26360c8e95e227a9500182a7fe0afcc500ce0", size = 456198, upload-time = "2025-04-08T10:36:23.884Z" }, - { url = "https://files.pythonhosted.org/packages/46/d3/ecc62cbd7054f0812f3a7ca7c1c9f7ba99ba45efcfc8297a9fcd2c87b31c/watchfiles-1.0.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0901429650652d3f0da90bad42bdafc1f9143ff3605633c455c999a2d786cac", size = 456511, upload-time = "2025-04-08T10:36:25.42Z" }, + { url = "https://files.pythonhosted.org/packages/b9/dd/579d1dc57f0f895426a1211c4ef3b0cb37eb9e642bb04bdcd962b5df206a/watchfiles-1.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:27f30e14aa1c1e91cb653f03a63445739919aef84c8d2517997a83155e7a2fcc", size = 405757, upload-time = "2025-06-15T19:04:51.058Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a0/7a0318cd874393344d48c34d53b3dd419466adf59a29ba5b51c88dd18b86/watchfiles-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3366f56c272232860ab45c77c3ca7b74ee819c8e1f6f35a7125556b198bbc6df", size = 397511, upload-time = "2025-06-15T19:04:52.79Z" }, + { url = "https://files.pythonhosted.org/packages/06/be/503514656d0555ec2195f60d810eca29b938772e9bfb112d5cd5ad6f6a9e/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8412eacef34cae2836d891836a7fff7b754d6bcac61f6c12ba5ca9bc7e427b68", size = 450739, upload-time = "2025-06-15T19:04:54.203Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0d/a05dd9e5f136cdc29751816d0890d084ab99f8c17b86f25697288ca09bc7/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df670918eb7dd719642e05979fc84704af913d563fd17ed636f7c4783003fdcc", size = 458106, upload-time = "2025-06-15T19:04:55.607Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fa/9cd16e4dfdb831072b7ac39e7bea986e52128526251038eb481effe9f48e/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7642b9bc4827b5518ebdb3b82698ada8c14c7661ddec5fe719f3e56ccd13c97", size = 484264, upload-time = "2025-06-15T19:04:57.009Z" }, + { url = "https://files.pythonhosted.org/packages/32/04/1da8a637c7e2b70e750a0308e9c8e662ada0cca46211fa9ef24a23937e0b/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:199207b2d3eeaeb80ef4411875a6243d9ad8bc35b07fc42daa6b801cc39cc41c", size = 597612, upload-time = "2025-06-15T19:04:58.409Z" }, + { url = "https://files.pythonhosted.org/packages/30/01/109f2762e968d3e58c95731a206e5d7d2a7abaed4299dd8a94597250153c/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a479466da6db5c1e8754caee6c262cd373e6e6c363172d74394f4bff3d84d7b5", size = 477242, upload-time = "2025-06-15T19:04:59.786Z" }, + { url = "https://files.pythonhosted.org/packages/b5/b8/46f58cf4969d3b7bc3ca35a98e739fa4085b0657a1540ccc29a1a0bc016f/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935f9edd022ec13e447e5723a7d14456c8af254544cefbc533f6dd276c9aa0d9", size = 453148, upload-time = "2025-06-15T19:05:01.103Z" }, + { url = "https://files.pythonhosted.org/packages/a5/cd/8267594263b1770f1eb76914940d7b2d03ee55eca212302329608208e061/watchfiles-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8076a5769d6bdf5f673a19d51da05fc79e2bbf25e9fe755c47595785c06a8c72", size = 626574, upload-time = "2025-06-15T19:05:02.582Z" }, + { url = "https://files.pythonhosted.org/packages/a1/2f/7f2722e85899bed337cba715723e19185e288ef361360718973f891805be/watchfiles-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86b1e28d4c37e89220e924305cd9f82866bb0ace666943a6e4196c5df4d58dcc", size = 624378, upload-time = "2025-06-15T19:05:03.719Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/64c88ec43d90a568234d021ab4b2a6f42a5230d772b987c3f9c00cc27b8b/watchfiles-1.1.0-cp310-cp310-win32.whl", hash = "sha256:d1caf40c1c657b27858f9774d5c0e232089bca9cb8ee17ce7478c6e9264d2587", size = 279829, upload-time = "2025-06-15T19:05:04.822Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/a9c1ed33de7af80935e4eac09570de679c6e21c07070aa99f74b4431f4d6/watchfiles-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a89c75a5b9bc329131115a409d0acc16e8da8dfd5867ba59f1dd66ae7ea8fa82", size = 292192, upload-time = "2025-06-15T19:05:06.348Z" }, + { url = "https://files.pythonhosted.org/packages/8b/78/7401154b78ab484ccaaeef970dc2af0cb88b5ba8a1b415383da444cdd8d3/watchfiles-1.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c9649dfc57cc1f9835551deb17689e8d44666315f2e82d337b9f07bd76ae3aa2", size = 405751, upload-time = "2025-06-15T19:05:07.679Z" }, + { url = "https://files.pythonhosted.org/packages/76/63/e6c3dbc1f78d001589b75e56a288c47723de28c580ad715eb116639152b5/watchfiles-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:406520216186b99374cdb58bc48e34bb74535adec160c8459894884c983a149c", size = 397313, upload-time = "2025-06-15T19:05:08.764Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a2/8afa359ff52e99af1632f90cbf359da46184207e893a5f179301b0c8d6df/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45350fd1dc75cd68d3d72c47f5b513cb0578da716df5fba02fff31c69d5f2d", size = 450792, upload-time = "2025-06-15T19:05:09.869Z" }, + { url = "https://files.pythonhosted.org/packages/1d/bf/7446b401667f5c64972a57a0233be1104157fc3abf72c4ef2666c1bd09b2/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11ee4444250fcbeb47459a877e5e80ed994ce8e8d20283857fc128be1715dac7", size = 458196, upload-time = "2025-06-15T19:05:11.91Z" }, + { url = "https://files.pythonhosted.org/packages/58/2f/501ddbdfa3fa874ea5597c77eeea3d413579c29af26c1091b08d0c792280/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda8136e6a80bdea23e5e74e09df0362744d24ffb8cd59c4a95a6ce3d142f79c", size = 484788, upload-time = "2025-06-15T19:05:13.373Z" }, + { url = "https://files.pythonhosted.org/packages/61/1e/9c18eb2eb5c953c96bc0e5f626f0e53cfef4bd19bd50d71d1a049c63a575/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b915daeb2d8c1f5cee4b970f2e2c988ce6514aace3c9296e58dd64dc9aa5d575", size = 597879, upload-time = "2025-06-15T19:05:14.725Z" }, + { url = "https://files.pythonhosted.org/packages/8b/6c/1467402e5185d89388b4486745af1e0325007af0017c3384cc786fff0542/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed8fc66786de8d0376f9f913c09e963c66e90ced9aa11997f93bdb30f7c872a8", size = 477447, upload-time = "2025-06-15T19:05:15.775Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a1/ec0a606bde4853d6c4a578f9391eeb3684a9aea736a8eb217e3e00aa89a1/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe4371595edf78c41ef8ac8df20df3943e13defd0efcb732b2e393b5a8a7a71f", size = 453145, upload-time = "2025-06-15T19:05:17.17Z" }, + { url = "https://files.pythonhosted.org/packages/90/b9/ef6f0c247a6a35d689fc970dc7f6734f9257451aefb30def5d100d6246a5/watchfiles-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b7c5f6fe273291f4d414d55b2c80d33c457b8a42677ad14b4b47ff025d0893e4", size = 626539, upload-time = "2025-06-15T19:05:18.557Z" }, + { url = "https://files.pythonhosted.org/packages/34/44/6ffda5537085106ff5aaa762b0d130ac6c75a08015dd1621376f708c94de/watchfiles-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7738027989881e70e3723c75921f1efa45225084228788fc59ea8c6d732eb30d", size = 624472, upload-time = "2025-06-15T19:05:19.588Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e3/71170985c48028fa3f0a50946916a14055e741db11c2e7bc2f3b61f4d0e3/watchfiles-1.1.0-cp311-cp311-win32.whl", hash = "sha256:622d6b2c06be19f6e89b1d951485a232e3b59618def88dbeda575ed8f0d8dbf2", size = 279348, upload-time = "2025-06-15T19:05:20.856Z" }, + { url = "https://files.pythonhosted.org/packages/89/1b/3e39c68b68a7a171070f81fc2561d23ce8d6859659406842a0e4bebf3bba/watchfiles-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:48aa25e5992b61debc908a61ab4d3f216b64f44fdaa71eb082d8b2de846b7d12", size = 292607, upload-time = "2025-06-15T19:05:21.937Z" }, + { url = "https://files.pythonhosted.org/packages/61/9f/2973b7539f2bdb6ea86d2c87f70f615a71a1fc2dba2911795cea25968aea/watchfiles-1.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:00645eb79a3faa70d9cb15c8d4187bb72970b2470e938670240c7998dad9f13a", size = 285056, upload-time = "2025-06-15T19:05:23.12Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b8/858957045a38a4079203a33aaa7d23ea9269ca7761c8a074af3524fbb240/watchfiles-1.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179", size = 402339, upload-time = "2025-06-15T19:05:24.516Z" }, + { url = "https://files.pythonhosted.org/packages/80/28/98b222cca751ba68e88521fabd79a4fab64005fc5976ea49b53fa205d1fa/watchfiles-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5", size = 394409, upload-time = "2025-06-15T19:05:25.469Z" }, + { url = "https://files.pythonhosted.org/packages/86/50/dee79968566c03190677c26f7f47960aff738d32087087bdf63a5473e7df/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297", size = 450939, upload-time = "2025-06-15T19:05:26.494Z" }, + { url = "https://files.pythonhosted.org/packages/40/45/a7b56fb129700f3cfe2594a01aa38d033b92a33dddce86c8dfdfc1247b72/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0", size = 457270, upload-time = "2025-06-15T19:05:27.466Z" }, + { url = "https://files.pythonhosted.org/packages/b5/c8/fa5ef9476b1d02dc6b5e258f515fcaaecf559037edf8b6feffcbc097c4b8/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e", size = 483370, upload-time = "2025-06-15T19:05:28.548Z" }, + { url = "https://files.pythonhosted.org/packages/98/68/42cfcdd6533ec94f0a7aab83f759ec11280f70b11bfba0b0f885e298f9bd/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee", size = 598654, upload-time = "2025-06-15T19:05:29.997Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/b2a1544224118cc28df7e59008a929e711f9c68ce7d554e171b2dc531352/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd", size = 478667, upload-time = "2025-06-15T19:05:31.172Z" }, + { url = "https://files.pythonhosted.org/packages/8c/77/e3362fe308358dc9f8588102481e599c83e1b91c2ae843780a7ded939a35/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f", size = 452213, upload-time = "2025-06-15T19:05:32.299Z" }, + { url = "https://files.pythonhosted.org/packages/6e/17/c8f1a36540c9a1558d4faf08e909399e8133599fa359bf52ec8fcee5be6f/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4", size = 626718, upload-time = "2025-06-15T19:05:33.415Z" }, + { url = "https://files.pythonhosted.org/packages/26/45/fb599be38b4bd38032643783d7496a26a6f9ae05dea1a42e58229a20ac13/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f", size = 623098, upload-time = "2025-06-15T19:05:34.534Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/fdf40e038475498e160cd167333c946e45d8563ae4dd65caf757e9ffe6b4/watchfiles-1.1.0-cp312-cp312-win32.whl", hash = "sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd", size = 279209, upload-time = "2025-06-15T19:05:35.577Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d3/3ae9d5124ec75143bdf088d436cba39812122edc47709cd2caafeac3266f/watchfiles-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47", size = 292786, upload-time = "2025-06-15T19:05:36.559Z" }, + { url = "https://files.pythonhosted.org/packages/26/2f/7dd4fc8b5f2b34b545e19629b4a018bfb1de23b3a496766a2c1165ca890d/watchfiles-1.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6", size = 284343, upload-time = "2025-06-15T19:05:37.5Z" }, + { url = "https://files.pythonhosted.org/packages/d3/42/fae874df96595556a9089ade83be34a2e04f0f11eb53a8dbf8a8a5e562b4/watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30", size = 402004, upload-time = "2025-06-15T19:05:38.499Z" }, + { url = "https://files.pythonhosted.org/packages/fa/55/a77e533e59c3003d9803c09c44c3651224067cbe7fb5d574ddbaa31e11ca/watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a", size = 393671, upload-time = "2025-06-15T19:05:39.52Z" }, + { url = "https://files.pythonhosted.org/packages/05/68/b0afb3f79c8e832e6571022611adbdc36e35a44e14f129ba09709aa4bb7a/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc", size = 449772, upload-time = "2025-06-15T19:05:40.897Z" }, + { url = "https://files.pythonhosted.org/packages/ff/05/46dd1f6879bc40e1e74c6c39a1b9ab9e790bf1f5a2fe6c08b463d9a807f4/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b", size = 456789, upload-time = "2025-06-15T19:05:42.045Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ca/0eeb2c06227ca7f12e50a47a3679df0cd1ba487ea19cf844a905920f8e95/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895", size = 482551, upload-time = "2025-06-15T19:05:43.781Z" }, + { url = "https://files.pythonhosted.org/packages/31/47/2cecbd8694095647406645f822781008cc524320466ea393f55fe70eed3b/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a", size = 597420, upload-time = "2025-06-15T19:05:45.244Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7e/82abc4240e0806846548559d70f0b1a6dfdca75c1b4f9fa62b504ae9b083/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b", size = 477950, upload-time = "2025-06-15T19:05:46.332Z" }, + { url = "https://files.pythonhosted.org/packages/25/0d/4d564798a49bf5482a4fa9416dea6b6c0733a3b5700cb8a5a503c4b15853/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c", size = 451706, upload-time = "2025-06-15T19:05:47.459Z" }, + { url = "https://files.pythonhosted.org/packages/81/b5/5516cf46b033192d544102ea07c65b6f770f10ed1d0a6d388f5d3874f6e4/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b", size = 625814, upload-time = "2025-06-15T19:05:48.654Z" }, + { url = "https://files.pythonhosted.org/packages/0c/dd/7c1331f902f30669ac3e754680b6edb9a0dd06dea5438e61128111fadd2c/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb", size = 622820, upload-time = "2025-06-15T19:05:50.088Z" }, + { url = "https://files.pythonhosted.org/packages/1b/14/36d7a8e27cd128d7b1009e7715a7c02f6c131be9d4ce1e5c3b73d0e342d8/watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9", size = 279194, upload-time = "2025-06-15T19:05:51.186Z" }, + { url = "https://files.pythonhosted.org/packages/25/41/2dd88054b849aa546dbeef5696019c58f8e0774f4d1c42123273304cdb2e/watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7", size = 292349, upload-time = "2025-06-15T19:05:52.201Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cf/421d659de88285eb13941cf11a81f875c176f76a6d99342599be88e08d03/watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5", size = 283836, upload-time = "2025-06-15T19:05:53.265Z" }, + { url = "https://files.pythonhosted.org/packages/45/10/6faf6858d527e3599cc50ec9fcae73590fbddc1420bd4fdccfebffeedbc6/watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1", size = 400343, upload-time = "2025-06-15T19:05:54.252Z" }, + { url = "https://files.pythonhosted.org/packages/03/20/5cb7d3966f5e8c718006d0e97dfe379a82f16fecd3caa7810f634412047a/watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339", size = 392916, upload-time = "2025-06-15T19:05:55.264Z" }, + { url = "https://files.pythonhosted.org/packages/8c/07/d8f1176328fa9e9581b6f120b017e286d2a2d22ae3f554efd9515c8e1b49/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633", size = 449582, upload-time = "2025-06-15T19:05:56.317Z" }, + { url = "https://files.pythonhosted.org/packages/66/e8/80a14a453cf6038e81d072a86c05276692a1826471fef91df7537dba8b46/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011", size = 456752, upload-time = "2025-06-15T19:05:57.359Z" }, + { url = "https://files.pythonhosted.org/packages/5a/25/0853b3fe0e3c2f5af9ea60eb2e781eade939760239a72c2d38fc4cc335f6/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670", size = 481436, upload-time = "2025-06-15T19:05:58.447Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9e/4af0056c258b861fbb29dcb36258de1e2b857be4a9509e6298abcf31e5c9/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf", size = 596016, upload-time = "2025-06-15T19:05:59.59Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fa/95d604b58aa375e781daf350897aaaa089cff59d84147e9ccff2447c8294/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4", size = 476727, upload-time = "2025-06-15T19:06:01.086Z" }, + { url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864, upload-time = "2025-06-15T19:06:02.144Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626, upload-time = "2025-06-15T19:06:03.578Z" }, + { url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" }, + { url = "https://files.pythonhosted.org/packages/2c/00/70f75c47f05dea6fd30df90f047765f6fc2d6eb8b5a3921379b0b04defa2/watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297", size = 402114, upload-time = "2025-06-15T19:06:06.186Z" }, + { url = "https://files.pythonhosted.org/packages/53/03/acd69c48db4a1ed1de26b349d94077cca2238ff98fd64393f3e97484cae6/watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018", size = 393879, upload-time = "2025-06-15T19:06:07.369Z" }, + { url = "https://files.pythonhosted.org/packages/2f/c8/a9a2a6f9c8baa4eceae5887fecd421e1b7ce86802bcfc8b6a942e2add834/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0", size = 450026, upload-time = "2025-06-15T19:06:08.476Z" }, + { url = "https://files.pythonhosted.org/packages/fe/51/d572260d98388e6e2b967425c985e07d47ee6f62e6455cefb46a6e06eda5/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12", size = 457917, upload-time = "2025-06-15T19:06:09.988Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2d/4258e52917bf9f12909b6ec314ff9636276f3542f9d3807d143f27309104/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb", size = 483602, upload-time = "2025-06-15T19:06:11.088Z" }, + { url = "https://files.pythonhosted.org/packages/84/99/bee17a5f341a4345fe7b7972a475809af9e528deba056f8963d61ea49f75/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77", size = 596758, upload-time = "2025-06-15T19:06:12.197Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/e4bec1d59b25b89d2b0716b41b461ed655a9a53c60dc78ad5771fda5b3e6/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92", size = 477601, upload-time = "2025-06-15T19:06:13.391Z" }, + { url = "https://files.pythonhosted.org/packages/1f/fa/a514292956f4a9ce3c567ec0c13cce427c158e9f272062685a8a727d08fc/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e", size = 451936, upload-time = "2025-06-15T19:06:14.656Z" }, + { url = "https://files.pythonhosted.org/packages/32/5d/c3bf927ec3bbeb4566984eba8dd7a8eb69569400f5509904545576741f88/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b", size = 626243, upload-time = "2025-06-15T19:06:16.232Z" }, + { url = "https://files.pythonhosted.org/packages/e6/65/6e12c042f1a68c556802a84d54bb06d35577c81e29fba14019562479159c/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259", size = 623073, upload-time = "2025-06-15T19:06:17.457Z" }, + { url = "https://files.pythonhosted.org/packages/89/ab/7f79d9bf57329e7cbb0a6fd4c7bd7d0cee1e4a8ef0041459f5409da3506c/watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f", size = 400872, upload-time = "2025-06-15T19:06:18.57Z" }, + { url = "https://files.pythonhosted.org/packages/df/d5/3f7bf9912798e9e6c516094db6b8932df53b223660c781ee37607030b6d3/watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e", size = 392877, upload-time = "2025-06-15T19:06:19.55Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c5/54ec7601a2798604e01c75294770dbee8150e81c6e471445d7601610b495/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa", size = 449645, upload-time = "2025-06-15T19:06:20.66Z" }, + { url = "https://files.pythonhosted.org/packages/0a/04/c2f44afc3b2fce21ca0b7802cbd37ed90a29874f96069ed30a36dfe57c2b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8", size = 457424, upload-time = "2025-06-15T19:06:21.712Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b0/eec32cb6c14d248095261a04f290636da3df3119d4040ef91a4a50b29fa5/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f", size = 481584, upload-time = "2025-06-15T19:06:22.777Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/ca4bb71c68a937d7145aa25709e4f5d68eb7698a25ce266e84b55d591bbd/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e", size = 596675, upload-time = "2025-06-15T19:06:24.226Z" }, + { url = "https://files.pythonhosted.org/packages/a1/dd/b0e4b7fb5acf783816bc950180a6cd7c6c1d2cf7e9372c0ea634e722712b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb", size = 477363, upload-time = "2025-06-15T19:06:25.42Z" }, + { url = "https://files.pythonhosted.org/packages/69/c4/088825b75489cb5b6a761a4542645718893d395d8c530b38734f19da44d2/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147", size = 452240, upload-time = "2025-06-15T19:06:26.552Z" }, + { url = "https://files.pythonhosted.org/packages/10/8c/22b074814970eeef43b7c44df98c3e9667c1f7bf5b83e0ff0201b0bd43f9/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8", size = 625607, upload-time = "2025-06-15T19:06:27.606Z" }, + { url = "https://files.pythonhosted.org/packages/32/fa/a4f5c2046385492b2273213ef815bf71a0d4c1943b784fb904e184e30201/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db", size = 623315, upload-time = "2025-06-15T19:06:29.076Z" }, + { url = "https://files.pythonhosted.org/packages/47/8a/a45db804b9f0740f8408626ab2bca89c3136432e57c4673b50180bf85dd9/watchfiles-1.1.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:865c8e95713744cf5ae261f3067861e9da5f1370ba91fc536431e29b418676fa", size = 406400, upload-time = "2025-06-15T19:06:30.233Z" }, + { url = "https://files.pythonhosted.org/packages/64/06/a08684f628fb41addd451845aceedc2407dc3d843b4b060a7c4350ddee0c/watchfiles-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42f92befc848bb7a19658f21f3e7bae80d7d005d13891c62c2cd4d4d0abb3433", size = 397920, upload-time = "2025-06-15T19:06:31.315Z" }, + { url = "https://files.pythonhosted.org/packages/79/e6/e10d5675af653b1b07d4156906858041149ca222edaf8995877f2605ba9e/watchfiles-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa0cc8365ab29487eb4f9979fd41b22549853389e22d5de3f134a6796e1b05a4", size = 451196, upload-time = "2025-06-15T19:06:32.435Z" }, + { url = "https://files.pythonhosted.org/packages/f6/8a/facd6988100cd0f39e89f6c550af80edb28e3a529e1ee662e750663e6b36/watchfiles-1.1.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:90ebb429e933645f3da534c89b29b665e285048973b4d2b6946526888c3eb2c7", size = 458218, upload-time = "2025-06-15T19:06:33.503Z" }, + { url = "https://files.pythonhosted.org/packages/90/26/34cbcbc4d0f2f8f9cc243007e65d741ae039f7a11ef8ec6e9cd25bee08d1/watchfiles-1.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c588c45da9b08ab3da81d08d7987dae6d2a3badd63acdb3e206a42dbfa7cb76f", size = 484851, upload-time = "2025-06-15T19:06:34.541Z" }, + { url = "https://files.pythonhosted.org/packages/d7/1f/f59faa9fc4b0e36dbcdd28a18c430416443b309d295d8b82e18192d120ad/watchfiles-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c55b0f9f68590115c25272b06e63f0824f03d4fc7d6deed43d8ad5660cabdbf", size = 599520, upload-time = "2025-06-15T19:06:35.785Z" }, + { url = "https://files.pythonhosted.org/packages/83/72/3637abecb3bf590529f5154ca000924003e5f4bbb9619744feeaf6f0b70b/watchfiles-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd17a1e489f02ce9117b0de3c0b1fab1c3e2eedc82311b299ee6b6faf6c23a29", size = 477956, upload-time = "2025-06-15T19:06:36.965Z" }, + { url = "https://files.pythonhosted.org/packages/f7/f3/d14ffd9acc0c1bd4790378995e320981423263a5d70bd3929e2e0dc87fff/watchfiles-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da71945c9ace018d8634822f16cbc2a78323ef6c876b1d34bbf5d5222fd6a72e", size = 453196, upload-time = "2025-06-15T19:06:38.024Z" }, + { url = "https://files.pythonhosted.org/packages/7f/38/78ad77bd99e20c0fdc82262be571ef114fc0beef9b43db52adb939768c38/watchfiles-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:51556d5004887045dba3acdd1fdf61dddea2be0a7e18048b5e853dcd37149b86", size = 627479, upload-time = "2025-06-15T19:06:39.442Z" }, + { url = "https://files.pythonhosted.org/packages/e6/cf/549d50a22fcc83f1017c6427b1c76c053233f91b526f4ad7a45971e70c0b/watchfiles-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04e4ed5d1cd3eae68c89bcc1a485a109f39f2fd8de05f705e98af6b5f1861f1f", size = 624414, upload-time = "2025-06-15T19:06:40.859Z" }, + { url = "https://files.pythonhosted.org/packages/72/de/57d6e40dc9140af71c12f3a9fc2d3efc5529d93981cd4d265d484d7c9148/watchfiles-1.1.0-cp39-cp39-win32.whl", hash = "sha256:c600e85f2ffd9f1035222b1a312aff85fd11ea39baff1d705b9b047aad2ce267", size = 280020, upload-time = "2025-06-15T19:06:41.89Z" }, + { url = "https://files.pythonhosted.org/packages/88/bb/7d287fc2a762396b128a0fca2dbae29386e0a242b81d1046daf389641db3/watchfiles-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3aba215958d88182e8d2acba0fdaf687745180974946609119953c0e112397dc", size = 292758, upload-time = "2025-06-15T19:06:43.251Z" }, + { url = "https://files.pythonhosted.org/packages/be/7c/a3d7c55cfa377c2f62c4ae3c6502b997186bc5e38156bafcb9b653de9a6d/watchfiles-1.1.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a6fd40bbb50d24976eb275ccb55cd1951dfb63dbc27cae3066a6ca5f4beabd5", size = 406748, upload-time = "2025-06-15T19:06:44.2Z" }, + { url = "https://files.pythonhosted.org/packages/38/d0/c46f1b2c0ca47f3667b144de6f0515f6d1c670d72f2ca29861cac78abaa1/watchfiles-1.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f811079d2f9795b5d48b55a37aa7773680a5659afe34b54cc1d86590a51507d", size = 398801, upload-time = "2025-06-15T19:06:45.774Z" }, + { url = "https://files.pythonhosted.org/packages/70/9c/9a6a42e97f92eeed77c3485a43ea96723900aefa3ac739a8c73f4bff2cd7/watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2726d7bfd9f76158c84c10a409b77a320426540df8c35be172444394b17f7ea", size = 451528, upload-time = "2025-06-15T19:06:46.791Z" }, + { url = "https://files.pythonhosted.org/packages/51/7b/98c7f4f7ce7ff03023cf971cd84a3ee3b790021ae7584ffffa0eb2554b96/watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df32d59cb9780f66d165a9a7a26f19df2c7d24e3bd58713108b41d0ff4f929c6", size = 454095, upload-time = "2025-06-15T19:06:48.211Z" }, + { url = "https://files.pythonhosted.org/packages/8c/6b/686dcf5d3525ad17b384fd94708e95193529b460a1b7bf40851f1328ec6e/watchfiles-1.1.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0ece16b563b17ab26eaa2d52230c9a7ae46cf01759621f4fbbca280e438267b3", size = 406910, upload-time = "2025-06-15T19:06:49.335Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d3/71c2dcf81dc1edcf8af9f4d8d63b1316fb0a2dd90cbfd427e8d9dd584a90/watchfiles-1.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:51b81e55d40c4b4aa8658427a3ee7ea847c591ae9e8b81ef94a90b668999353c", size = 398816, upload-time = "2025-06-15T19:06:50.433Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fa/12269467b2fc006f8fce4cd6c3acfa77491dd0777d2a747415f28ccc8c60/watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2bcdc54ea267fe72bfc7d83c041e4eb58d7d8dc6f578dfddb52f037ce62f432", size = 451584, upload-time = "2025-06-15T19:06:51.834Z" }, + { url = "https://files.pythonhosted.org/packages/bd/d3/254cea30f918f489db09d6a8435a7de7047f8cb68584477a515f160541d6/watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923fec6e5461c42bd7e3fd5ec37492c6f3468be0499bc0707b4bbbc16ac21792", size = 454009, upload-time = "2025-06-15T19:06:52.896Z" }, + { url = "https://files.pythonhosted.org/packages/48/93/5c96bdb65e7f88f7da40645f34c0a3c317a2931ed82161e93c91e8eddd27/watchfiles-1.1.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7b3443f4ec3ba5aa00b0e9fa90cf31d98321cbff8b925a7c7b84161619870bc9", size = 406640, upload-time = "2025-06-15T19:06:54.868Z" }, + { url = "https://files.pythonhosted.org/packages/e3/25/09204836e93e1b99cce88802ce87264a1d20610c7a8f6de24def27ad95b1/watchfiles-1.1.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7049e52167fc75fc3cc418fc13d39a8e520cbb60ca08b47f6cedb85e181d2f2a", size = 398543, upload-time = "2025-06-15T19:06:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/5e/dc/6f324a6f32c5ab73b54311b5f393a79df34c1584b8d2404cf7e6d780aa5d/watchfiles-1.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54062ef956807ba806559b3c3d52105ae1827a0d4ab47b621b31132b6b7e2866", size = 451787, upload-time = "2025-06-15T19:06:56.998Z" }, + { url = "https://files.pythonhosted.org/packages/45/5d/1d02ef4caa4ec02389e72d5594cdf9c67f1800a7c380baa55063c30c6598/watchfiles-1.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a7bd57a1bb02f9d5c398c0c1675384e7ab1dd39da0ca50b7f09af45fa435277", size = 454272, upload-time = "2025-06-15T19:06:58.055Z" }, ] [[package]] @@ -3409,11 +3428,11 @@ wheels = [ [[package]] name = "zipp" -version = "3.22.0" +version = "3.23.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/b6/7b3d16792fdf94f146bed92be90b4eb4563569eca91513c8609aebf0c167/zipp-3.22.0.tar.gz", hash = "sha256:dd2f28c3ce4bc67507bfd3781d21b7bb2be31103b51a4553ad7d90b84e57ace5", size = 25257, upload-time = "2025-05-26T14:46:32.217Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/da/f64669af4cae46f17b90798a827519ce3737d31dbafad65d391e49643dc4/zipp-3.22.0-py3-none-any.whl", hash = "sha256:fe208f65f2aca48b81f9e6fd8cf7b8b32c26375266b009b413d45306b6148343", size = 9796, upload-time = "2025-05-26T14:46:30.775Z" }, + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, ] [[package]] diff --git a/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py b/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py index 6425c7c3c..a6ac2f9d4 100644 --- a/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py +++ b/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py @@ -34,6 +34,7 @@ from langchain_core.tools import BaseTool from pydantic import BaseModel from typing_extensions import Annotated, TypedDict +from langgraph._internal._runnable import RunnableCallable, RunnableLike from langgraph.errors import ErrorCode, create_error_message from langgraph.graph import END, StateGraph from langgraph.graph.message import add_messages @@ -42,7 +43,6 @@ from langgraph.managed import IsLastStep, RemainingSteps from langgraph.prebuilt.tool_node import ToolNode from langgraph.store.base import BaseStore from langgraph.types import Checkpointer, Send -from langgraph.utils.runnable import RunnableCallable, RunnableLike StructuredResponse = Union[dict, BaseModel] StructuredResponseSchema = Union[dict, type[BaseModel]] diff --git a/libs/prebuilt/langgraph/prebuilt/tool_node.py b/libs/prebuilt/langgraph/prebuilt/tool_node.py index 111f1f228..3de19db62 100644 --- a/libs/prebuilt/langgraph/prebuilt/tool_node.py +++ b/libs/prebuilt/langgraph/prebuilt/tool_node.py @@ -37,10 +37,10 @@ from langchain_core.tools.base import ( from pydantic import BaseModel from typing_extensions import Annotated, get_args, get_origin +from langgraph._internal._runnable import RunnableCallable from langgraph.errors import GraphBubbleUp from langgraph.store.base import BaseStore from langgraph.types import Command, Send -from langgraph.utils.runnable import RunnableCallable INVALID_TOOL_NAME_ERROR_TEMPLATE = ( "Error: {requested_tool} is not a valid tool, try one of [{available_tools}]." diff --git a/libs/prebuilt/langgraph/prebuilt/tool_validator.py b/libs/prebuilt/langgraph/prebuilt/tool_validator.py index 0e58c7d6c..d63e0c535 100644 --- a/libs/prebuilt/langgraph/prebuilt/tool_validator.py +++ b/libs/prebuilt/langgraph/prebuilt/tool_validator.py @@ -34,7 +34,7 @@ from pydantic import BaseModel, ValidationError from pydantic.v1 import BaseModel as BaseModelV1 from pydantic.v1 import ValidationError as ValidationErrorV1 -from langgraph.utils.runnable import RunnableCallable +from langgraph._internal._runnable import RunnableCallable def _default_format_error( diff --git a/libs/prebuilt/tests/memory_assert.py b/libs/prebuilt/tests/memory_assert.py index 10b93fdbd..872ea57ab 100644 --- a/libs/prebuilt/tests/memory_assert.py +++ b/libs/prebuilt/tests/memory_assert.py @@ -15,7 +15,7 @@ from langgraph.checkpoint.base import ( SerializerProtocol, ) from langgraph.checkpoint.memory import InMemorySaver, PersistentDict -from langgraph.pregel.checkpoint import copy_checkpoint +from langgraph.pregel._checkpoint import copy_checkpoint class NoopSerializer(SerializerProtocol): diff --git a/libs/prebuilt/tests/test_react_agent.py b/libs/prebuilt/tests/test_react_agent.py index 8c110126b..d704616db 100644 --- a/libs/prebuilt/tests/test_react_agent.py +++ b/libs/prebuilt/tests/test_react_agent.py @@ -29,6 +29,7 @@ from pydantic.v1 import BaseModel as BaseModelV1 from typing_extensions import TypedDict from langgraph.checkpoint.base import BaseCheckpointSaver +from langgraph.config import get_stream_writer from langgraph.graph import START, MessagesState, StateGraph, add_messages from langgraph.graph.message import REMOVE_ALL_MESSAGES from langgraph.prebuilt import ( @@ -53,7 +54,6 @@ from langgraph.prebuilt.tool_node import ( from langgraph.store.base import BaseStore from langgraph.store.memory import InMemoryStore from langgraph.types import Command, Interrupt, interrupt -from langgraph.utils.config import get_stream_writer from tests.any_str import AnyStr from tests.messages import _AnyIdHumanMessage, _AnyIdToolMessage from tests.model import FakeToolCallingModel