diff --git a/libs/scheduler-kafka/LICENSE b/libs/scheduler-kafka/LICENSE deleted file mode 100644 index fc0602fee..000000000 --- a/libs/scheduler-kafka/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 LangChain, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/libs/scheduler-kafka/Makefile b/libs/scheduler-kafka/Makefile deleted file mode 100644 index 8d62c9df2..000000000 --- a/libs/scheduler-kafka/Makefile +++ /dev/null @@ -1,48 +0,0 @@ -.PHONY: test test_watch lint format - -###################### -# TESTING AND COVERAGE -###################### - -start-services: - docker compose -f tests/compose.yml up -V --force-recreate --wait --remove-orphans - -stop-services: - docker compose -f tests/compose.yml down - -TEST_PATH ?= . - -test: - make start-services && poetry run pytest $(TEST_PATH); \ - EXIT_CODE=$$?; \ - make stop-services; \ - exit $$EXIT_CODE - -test_watch: - make start-services && poetry run ptw . -- -x $(TEST_PATH); \ - EXIT_CODE=$$?; \ - make stop-services; \ - exit $$EXIT_CODE - -###################### -# LINTING AND FORMATTING -###################### - -# Define a variable for Python and notebook files. -PYTHON_FILES=. -MYPY_CACHE=.mypy_cache -lint format: PYTHON_FILES=. -lint_diff format_diff: PYTHON_FILES=$(shell git diff --name-only --relative --diff-filter=d main . | grep -E '\.py$$|\.ipynb$$') -lint_package: PYTHON_FILES=langgraph -lint_tests: PYTHON_FILES=tests -lint_tests: MYPY_CACHE=.mypy_cache_test - -lint lint_diff lint_package lint_tests: - poetry run ruff check . - [ "$(PYTHON_FILES)" = "" ] || poetry run ruff format $(PYTHON_FILES) --diff - [ "$(PYTHON_FILES)" = "" ] || poetry run ruff check --select I $(PYTHON_FILES) - [ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE) || poetry run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE) - -format format_diff: - poetry run ruff format $(PYTHON_FILES) - poetry run ruff check --select I --fix $(PYTHON_FILES) diff --git a/libs/scheduler-kafka/README.md b/libs/scheduler-kafka/README.md deleted file mode 100644 index fd65d7f3c..000000000 --- a/libs/scheduler-kafka/README.md +++ /dev/null @@ -1,139 +0,0 @@ -# LangGraph Scheduler for Kafka - -This library implements a distributed scheduler for LangGraph using Kafka as the message broker. - -## Architecture - -![](./langgraph-distributed.png) - -- Combination of Kafka (at least once) with a LangGraph Checkpointer provides exactly once semantics both for orchestrator and executor messages -- Checkpointer ensures writes for a given task are saved only once, even if the task is re-executed -- Checkpointer is used to record whether each task in each step has been successfully published to Kafka, to ensure tasks aren't lost, or published more than once -- Orchestrator and Executor manage commit of offsets manually to ensure tasks are marked as done only after finished processing -- Orchestrator and Executor pick up from the earliest message not yet consumed when restarted, to ensure no message is lost, and avoid processing messages more than once -- Orchestrator messages are keyed by thread ID and checkpoint NS, to ensure that no two consumers can process updates for same step of same thread concurrently -- Executor messages are not keyed, as they can be processed concurrently -- Orchestrator and Executor execute messages in configurable batches (up to N messages within space of X seconds), and dedupe messages intra-batch where appropriate (this is purely a performance optimization, with no impact on correctness whether applied or not) - -## Basic Usage - -Launch orchestrator and executor processes: - -`orchestrator.py` - -```python -import asyncio -import logging -import os - -from langgraph.scheduler.kafka.orchestrator import AsyncKafkaOrchestrator -from langgraph.scheduler.kafka.types import Topics - -from your_lib import graph # graph expected to be a compiled LangGraph graph - -logger = logging.getLogger(__name__) - -topics = Topics( - orchestrator=os.environ['KAFKA_TOPIC_ORCHESTRATOR'], - executor=os.environ['KAFKA_TOPIC_EXECUTOR'], - error=os.environ['KAFKA_TOPIC_ERROR'], -) - -async def main(): - async with AsyncKafkaOrchestrator(graph, topics) as orch: - async for msgs in orch: - logger.info('Procesed %d messages', len(msgs)) - -if __name__ == '__main__': - logging.basicConfig(level=logging.INFO) - asyncio.run(main()) -``` - -`executor.py` - -```python -import asyncio -import logging -import os - -from langgraph.scheduler.kafka.executor import AsyncKafkaExecutor -from langgraph.scheduler.kafka.types import Topics - -from your_lib import graph # graph expected to be a compiled LangGraph graph - -logger = logging.getLogger(__name__) - -topics = Topics( - orchestrator=os.environ['KAFKA_TOPIC_ORCHESTRATOR'], - executor=os.environ['KAFKA_TOPIC_EXECUTOR'], - error=os.environ['KAFKA_TOPIC_ERROR'], -) - -async def main(): - async with AsyncKafkaExecutor(graph, topics) as orch: - async for msgs in orch: - logger.info('Procesed %d messages', len(msgs)) - -if __name__ == '__main__': - logging.basicConfig(level=logging.INFO) - asyncio.run(main()) -``` - -```bash -export KAFKA_TOPIC_ORCHESTRATOR='orchestrator' -export KAFKA_TOPIC_EXECUTOR='executor' -export KAFKA_TOPIC_ERROR='error' -python orchestrator.py & -python executor.py & -``` - -## Configuration - -We offer sync and async versions of the orchestrator and executor, `KafkaOrchestrator` and `AsyncKafkaOrchestrator`, and `KafkaExecutor` and `AsyncKafkaExecutor` respectively. The async versions are recommended, especially if you want to process tasks in batches. With the async classes we recommend using `uvloop` for better performance. - -You can pass any of the following values as `kwargs` to either `KafkaOrchestrator` or `KafkaExecutor` to configure the consumer: - -- batch_max_n (int): Maximum number of messages to include in a single batch. Default: 10. -- batch_max_ms (int): Maximum time in milliseconds to wait for messages to include in a batch. Default: 1000. -- retry_policy (langgraph.types.RetryPolicy): Controls which graph-level errors will be retried when processing messages. A good use for this is to retry database errors thrown by the checkpointer. Defaults to None. - -### Connection settings - -By default the orchestrator and executor will attempt to connect to a Kafka broker running on `localhost:9092`. You can change connection settings by passing any of the following values as `kwargs` to either `KafkaOrchestrator` or `KafkaExecutor`: - -- bootstrap_servers: 'host[:port]' string (or list of 'host[:port]' - strings) that the consumer should contact to bootstrap initial - cluster metadata. This does not have to be the full node list. - It just needs to have at least one broker that will respond to - Metadata API Request. Default port is 9092. If no servers are - specified, will default to localhost:9092. -- client_id (str): a name for this client. This string is passed in - each request to servers and can be used to identify specific - server-side log entries that correspond to this client. Also - submitted to GroupCoordinator for logging with respect to - consumer group administration. Default: 'aiokafka-{ver}' -- request_timeout_ms (int): Client request timeout in milliseconds. - Default: 40000. -- metadata_max_age_ms (int): The period of time in milliseconds after - which we force a refresh of metadata even if we haven't seen - any partition leadership changes to proactively discover any - new brokers or partitions. Default: 300000 -- retry_backoff_ms (int): Milliseconds to backoff when retrying on - errors. Default: 100. -- api_version (str): specify which kafka API version to use. - AIOKafka supports Kafka API versions >=0.9 only. - If set to 'auto', will attempt to infer the broker version by - probing various APIs. Default: auto -- security_protocol (str): Protocol used to communicate with brokers. - Valid values are: PLAINTEXT, SSL, SASL_PLAINTEXT, SASL_SSL. - Default: PLAINTEXT. -- ssl_context (ssl.SSLContext): pre-configured SSLContext for wrapping - socket connections. For more information see :ref:`ssl_auth`. - Default: None. -- connections_max_idle_ms (int): Close idle connections after the number - of milliseconds specified by this config. Specifying `None` will - disable idle checks. Default: 540000 (9 minutes). - -### Custom consumer/producer - -Both the orchestrator and executor accept a `consumer` and `producer` argument, which should implement the `Consumer` or `Producer` protocols respectively. We expect the consumer to have auto-commit disabled, and the producer and consumer to have no serializers/deserializers set. diff --git a/libs/scheduler-kafka/langgraph-distributed.png b/libs/scheduler-kafka/langgraph-distributed.png deleted file mode 100644 index 4315a01b5..000000000 Binary files a/libs/scheduler-kafka/langgraph-distributed.png and /dev/null differ diff --git a/libs/scheduler-kafka/langgraph/scheduler/kafka/__init__.py b/libs/scheduler-kafka/langgraph/scheduler/kafka/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/libs/scheduler-kafka/langgraph/scheduler/kafka/default_async.py b/libs/scheduler-kafka/langgraph/scheduler/kafka/default_async.py deleted file mode 100644 index 6b6e762bc..000000000 --- a/libs/scheduler-kafka/langgraph/scheduler/kafka/default_async.py +++ /dev/null @@ -1,9 +0,0 @@ -import aiokafka - - -class DefaultAsyncConsumer(aiokafka.AIOKafkaConsumer): - pass - - -class DefaultAsyncProducer(aiokafka.AIOKafkaProducer): - pass diff --git a/libs/scheduler-kafka/langgraph/scheduler/kafka/default_sync.py b/libs/scheduler-kafka/langgraph/scheduler/kafka/default_sync.py deleted file mode 100644 index 30dda7bf1..000000000 --- a/libs/scheduler-kafka/langgraph/scheduler/kafka/default_sync.py +++ /dev/null @@ -1,39 +0,0 @@ -import concurrent.futures -from typing import Optional, Sequence - -from kafka import KafkaConsumer, KafkaProducer -from langgraph.scheduler.kafka.types import ConsumerRecord, TopicPartition - - -class DefaultConsumer(KafkaConsumer): - def getmany( - self, timeout_ms: int, max_records: int - ) -> dict[TopicPartition, Sequence[ConsumerRecord]]: - return self.poll(timeout_ms=timeout_ms, max_records=max_records) - - def __enter__(self): - return self - - def __exit__(self, *args): - self.close() - - -class DefaultProducer(KafkaProducer): - def send( - self, - topic: str, - *, - key: Optional[bytes] = None, - value: Optional[bytes] = None, - ) -> concurrent.futures.Future: - fut = concurrent.futures.Future() - kfut = super().send(topic, key=key, value=value) - kfut.add_callback(fut.set_result) - kfut.add_errback(fut.set_exception) - return fut - - def __enter__(self): - return self - - def __exit__(self, *args): - self.close() diff --git a/libs/scheduler-kafka/langgraph/scheduler/kafka/executor.py b/libs/scheduler-kafka/langgraph/scheduler/kafka/executor.py deleted file mode 100644 index fa9a221d0..000000000 --- a/libs/scheduler-kafka/langgraph/scheduler/kafka/executor.py +++ /dev/null @@ -1,473 +0,0 @@ -import asyncio -import concurrent.futures -from collections.abc import Sequence -from contextlib import ( - AbstractAsyncContextManager, - AbstractContextManager, - AsyncExitStack, - ExitStack, -) -from functools import partial -from typing import Any, Optional -from uuid import UUID - -import orjson -from langchain_core.runnables import RunnableConfig -from typing_extensions import Self - -import langgraph.scheduler.kafka.serde as serde -from langgraph.constants import CONFIG_KEY_DELEGATE, ERROR -from langgraph.errors import CheckpointNotLatest, GraphDelegate, TaskNotFound -from langgraph.pregel import Pregel -from langgraph.pregel.algo import prepare_single_task -from langgraph.pregel.executor import ( - AsyncBackgroundExecutor, - BackgroundExecutor, - Submit, -) -from langgraph.pregel.manager import AsyncChannelsManager, ChannelsManager -from langgraph.pregel.runner import PregelRunner -from langgraph.scheduler.kafka.retry import aretry, retry -from langgraph.scheduler.kafka.types import ( - AsyncConsumer, - AsyncProducer, - Consumer, - ErrorMessage, - MessageToExecutor, - MessageToOrchestrator, - Producer, - Sendable, - Topics, -) -from langgraph.types import LoopProtocol, PregelExecutableTask, RetryPolicy -from langgraph.utils.config import patch_configurable, recast_checkpoint_ns - - -class AsyncKafkaExecutor(AbstractAsyncContextManager): - consumer: AsyncConsumer - - producer: AsyncProducer - - def __init__( - self, - graph: Pregel, - topics: Topics, - *, - batch_max_n: int = 10, - batch_max_ms: int = 1000, - retry_policy: Optional[RetryPolicy] = None, - consumer: Optional[AsyncConsumer] = None, - producer: Optional[AsyncProducer] = None, - **kwargs: Any, - ) -> None: - self.graph = graph - self.topics = topics - self.stack = AsyncExitStack() - self.kwargs = kwargs - self.consumer = consumer - self.producer = producer - self.batch_max_n = batch_max_n - self.batch_max_ms = batch_max_ms - self.retry_policy = retry_policy - - async def __aenter__(self) -> Self: - loop = asyncio.get_running_loop() - self.subgraphs = { - k: v async for k, v in self.graph.aget_subgraphs(recurse=True) - } - if self.consumer is None: - from langgraph.scheduler.kafka.default_async import DefaultAsyncConsumer - - self.consumer = await self.stack.enter_async_context( - DefaultAsyncConsumer( - self.topics.executor, - auto_offset_reset="earliest", - group_id="executor", - enable_auto_commit=False, - loop=loop, - **self.kwargs, - ) - ) - if self.producer is None: - from langgraph.scheduler.kafka.default_async import DefaultAsyncProducer - - self.producer = await self.stack.enter_async_context( - DefaultAsyncProducer( - loop=loop, - **self.kwargs, - ) - ) - return self - - async def __aexit__(self, *args: Any) -> None: - return await self.stack.__aexit__(*args) - - def __aiter__(self) -> Self: - return self - - async def __anext__(self) -> Sequence[MessageToExecutor]: - # wait for next batch - recs = await self.consumer.getmany( - timeout_ms=self.batch_max_ms, max_records=self.batch_max_n - ) - msgs: list[MessageToExecutor] = [ - serde.loads(msg.value) for msgs in recs.values() for msg in msgs - ] - # process batch - await asyncio.gather(*(self.each(msg) for msg in msgs)) - # commit offsets - await self.consumer.commit() - # return message - return msgs - - async def each(self, msg: MessageToExecutor) -> None: - try: - await aretry(self.retry_policy, self.attempt, msg) - except CheckpointNotLatest: - pass - except GraphDelegate as exc: - for arg in exc.args: - fut = await self.producer.send( - self.topics.orchestrator, - value=serde.dumps( - MessageToOrchestrator( - config=arg["config"], - input=orjson.Fragment( - self.graph.checkpointer.serde.dumps(arg["input"]) - ), - finally_send=[ - Sendable(topic=self.topics.executor, value=msg) - ], - ) - ), - # use thread_id, checkpoint_ns as partition key - key=serde.dumps( - ( - arg["config"]["configurable"]["thread_id"], - arg["config"]["configurable"].get("checkpoint_ns"), - ) - ), - ) - await fut - except Exception as exc: - fut = await self.producer.send( - self.topics.error, - value=serde.dumps( - ErrorMessage( - topic=self.topics.executor, - msg=msg, - error=repr(exc), - ) - ), - ) - await fut - - async def attempt(self, msg: MessageToExecutor) -> None: - # find graph - if checkpoint_ns := msg["config"]["configurable"].get("checkpoint_ns"): - # remove task_ids from checkpoint_ns - recast = recast_checkpoint_ns(checkpoint_ns) - # find the subgraph with the matching name - if recast in self.subgraphs: - graph = self.subgraphs[recast] - else: - raise ValueError(f"Subgraph {recast} not found") - else: - graph = self.graph - # process message - saved = await self.graph.checkpointer.aget_tuple( - patch_configurable(msg["config"], {"checkpoint_id": None}) - ) - if saved is None: - raise RuntimeError("Checkpoint not found") - if saved.checkpoint["id"] != msg["config"]["configurable"]["checkpoint_id"]: - raise CheckpointNotLatest() - async with ( - AsyncChannelsManager( - graph.channels, - saved.checkpoint, - LoopProtocol( - config=msg["config"], - store=self.graph.store, - step=saved.metadata["step"] + 1, - stop=saved.metadata["step"] + 2, - ), - ) as (channels, managed), - AsyncBackgroundExecutor(msg["config"]) as submit, - ): - if task := await asyncio.to_thread( - prepare_single_task, - msg["task"]["path"], - msg["task"]["id"], - checkpoint=saved.checkpoint, - pending_writes=saved.pending_writes or [], - processes=graph.nodes, - channels=channels, - managed=managed, - config=patch_configurable(msg["config"], {CONFIG_KEY_DELEGATE: True}), - step=saved.metadata["step"] + 1, - for_execution=True, - checkpointer=self.graph.checkpointer, - store=self.graph.store, - ): - # execute task, saving writes - runner = PregelRunner( - submit=submit, - put_writes=partial(self._put_writes, submit, msg["config"]), - schedule_task=self._schedule_task, - ) - async for _ in runner.atick([task], reraise=False): - pass - else: - # task was not found - await self.graph.checkpointer.aput_writes( - msg["config"], [(ERROR, TaskNotFound())], str(UUID(int=0)) - ) - # notify orchestrator - fut = await self.producer.send( - self.topics.orchestrator, - value=serde.dumps( - MessageToOrchestrator( - input=None, - config=msg["config"], - finally_send=msg.get("finally_send"), - ) - ), - # use thread_id, checkpoint_ns as partition key - key=serde.dumps( - ( - msg["config"]["configurable"]["thread_id"], - msg["config"]["configurable"].get("checkpoint_ns"), - ) - ), - ) - await fut - - def _schedule_task( - self, - task: PregelExecutableTask, - idx: int, - ) -> None: - # will be scheduled by orchestrator when executor finishes - pass - - def _put_writes( - self, - submit: Submit, - config: RunnableConfig, - task_id: str, - writes: list[tuple[str, Any]], - ) -> None: - return submit(self.graph.checkpointer.aput_writes, config, writes, task_id) - - -class KafkaExecutor(AbstractContextManager): - consumer: Consumer - - producer: Producer - - def __init__( - self, - graph: Pregel, - topics: Topics, - *, - batch_max_n: int = 10, - batch_max_ms: int = 1000, - retry_policy: Optional[RetryPolicy] = None, - consumer: Optional[Consumer] = None, - producer: Optional[Producer] = None, - **kwargs: Any, - ) -> None: - self.graph = graph - self.topics = topics - self.stack = ExitStack() - self.kwargs = kwargs - self.consumer = consumer - self.producer = producer - self.batch_max_n = batch_max_n - self.batch_max_ms = batch_max_ms - self.retry_policy = retry_policy - - def __enter__(self) -> Self: - self.subgraphs = dict(self.graph.get_subgraphs(recurse=True)) - self.submit = self.stack.enter_context(BackgroundExecutor({})) - if self.consumer is None: - from langgraph.scheduler.kafka.default_sync import DefaultConsumer - - self.consumer = self.stack.enter_context( - DefaultConsumer( - self.topics.executor, - auto_offset_reset="earliest", - group_id="executor", - enable_auto_commit=False, - **self.kwargs, - ) - ) - if self.producer is None: - from langgraph.scheduler.kafka.default_sync import DefaultProducer - - self.producer = self.stack.enter_context( - DefaultProducer( - **self.kwargs, - ) - ) - return self - - def __exit__(self, *args: Any) -> None: - return self.stack.__exit__(*args) - - def __iter__(self) -> Self: - return self - - def __next__(self) -> Sequence[MessageToExecutor]: - # wait for next batch - recs = self.consumer.getmany( - timeout_ms=self.batch_max_ms, max_records=self.batch_max_n - ) - msgs: list[MessageToExecutor] = [ - serde.loads(msg.value) for msgs in recs.values() for msg in msgs - ] - # process batch - concurrent.futures.wait(self.submit(self.each, msg) for msg in msgs) - # commit offsets - self.consumer.commit() - # return message - return msgs - - def each(self, msg: MessageToExecutor) -> None: - try: - retry(self.retry_policy, self.attempt, msg) - except CheckpointNotLatest: - pass - except GraphDelegate as exc: - for arg in exc.args: - fut = self.producer.send( - self.topics.orchestrator, - value=serde.dumps( - MessageToOrchestrator( - config=arg["config"], - input=orjson.Fragment( - self.graph.checkpointer.serde.dumps(arg["input"]) - ), - finally_send=[ - Sendable(topic=self.topics.executor, value=msg) - ], - ) - ), - # use thread_id, checkpoint_ns as partition key - key=serde.dumps( - ( - arg["config"]["configurable"]["thread_id"], - arg["config"]["configurable"].get("checkpoint_ns"), - ) - ), - ) - fut.result() - except Exception as exc: - fut = self.producer.send( - self.topics.error, - value=serde.dumps( - ErrorMessage( - topic=self.topics.executor, - msg=msg, - error=repr(exc), - ) - ), - ) - fut.result() - - def attempt(self, msg: MessageToExecutor) -> None: - # find graph - if checkpoint_ns := msg["config"]["configurable"].get("checkpoint_ns"): - # remove task_ids from checkpoint_ns - recast = recast_checkpoint_ns(checkpoint_ns) - # find the subgraph with the matching name - if recast in self.subgraphs: - graph = self.subgraphs[recast] - else: - raise ValueError(f"Subgraph {recast} not found") - else: - graph = self.graph - # process message - saved = self.graph.checkpointer.get_tuple( - patch_configurable(msg["config"], {"checkpoint_id": None}) - ) - if saved is None: - raise RuntimeError("Checkpoint not found") - if saved.checkpoint["id"] != msg["config"]["configurable"]["checkpoint_id"]: - raise CheckpointNotLatest() - with ( - ChannelsManager( - graph.channels, - saved.checkpoint, - LoopProtocol( - config=msg["config"], - store=self.graph.store, - step=saved.metadata["step"] + 1, - stop=saved.metadata["step"] + 2, - ), - ) as (channels, managed), - BackgroundExecutor({}) as submit, - ): - if task := prepare_single_task( - msg["task"]["path"], - msg["task"]["id"], - checkpoint=saved.checkpoint, - pending_writes=saved.pending_writes or [], - processes=graph.nodes, - channels=channels, - managed=managed, - config=patch_configurable(msg["config"], {CONFIG_KEY_DELEGATE: True}), - step=saved.metadata["step"] + 1, - for_execution=True, - checkpointer=self.graph.checkpointer, - ): - # execute task, saving writes - runner = PregelRunner( - submit=submit, - put_writes=partial(self._put_writes, submit, msg["config"]), - schedule_task=self._schedule_task, - ) - for _ in runner.tick([task], reraise=False): - pass - else: - # task was not found - self.graph.checkpointer.put_writes( - msg["config"], [(ERROR, TaskNotFound())], str(UUID(int=0)) - ) - # notify orchestrator - fut = self.producer.send( - self.topics.orchestrator, - value=serde.dumps( - MessageToOrchestrator( - input=None, - config=msg["config"], - finally_send=msg.get("finally_send"), - ) - ), - # use thread_id, checkpoint_ns as partition key - key=serde.dumps( - ( - msg["config"]["configurable"]["thread_id"], - msg["config"]["configurable"].get("checkpoint_ns"), - ) - ), - ) - fut.result() - - def _schedule_task( - self, - task: PregelExecutableTask, - idx: int, - ) -> None: - # will be scheduled by orchestrator when executor finishes - pass - - def _put_writes( - self, - submit: Submit, - config: RunnableConfig, - task_id: str, - writes: list[tuple[str, Any]], - ) -> None: - return submit(self.graph.checkpointer.put_writes, config, writes, task_id) diff --git a/libs/scheduler-kafka/langgraph/scheduler/kafka/orchestrator.py b/libs/scheduler-kafka/langgraph/scheduler/kafka/orchestrator.py deleted file mode 100644 index 3e4499266..000000000 --- a/libs/scheduler-kafka/langgraph/scheduler/kafka/orchestrator.py +++ /dev/null @@ -1,408 +0,0 @@ -import asyncio -import concurrent.futures -from contextlib import ( - AbstractAsyncContextManager, - AbstractContextManager, - AsyncExitStack, - ExitStack, -) -from typing import Any, Optional - -from langchain_core.runnables import ensure_config -from typing_extensions import Self - -import langgraph.scheduler.kafka.serde as serde -from langgraph.constants import ( - CONFIG_KEY_DEDUPE_TASKS, - CONFIG_KEY_ENSURE_LATEST, - INTERRUPT, - SCHEDULED, -) -from langgraph.errors import CheckpointNotLatest, GraphInterrupt -from langgraph.pregel import Pregel -from langgraph.pregel.executor import BackgroundExecutor, Submit -from langgraph.pregel.loop import AsyncPregelLoop, SyncPregelLoop -from langgraph.scheduler.kafka.retry import aretry, retry -from langgraph.scheduler.kafka.types import ( - AsyncConsumer, - AsyncProducer, - Consumer, - ErrorMessage, - ExecutorTask, - MessageToExecutor, - MessageToOrchestrator, - Producer, - Topics, -) -from langgraph.types import RetryPolicy -from langgraph.utils.config import patch_configurable, recast_checkpoint_ns - - -class AsyncKafkaOrchestrator(AbstractAsyncContextManager): - consumer: AsyncConsumer - - producer: AsyncProducer - - def __init__( - self, - graph: Pregel, - topics: Topics, - batch_max_n: int = 10, - batch_max_ms: int = 1000, - retry_policy: Optional[RetryPolicy] = None, - consumer: Optional[AsyncConsumer] = None, - producer: Optional[AsyncProducer] = None, - **kwargs: Any, - ) -> None: - self.graph = graph - self.topics = topics - self.stack = AsyncExitStack() - self.kwargs = kwargs - self.consumer = consumer - self.producer = producer - self.batch_max_n = batch_max_n - self.batch_max_ms = batch_max_ms - self.retry_policy = retry_policy - - async def __aenter__(self) -> Self: - loop = asyncio.get_running_loop() - self.subgraphs = { - k: v async for k, v in self.graph.aget_subgraphs(recurse=True) - } - if self.consumer is None: - from langgraph.scheduler.kafka.default_async import DefaultAsyncConsumer - - self.consumer = await self.stack.enter_async_context( - DefaultAsyncConsumer( - self.topics.orchestrator, - auto_offset_reset="earliest", - group_id="orchestrator", - enable_auto_commit=False, - loop=loop, - **self.kwargs, - ) - ) - if self.producer is None: - from langgraph.scheduler.kafka.default_async import DefaultAsyncProducer - - self.producer = await self.stack.enter_async_context( - DefaultAsyncProducer( - loop=loop, - **self.kwargs, - ) - ) - return self - - async def __aexit__(self, *args: Any) -> None: - return await self.stack.__aexit__(*args) - - def __aiter__(self) -> Self: - return self - - async def __anext__(self) -> list[MessageToOrchestrator]: - # wait for next batch - recs = await self.consumer.getmany( - timeout_ms=self.batch_max_ms, max_records=self.batch_max_n - ) - # dedupe messages, eg. if multiple nodes finish around same time - uniq = set(msg.value for msgs in recs.values() for msg in msgs) - msgs: list[MessageToOrchestrator] = [serde.loads(msg) for msg in uniq] - # process batch - await asyncio.gather(*(self.each(msg) for msg in msgs)) - # commit offsets - await self.consumer.commit() - # return message - return msgs - - async def each(self, msg: MessageToOrchestrator) -> None: - try: - await aretry(self.retry_policy, self.attempt, msg) - except CheckpointNotLatest: - pass - except GraphInterrupt: - pass - except Exception as exc: - fut = await self.producer.send( - self.topics.error, - value=serde.dumps( - ErrorMessage( - topic=self.topics.orchestrator, - msg=msg, - error=repr(exc), - ) - ), - ) - await fut - - async def attempt(self, msg: MessageToOrchestrator) -> None: - # find graph - if checkpoint_ns := msg["config"]["configurable"].get("checkpoint_ns"): - # remove task_ids from checkpoint_ns - recast = recast_checkpoint_ns(checkpoint_ns) - # find the subgraph with the matching name - if recast in self.subgraphs: - graph = self.subgraphs[recast] - else: - raise ValueError(f"Subgraph {recast} not found") - else: - graph = self.graph - # process message - async with AsyncPregelLoop( - msg["input"], - config=ensure_config(msg["config"]), - stream=None, - store=self.graph.store, - checkpointer=self.graph.checkpointer, - nodes=graph.nodes, - specs=graph.channels, - output_keys=graph.output_channels, - stream_keys=graph.stream_channels, - interrupt_after=graph.interrupt_after_nodes, - interrupt_before=graph.interrupt_before_nodes, - ) as loop: - if loop.tick(input_keys=graph.input_channels): - # wait for checkpoint to be saved - if hasattr(loop, "_put_checkpoint_fut"): - await loop._put_checkpoint_fut - # schedule any new tasks - if new_tasks := [ - t for t in loop.tasks.values() if not t.scheduled and not t.writes - ]: - config = patch_configurable( - loop.config, - { - **loop.checkpoint_config["configurable"], - CONFIG_KEY_DEDUPE_TASKS: True, - CONFIG_KEY_ENSURE_LATEST: True, - }, - ) - # send messages to executor - futures = await asyncio.gather( - *( - self.producer.send( - self.topics.executor, - value=serde.dumps( - MessageToExecutor( - config=config, - task=ExecutorTask(id=task.id, path=task.path), - finally_send=msg.get("finally_send"), - ) - ), - ) - for task in new_tasks - ) - ) - # wait for messages to be sent - await asyncio.gather(*futures) - # mark as scheduled - for task in new_tasks: - loop.put_writes( - task.id, - [ - ( - SCHEDULED, - max( - loop.checkpoint["versions_seen"] - .get(INTERRUPT, {}) - .values(), - default=None, - ), - ) - ], - ) - elif loop.status == "done" and msg.get("finally_send"): - # send any finally_send messages - futs = await asyncio.gather( - *( - self.producer.send( - m["topic"], - value=serde.dumps(m["value"]) if m.get("value") else None, - key=serde.dumps(m["key"]) if m.get("key") else None, - ) - for m in msg["finally_send"] - ) - ) - # wait for messages to be sent - await asyncio.gather(*futs) - - -class KafkaOrchestrator(AbstractContextManager): - consumer: Consumer - - producer: Producer - - submit: Submit - - def __init__( - self, - graph: Pregel, - topics: Topics, - batch_max_n: int = 10, - batch_max_ms: int = 1000, - retry_policy: Optional[RetryPolicy] = None, - consumer: Optional[Consumer] = None, - producer: Optional[Producer] = None, - **kwargs: Any, - ) -> None: - self.graph = graph - self.topics = topics - self.stack = ExitStack() - self.kwargs = kwargs - self.consumer = consumer - self.producer = producer - self.batch_max_n = batch_max_n - self.batch_max_ms = batch_max_ms - self.retry_policy = retry_policy - - def __enter__(self) -> Self: - self.subgraphs = dict(self.graph.get_subgraphs(recurse=True)) - self.submit = self.stack.enter_context(BackgroundExecutor({})) - if self.consumer is None: - from langgraph.scheduler.kafka.default_sync import DefaultConsumer - - self.consumer = self.stack.enter_context( - DefaultConsumer( - self.topics.orchestrator, - auto_offset_reset="earliest", - group_id="orchestrator", - enable_auto_commit=False, - **self.kwargs, - ) - ) - if self.producer is None: - from langgraph.scheduler.kafka.default_sync import DefaultProducer - - self.producer = self.stack.enter_context( - DefaultProducer( - **self.kwargs, - ) - ) - return self - - def __exit__(self, *args: Any) -> None: - return self.stack.__exit__(*args) - - def __iter__(self) -> Self: - return self - - def __next__(self) -> list[MessageToOrchestrator]: - # wait for next batch - recs = self.consumer.getmany( - timeout_ms=self.batch_max_ms, max_records=self.batch_max_n - ) - # dedupe messages, eg. if multiple nodes finish around same time - uniq = set(msg.value for msgs in recs.values() for msg in msgs) - msgs: list[MessageToOrchestrator] = [serde.loads(msg) for msg in uniq] - # process batch - concurrent.futures.wait(self.submit(self.each, msg) for msg in msgs) - # commit offsets - self.consumer.commit() - # return message - return msgs - - def each(self, msg: MessageToOrchestrator) -> None: - try: - retry(self.retry_policy, self.attempt, msg) - except CheckpointNotLatest: - pass - except GraphInterrupt: - pass - except Exception as exc: - fut = self.producer.send( - self.topics.error, - value=serde.dumps( - ErrorMessage( - topic=self.topics.orchestrator, - msg=msg, - error=repr(exc), - ) - ), - ) - fut.result() - - def attempt(self, msg: MessageToOrchestrator) -> None: - # find graph - if checkpoint_ns := msg["config"]["configurable"].get("checkpoint_ns"): - # remove task_ids from checkpoint_ns - recast = recast_checkpoint_ns(checkpoint_ns) - # find the subgraph with the matching name - if recast in self.subgraphs: - graph = self.subgraphs[recast] - else: - raise ValueError(f"Subgraph {recast} not found") - else: - graph = self.graph - # process message - with SyncPregelLoop( - msg["input"], - config=ensure_config(msg["config"]), - stream=None, - store=self.graph.store, - checkpointer=self.graph.checkpointer, - nodes=graph.nodes, - specs=graph.channels, - output_keys=graph.output_channels, - stream_keys=graph.stream_channels, - interrupt_after=graph.interrupt_after_nodes, - interrupt_before=graph.interrupt_before_nodes, - ) as loop: - if loop.tick(input_keys=graph.input_channels): - # wait for checkpoint to be saved - if hasattr(loop, "_put_checkpoint_fut"): - loop._put_checkpoint_fut.result() - # schedule any new tasks - if new_tasks := [ - t for t in loop.tasks.values() if not t.scheduled and not t.writes - ]: - config = patch_configurable( - loop.config, - { - **loop.checkpoint_config["configurable"], - CONFIG_KEY_DEDUPE_TASKS: True, - CONFIG_KEY_ENSURE_LATEST: True, - }, - ) - # send messages to executor - futures = [ - self.producer.send( - self.topics.executor, - value=serde.dumps( - MessageToExecutor( - config=config, - task=ExecutorTask(id=task.id, path=task.path), - finally_send=msg.get("finally_send"), - ) - ), - ) - for task in new_tasks - ] - # wait for messages to be sent - concurrent.futures.wait(futures) - # mark as scheduled - for task in new_tasks: - loop.put_writes( - task.id, - [ - ( - SCHEDULED, - max( - loop.checkpoint["versions_seen"] - .get(INTERRUPT, {}) - .values(), - default=None, - ), - ) - ], - ) - elif loop.status == "done" and msg.get("finally_send"): - # schedule any finally_send msgs - futs = [ - self.producer.send( - m["topic"], - value=serde.dumps(m["value"]) if m.get("value") else None, - key=serde.dumps(m["key"]) if m.get("key") else None, - ) - for m in msg["finally_send"] - ] - # wait for messages to be sent - concurrent.futures.wait(futs) diff --git a/libs/scheduler-kafka/langgraph/scheduler/kafka/py.typed b/libs/scheduler-kafka/langgraph/scheduler/kafka/py.typed deleted file mode 100644 index e69de29bb..000000000 diff --git a/libs/scheduler-kafka/langgraph/scheduler/kafka/retry.py b/libs/scheduler-kafka/langgraph/scheduler/kafka/retry.py deleted file mode 100644 index 74dbe3e27..000000000 --- a/libs/scheduler-kafka/langgraph/scheduler/kafka/retry.py +++ /dev/null @@ -1,98 +0,0 @@ -import asyncio -import logging -import random -import time -from typing import Awaitable, Callable, Optional - -from typing_extensions import ParamSpec - -from langgraph.types import RetryPolicy - -logger = logging.getLogger(__name__) -P = ParamSpec("P") - - -def retry( - retry_policy: Optional[RetryPolicy], - func: Callable[P, None], - *args: P.args, - **kwargs: P.kwargs, -) -> None: - """Run a task asynchronously with retries.""" - interval = retry_policy.initial_interval if retry_policy else 0 - attempts = 0 - while True: - try: - func(*args, **kwargs) - # if successful, end - break - except Exception as exc: - if retry_policy is None: - raise - # increment attempts - attempts += 1 - # check if we should retry - if callable(retry_policy.retry_on): - if not retry_policy.retry_on(exc): - raise - elif not isinstance(exc, retry_policy.retry_on): - raise - # check if we should give up - if attempts >= retry_policy.max_attempts: - raise - # sleep before retrying - interval = min( - retry_policy.max_interval, - interval * retry_policy.backoff_factor, - ) - time.sleep( - interval + random.uniform(0, 1) if retry_policy.jitter else interval - ) - # log the retry - logger.info( - f"Retrying function {func} with {args} after {interval:.2f} seconds (attempt {attempts}) after {exc.__class__.__name__} {exc}", - exc_info=exc, - ) - - -async def aretry( - retry_policy: Optional[RetryPolicy], - func: Callable[P, Awaitable[None]], - *args: P.args, - **kwargs: P.kwargs, -) -> None: - """Run a task asynchronously with retries.""" - interval = retry_policy.initial_interval if retry_policy else 0 - attempts = 0 - while True: - try: - await func(*args, **kwargs) - # if successful, end - break - except Exception as exc: - if retry_policy is None: - raise - # increment attempts - attempts += 1 - # check if we should retry - if callable(retry_policy.retry_on): - if not retry_policy.retry_on(exc): - raise - elif not isinstance(exc, retry_policy.retry_on): - raise - # check if we should give up - if attempts >= retry_policy.max_attempts: - raise - # sleep before retrying - interval = min( - retry_policy.max_interval, - interval * retry_policy.backoff_factor, - ) - await asyncio.sleep( - interval + random.uniform(0, 1) if retry_policy.jitter else interval - ) - # log the retry - logger.info( - f"Retrying function {func} with {args} after {interval:.2f} seconds (attempt {attempts}) after {exc.__class__.__name__} {exc}", - exc_info=exc, - ) diff --git a/libs/scheduler-kafka/langgraph/scheduler/kafka/serde.py b/libs/scheduler-kafka/langgraph/scheduler/kafka/serde.py deleted file mode 100644 index 9868f5ae8..000000000 --- a/libs/scheduler-kafka/langgraph/scheduler/kafka/serde.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Any - -import orjson - -from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer - -SERIALIZER = JsonPlusSerializer() - - -def loads(v: bytes) -> Any: - return SERIALIZER.loads(v) - - -def dumps(v: Any) -> bytes: - return orjson.dumps(v, default=_default) - - -def _default(v: Any) -> Any: - # things we don't know how to serialize (eg. functions) ignore - return None diff --git a/libs/scheduler-kafka/langgraph/scheduler/kafka/types.py b/libs/scheduler-kafka/langgraph/scheduler/kafka/types.py deleted file mode 100644 index 8a109631b..000000000 --- a/libs/scheduler-kafka/langgraph/scheduler/kafka/types.py +++ /dev/null @@ -1,98 +0,0 @@ -import asyncio -import concurrent.futures -from typing import Any, NamedTuple, Optional, Protocol, Sequence, TypedDict, Union - -from langchain_core.runnables import RunnableConfig - - -class Topics(NamedTuple): - orchestrator: str - executor: str - error: str - - -class Sendable(TypedDict): - topic: str - value: Optional[Any] - key: Optional[Any] - - -class MessageToOrchestrator(TypedDict): - input: Optional[dict[str, Any]] - config: RunnableConfig - finally_send: Optional[Sequence[Sendable]] - - -class ExecutorTask(TypedDict): - id: Optional[str] - path: tuple[Union[str, int], ...] - - -class MessageToExecutor(TypedDict): - config: RunnableConfig - task: ExecutorTask - finally_send: Optional[Sequence[Sendable]] - - -class ErrorMessage(TypedDict): - topic: str - error: str - msg: Union[MessageToExecutor, MessageToOrchestrator] - - -class TopicPartition(Protocol): - topic: str - partition: int - - -class ConsumerRecord(Protocol): - topic: str - "The topic this record is received from" - partition: int - "The partition from which this record is received" - offset: int - "The position of this record in the corresponding Kafka partition." - timestamp: int - "The timestamp of this record" - timestamp_type: int - "The timestamp type of this record" - key: Optional[bytes] - "The key (or `None` if no key is specified)" - value: Optional[bytes] - "The value" - - -class Consumer(Protocol): - def getmany( - self, timeout_ms: int, max_records: int - ) -> dict[TopicPartition, Sequence[ConsumerRecord]]: ... - - def commit(self) -> None: ... - - -class AsyncConsumer(Protocol): - async def getmany( - self, timeout_ms: int, max_records: int - ) -> dict[TopicPartition, Sequence[ConsumerRecord]]: ... - - async def commit(self) -> None: ... - - -class Producer(Protocol): - def send( - self, - topic: str, - *, - key: Optional[bytes] = None, - value: Optional[bytes] = None, - ) -> concurrent.futures.Future: ... - - -class AsyncProducer(Protocol): - async def send( - self, - topic: str, - *, - key: Optional[bytes] = None, - value: Optional[bytes] = None, - ) -> asyncio.Future: ... diff --git a/libs/scheduler-kafka/poetry.lock b/libs/scheduler-kafka/poetry.lock deleted file mode 100644 index bd2ddfbfd..000000000 --- a/libs/scheduler-kafka/poetry.lock +++ /dev/null @@ -1,1256 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. - -[[package]] -name = "aiokafka" -version = "0.11.0" -description = "Kafka integration with asyncio" -optional = false -python-versions = ">=3.8" -files = [ - {file = "aiokafka-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:926f93fb6a39891fd4364494432b479c0602f9cac708778d4a262a2c2e20d3b4"}, - {file = "aiokafka-0.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38e1917e706c1158d5e1f612d1fc1b40f706dc46c534e73ab4de8ae2868a31be"}, - {file = "aiokafka-0.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:516e1d68d9a377860b2e17453580afe304605bc71894f684d3e7b6618f6f939f"}, - {file = "aiokafka-0.11.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acfd0a5d0aec762ba73eeab73b23edce14f315793f063b6a4b223b6f79e36bb8"}, - {file = "aiokafka-0.11.0-cp310-cp310-win32.whl", hash = "sha256:0d80590c4ef0ba546a299cee22ea27c3360c14241ec43a8e6904653f7b22d328"}, - {file = "aiokafka-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:1d519bf9875ac867fb19d55de3750833b1eb6379a08de29a68618e24e6a49fc0"}, - {file = "aiokafka-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0e957b42ae959365efbb45c9b5de38032c573608553c3670ad8695cc210abec9"}, - {file = "aiokafka-0.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:224db2447f6c1024198d8342e7099198f90401e2fa29c0762afbc51eadf5c490"}, - {file = "aiokafka-0.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef3e7c8a923e502caa4d24041f2be778fd7f9ee4587bf0bcb4f74cac05122fa"}, - {file = "aiokafka-0.11.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59f4b935589ebb244620afad8bf3320e3bc86879a8b1c692ad06bd324f6c6127"}, - {file = "aiokafka-0.11.0-cp311-cp311-win32.whl", hash = "sha256:560839ae6bc13e71025d71e94df36980f5c6e36a64916439e598b6457267a37f"}, - {file = "aiokafka-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:1f8ae91f0373830e4664376157fe61b611ca7e573d8a559b151aef5bf53df46c"}, - {file = "aiokafka-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4e0cc080a7f4c659ee4e1baa1c32adedcccb105a52156d4909f357d76fac0dc1"}, - {file = "aiokafka-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55a07a39d82c595223a17015ea738d152544cee979d3d6d822707a082465621c"}, - {file = "aiokafka-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3711fa64ee8640dcd4cb640f1030f9439d02e85acd57010d09053017092d8cc2"}, - {file = "aiokafka-0.11.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:818a6f8e44b02113b9e795bee6029c8a4e525ab38f29d7adb0201f3fec74c808"}, - {file = "aiokafka-0.11.0-cp312-cp312-win32.whl", hash = "sha256:8ba981956243767b37c929845c398fda2a2e35a4034d218badbe2b62e6f98f96"}, - {file = "aiokafka-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a478a14fd23fd1ffe9c7a21238d818b5f5e0626f7f06146b687f3699298391b"}, - {file = "aiokafka-0.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0973a245b8b9daf8ef6814253a80a700f1f54d2da7d88f6fe479f46e0fd83053"}, - {file = "aiokafka-0.11.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee0c61a2dcabbe4474ff237d708f9bd663dd2317e03a9cb7239a212c9ee05b12"}, - {file = "aiokafka-0.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:230170ce2e8a0eb852e2e8b78b08ce2e29b77dfe2c51bd56f5ab4be0f332a63b"}, - {file = "aiokafka-0.11.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eac78a009b713e28b5b4c4daae9d062acbf2b7980e5734467643a810134583b5"}, - {file = "aiokafka-0.11.0-cp38-cp38-win32.whl", hash = "sha256:73584be8ba7906e3f33ca0f08f6af21a9ae31b86c6b635b93db3b1e6f452657b"}, - {file = "aiokafka-0.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:d724b6fc484e453b373052813e4e543fc028a22c3fbda10e13b6829740000b8a"}, - {file = "aiokafka-0.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:419dd28c8ed6e926061bdc60929af08a6b52f1721e1179d9d21cc72ae28fd6f6"}, - {file = "aiokafka-0.11.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1c85f66eb3564c5e74d8e4c25df4ac1fd94f1a6f6e66f005aafa6f791bde215"}, - {file = "aiokafka-0.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaafe134de57b184f3c030e1a11051590caff7953c8bf58048eefd8d828e39d7"}, - {file = "aiokafka-0.11.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:807f699cf916369b1a512e4f2eaec714398c202d8803328ef8711967d99a56ce"}, - {file = "aiokafka-0.11.0-cp39-cp39-win32.whl", hash = "sha256:d59fc7aec088c9ffc02d37e61591f053459bd11912cf04c70ac4f7e60405667d"}, - {file = "aiokafka-0.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:702aec15b63bad5e4476294bcb1cb177559149fce3e59335794f004c279cbd6a"}, - {file = "aiokafka-0.11.0.tar.gz", hash = "sha256:f2def07fe1720c4fe37c0309e355afa9ff4a28e0aabfe847be0692461ac69352"}, -] - -[package.dependencies] -async-timeout = "*" -packaging = "*" -typing-extensions = ">=4.10.0" - -[package.extras] -all = ["cramjam (>=2.8.0)", "gssapi"] -gssapi = ["gssapi"] -lz4 = ["cramjam (>=2.8.0)"] -snappy = ["cramjam"] -zstd = ["cramjam"] - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "anyio" -version = "4.4.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.8" -files = [ - {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, - {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, -] - -[package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} -idna = ">=2.8" -sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} - -[package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] - -[[package]] -name = "async-timeout" -version = "4.0.3" -description = "Timeout context manager for asyncio programs" -optional = false -python-versions = ">=3.7" -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] - -[[package]] -name = "certifi" -version = "2024.8.30" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "codespell" -version = "2.3.0" -description = "Codespell" -optional = false -python-versions = ">=3.8" -files = [ - {file = "codespell-2.3.0-py3-none-any.whl", hash = "sha256:a9c7cef2501c9cfede2110fd6d4e5e62296920efe9abfb84648df866e47f58d1"}, - {file = "codespell-2.3.0.tar.gz", hash = "sha256:360c7d10f75e65f67bad720af7007e1060a5d395670ec11a7ed1fed9dd17471f"}, -] - -[package.extras] -dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"] -hard-encoding-detection = ["chardet"] -toml = ["tomli"] -types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "crc32c" -version = "2.7.post1" -description = "A python package implementing the crc32c algorithm in hardware and software" -optional = false -python-versions = ">=3.7" -files = [ - {file = "crc32c-2.7.post1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c48ff8e53243839c75a5563f6cf82f60285307d58c79142a51854666f6ff0b51"}, - {file = "crc32c-2.7.post1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:60ddb274271b94e96bb3d906f5d7d6780f79365071333eac5552e6cc79934b14"}, - {file = "crc32c-2.7.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:375f02c2555d733092af02b942e56bc86277ff95c9dc5e1e1cb7573a9b03d11f"}, - {file = "crc32c-2.7.post1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5ed125c3add2abcbeeb37c6162451e3367e30ee328bfd7f43b4d048577b4242"}, - {file = "crc32c-2.7.post1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebd725aef659604f1be1e3d8b36ae58740adc1d3aee61e3794ae153c10aefeca"}, - {file = "crc32c-2.7.post1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fcf277b5277a458c70c5777d1adae58eb772fe3afb5418b8bb43d2cca059f18"}, - {file = "crc32c-2.7.post1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00ce24782d166115ab8408c2e055028cf03a20a807a14aba51d87214561b699"}, - {file = "crc32c-2.7.post1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3147afad2134145d77b39c1b0add0f47fbbe6f843032ed72d0ee5d87cf31a8b9"}, - {file = "crc32c-2.7.post1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5cd10562a9715d0937ee680f9ef01f5e3ee6a3850113be1713234b3bac5d2348"}, - {file = "crc32c-2.7.post1-cp310-cp310-win32.whl", hash = "sha256:c38e981631c5063ae33cc07bec3edc47ed9afb831231f26f90b2133d90cb29ed"}, - {file = "crc32c-2.7.post1-cp310-cp310-win_amd64.whl", hash = "sha256:d5be25568c52a92b5b9ac4030935c591bc00020bed48f1b36bbffbc8382705d9"}, - {file = "crc32c-2.7.post1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1aef7771e50e9863fc642ed00b6767f045d1ad227f13e7c7f0fd14f45e597e88"}, - {file = "crc32c-2.7.post1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e3a5381bfd8ff447639fd2fbc31eea392a1c79a8f3a67f3a9df4fb7c198593c"}, - {file = "crc32c-2.7.post1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bbb6bcddc076a95cebc578b23e28a4bef33908c5a37b11f932533baa3d873c04"}, - {file = "crc32c-2.7.post1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e1d3f3edb8a4dd04a2eba705a2defb646a1998ac9b4ae821239af8525385364"}, - {file = "crc32c-2.7.post1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fad064f194c65e291a056ed5e5d1a4e6d2a6fbaf10e618926295f4ec0b45c335"}, - {file = "crc32c-2.7.post1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2e90f8832a2faa8b3dc1426fb1229de0834e76f8ebcea0329822a0d390cea0"}, - {file = "crc32c-2.7.post1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0b555694a1db21bcfa87ee9629d259dc92600b078cec8cd35c89ad2b10d397b9"}, - {file = "crc32c-2.7.post1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:78a833e3d923b781179cfcbde6be63d5d5a40ffcae592141447abedcb26f482b"}, - {file = "crc32c-2.7.post1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:328071b582c245b4653dad1671900fb3f9408eba4a42aabaefc975dabfcb6b1b"}, - {file = "crc32c-2.7.post1-cp311-cp311-win32.whl", hash = "sha256:51866ea030fd74a380cc77ed44edb48b9335b449d02bf719236e9b96a27010a5"}, - {file = "crc32c-2.7.post1-cp311-cp311-win_amd64.whl", hash = "sha256:cfb02ddb24246ec604ccf65fc8cb16620ed355a1d88725b32a2baff255924290"}, - {file = "crc32c-2.7.post1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5ea2ce9b93cdca9c6aaa36a4fbbc963104007a81b9c8e9a0880e22967f1f09c8"}, - {file = "crc32c-2.7.post1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2739837f83a96230b2bff39153b1f307eecb1f49c8414d1b0f5f62b6974c4721"}, - {file = "crc32c-2.7.post1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4662cacdf0094056837a517869e69aa5b1f1347b93eb30da92619f779d6e4d10"}, - {file = "crc32c-2.7.post1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b098b018cf9d5cdab845f03225fee8f345140b4a5bc09f220405368ec4772443"}, - {file = "crc32c-2.7.post1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d1befa6ab8adab06829bb104d22e55513e852dbd8bae2d6d57721de8ab25e55"}, - {file = "crc32c-2.7.post1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa676cec7b5a4840a2639f189a003c2664ae19d6540520fccadcd89a1ae67483"}, - {file = "crc32c-2.7.post1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9543f35e87f850039f8270c9832393f22358b85be56537311ec59f038f89e1a6"}, - {file = "crc32c-2.7.post1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:017b745d622ae11ce7adf872bd25033dd3d774ed85f33d1f5fa6e98ed04ee35b"}, - {file = "crc32c-2.7.post1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0f485cb6f5efb0b50fa156a6ace781ae5c5fdb1f8b337a5af9b1231ac822bc60"}, - {file = "crc32c-2.7.post1-cp312-cp312-win32.whl", hash = "sha256:8ba922650aac153b5f52bd76ff611ac0543b4a5eb6cfd865b0e1e3d6d62fde59"}, - {file = "crc32c-2.7.post1-cp312-cp312-win_amd64.whl", hash = "sha256:8da1aec4f285b5beec53dc996af8a4274aa2419bd0e3e892cd13d93b12c389f2"}, - {file = "crc32c-2.7.post1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:76a4d48ae0a973f6cfd8e4a4d6d9232bf52b40df199bb7e0905060cc62dc93aa"}, - {file = "crc32c-2.7.post1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:acc52cc9736b4fba2b55f2c471cae21a1a9449736c553ef2423d16f502fd3076"}, - {file = "crc32c-2.7.post1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:930b8b18baa367f0924c74deedb2eab14f0d87560ce2887019cb8eebffc72537"}, - {file = "crc32c-2.7.post1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc35e5df54eae762b915007a0c247a5fb1a26e3a4bcdcd17942d2bdc52917576"}, - {file = "crc32c-2.7.post1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a300cd431fca617ebc7d96f401c6cf7ef9f3e1caa1edf0af2f93eee864f1cae5"}, - {file = "crc32c-2.7.post1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7d12bbfa9001414892cc2bead57a2c1c7b2096de51e0517dc69720d49c4dd18"}, - {file = "crc32c-2.7.post1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b0d16f5f441eea70529c7b5354c3e6736e4b41b3ee23167d7dc149fc9827dd0"}, - {file = "crc32c-2.7.post1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a72af86290a77c374a70ba66538710154ea2d785f1f92df803d61a2d2e26fcb9"}, - {file = "crc32c-2.7.post1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c69dc0af50f880cc21a75870c066feb41170ad4ebf57a61c6bd267fc09409113"}, - {file = "crc32c-2.7.post1-cp313-cp313-win32.whl", hash = "sha256:3d72285ba0ec2b617535a3fb1891df63c66bd23afcfe48f49c37571d494c8e80"}, - {file = "crc32c-2.7.post1-cp313-cp313-win_amd64.whl", hash = "sha256:cf029cafc0f4dc4909657630c2a9e0f64515a00d3ed2ba8e8680230aba750d32"}, - {file = "crc32c-2.7.post1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a5b328c2e0c7aba774a5cf6b5abecab6a44da2fb65ad054cfbecfbd9d56bd919"}, - {file = "crc32c-2.7.post1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:707901882f983f532a59f0dfe3188311c4c259c5184030a798669ea0a3c6df02"}, - {file = "crc32c-2.7.post1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a57ab75a795d2623cfdecfabd228415512f08436bff094f0b8d58cd99d00749d"}, - {file = "crc32c-2.7.post1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab1510d64207deeb4d984bd1fbfddef4aaeb690c895d8319093974a2acbe1eb5"}, - {file = "crc32c-2.7.post1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6334e737fc6fc686b468cb8ee04c77612bd3c5134034d4ca43f11feba4aac38"}, - {file = "crc32c-2.7.post1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cb4ed6e317f63205007a85f6b1c47ce4d7017ec79218d76544ad5bcffd86471"}, - {file = "crc32c-2.7.post1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3919b77691c7abb53111af5fda909ea3411de54547823098bb178792baca8d8e"}, - {file = "crc32c-2.7.post1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:56baff3205a4d7b7997ded7b75a25a8999093e2be8ff007ae473da9b5d6820d3"}, - {file = "crc32c-2.7.post1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:69a40ac191cb68a0feb18f9b9c27ebce9eed1ff24b4a43bede507da58ab12bd7"}, - {file = "crc32c-2.7.post1-cp313-cp313t-win32.whl", hash = "sha256:ac23976fb8ff8d80ce9fa5f1674226b9bda5ee0724ff32629b90c959e3889e1b"}, - {file = "crc32c-2.7.post1-cp313-cp313t-win_amd64.whl", hash = "sha256:e034c90baae74e20760dede028986fb90356209b16fa5bc6b67e275009c7633e"}, - {file = "crc32c-2.7.post1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88326cc81a4ffb043118d7d14226746264a70f2915f616818c4c447797728811"}, - {file = "crc32c-2.7.post1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c52d6fb05713e0b7ffe527e7c82fb53f53ad2cbc75ccae6daa3c6b05672b10f5"}, - {file = "crc32c-2.7.post1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43ec3705d555deefc6cf4585c261f43e6398866b4c0ecf31bfd6fd7ae7b8f4d"}, - {file = "crc32c-2.7.post1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e1e2a143aee338800037fc690d73e50fa48d62a502b91323025d78bdcfb9b8d"}, - {file = "crc32c-2.7.post1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:b98c35b65f94a6412cca8008acc56d4de9c142690eda582a6e6a56f4a5705336"}, - {file = "crc32c-2.7.post1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:6b046eb3bbe08f575ca16ed9c2ca3f1ad80c027ed26589edcb01afea46609263"}, - {file = "crc32c-2.7.post1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:24279b85bff8afbddf843f0b1db1c558e561ffc81506b3efefc848cc73c3ab74"}, - {file = "crc32c-2.7.post1-cp37-cp37m-win32.whl", hash = "sha256:fbdb75248183dbeb7d20ad367d22984091486a1c6dde005f3a27155e6922ae54"}, - {file = "crc32c-2.7.post1-cp37-cp37m-win_amd64.whl", hash = "sha256:ad25a9902c9fdfbd6398626f38d042cadbb8cf2356179d41ca973a91b6c21cde"}, - {file = "crc32c-2.7.post1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:04ed7760775cd3bc4b0b12c2709e0754718d384e8e2c071906dd3bdc951c3e29"}, - {file = "crc32c-2.7.post1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a652fae8937e87a1f407adc26059e02c2d3f55cf6054a1dac4c5493b10de4bf7"}, - {file = "crc32c-2.7.post1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88e33b3626c3e41aad95ff5ff4a58776f4bdac47162326fda7e666022ae964a1"}, - {file = "crc32c-2.7.post1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33df9ded1059ba6400c96250e382d46ebc801b900ab570ee65501a89d0e80de9"}, - {file = "crc32c-2.7.post1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7627b798f43b474768bbaaa9aa90b527bc900486e101d11ec951b030d92b10bc"}, - {file = "crc32c-2.7.post1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a18ce07651c8aa4269f92c47db82ab706e7280fc13780996a572463b2b80c88c"}, - {file = "crc32c-2.7.post1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:43c30e2bc58b30829bee585aec679a1c51f8d6694dc1e80bca02402e0a666c3e"}, - {file = "crc32c-2.7.post1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ef825c6d1d38313856bcaf525d53a74d8a5c46dd486debef4683cbc2749c6503"}, - {file = "crc32c-2.7.post1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2ee908418a4a4bac14b401324583e76338cd42ecb3dce542f2605a2d424aa986"}, - {file = "crc32c-2.7.post1-cp38-cp38-win32.whl", hash = "sha256:ced5d7f244376eb93cd982f91086aa23fed0e35063f23f53a584f295c76c0175"}, - {file = "crc32c-2.7.post1-cp38-cp38-win_amd64.whl", hash = "sha256:720bb1fdb95c40844c210c9ddbc2126599072973e99361d78312991332e9d70d"}, - {file = "crc32c-2.7.post1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f447d50006ac554fd826ebde05df35111cc8b40278a5e024ebd3235c3992c153"}, - {file = "crc32c-2.7.post1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c543c454610ef6408b7cd58a9bc903aab93940e56d5487d516cb8f2045f489e5"}, - {file = "crc32c-2.7.post1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:560b3106b8cee56a4021688dd2d355367856e7bb2c6202df47945e616b6d11d9"}, - {file = "crc32c-2.7.post1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f54603d75db46dd4f356790cb22c5c916f34d78f831056005cdd82a162b8fb0f"}, - {file = "crc32c-2.7.post1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f2e85bbe0fd665af8dfbed561a29d5f722d4a6b332048675131ca8aed1174e5"}, - {file = "crc32c-2.7.post1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a37f866a54e4d953c122324a4480a226e9c86287f79fdbb4e925e29dfb69ed4f"}, - {file = "crc32c-2.7.post1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dea502de4416a98555bef2bdde35dad84dba9420dec61c8a8dc7c5ba24f695bd"}, - {file = "crc32c-2.7.post1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ef67964859fdf9eff810189584f1bdfb6fb8795a64cc7df3f8ed554262886fe3"}, - {file = "crc32c-2.7.post1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c705fff86da8f99afdf2e7956671b04b1cbd895377a60f1f2e6323feea34e7ff"}, - {file = "crc32c-2.7.post1-cp39-cp39-win32.whl", hash = "sha256:5e4e5be1dd9c9f9db6353b902b4028661a3ecd70e066012b75edc34f5f800b9a"}, - {file = "crc32c-2.7.post1-cp39-cp39-win_amd64.whl", hash = "sha256:6ac6436df05f7ed2ac781d87842857ced99d385da303c8357f10e0731384c6c3"}, - {file = "crc32c-2.7.post1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6e17edb22e7b79ec1c5637cda952efe331c0e4b461d36cee6a9c88627f191116"}, - {file = "crc32c-2.7.post1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:565da96247966333ad4c61e23b2d6515bc6c9b903f6adba1e02f2236c45becda"}, - {file = "crc32c-2.7.post1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a31c813184a858fc808b47f2300b5d10dc042b47a5d19321f6a0af63f1758cf7"}, - {file = "crc32c-2.7.post1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e889d50d3cf6d7aa5e3e21574eb31430f41587ee9d625fd47fb079f544f28eb6"}, - {file = "crc32c-2.7.post1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fea80119cd48e6081585313527770d31520031193c2c618f3cd1a956febbed3c"}, - {file = "crc32c-2.7.post1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8e9ab5f5b34c31064debf761dd794b2584236b4aad963c6f14888027bd15b754"}, - {file = "crc32c-2.7.post1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62d32f74e3314d81a8fa1aedc2a5e11e8827be6fe2c4491e013fb76a0b1a376a"}, - {file = "crc32c-2.7.post1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d8ea8249effc834a863b8eac39ddaedd65dc6cc7ce42b8458e02d7f2aaaf8f7"}, - {file = "crc32c-2.7.post1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd31b3192bb5f8a6b3f4fea3acb45ee1c35b0440292b23ad9fb32dae3b73b595"}, - {file = "crc32c-2.7.post1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:c0f66f76bb3674d7698ec36cf23d843bedda1149dcf884c22ba4499bf2d199d1"}, - {file = "crc32c-2.7.post1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c60a25729068d6d0153e31b52f3575125d63a1beb0a86e68392ce5e71147b3c1"}, - {file = "crc32c-2.7.post1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60deb9566de2336a1bf84c1f4569f30d2aae29176d63fe125479df81dd154aff"}, - {file = "crc32c-2.7.post1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b392645b57f8db8f45d447b37554b5feea09d87b4cb0d9ba5ae6632efaf4f204"}, - {file = "crc32c-2.7.post1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b00926c31de9f5bf94725bf0c9494d4fb67a86a59abf061e3ec46f751d7071d"}, - {file = "crc32c-2.7.post1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d01673c3b9f839e2a86d64ea6765b580fe99210b14b7cd1c75b4d8886d154f80"}, - {file = "crc32c-2.7.post1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4cc12415c7370457ca102e712642160f718df4d42def8fd16839d203ad155d85"}, - {file = "crc32c-2.7.post1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a122287d19c52d34e050ffa099c139deb592452fed69dc1b64e1832cc7c9eb48"}, - {file = "crc32c-2.7.post1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4002200c2524eb828907d5d581156cebff3bca8798fb995ad3869293fda18ff6"}, - {file = "crc32c-2.7.post1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ffebfe6f5f2514bf284fb9abfa1970096e580b113b8ec2b93f2a65c13769d36"}, - {file = "crc32c-2.7.post1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7a6224f895b4bcf115471c211d6f4c1e8f17eca1aaf1fb4b2bf65e61c0038eee"}, - {file = "crc32c-2.7.post1.tar.gz", hash = "sha256:645aa2738710f42b9f33352ebd79bf0de6b7f9e715642d93a939b5b57452871d"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[[package]] -name = "httpcore" -version = "1.0.5" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, - {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.13,<0.15" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.26.0)"] - -[[package]] -name = "httpx" -version = "0.27.2" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, - {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" -sniffio = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "idna" -version = "3.8" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -files = [ - {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, - {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, -] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "jsonpatch" -version = "1.33" -description = "Apply JSON-Patches (RFC 6902)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -files = [ - {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, - {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, -] - -[package.dependencies] -jsonpointer = ">=1.9" - -[[package]] -name = "jsonpointer" -version = "3.0.0" -description = "Identify specific nodes in a JSON document (RFC 6901)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, - {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, -] - -[[package]] -name = "kafka-python-ng" -version = "2.2.2" -description = "Pure Python client for Apache Kafka" -optional = false -python-versions = ">=3.8" -files = [ - {file = "kafka-python-ng-2.2.2.tar.gz", hash = "sha256:87ad3a766e2c0bec71d9b99bdd9e9c5cda62d96cfda61a8ca16510484d6ad7d4"}, - {file = "kafka_python_ng-2.2.2-py2.py3-none-any.whl", hash = "sha256:3fab1a03133fade1b6fd5367ff726d980e59031c4aaca9bf02c516840a4f8406"}, -] - -[package.extras] -boto = ["botocore"] -crc32c = ["crc32c"] -lz4 = ["lz4"] -snappy = ["python-snappy"] -zstd = ["zstandard"] - -[[package]] -name = "langchain-core" -version = "0.3.0" -description = "Building applications with LLMs through composability" -optional = false -python-versions = "<4.0,>=3.9" -files = [ - {file = "langchain_core-0.3.0-py3-none-any.whl", hash = "sha256:bee6dae2366d037ef0c5b87401fed14b5497cad26f97724e8c9ca7bc9239e847"}, - {file = "langchain_core-0.3.0.tar.gz", hash = "sha256:1249149ea3ba24c9c761011483c14091573a5eb1a773aa0db9c8ad155dd4a69d"}, -] - -[package.dependencies] -jsonpatch = ">=1.33,<2.0" -langsmith = ">=0.1.117,<0.2.0" -packaging = ">=23.2,<25" -pydantic = [ - {version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""}, - {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, -] -PyYAML = ">=5.3" -tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0" -typing-extensions = ">=4.7" - -[[package]] -name = "langgraph" -version = "0.2.20" -description = "Building stateful, multi-actor applications with LLMs" -optional = false -python-versions = ">=3.9.0,<4.0" -files = [] -develop = true - -[package.dependencies] -langchain-core = ">=0.2.39,<0.4" -langgraph-checkpoint = "^1.0.2" - -[package.source] -type = "directory" -url = "../langgraph" - -[[package]] -name = "langgraph-checkpoint" -version = "1.0.9" -description = "Library with base interfaces for LangGraph checkpoint savers." -optional = false -python-versions = "^3.9.0,<4.0" -files = [] -develop = true - -[package.dependencies] -langchain-core = ">=0.2.38,<0.4" -msgpack = "^1.1.0" - -[package.source] -type = "directory" -url = "../checkpoint" - -[[package]] -name = "langgraph-checkpoint-postgres" -version = "1.0.6" -description = "Library with a Postgres implementation of LangGraph checkpoint saver." -optional = false -python-versions = "^3.9.0,<4.0" -files = [] -develop = true - -[package.dependencies] -langgraph-checkpoint = "^1.0.8" -orjson = ">=3.10.1" -psycopg = "^3.0.0" -psycopg-pool = "^3.0.0" - -[package.source] -type = "directory" -url = "../checkpoint-postgres" - -[[package]] -name = "langsmith" -version = "0.1.120" -description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." -optional = false -python-versions = "<4.0,>=3.8.1" -files = [ - {file = "langsmith-0.1.120-py3-none-any.whl", hash = "sha256:54d2785e301646c0988e0a69ebe4d976488c87b41928b358cb153b6ddd8db62b"}, - {file = "langsmith-0.1.120.tar.gz", hash = "sha256:25499ca187b41bd89d784b272b97a8d76f60e0e21bdf20336e8a2aa6a9b23ac9"}, -] - -[package.dependencies] -httpx = ">=0.23.0,<1" -orjson = ">=3.9.14,<4.0.0" -pydantic = [ - {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""}, - {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, -] -requests = ">=2,<3" - -[[package]] -name = "msgpack" -version = "1.1.0" -description = "MessagePack serializer" -optional = false -python-versions = ">=3.8" -files = [ - {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, - {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, - {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"}, - {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"}, - {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"}, - {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"}, - {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"}, - {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"}, - {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"}, - {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"}, - {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"}, - {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"}, - {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"}, - {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"}, - {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"}, - {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, -] - -[[package]] -name = "mypy" -version = "1.11.0" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy-1.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3824187c99b893f90c845bab405a585d1ced4ff55421fdf5c84cb7710995229"}, - {file = "mypy-1.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:96f8dbc2c85046c81bcddc246232d500ad729cb720da4e20fce3b542cab91287"}, - {file = "mypy-1.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a5d8d8dd8613a3e2be3eae829ee891b6b2de6302f24766ff06cb2875f5be9c6"}, - {file = "mypy-1.11.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72596a79bbfb195fd41405cffa18210af3811beb91ff946dbcb7368240eed6be"}, - {file = "mypy-1.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:35ce88b8ed3a759634cb4eb646d002c4cef0a38f20565ee82b5023558eb90c00"}, - {file = "mypy-1.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:98790025861cb2c3db8c2f5ad10fc8c336ed2a55f4daf1b8b3f877826b6ff2eb"}, - {file = "mypy-1.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25bcfa75b9b5a5f8d67147a54ea97ed63a653995a82798221cca2a315c0238c1"}, - {file = "mypy-1.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bea2a0e71c2a375c9fa0ede3d98324214d67b3cbbfcbd55ac8f750f85a414e3"}, - {file = "mypy-1.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2b3d36baac48e40e3064d2901f2fbd2a2d6880ec6ce6358825c85031d7c0d4d"}, - {file = "mypy-1.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:d8e2e43977f0e09f149ea69fd0556623919f816764e26d74da0c8a7b48f3e18a"}, - {file = "mypy-1.11.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d44c1e44a8be986b54b09f15f2c1a66368eb43861b4e82573026e04c48a9e20"}, - {file = "mypy-1.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cea3d0fb69637944dd321f41bc896e11d0fb0b0aa531d887a6da70f6e7473aba"}, - {file = "mypy-1.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a83ec98ae12d51c252be61521aa5731f5512231d0b738b4cb2498344f0b840cd"}, - {file = "mypy-1.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c7b73a856522417beb78e0fb6d33ef89474e7a622db2653bc1285af36e2e3e3d"}, - {file = "mypy-1.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:f2268d9fcd9686b61ab64f077be7ffbc6fbcdfb4103e5dd0cc5eaab53a8886c2"}, - {file = "mypy-1.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:940bfff7283c267ae6522ef926a7887305945f716a7704d3344d6d07f02df850"}, - {file = "mypy-1.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:14f9294528b5f5cf96c721f231c9f5b2733164e02c1c018ed1a0eff8a18005ac"}, - {file = "mypy-1.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7b54c27783991399046837df5c7c9d325d921394757d09dbcbf96aee4649fe9"}, - {file = "mypy-1.11.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:65f190a6349dec29c8d1a1cd4aa71284177aee5949e0502e6379b42873eddbe7"}, - {file = "mypy-1.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:dbe286303241fea8c2ea5466f6e0e6a046a135a7e7609167b07fd4e7baf151bf"}, - {file = "mypy-1.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:104e9c1620c2675420abd1f6c44bab7dd33cc85aea751c985006e83dcd001095"}, - {file = "mypy-1.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f006e955718ecd8d159cee9932b64fba8f86ee6f7728ca3ac66c3a54b0062abe"}, - {file = "mypy-1.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:becc9111ca572b04e7e77131bc708480cc88a911adf3d0239f974c034b78085c"}, - {file = "mypy-1.11.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6801319fe76c3f3a3833f2b5af7bd2c17bb93c00026a2a1b924e6762f5b19e13"}, - {file = "mypy-1.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:c1a184c64521dc549324ec6ef7cbaa6b351912be9cb5edb803c2808a0d7e85ac"}, - {file = "mypy-1.11.0-py3-none-any.whl", hash = "sha256:56913ec8c7638b0091ef4da6fcc9136896914a9d60d54670a75880c3e5b99ace"}, - {file = "mypy-1.11.0.tar.gz", hash = "sha256:93743608c7348772fdc717af4aeee1997293a1ad04bc0ea6efa15bf65385c538"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "orjson" -version = "3.10.7" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -optional = false -python-versions = ">=3.8" -files = [ - {file = "orjson-3.10.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:74f4544f5a6405b90da8ea724d15ac9c36da4d72a738c64685003337401f5c12"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34a566f22c28222b08875b18b0dfbf8a947e69df21a9ed5c51a6bf91cfb944ac"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf6ba8ebc8ef5792e2337fb0419f8009729335bb400ece005606336b7fd7bab7"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac7cf6222b29fbda9e3a472b41e6a5538b48f2c8f99261eecd60aafbdb60690c"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de817e2f5fc75a9e7dd350c4b0f54617b280e26d1631811a43e7e968fa71e3e9"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:348bdd16b32556cf8d7257b17cf2bdb7ab7976af4af41ebe79f9796c218f7e91"}, - {file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:479fd0844ddc3ca77e0fd99644c7fe2de8e8be1efcd57705b5c92e5186e8a250"}, - {file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fdf5197a21dd660cf19dfd2a3ce79574588f8f5e2dbf21bda9ee2d2b46924d84"}, - {file = "orjson-3.10.7-cp310-none-win32.whl", hash = "sha256:d374d36726746c81a49f3ff8daa2898dccab6596864ebe43d50733275c629175"}, - {file = "orjson-3.10.7-cp310-none-win_amd64.whl", hash = "sha256:cb61938aec8b0ffb6eef484d480188a1777e67b05d58e41b435c74b9d84e0b9c"}, - {file = "orjson-3.10.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7db8539039698ddfb9a524b4dd19508256107568cdad24f3682d5773e60504a2"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:480f455222cb7a1dea35c57a67578848537d2602b46c464472c995297117fa09"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8a9c9b168b3a19e37fe2778c0003359f07822c90fdff8f98d9d2a91b3144d8e0"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8de062de550f63185e4c1c54151bdddfc5625e37daf0aa1e75d2a1293e3b7d9a"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b0dd04483499d1de9c8f6203f8975caf17a6000b9c0c54630cef02e44ee624e"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b58d3795dafa334fc8fd46f7c5dc013e6ad06fd5b9a4cc98cb1456e7d3558bd6"}, - {file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33cfb96c24034a878d83d1a9415799a73dc77480e6c40417e5dda0710d559ee6"}, - {file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e724cebe1fadc2b23c6f7415bad5ee6239e00a69f30ee423f319c6af70e2a5c0"}, - {file = "orjson-3.10.7-cp311-none-win32.whl", hash = "sha256:82763b46053727a7168d29c772ed5c870fdae2f61aa8a25994c7984a19b1021f"}, - {file = "orjson-3.10.7-cp311-none-win_amd64.whl", hash = "sha256:eb8d384a24778abf29afb8e41d68fdd9a156cf6e5390c04cc07bbc24b89e98b5"}, - {file = "orjson-3.10.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44a96f2d4c3af51bfac6bc4ef7b182aa33f2f054fd7f34cc0ee9a320d051d41f"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ac14cd57df0572453543f8f2575e2d01ae9e790c21f57627803f5e79b0d3c3"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bdbb61dcc365dd9be94e8f7df91975edc9364d6a78c8f7adb69c1cdff318ec93"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b48b3db6bb6e0a08fa8c83b47bc169623f801e5cc4f24442ab2b6617da3b5313"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23820a1563a1d386414fef15c249040042b8e5d07b40ab3fe3efbfbbcbcb8864"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0c6a008e91d10a2564edbb6ee5069a9e66df3fbe11c9a005cb411f441fd2c09"}, - {file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d352ee8ac1926d6193f602cbe36b1643bbd1bbcb25e3c1a657a4390f3000c9a5"}, - {file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d2d9f990623f15c0ae7ac608103c33dfe1486d2ed974ac3f40b693bad1a22a7b"}, - {file = "orjson-3.10.7-cp312-none-win32.whl", hash = "sha256:7c4c17f8157bd520cdb7195f75ddbd31671997cbe10aee559c2d613592e7d7eb"}, - {file = "orjson-3.10.7-cp312-none-win_amd64.whl", hash = "sha256:1d9c0e733e02ada3ed6098a10a8ee0052dd55774de3d9110d29868d24b17faa1"}, - {file = "orjson-3.10.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:77d325ed866876c0fa6492598ec01fe30e803272a6e8b10e992288b009cbe149"}, - {file = "orjson-3.10.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ea2c232deedcb605e853ae1db2cc94f7390ac776743b699b50b071b02bea6fe"}, - {file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3dcfbede6737fdbef3ce9c37af3fb6142e8e1ebc10336daa05872bfb1d87839c"}, - {file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11748c135f281203f4ee695b7f80bb1358a82a63905f9f0b794769483ea854ad"}, - {file = "orjson-3.10.7-cp313-none-win32.whl", hash = "sha256:a7e19150d215c7a13f39eb787d84db274298d3f83d85463e61d277bbd7f401d2"}, - {file = "orjson-3.10.7-cp313-none-win_amd64.whl", hash = "sha256:eef44224729e9525d5261cc8d28d6b11cafc90e6bd0be2157bde69a52ec83024"}, - {file = "orjson-3.10.7-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6ea2b2258eff652c82652d5e0f02bd5e0463a6a52abb78e49ac288827aaa1469"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:430ee4d85841e1483d487e7b81401785a5dfd69db5de01314538f31f8fbf7ee1"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4b6146e439af4c2472c56f8540d799a67a81226e11992008cb47e1267a9b3225"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:084e537806b458911137f76097e53ce7bf5806dda33ddf6aaa66a028f8d43a23"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4829cf2195838e3f93b70fd3b4292156fc5e097aac3739859ac0dcc722b27ac0"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1193b2416cbad1a769f868b1749535d5da47626ac29445803dae7cc64b3f5c98"}, - {file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4e6c3da13e5a57e4b3dca2de059f243ebec705857522f188f0180ae88badd354"}, - {file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c31008598424dfbe52ce8c5b47e0752dca918a4fdc4a2a32004efd9fab41d866"}, - {file = "orjson-3.10.7-cp38-none-win32.whl", hash = "sha256:7122a99831f9e7fe977dc45784d3b2edc821c172d545e6420c375e5a935f5a1c"}, - {file = "orjson-3.10.7-cp38-none-win_amd64.whl", hash = "sha256:a763bc0e58504cc803739e7df040685816145a6f3c8a589787084b54ebc9f16e"}, - {file = "orjson-3.10.7-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e76be12658a6fa376fcd331b1ea4e58f5a06fd0220653450f0d415b8fd0fbe20"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed350d6978d28b92939bfeb1a0570c523f6170efc3f0a0ef1f1df287cd4f4960"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:144888c76f8520e39bfa121b31fd637e18d4cc2f115727865fdf9fa325b10412"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09b2d92fd95ad2402188cf51573acde57eb269eddabaa60f69ea0d733e789fe9"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b24a579123fa884f3a3caadaed7b75eb5715ee2b17ab5c66ac97d29b18fe57f"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591bcfe7512353bd609875ab38050efe3d55e18934e2f18950c108334b4ff"}, - {file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f4db56635b58cd1a200b0a23744ff44206ee6aa428185e2b6c4a65b3197abdcd"}, - {file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0fa5886854673222618638c6df7718ea7fe2f3f2384c452c9ccedc70b4a510a5"}, - {file = "orjson-3.10.7-cp39-none-win32.whl", hash = "sha256:8272527d08450ab16eb405f47e0f4ef0e5ff5981c3d82afe0efd25dcbef2bcd2"}, - {file = "orjson-3.10.7-cp39-none-win_amd64.whl", hash = "sha256:974683d4618c0c7dbf4f69c95a979734bf183d0658611760017f6e70a145af58"}, - {file = "orjson-3.10.7.tar.gz", hash = "sha256:75ef0640403f945f3a1f9f6400686560dbfb0fb5b16589ad62cd477043c4eee3"}, -] - -[[package]] -name = "packaging" -version = "24.1" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, -] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "psycopg" -version = "3.2.1" -description = "PostgreSQL database adapter for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "psycopg-3.2.1-py3-none-any.whl", hash = "sha256:ece385fb413a37db332f97c49208b36cf030ff02b199d7635ed2fbd378724175"}, - {file = "psycopg-3.2.1.tar.gz", hash = "sha256:dc8da6dc8729dacacda3cc2f17d2c9397a70a66cf0d2b69c91065d60d5f00cb7"}, -] - -[package.dependencies] -typing-extensions = ">=4.4" -tzdata = {version = "*", markers = "sys_platform == \"win32\""} - -[package.extras] -binary = ["psycopg-binary (==3.2.1)"] -c = ["psycopg-c (==3.2.1)"] -dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.6)", "types-setuptools (>=57.4)", "wheel (>=0.37)"] -docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"] -pool = ["psycopg-pool"] -test = ["anyio (>=4.0)", "mypy (>=1.6)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] - -[[package]] -name = "psycopg-pool" -version = "3.2.2" -description = "Connection Pool for Psycopg" -optional = false -python-versions = ">=3.8" -files = [ - {file = "psycopg_pool-3.2.2-py3-none-any.whl", hash = "sha256:273081d0fbfaced4f35e69200c89cb8fbddfe277c38cc86c235b90a2ec2c8153"}, - {file = "psycopg_pool-3.2.2.tar.gz", hash = "sha256:9e22c370045f6d7f2666a5ad1b0caf345f9f1912195b0b25d0d3bcc4f3a7389c"}, -] - -[package.dependencies] -typing-extensions = ">=4.4" - -[[package]] -name = "pydantic" -version = "2.9.0" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic-2.9.0-py3-none-any.whl", hash = "sha256:f66a7073abd93214a20c5f7b32d56843137a7a2e70d02111f3be287035c45370"}, - {file = "pydantic-2.9.0.tar.gz", hash = "sha256:c7a8a9fdf7d100afa49647eae340e2d23efa382466a8d177efcd1381e9be5598"}, -] - -[package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.23.2" -typing-extensions = [ - {version = ">=4.6.1", markers = "python_version < \"3.13\""}, - {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, -] -tzdata = {version = "*", markers = "python_version >= \"3.9\""} - -[package.extras] -email = ["email-validator (>=2.0.0)"] - -[[package]] -name = "pydantic-core" -version = "2.23.2" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic_core-2.23.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7d0324a35ab436c9d768753cbc3c47a865a2cbc0757066cb864747baa61f6ece"}, - {file = "pydantic_core-2.23.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:276ae78153a94b664e700ac362587c73b84399bd1145e135287513442e7dfbc7"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:964c7aa318da542cdcc60d4a648377ffe1a2ef0eb1e996026c7f74507b720a78"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1cf842265a3a820ebc6388b963ead065f5ce8f2068ac4e1c713ef77a67b71f7c"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae90b9e50fe1bd115b24785e962b51130340408156d34d67b5f8f3fa6540938e"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ae65fdfb8a841556b52935dfd4c3f79132dc5253b12c0061b96415208f4d622"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c8aa40f6ca803f95b1c1c5aeaee6237b9e879e4dfb46ad713229a63651a95fb"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c53100c8ee5a1e102766abde2158077d8c374bee0639201f11d3032e3555dfbc"}, - {file = "pydantic_core-2.23.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6b9dd6aa03c812017411734e496c44fef29b43dba1e3dd1fa7361bbacfc1354"}, - {file = "pydantic_core-2.23.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b18cf68255a476b927910c6873d9ed00da692bb293c5b10b282bd48a0afe3ae2"}, - {file = "pydantic_core-2.23.2-cp310-none-win32.whl", hash = "sha256:e460475719721d59cd54a350c1f71c797c763212c836bf48585478c5514d2854"}, - {file = "pydantic_core-2.23.2-cp310-none-win_amd64.whl", hash = "sha256:5f3cf3721eaf8741cffaf092487f1ca80831202ce91672776b02b875580e174a"}, - {file = "pydantic_core-2.23.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7ce8e26b86a91e305858e018afc7a6e932f17428b1eaa60154bd1f7ee888b5f8"}, - {file = "pydantic_core-2.23.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e9b24cca4037a561422bf5dc52b38d390fb61f7bfff64053ce1b72f6938e6b2"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753294d42fb072aa1775bfe1a2ba1012427376718fa4c72de52005a3d2a22178"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:257d6a410a0d8aeb50b4283dea39bb79b14303e0fab0f2b9d617701331ed1515"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8319e0bd6a7b45ad76166cc3d5d6a36c97d0c82a196f478c3ee5346566eebfd"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a05c0240f6c711eb381ac392de987ee974fa9336071fb697768dfdb151345ce"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d5b0ff3218858859910295df6953d7bafac3a48d5cd18f4e3ed9999efd2245f"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:96ef39add33ff58cd4c112cbac076726b96b98bb8f1e7f7595288dcfb2f10b57"}, - {file = "pydantic_core-2.23.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0102e49ac7d2df3379ef8d658d3bc59d3d769b0bdb17da189b75efa861fc07b4"}, - {file = "pydantic_core-2.23.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a6612c2a844043e4d10a8324c54cdff0042c558eef30bd705770793d70b224aa"}, - {file = "pydantic_core-2.23.2-cp311-none-win32.whl", hash = "sha256:caffda619099cfd4f63d48462f6aadbecee3ad9603b4b88b60cb821c1b258576"}, - {file = "pydantic_core-2.23.2-cp311-none-win_amd64.whl", hash = "sha256:6f80fba4af0cb1d2344869d56430e304a51396b70d46b91a55ed4959993c0589"}, - {file = "pydantic_core-2.23.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c83c64d05ffbbe12d4e8498ab72bdb05bcc1026340a4a597dc647a13c1605ec"}, - {file = "pydantic_core-2.23.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6294907eaaccf71c076abdd1c7954e272efa39bb043161b4b8aa1cd76a16ce43"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a801c5e1e13272e0909c520708122496647d1279d252c9e6e07dac216accc41"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc0c316fba3ce72ac3ab7902a888b9dc4979162d320823679da270c2d9ad0cad"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b06c5d4e8701ac2ba99a2ef835e4e1b187d41095a9c619c5b185c9068ed2a49"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82764c0bd697159fe9947ad59b6db6d7329e88505c8f98990eb07e84cc0a5d81"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b1a195efd347ede8bcf723e932300292eb13a9d2a3c1f84eb8f37cbbc905b7f"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7efb12e5071ad8d5b547487bdad489fbd4a5a35a0fc36a1941517a6ad7f23e0"}, - {file = "pydantic_core-2.23.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5dd0ec5f514ed40e49bf961d49cf1bc2c72e9b50f29a163b2cc9030c6742aa73"}, - {file = "pydantic_core-2.23.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:820f6ee5c06bc868335e3b6e42d7ef41f50dfb3ea32fbd523ab679d10d8741c0"}, - {file = "pydantic_core-2.23.2-cp312-none-win32.whl", hash = "sha256:3713dc093d5048bfaedbba7a8dbc53e74c44a140d45ede020dc347dda18daf3f"}, - {file = "pydantic_core-2.23.2-cp312-none-win_amd64.whl", hash = "sha256:e1895e949f8849bc2757c0dbac28422a04be031204df46a56ab34bcf98507342"}, - {file = "pydantic_core-2.23.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:da43cbe593e3c87d07108d0ebd73771dc414488f1f91ed2e204b0370b94b37ac"}, - {file = "pydantic_core-2.23.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:64d094ea1aa97c6ded4748d40886076a931a8bf6f61b6e43e4a1041769c39dd2"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:084414ffe9a85a52940b49631321d636dadf3576c30259607b75516d131fecd0"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:043ef8469f72609c4c3a5e06a07a1f713d53df4d53112c6d49207c0bd3c3bd9b"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3649bd3ae6a8ebea7dc381afb7f3c6db237fc7cebd05c8ac36ca8a4187b03b30"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6db09153d8438425e98cdc9a289c5fade04a5d2128faff8f227c459da21b9703"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5668b3173bb0b2e65020b60d83f5910a7224027232c9f5dc05a71a1deac9f960"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1c7b81beaf7c7ebde978377dc53679c6cba0e946426fc7ade54251dfe24a7604"}, - {file = "pydantic_core-2.23.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:ae579143826c6f05a361d9546446c432a165ecf1c0b720bbfd81152645cb897d"}, - {file = "pydantic_core-2.23.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:19f1352fe4b248cae22a89268720fc74e83f008057a652894f08fa931e77dced"}, - {file = "pydantic_core-2.23.2-cp313-none-win32.whl", hash = "sha256:e1a79ad49f346aa1a2921f31e8dbbab4d64484823e813a002679eaa46cba39e1"}, - {file = "pydantic_core-2.23.2-cp313-none-win_amd64.whl", hash = "sha256:582871902e1902b3c8e9b2c347f32a792a07094110c1bca6c2ea89b90150caac"}, - {file = "pydantic_core-2.23.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:743e5811b0c377eb830150d675b0847a74a44d4ad5ab8845923d5b3a756d8100"}, - {file = "pydantic_core-2.23.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6650a7bbe17a2717167e3e23c186849bae5cef35d38949549f1c116031b2b3aa"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56e6a12ec8d7679f41b3750ffa426d22b44ef97be226a9bab00a03365f217b2b"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:810ca06cca91de9107718dc83d9ac4d2e86efd6c02cba49a190abcaf33fb0472"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:785e7f517ebb9890813d31cb5d328fa5eda825bb205065cde760b3150e4de1f7"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ef71ec876fcc4d3bbf2ae81961959e8d62f8d74a83d116668409c224012e3af"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d50ac34835c6a4a0d456b5db559b82047403c4317b3bc73b3455fefdbdc54b0a"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16b25a4a120a2bb7dab51b81e3d9f3cde4f9a4456566c403ed29ac81bf49744f"}, - {file = "pydantic_core-2.23.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:41ae8537ad371ec018e3c5da0eb3f3e40ee1011eb9be1da7f965357c4623c501"}, - {file = "pydantic_core-2.23.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07049ec9306ec64e955b2e7c40c8d77dd78ea89adb97a2013d0b6e055c5ee4c5"}, - {file = "pydantic_core-2.23.2-cp38-none-win32.whl", hash = "sha256:086c5db95157dc84c63ff9d96ebb8856f47ce113c86b61065a066f8efbe80acf"}, - {file = "pydantic_core-2.23.2-cp38-none-win_amd64.whl", hash = "sha256:67b6655311b00581914aba481729971b88bb8bc7996206590700a3ac85e457b8"}, - {file = "pydantic_core-2.23.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:358331e21a897151e54d58e08d0219acf98ebb14c567267a87e971f3d2a3be59"}, - {file = "pydantic_core-2.23.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c4d9f15ffe68bcd3898b0ad7233af01b15c57d91cd1667f8d868e0eacbfe3f87"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0123655fedacf035ab10c23450163c2f65a4174f2bb034b188240a6cf06bb123"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e6e3ccebdbd6e53474b0bb7ab8b88e83c0cfe91484b25e058e581348ee5a01a5"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc535cb898ef88333cf317777ecdfe0faac1c2a3187ef7eb061b6f7ecf7e6bae"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aab9e522efff3993a9e98ab14263d4e20211e62da088298089a03056980a3e69"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05b366fb8fe3d8683b11ac35fa08947d7b92be78ec64e3277d03bd7f9b7cda79"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7568f682c06f10f30ef643a1e8eec4afeecdafde5c4af1b574c6df079e96f96c"}, - {file = "pydantic_core-2.23.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cdd02a08205dc90238669f082747612cb3c82bd2c717adc60f9b9ecadb540f80"}, - {file = "pydantic_core-2.23.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a2ab4f410f4b886de53b6bddf5dd6f337915a29dd9f22f20f3099659536b2f6"}, - {file = "pydantic_core-2.23.2-cp39-none-win32.whl", hash = "sha256:0448b81c3dfcde439551bb04a9f41d7627f676b12701865c8a2574bcea034437"}, - {file = "pydantic_core-2.23.2-cp39-none-win_amd64.whl", hash = "sha256:4cebb9794f67266d65e7e4cbe5dcf063e29fc7b81c79dc9475bd476d9534150e"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e758d271ed0286d146cf7c04c539a5169a888dd0b57026be621547e756af55bc"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f477d26183e94eaafc60b983ab25af2a809a1b48ce4debb57b343f671b7a90b6"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da3131ef2b940b99106f29dfbc30d9505643f766704e14c5d5e504e6a480c35e"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329a721253c7e4cbd7aad4a377745fbcc0607f9d72a3cc2102dd40519be75ed2"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7706e15cdbf42f8fab1e6425247dfa98f4a6f8c63746c995d6a2017f78e619ae"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e64ffaf8f6e17ca15eb48344d86a7a741454526f3a3fa56bc493ad9d7ec63936"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dd59638025160056687d598b054b64a79183f8065eae0d3f5ca523cde9943940"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:12625e69b1199e94b0ae1c9a95d000484ce9f0182f9965a26572f054b1537e44"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5d813fd871b3d5c3005157622ee102e8908ad6011ec915a18bd8fde673c4360e"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1eb37f7d6a8001c0f86dc8ff2ee8d08291a536d76e49e78cda8587bb54d8b329"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ce7eaf9a98680b4312b7cebcdd9352531c43db00fca586115845df388f3c465"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f087879f1ffde024dd2788a30d55acd67959dcf6c431e9d3682d1c491a0eb474"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ce883906810b4c3bd90e0ada1f9e808d9ecf1c5f0b60c6b8831d6100bcc7dd6"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a8031074a397a5925d06b590121f8339d34a5a74cfe6970f8a1124eb8b83f4ac"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23af245b8f2f4ee9e2c99cb3f93d0e22fb5c16df3f2f643f5a8da5caff12a653"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c57e493a0faea1e4c38f860d6862ba6832723396c884fbf938ff5e9b224200e2"}, - {file = "pydantic_core-2.23.2.tar.gz", hash = "sha256:95d6bf449a1ac81de562d65d180af5d8c19672793c81877a2eda8fde5d08f2fd"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pytest" -version = "7.4.4" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-mock" -version = "3.14.0" -description = "Thin-wrapper around the mock package for easier use with pytest" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, - {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, -] - -[package.dependencies] -pytest = ">=6.2.5" - -[package.extras] -dev = ["pre-commit", "pytest-asyncio", "tox"] - -[[package]] -name = "pytest-watcher" -version = "0.4.2" -description = "Automatically rerun your tests on file modifications" -optional = false -python-versions = "<4.0.0,>=3.7.0" -files = [ - {file = "pytest_watcher-0.4.2-py3-none-any.whl", hash = "sha256:a43949ba67dd8d7e1fd0de5eea44a999081f0aec9f93b4e744264b4c6a3d9bbe"}, - {file = "pytest_watcher-0.4.2.tar.gz", hash = "sha256:7b292f025ca19617cd7567c228c6187b5087f2da9e4d2cf6e144e5764a0471b0"}, -] - -[package.dependencies] -tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} -watchdog = ">=2.0.0" - -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "ruff" -version = "0.6.2" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -files = [ - {file = "ruff-0.6.2-py3-none-linux_armv6l.whl", hash = "sha256:5c8cbc6252deb3ea840ad6a20b0f8583caab0c5ef4f9cca21adc5a92b8f79f3c"}, - {file = "ruff-0.6.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:17002fe241e76544448a8e1e6118abecbe8cd10cf68fde635dad480dba594570"}, - {file = "ruff-0.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3dbeac76ed13456f8158b8f4fe087bf87882e645c8e8b606dd17b0b66c2c1158"}, - {file = "ruff-0.6.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:094600ee88cda325988d3f54e3588c46de5c18dae09d683ace278b11f9d4d534"}, - {file = "ruff-0.6.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:316d418fe258c036ba05fbf7dfc1f7d3d4096db63431546163b472285668132b"}, - {file = "ruff-0.6.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d72b8b3abf8a2d51b7b9944a41307d2f442558ccb3859bbd87e6ae9be1694a5d"}, - {file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2aed7e243be68487aa8982e91c6e260982d00da3f38955873aecd5a9204b1d66"}, - {file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d371f7fc9cec83497fe7cf5eaf5b76e22a8efce463de5f775a1826197feb9df8"}, - {file = "ruff-0.6.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8f310d63af08f583363dfb844ba8f9417b558199c58a5999215082036d795a1"}, - {file = "ruff-0.6.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7db6880c53c56addb8638fe444818183385ec85eeada1d48fc5abe045301b2f1"}, - {file = "ruff-0.6.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1175d39faadd9a50718f478d23bfc1d4da5743f1ab56af81a2b6caf0a2394f23"}, - {file = "ruff-0.6.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939f9c86d51635fe486585389f54582f0d65b8238e08c327c1534844b3bb9a"}, - {file = "ruff-0.6.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d0d62ca91219f906caf9b187dea50d17353f15ec9bb15aae4a606cd697b49b4c"}, - {file = "ruff-0.6.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7438a7288f9d67ed3c8ce4d059e67f7ed65e9fe3aa2ab6f5b4b3610e57e3cb56"}, - {file = "ruff-0.6.2-py3-none-win32.whl", hash = "sha256:279d5f7d86696df5f9549b56b9b6a7f6c72961b619022b5b7999b15db392a4da"}, - {file = "ruff-0.6.2-py3-none-win_amd64.whl", hash = "sha256:d9f3469c7dd43cd22eb1c3fc16926fb8258d50cb1b216658a07be95dd117b0f2"}, - {file = "ruff-0.6.2-py3-none-win_arm64.whl", hash = "sha256:f28fcd2cd0e02bdf739297516d5643a945cc7caf09bd9bcb4d932540a5ea4fa9"}, - {file = "ruff-0.6.2.tar.gz", hash = "sha256:239ee6beb9e91feb8e0ec384204a763f36cb53fb895a1a364618c6abb076b3be"}, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "tenacity" -version = "8.5.0" -description = "Retry code until it succeeds" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687"}, - {file = "tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78"}, -] - -[package.extras] -doc = ["reno", "sphinx"] -test = ["pytest", "tornado (>=4.5)", "typeguard"] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "tzdata" -version = "2024.1" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, -] - -[[package]] -name = "urllib3" -version = "2.2.2" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -files = [ - {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, - {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "watchdog" -version = "4.0.1" -description = "Filesystem events monitoring" -optional = false -python-versions = ">=3.8" -files = [ - {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"}, - {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"}, - {file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"}, - {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"}, - {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"}, - {file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"}, - {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"}, - {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"}, - {file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"}, - {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35"}, - {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db"}, - {file = "watchdog-4.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709"}, - {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba"}, - {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235"}, - {file = "watchdog-4.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682"}, - {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"}, - {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"}, - {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193"}, - {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625"}, - {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd"}, - {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"}, - {file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"}, - {file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"}, - {file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"}, - {file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"}, -] - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[metadata] -lock-version = "2.0" -python-versions = "^3.9.0,<4.0" -content-hash = "4fd0a2d16956a5e92ef42cbd23a1649cc5cefcc2da1d58d0065fa355668dfaa9" diff --git a/libs/scheduler-kafka/pyproject.toml b/libs/scheduler-kafka/pyproject.toml deleted file mode 100644 index 84f7b7fb3..000000000 --- a/libs/scheduler-kafka/pyproject.toml +++ /dev/null @@ -1,58 +0,0 @@ -[tool.poetry] -name = "langgraph-scheduler-kafka" -version = "1.0.0" -description = "Library with Kafka-based work scheduler." -authors = [] -license = "MIT" -readme = "README.md" -repository = "https://www.github.com/langchain-ai/langgraph" -packages = [{ include = "langgraph" }] - -[tool.poetry.dependencies] -python = "^3.9.0,<4.0" -orjson = "^3.10.7" -crc32c = "^2.7.post1" -aiokafka = "^0.11.0" -langgraph = "^0.2.19" - -[tool.poetry.group.dev.dependencies] -ruff = "^0.6.2" -codespell = "^2.2.0" -pytest = "^7.2.1" -pytest-mock = "^3.11.1" -pytest-watcher = "^0.4.1" -mypy = "^1.10.0" -langgraph = {path = "../langgraph", develop = true} -langgraph-checkpoint-postgres = {path = "../checkpoint-postgres", develop = true} -langgraph-checkpoint = {path = "../checkpoint", develop = true} -kafka-python-ng = "^2.2.2" - -[tool.pytest.ini_options] -# --strict-markers will raise errors on unknown marks. -# https://docs.pytest.org/en/7.1.x/how-to/mark.html#raising-errors-on-unknown-marks -# -# https://docs.pytest.org/en/7.1.x/reference/reference.html -# --strict-config any warnings encountered while parsing the `pytest` -# section of the configuration file raise errors. -addopts = "--strict-markers --strict-config --durations=5 -vv" - - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" - -[tool.ruff] -lint.select = [ - "E", # pycodestyle - "F", # Pyflakes - "UP", # pyupgrade - "B", # flake8-bugbear - "I", # isort -] -lint.ignore = ["E501", "B008", "UP007", "UP006"] - -[tool.pytest-watcher] -now = true -delay = 0.1 -runner_args = ["--ff", "-v", "--tb", "short", "-s"] -patterns = ["*.py"] diff --git a/libs/scheduler-kafka/tests/__init__.py b/libs/scheduler-kafka/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/libs/scheduler-kafka/tests/any.py b/libs/scheduler-kafka/tests/any.py deleted file mode 100644 index 0336d8506..000000000 --- a/libs/scheduler-kafka/tests/any.py +++ /dev/null @@ -1,63 +0,0 @@ -import re -from typing import Union - - -class AnyStr(str): - def __init__(self, prefix: Union[str, re.Pattern] = "") -> None: - super().__init__() - self.prefix = prefix - - def __eq__(self, other: object) -> bool: - return isinstance(other, str) and ( - other.startswith(self.prefix) - if isinstance(self.prefix, str) - else self.prefix.match(other) - ) - - def __hash__(self) -> int: - return hash((str(self), self.prefix)) - - -class AnyDict(dict): - def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - - def __eq__(self, other: object) -> bool: - if not self and isinstance(other, dict): - return True - if not isinstance(other, dict) or len(self) != len(other): - return False - for k, v in self.items(): - if kk := next((kk for kk in other if kk == k), None): - if v == other[kk]: - continue - else: - return False - else: - return True - - -class AnyList(list): - def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - - def __eq__(self, other: object) -> bool: - if not self and isinstance(other, list): - return True - if not isinstance(other, list) or len(self) != len(other): - return False - for i, v in enumerate(self): - if v == other[i]: - continue - else: - return False - else: - return True - - -class AnyInt(int): - def __init__(self) -> None: - super().__init__() - - def __eq__(self, other: object) -> bool: - return isinstance(other, int) diff --git a/libs/scheduler-kafka/tests/compose.yml b/libs/scheduler-kafka/tests/compose.yml deleted file mode 100644 index 675769a0d..000000000 --- a/libs/scheduler-kafka/tests/compose.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: scheduler-kafka-tests -services: - broker: - image: apache/kafka:latest - ports: - - "9092:9092" - postgres: - image: postgres:16 - ports: - - "5443:5432" - environment: - POSTGRES_DB: postgres - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - healthcheck: - test: pg_isready -U postgres - start_period: 10s - timeout: 1s - retries: 5 - interval: 60s - start_interval: 1s diff --git a/libs/scheduler-kafka/tests/conftest.py b/libs/scheduler-kafka/tests/conftest.py deleted file mode 100644 index 331855d46..000000000 --- a/libs/scheduler-kafka/tests/conftest.py +++ /dev/null @@ -1,83 +0,0 @@ -from typing import AsyncIterator, Iterator -from uuid import uuid4 - -import kafka.admin -import pytest -from psycopg import AsyncConnection, Connection -from psycopg_pool import AsyncConnectionPool, ConnectionPool - -from langgraph.checkpoint.postgres import PostgresSaver -from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver -from langgraph.scheduler.kafka.types import Topics - -DEFAULT_POSTGRES_URI = "postgres://postgres:postgres@localhost:5443/" - - -@pytest.fixture -def anyio_backend(): - return "asyncio" - - -@pytest.fixture -def topics() -> Iterator[Topics]: - o = f"test_o_{uuid4().hex[:16]}" - e = f"test_e_{uuid4().hex[:16]}" - z = f"test_z_{uuid4().hex[:16]}" - admin = kafka.admin.KafkaAdminClient() - # create topics - admin.create_topics( - [ - kafka.admin.NewTopic(name=o, num_partitions=1, replication_factor=1), - kafka.admin.NewTopic(name=e, num_partitions=1, replication_factor=1), - kafka.admin.NewTopic(name=z, num_partitions=1, replication_factor=1), - ] - ) - # yield topics - yield Topics(orchestrator=o, executor=e, error=z) - # delete topics - admin.delete_topics([o, e, z]) - admin.close() - - -@pytest.fixture -async def acheckpointer() -> AsyncIterator[AsyncPostgresSaver]: - database = f"test_{uuid4().hex[:16]}" - # create unique db - async with await AsyncConnection.connect( - DEFAULT_POSTGRES_URI, autocommit=True - ) as conn: - await conn.execute(f"CREATE DATABASE {database}") - try: - # yield checkpointer - async with AsyncConnectionPool( - DEFAULT_POSTGRES_URI + database, max_size=10, kwargs={"autocommit": True} - ) as pool: - checkpointer = AsyncPostgresSaver(pool) - await checkpointer.setup() - yield checkpointer - finally: - # drop unique db - async with await AsyncConnection.connect( - DEFAULT_POSTGRES_URI, autocommit=True - ) as conn: - await conn.execute(f"DROP DATABASE {database}") - - -@pytest.fixture -def checkpointer() -> Iterator[PostgresSaver]: - database = f"test_{uuid4().hex[:16]}" - # create unique db - with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn: - conn.execute(f"CREATE DATABASE {database}") - try: - # yield checkpointer - with ConnectionPool( - DEFAULT_POSTGRES_URI + database, max_size=10, kwargs={"autocommit": True} - ) as pool: - checkpointer = PostgresSaver(pool) - checkpointer.setup() - yield checkpointer - finally: - # drop unique db - with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn: - conn.execute(f"DROP DATABASE {database}") diff --git a/libs/scheduler-kafka/tests/drain.py b/libs/scheduler-kafka/tests/drain.py deleted file mode 100644 index 84a3463c2..000000000 --- a/libs/scheduler-kafka/tests/drain.py +++ /dev/null @@ -1,168 +0,0 @@ -import asyncio -import threading -import time -from concurrent.futures import ThreadPoolExecutor -from typing import Optional, TypeVar - -import anyio -from aiokafka import AIOKafkaConsumer -from typing_extensions import ParamSpec - -from langgraph.pregel import Pregel -from langgraph.scheduler.kafka.default_sync import DefaultConsumer -from langgraph.scheduler.kafka.executor import AsyncKafkaExecutor, KafkaExecutor -from langgraph.scheduler.kafka.orchestrator import ( - AsyncKafkaOrchestrator, - KafkaOrchestrator, -) -from langgraph.scheduler.kafka.types import MessageToOrchestrator, Topics - -C = ParamSpec("C") -R = TypeVar("R") - - -async def drain_topics_async( - topics: Topics, graph: Pregel, *, debug: bool = False -) -> tuple[list[MessageToOrchestrator], list[MessageToOrchestrator]]: - scope: Optional[anyio.CancelScope] = None - orch_msgs = [] - exec_msgs = [] - errors = [] - - def done() -> bool: - return ( - len(orch_msgs) > 0 - and any(orch_msgs) - and len(exec_msgs) > 0 - and any(exec_msgs) - and not orch_msgs[-1] - and not exec_msgs[-1] - ) - - async def orchestrator() -> None: - async with AsyncKafkaOrchestrator(graph, topics) as orch: - async for msgs in orch: - orch_msgs.append(msgs) - if debug: - print("\n---\norch", len(msgs), msgs) - if done(): - scope.cancel() - - async def executor() -> None: - async with AsyncKafkaExecutor(graph, topics) as exec: - async for msgs in exec: - exec_msgs.append(msgs) - if debug: - print("\n---\nexec", len(msgs), msgs) - if done(): - scope.cancel() - - async def error_consumer() -> None: - async with AIOKafkaConsumer(topics.error) as consumer: - async for msg in consumer: - errors.append(msg) - if scope: - scope.cancel() - - # start error consumer - error_task = asyncio.create_task(error_consumer(), name="error_consumer") - - # run the orchestrator and executor until break_when - async with anyio.create_task_group() as tg: - tg.cancel_scope.deadline = anyio.current_time() + 20 - scope = tg.cancel_scope - tg.start_soon(orchestrator, name="orchestrator") - tg.start_soon(executor, name="executor") - - # cancel error consumer - error_task.cancel() - - try: - await error_task - except asyncio.CancelledError: - pass - - # check no errors - assert not errors, errors - - return [m for mm in orch_msgs for m in mm], [m for mm in exec_msgs for m in mm] - - -def drain_topics( - topics: Topics, graph: Pregel, *, debug: bool = False -) -> tuple[list[MessageToOrchestrator], list[MessageToOrchestrator]]: - orch_msgs = [] - exec_msgs = [] - errors = [] - event = threading.Event() - - def done() -> bool: - return ( - len(orch_msgs) > 0 - and any(orch_msgs) - and len(exec_msgs) > 0 - and any(exec_msgs) - and not orch_msgs[-1] - and not exec_msgs[-1] - ) - - def orchestrator() -> None: - try: - with KafkaOrchestrator(graph, topics) as orch: - for msgs in orch: - orch_msgs.append(msgs) - if debug: - print("\n---\norch", len(msgs), msgs) - if done(): - event.set() - if event.is_set(): - break - except Exception as e: - errors.append(e) - event.set() - - def executor() -> None: - try: - with KafkaExecutor(graph, topics) as exec: - for msgs in exec: - exec_msgs.append(msgs) - if debug: - print("\n---\nexec", len(msgs), msgs) - if done(): - event.set() - if event.is_set(): - break - except Exception as e: - errors.append(e) - event.set() - - def error_consumer() -> None: - try: - with DefaultConsumer(topics.error) as consumer: - while not event.is_set(): - if msg := consumer.poll(timeout_ms=100): - errors.append(msg) - event.set() - except Exception as e: - errors.append(e) - event.set() - - with ThreadPoolExecutor() as pool: - # start error consumer - pool.submit(error_consumer) - - # run the orchestrator and executor until break_when - pool.submit(orchestrator) - pool.submit(executor) - - # timeout - start = time.time() - while not event.is_set(): - time.sleep(0.1) - if time.time() - start > 20: - event.set() - - # check no errors - assert not errors, errors - - return [m for mm in orch_msgs for m in mm], [m for mm in exec_msgs for m in mm] diff --git a/libs/scheduler-kafka/tests/messages.py b/libs/scheduler-kafka/tests/messages.py deleted file mode 100644 index 5f1dc1f0e..000000000 --- a/libs/scheduler-kafka/tests/messages.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Redefined messages as a work-around for pydantic issue with AnyStr. - -The code below creates version of pydantic models -that will work in unit tests with AnyStr as id field -Please note that the `id` field is assigned AFTER the model is created -to workaround an issue with pydantic ignoring the __eq__ method on -subclassed strings. -""" - -from typing import Any - -from langchain_core.messages import AIMessage, HumanMessage - -from tests.any import AnyStr - - -def _AnyIdAIMessage(**kwargs: Any) -> AIMessage: - """Create ai message with an any id field.""" - message = AIMessage(**kwargs) - message.id = AnyStr() - return message - - -def _AnyIdHumanMessage(**kwargs: Any) -> HumanMessage: - """Create a human message with an any id field.""" - message = HumanMessage(**kwargs) - message.id = AnyStr() - return message diff --git a/libs/scheduler-kafka/tests/test_fanout.py b/libs/scheduler-kafka/tests/test_fanout.py deleted file mode 100644 index 46c993576..000000000 --- a/libs/scheduler-kafka/tests/test_fanout.py +++ /dev/null @@ -1,274 +0,0 @@ -import asyncio -import operator -from typing import ( - Annotated, - Sequence, - TypedDict, - Union, -) - -import pytest -from aiokafka import AIOKafkaProducer - -from langgraph.checkpoint.base import BaseCheckpointSaver -from langgraph.graph.state import StateGraph -from langgraph.pregel import Pregel -from langgraph.scheduler.kafka import serde -from langgraph.scheduler.kafka.types import MessageToOrchestrator, Topics -from tests.any import AnyDict -from tests.drain import drain_topics_async - -pytestmark = pytest.mark.anyio - - -def mk_fanout_graph( - checkpointer: BaseCheckpointSaver, interrupt_before: Sequence[str] = () -) -> Pregel: - # copied from test_in_one_fan_out_state_graph_waiting_edge_multiple_cond_edge - def sorted_add( - x: list[str], y: Union[list[str], list[tuple[str, str]]] - ) -> list[str]: - if isinstance(y[0], tuple): - for rem, _ in y: - x.remove(rem) - y = [t[1] for t in y] - return sorted(operator.add(x, y)) - - class State(TypedDict, total=False): - query: str - answer: str - docs: Annotated[list[str], sorted_add] - - async def rewrite_query(data: State) -> State: - return {"query": f'query: {data["query"]}'} - - async def retriever_picker(data: State) -> list[str]: - return ["analyzer_one", "retriever_two"] - - async def analyzer_one(data: State) -> State: - return {"query": f'analyzed: {data["query"]}'} - - async def retriever_one(data: State) -> State: - return {"docs": ["doc1", "doc2"]} - - async def retriever_two(data: State) -> State: - await asyncio.sleep(0.1) - return {"docs": ["doc3", "doc4"]} - - async def qa(data: State) -> State: - return {"answer": ",".join(data["docs"])} - - async def decider(data: State) -> None: - return None - - def decider_cond(data: State) -> str: - if data["query"].count("analyzed") > 1: - return "qa" - else: - return "rewrite_query" - - builder = StateGraph(State) - - builder.add_node("rewrite_query", rewrite_query) - builder.add_node("analyzer_one", analyzer_one) - builder.add_node("retriever_one", retriever_one) - builder.add_node("retriever_two", retriever_two) - builder.add_node("decider", decider) - builder.add_node("qa", qa) - - builder.set_entry_point("rewrite_query") - builder.add_conditional_edges("rewrite_query", retriever_picker) - builder.add_edge("analyzer_one", "retriever_one") - builder.add_edge(["retriever_one", "retriever_two"], "decider") - builder.add_conditional_edges("decider", decider_cond) - builder.set_finish_point("qa") - - return builder.compile(checkpointer, interrupt_before=interrupt_before) - - -async def test_fanout_graph(topics: Topics, acheckpointer: BaseCheckpointSaver) -> None: - input = {"query": "what is weather in sf"} - config = {"configurable": {"thread_id": "1"}} - graph = mk_fanout_graph(acheckpointer) - - # start a new run - async with AIOKafkaProducer(value_serializer=serde.dumps) as producer: - await producer.send_and_wait( - topics.orchestrator, - MessageToOrchestrator(input=input, config=config), - ) - - # drain topics - orch_msgs, exec_msgs = await drain_topics_async(topics, graph) - - # check state - state = await graph.aget_state(config) - assert state.next == () - assert ( - state.values - == await graph.ainvoke(input, {"configurable": {"thread_id": "2"}}) - == { - "docs": ["doc1", "doc1", "doc2", "doc2", "doc3", "doc3", "doc4", "doc4"], - "query": "analyzed: query: analyzed: query: what is weather in sf", - "answer": "doc1,doc1,doc2,doc2,doc3,doc3,doc4,doc4", - } - ) - - # check history - history = [c async for c in graph.aget_state_history(config)] - assert len(history) == 11 - - # check messages - assert orch_msgs == [MessageToOrchestrator(input=input, config=config)] + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": None, - "finally_send": None, - } - for c in reversed(history) - for _ in c.tasks - ] - assert exec_msgs == [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "task": { - "id": t.id, - "path": list(t.path), - }, - "finally_send": None, - } - for c in reversed(history) - for t in c.tasks - ] - - -async def test_fanout_graph_w_interrupt( - topics: Topics, acheckpointer: BaseCheckpointSaver -) -> None: - input = {"query": "what is weather in sf"} - config = {"configurable": {"thread_id": "1"}} - graph = mk_fanout_graph(acheckpointer, interrupt_before=["qa"]) - - # start a new run - async with AIOKafkaProducer(value_serializer=serde.dumps) as producer: - await producer.send_and_wait( - topics.orchestrator, - MessageToOrchestrator(input=input, config=config), - ) - - orch_msgs, exec_msgs = await drain_topics_async(topics, graph) - - # check interrupted state - state = await graph.aget_state(config) - assert state.next == ("qa",) - assert ( - state.values - == await graph.ainvoke(input, {"configurable": {"thread_id": "2"}}) - == { - "docs": ["doc1", "doc1", "doc2", "doc2", "doc3", "doc3", "doc4", "doc4"], - "query": "analyzed: query: analyzed: query: what is weather in sf", - } - ) - - # check history - history = [c async for c in graph.aget_state_history(config)] - assert len(history) == 10 - - # check messages - assert orch_msgs == [MessageToOrchestrator(input=input, config=config)] + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": None, - "finally_send": None, - } - for c in reversed(history[1:]) # the last one wasn't executed - # orchestrator messages appear only after tasks for that checkpoint - # finish executing, ie. after executor sends message to resume checkpoint - for _ in c.tasks - ] - assert exec_msgs == [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "task": { - "id": t.id, - "path": list(t.path), - }, - "finally_send": None, - } - for c in reversed(history[1:]) # the last one wasn't executed - for t in c.tasks - ] - - # resume the thread - async with AIOKafkaProducer(value_serializer=serde.dumps) as producer: - await producer.send_and_wait( - topics.orchestrator, - MessageToOrchestrator(input=None, config=config), - ) - - orch_msgs, exec_msgs = await drain_topics_async(topics, graph) - - # check final state - state = await graph.aget_state(config) - assert state.next == () - assert ( - state.values - == await graph.ainvoke(None, {"configurable": {"thread_id": "2"}}) - == { - "answer": "doc1,doc1,doc2,doc2,doc3,doc3,doc4,doc4", - "docs": ["doc1", "doc1", "doc2", "doc2", "doc3", "doc3", "doc4", "doc4"], - "query": "analyzed: query: analyzed: query: what is weather in sf", - } - ) diff --git a/libs/scheduler-kafka/tests/test_fanout_sync.py b/libs/scheduler-kafka/tests/test_fanout_sync.py deleted file mode 100644 index 584254a72..000000000 --- a/libs/scheduler-kafka/tests/test_fanout_sync.py +++ /dev/null @@ -1,273 +0,0 @@ -import operator -import time -from typing import ( - Annotated, - Sequence, - TypedDict, - Union, -) - -from langgraph.checkpoint.base import BaseCheckpointSaver -from langgraph.graph.state import StateGraph -from langgraph.pregel import Pregel -from langgraph.scheduler.kafka import serde -from langgraph.scheduler.kafka.default_sync import DefaultProducer -from langgraph.scheduler.kafka.types import MessageToOrchestrator, Topics -from tests.any import AnyDict -from tests.drain import drain_topics - - -def mk_fanout_graph( - checkpointer: BaseCheckpointSaver, interrupt_before: Sequence[str] = () -) -> Pregel: - # copied from test_in_one_fan_out_state_graph_waiting_edge_multiple_cond_edge - def sorted_add( - x: list[str], y: Union[list[str], list[tuple[str, str]]] - ) -> list[str]: - if isinstance(y[0], tuple): - for rem, _ in y: - x.remove(rem) - y = [t[1] for t in y] - return sorted(operator.add(x, y)) - - class State(TypedDict, total=False): - query: str - answer: str - docs: Annotated[list[str], sorted_add] - - def rewrite_query(data: State) -> State: - return {"query": f'query: {data["query"]}'} - - def retriever_picker(data: State) -> list[str]: - return ["analyzer_one", "retriever_two"] - - def analyzer_one(data: State) -> State: - return {"query": f'analyzed: {data["query"]}'} - - def retriever_one(data: State) -> State: - return {"docs": ["doc1", "doc2"]} - - def retriever_two(data: State) -> State: - time.sleep(0.1) - return {"docs": ["doc3", "doc4"]} - - def qa(data: State) -> State: - return {"answer": ",".join(data["docs"])} - - def decider(data: State) -> None: - return None - - def decider_cond(data: State) -> str: - if data["query"].count("analyzed") > 1: - return "qa" - else: - return "rewrite_query" - - builder = StateGraph(State) - - builder.add_node("rewrite_query", rewrite_query) - builder.add_node("analyzer_one", analyzer_one) - builder.add_node("retriever_one", retriever_one) - builder.add_node("retriever_two", retriever_two) - builder.add_node("decider", decider) - builder.add_node("qa", qa) - - builder.set_entry_point("rewrite_query") - builder.add_conditional_edges("rewrite_query", retriever_picker) - builder.add_edge("analyzer_one", "retriever_one") - builder.add_edge(["retriever_one", "retriever_two"], "decider") - builder.add_conditional_edges("decider", decider_cond) - builder.set_finish_point("qa") - - return builder.compile(checkpointer, interrupt_before=interrupt_before) - - -def test_fanout_graph(topics: Topics, checkpointer: BaseCheckpointSaver) -> None: - input = {"query": "what is weather in sf"} - config = {"configurable": {"thread_id": "1"}} - graph = mk_fanout_graph(checkpointer) - - # start a new run - with DefaultProducer() as producer: - producer.send( - topics.orchestrator, - value=serde.dumps(MessageToOrchestrator(input=input, config=config)), - ) - producer.flush() - - # drain topics - orch_msgs, exec_msgs = drain_topics(topics, graph, debug=1) - - # check state - state = graph.get_state(config) - assert state.next == () - assert ( - state.values - == graph.invoke(input, {"configurable": {"thread_id": "2"}}) - == { - "docs": ["doc1", "doc1", "doc2", "doc2", "doc3", "doc3", "doc4", "doc4"], - "query": "analyzed: query: analyzed: query: what is weather in sf", - "answer": "doc1,doc1,doc2,doc2,doc3,doc3,doc4,doc4", - } - ) - - # check history - history = [c for c in graph.get_state_history(config)] - assert len(history) == 11 - - # check messages - assert orch_msgs == [MessageToOrchestrator(input=input, config=config)] + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": None, - "finally_send": None, - } - for c in reversed(history) - for _ in c.tasks - ] - assert exec_msgs == [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "task": { - "id": t.id, - "path": list(t.path), - }, - "finally_send": None, - } - for c in reversed(history) - for t in c.tasks - ] - - -def test_fanout_graph_w_interrupt( - topics: Topics, checkpointer: BaseCheckpointSaver -) -> None: - input = {"query": "what is weather in sf"} - config = {"configurable": {"thread_id": "1"}} - graph = mk_fanout_graph(checkpointer, interrupt_before=["qa"]) - - # start a new run - with DefaultProducer() as producer: - producer.send( - topics.orchestrator, - value=serde.dumps(MessageToOrchestrator(input=input, config=config)), - ) - producer.flush() - - orch_msgs, exec_msgs = drain_topics(topics, graph, debug=1) - - # check interrupted state - state = graph.get_state(config) - assert state.next == ("qa",) - assert ( - state.values - == graph.invoke(input, {"configurable": {"thread_id": "2"}}) - == { - "docs": ["doc1", "doc1", "doc2", "doc2", "doc3", "doc3", "doc4", "doc4"], - "query": "analyzed: query: analyzed: query: what is weather in sf", - } - ) - - # check history - history = [c for c in graph.get_state_history(config)] - assert len(history) == 10 - - # check messages - assert orch_msgs == [MessageToOrchestrator(input=input, config=config)] + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": None, - "finally_send": None, - } - for c in reversed(history[1:]) # the last one wasn't executed - # orchestrator messages appear only after tasks for that checkpoint - # finish executing, ie. after executor sends message to resume checkpoint - for _ in c.tasks - ] - assert exec_msgs == [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "task": { - "id": t.id, - "path": list(t.path), - }, - "finally_send": None, - } - for c in reversed(history[1:]) # the last one wasn't executed - for t in c.tasks - ] - - # resume the thread - with DefaultProducer() as producer: - producer.send( - topics.orchestrator, - value=serde.dumps(MessageToOrchestrator(input=None, config=config)), - ) - producer.flush() - - orch_msgs, exec_msgs = drain_topics(topics, graph) - - # check final state - state = graph.get_state(config) - assert state.next == () - assert ( - state.values - == graph.invoke(None, {"configurable": {"thread_id": "2"}}) - == { - "answer": "doc1,doc1,doc2,doc2,doc3,doc3,doc4,doc4", - "docs": ["doc1", "doc1", "doc2", "doc2", "doc3", "doc3", "doc4", "doc4"], - "query": "analyzed: query: analyzed: query: what is weather in sf", - } - ) diff --git a/libs/scheduler-kafka/tests/test_push.py b/libs/scheduler-kafka/tests/test_push.py deleted file mode 100644 index 7bebc7d5a..000000000 --- a/libs/scheduler-kafka/tests/test_push.py +++ /dev/null @@ -1,206 +0,0 @@ -import operator -from typing import ( - Annotated, - Literal, - Union, -) - -import pytest -from aiokafka import AIOKafkaProducer - -from langgraph.checkpoint.base import BaseCheckpointSaver -from langgraph.constants import START -from langgraph.errors import NodeInterrupt -from langgraph.graph.state import CompiledStateGraph, StateGraph -from langgraph.scheduler.kafka import serde -from langgraph.scheduler.kafka.types import MessageToOrchestrator, Topics -from langgraph.types import Command, Send -from tests.any import AnyDict -from tests.drain import drain_topics_async - -pytestmark = pytest.mark.anyio - - -def mk_push_graph( - checkpointer: BaseCheckpointSaver, -) -> CompiledStateGraph: - # copied from test_send_dedupe_on_resume - - class InterruptOnce: - ticks: int = 0 - - def __call__(self, state): - self.ticks += 1 - if self.ticks == 1: - raise NodeInterrupt("Bahh") - return ["|".join(("flaky", str(state)))] - - class Node: - def __init__(self, name: str): - self.name = name - self.ticks = 0 - self.__name__ = name - - def __call__(self, state): - self.ticks += 1 - update = ( - [self.name] - if isinstance(state, list) - else ["|".join((self.name, str(state)))] - ) - if isinstance(state, Command): - return state.copy(update=update) - else: - return update - - def send_for_fun(state): - return [ - Send("2", Command(goto=Send("2", 3))), - Send("2", Command(goto=Send("flaky", 4))), - "3.1", - ] - - def route_to_three(state) -> Literal["3"]: - return "3" - - builder = StateGraph(Annotated[list, operator.add]) - builder.add_node(Node("1")) - builder.add_node(Node("2")) - builder.add_node(Node("3")) - builder.add_node(Node("3.1")) - builder.add_node("flaky", InterruptOnce()) - builder.add_edge(START, "1") - builder.add_conditional_edges("1", send_for_fun) - builder.add_conditional_edges("2", route_to_three) - - return builder.compile(checkpointer=checkpointer) - - -@pytest.mark.skip("TODO: re-enable in next PR") -async def test_push_graph(topics: Topics, acheckpointer: BaseCheckpointSaver) -> None: - input = ["0"] - config = {"configurable": {"thread_id": "1"}} - graph = mk_push_graph(acheckpointer) - graph_compare = mk_push_graph(acheckpointer) - - # start a new run - async with AIOKafkaProducer(value_serializer=serde.dumps) as producer: - await producer.send_and_wait( - topics.orchestrator, - MessageToOrchestrator(input=input, config=config), - ) - - # drain topics - orch_msgs, exec_msgs = await drain_topics_async(topics, graph) - - # check state - state = await graph.aget_state(config) - assert all(not t.error for t in state.tasks) - assert state.next == ("flaky",) - assert ( - state.values - == await graph_compare.ainvoke(input, {"configurable": {"thread_id": "2"}}) - == [ - "0", - "1", - "2|Control(goto=Send(node='2', arg=3))", - "2|Control(goto=Send(node='flaky', arg=4))", - "2|3", - ] - ) - - # check history - history = [c async for c in graph.aget_state_history(config)] - assert len(history) == 2 - - # check messages - assert orch_msgs == [MessageToOrchestrator(input=input, config=config)] + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": None, - "finally_send": None, - } - for c in reversed(history) - for _ in c.tasks - ] - assert exec_msgs == [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "task": { - "id": t.id, - "path": _convert_path(t.path), - }, - "finally_send": None, - } - for c in reversed(history) - for t in c.tasks - ] - - # resume the thread - async with AIOKafkaProducer(value_serializer=serde.dumps) as producer: - await producer.send_and_wait( - topics.orchestrator, - MessageToOrchestrator(input=None, config=config), - ) - - orch_msgs, exec_msgs = await drain_topics_async(topics, graph) - - # check final state - state = await graph.aget_state(config) - assert state.next == () - assert ( - state.values - == await graph_compare.ainvoke(None, {"configurable": {"thread_id": "2"}}) - == [ - "0", - "1", - "2|Control(goto=Send(node='2', arg=3))", - "2|Control(goto=Send(node='flaky', arg=4))", - "2|3", - "flaky|4", - "3", - "3.1", - ] - ) - - # check history - history = [c async for c in graph.aget_state_history(config)] - assert len(history) == 4 - - # check executions - # node "2" doesn't get called again, as we recover writes saved before - assert graph.builder.nodes["2"].runnable.func.ticks == 3 - # node "flaky" gets called again, as it was interrupted - assert graph.builder.nodes["flaky"].runnable.func.ticks == 2 - - -def _convert_path( - path: tuple[Union[str, int, tuple], ...], -) -> list[Union[str, int, list]]: - return list(_convert_path(p) if isinstance(p, tuple) else p for p in path) diff --git a/libs/scheduler-kafka/tests/test_push_sync.py b/libs/scheduler-kafka/tests/test_push_sync.py deleted file mode 100644 index ea9fc6463..000000000 --- a/libs/scheduler-kafka/tests/test_push_sync.py +++ /dev/null @@ -1,208 +0,0 @@ -import operator -from typing import ( - Annotated, - Literal, - Union, -) - -import pytest - -from langgraph.checkpoint.base import BaseCheckpointSaver -from langgraph.constants import START -from langgraph.errors import NodeInterrupt -from langgraph.graph.state import CompiledStateGraph, StateGraph -from langgraph.scheduler.kafka import serde -from langgraph.scheduler.kafka.default_sync import DefaultProducer -from langgraph.scheduler.kafka.types import MessageToOrchestrator, Topics -from langgraph.types import Command, Send -from tests.any import AnyDict -from tests.drain import drain_topics - -pytestmark = pytest.mark.anyio - - -def mk_push_graph( - checkpointer: BaseCheckpointSaver, -) -> CompiledStateGraph: - # copied from test_send_dedupe_on_resume - - class InterruptOnce: - ticks: int = 0 - - def __call__(self, state): - self.ticks += 1 - if self.ticks == 1: - raise NodeInterrupt("Bahh") - return ["|".join(("flaky", str(state)))] - - class Node: - def __init__(self, name: str): - self.name = name - self.ticks = 0 - self.__name__ = name - - def __call__(self, state): - self.ticks += 1 - update = ( - [self.name] - if isinstance(state, list) - else ["|".join((self.name, str(state)))] - ) - if isinstance(state, Command): - return state.copy(update=update) - else: - return update - - def send_for_fun(state): - return [ - Send("2", Command(goto=Send("2", 3))), - Send("2", Command(goto=Send("flaky", 4))), - "3.1", - ] - - def route_to_three(state) -> Literal["3"]: - return "3" - - builder = StateGraph(Annotated[list, operator.add]) - builder.add_node(Node("1")) - builder.add_node(Node("2")) - builder.add_node(Node("3")) - builder.add_node(Node("3.1")) - builder.add_node("flaky", InterruptOnce()) - builder.add_edge(START, "1") - builder.add_conditional_edges("1", send_for_fun) - builder.add_conditional_edges("2", route_to_three) - - return builder.compile(checkpointer=checkpointer) - - -@pytest.mark.skip("TODO: re-enable in next PR") -def test_push_graph(topics: Topics, acheckpointer: BaseCheckpointSaver) -> None: - input = ["0"] - config = {"configurable": {"thread_id": "1"}} - graph = mk_push_graph(acheckpointer) - graph_compare = mk_push_graph(acheckpointer) - - # start a new run - with DefaultProducer() as producer: - producer.send( - topics.orchestrator, - value=serde.dumps(MessageToOrchestrator(input=input, config=config)), - ) - producer.flush() - - # drain topics - orch_msgs, exec_msgs = drain_topics(topics, graph) - - # check state - state = graph.get_state(config) - assert all(not t.error for t in state.tasks) - assert state.next == ("flaky",) - assert ( - state.values - == graph_compare.invoke(input, {"configurable": {"thread_id": "2"}}) - == [ - "0", - "1", - "2|Control(goto=Send(node='2', arg=3))", - "2|Control(goto=Send(node='flaky', arg=4))", - "2|3", - ] - ) - - # check history - history = [c for c in graph.get_state_history(config)] - assert len(history) == 2 - - # check messages - assert orch_msgs == [MessageToOrchestrator(input=input, config=config)] + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": None, - "finally_send": None, - } - for c in reversed(history) - for _ in c.tasks - ] - assert exec_msgs == [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "task": { - "id": t.id, - "path": _convert_path(t.path), - }, - "finally_send": None, - } - for c in reversed(history) - for t in c.tasks - ] - - # resume the thread - with DefaultProducer() as producer: - producer.send( - topics.orchestrator, - value=serde.dumps(MessageToOrchestrator(input=None, config=config)), - ) - producer.flush() - - orch_msgs, exec_msgs = drain_topics(topics, graph) - - # check final state - state = graph.get_state(config) - assert state.next == () - assert ( - state.values - == graph_compare.invoke(None, {"configurable": {"thread_id": "2"}}) - == [ - "0", - "1", - "2|Control(goto=Send(node='2', arg=3))", - "2|Control(goto=Send(node='flaky', arg=4))", - "2|3", - "flaky|4", - "3", - "3.1", - ] - ) - - # check history - history = [c for c in graph.get_state_history(config)] - assert len(history) == 4 - - # check executions - # node "2" doesn't get called again, as we recover writes saved before - assert graph.builder.nodes["2"].runnable.func.ticks == 3 - # node "flaky" gets called again, as it was interrupted - assert graph.builder.nodes["flaky"].runnable.func.ticks == 2 - - -def _convert_path( - path: tuple[Union[str, int, tuple], ...], -) -> list[Union[str, int, list]]: - return list(_convert_path(p) if isinstance(p, tuple) else p for p in path) diff --git a/libs/scheduler-kafka/tests/test_subgraph.py b/libs/scheduler-kafka/tests/test_subgraph.py deleted file mode 100644 index 934e3a614..000000000 --- a/libs/scheduler-kafka/tests/test_subgraph.py +++ /dev/null @@ -1,765 +0,0 @@ -from typing import Literal, cast - -import pytest -from aiokafka import AIOKafkaProducer -from langchain_core.language_models.fake_chat_models import ( - FakeMessagesListChatModel, -) -from langchain_core.messages import AIMessage, ToolCall -from langchain_core.tools import tool - -from langgraph.checkpoint.base import BaseCheckpointSaver -from langgraph.constants import END, START -from langgraph.graph import MessagesState -from langgraph.graph.state import StateGraph -from langgraph.pregel import Pregel -from langgraph.scheduler.kafka import serde -from langgraph.scheduler.kafka.types import MessageToOrchestrator, Topics -from tests.any import AnyDict -from tests.drain import drain_topics_async -from tests.messages import _AnyIdAIMessage, _AnyIdHumanMessage - -pytestmark = pytest.mark.anyio - - -def mk_weather_graph(checkpointer: BaseCheckpointSaver) -> Pregel: - # copied from test_weather_subgraph - - # setup subgraph - - @tool - def get_weather(city: str): - """Get the weather for a specific city""" - return f"I'ts sunny in {city}!" - - weather_model = FakeMessagesListChatModel( - responses=[ - AIMessage( - content="", - tool_calls=[ - ToolCall( - id="tool_call123", - name="get_weather", - args={"city": "San Francisco"}, - ) - ], - ) - ] - ) - - class SubGraphState(MessagesState): - city: str - - def model_node(state: SubGraphState): - result = weather_model.invoke(state["messages"]) - return {"city": cast(AIMessage, result).tool_calls[0]["args"]["city"]} - - def weather_node(state: SubGraphState): - result = get_weather.invoke({"city": state["city"]}) - return {"messages": [{"role": "assistant", "content": result}]} - - subgraph = StateGraph(SubGraphState) - subgraph.add_node(model_node) - subgraph.add_node(weather_node) - subgraph.add_edge(START, "model_node") - subgraph.add_edge("model_node", "weather_node") - subgraph.add_edge("weather_node", END) - subgraph = subgraph.compile(interrupt_before=["weather_node"]) - - # setup main graph - - class RouterState(MessagesState): - route: Literal["weather", "other"] - - router_model = FakeMessagesListChatModel( - responses=[ - AIMessage( - content="", - tool_calls=[ - ToolCall( - id="tool_call123", - name="router", - args={"dest": "weather"}, - ) - ], - ) - ] - ) - - def router_node(state: RouterState): - system_message = "Classify the incoming query as either about weather or not." - messages = [{"role": "system", "content": system_message}] + state["messages"] - route = router_model.invoke(messages) - return {"route": cast(AIMessage, route).tool_calls[0]["args"]["dest"]} - - def normal_llm_node(state: RouterState): - return {"messages": [AIMessage("Hello!")]} - - def route_after_prediction(state: RouterState): - if state["route"] == "weather": - return "weather_graph" - else: - return "normal_llm_node" - - async def weather_graph(state: RouterState): - return await subgraph.ainvoke(state) - - graph = StateGraph(RouterState) - graph.add_node(router_node) - graph.add_node(normal_llm_node) - graph.add_node("weather_graph", weather_graph) - graph.add_edge(START, "router_node") - graph.add_conditional_edges("router_node", route_after_prediction) - graph.add_edge("normal_llm_node", END) - graph.add_edge("weather_graph", END) - - return graph.compile(checkpointer=checkpointer) - - -async def test_subgraph_w_interrupt( - topics: Topics, acheckpointer: BaseCheckpointSaver -) -> None: - input = {"messages": [{"role": "user", "content": "what's the weather in sf"}]} - config = {"configurable": {"thread_id": "1"}} - graph = mk_weather_graph(acheckpointer) - - # start a new run - async with AIOKafkaProducer(value_serializer=serde.dumps) as producer: - await producer.send_and_wait( - topics.orchestrator, - MessageToOrchestrator(input=input, config=config), - ) - - orch_msgs, exec_msgs = await drain_topics_async(topics, graph) - - # check interrupted state - state = await graph.aget_state(config) - assert state.next == ("weather_graph",) - assert state.values == { - "messages": [_AnyIdHumanMessage(content="what's the weather in sf")], - "route": "weather", - } - - # check outer history - history = [c async for c in graph.aget_state_history(config)] - assert len(history) == 3 - - # check child history - child_history = [ - c async for c in graph.aget_state_history(history[0].tasks[0].state) - ] - assert len(child_history) == 3 - - # check messages - assert ( - orch_msgs - == ( - # initial message to outer graph - [MessageToOrchestrator(input=input, config=config)] - # outer graph messages, until interrupted - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": None, - "finally_send": None, - } - for c in reversed(history[1:]) # the last one wasn't executed - # orchestrator messages appear only after tasks for that checkpoint - # finish executing, ie. after executor sends message to resume checkpoint - for _ in c.tasks - ] - # initial message to child graph - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_checkpointer": None, - "__pregel_delegate": False, - "__pregel_read": None, - "__pregel_send": None, - "__pregel_call": None, - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "__pregel_previous": None, - "__pregel_store": None, - "__pregel_task_id": history[0].tasks[0].id, - "__pregel_scratchpad": { - "subgraph_counter": None, - "call_counter": None, - "interrupt_counter": None, - "null_resume": None, - "resume": [], - }, - "checkpoint_id": None, - "checkpoint_map": { - "": history[0].config["configurable"]["checkpoint_id"] - }, - "checkpoint_ns": history[0] - .tasks[0] - .state["configurable"]["checkpoint_ns"], - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": { - "messages": [ - _AnyIdHumanMessage(content="what's the weather in sf") - ], - "route": "weather", - }, - "finally_send": [ - { - "topic": topics.executor, - "value": { - "config": { - "callbacks": None, - "configurable": { - "__pregel_dedupe_tasks": True, - "__pregel_ensure_latest": True, - "__pregel_resuming": False, - "checkpoint_id": history[0].config[ - "configurable" - ]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "finally_send": None, - "task": { - "id": history[0].tasks[0].id, - "path": list(history[0].tasks[0].path), - }, - }, - } - ], - } - ] - # child graph messages, until interrupted - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_checkpointer": None, - "__pregel_delegate": False, - "__pregel_read": None, - "__pregel_send": None, - "__pregel_call": None, - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "__pregel_previous": None, - "__pregel_store": None, - "__pregel_task_id": history[0].tasks[0].id, - "__pregel_scratchpad": { - "subgraph_counter": None, - "call_counter": None, - "interrupt_counter": None, - "null_resume": None, - "resume": [], - }, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_map": { - "": history[0].config["configurable"]["checkpoint_id"] - }, - "checkpoint_ns": history[0] - .tasks[0] - .state["configurable"]["checkpoint_ns"], - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": None, - "finally_send": [ - { - "topic": topics.executor, - "value": { - "config": { - "callbacks": None, - "configurable": { - "__pregel_dedupe_tasks": True, - "__pregel_ensure_latest": True, - "__pregel_resuming": False, - "checkpoint_id": history[0].config[ - "configurable" - ]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "finally_send": None, - "task": { - "id": history[0].tasks[0].id, - "path": list(history[0].tasks[0].path), - }, - }, - } - ], - } - for c in reversed(child_history[1:]) # the last one wasn't executed - # orchestrator messages appear only after tasks for that checkpoint - # finish executing, ie. after executor sends message to resume checkpoint - for _ in c.tasks - ] - ) - ) - assert ( - exec_msgs - == ( - # outer graph tasks - [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "task": { - "id": t.id, - "path": list(t.path), - }, - "finally_send": None, - } - for c in reversed(history) - for t in c.tasks - ] - # child graph tasks - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_checkpointer": None, - "__pregel_delegate": False, - "__pregel_read": None, - "__pregel_send": None, - "__pregel_call": None, - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "__pregel_previous": None, - "__pregel_store": None, - "__pregel_task_id": history[0].tasks[0].id, - "__pregel_scratchpad": { - "subgraph_counter": None, - "call_counter": None, - "interrupt_counter": None, - "null_resume": None, - "resume": [], - }, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_map": { - "": history[0].config["configurable"]["checkpoint_id"] - }, - "checkpoint_ns": history[0] - .tasks[0] - .state["configurable"]["checkpoint_ns"], - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "task": { - "id": t.id, - "path": list(t.path), - }, - "finally_send": [ - { - "topic": topics.executor, - "value": { - "config": { - "callbacks": None, - "configurable": { - "__pregel_dedupe_tasks": True, - "__pregel_ensure_latest": True, - "__pregel_resuming": False, - "checkpoint_id": history[0].config[ - "configurable" - ]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "finally_send": None, - "task": { - "id": history[0].tasks[0].id, - "path": list(history[0].tasks[0].path), - }, - }, - } - ], - } - for c in reversed(child_history[1:]) # the last one wasn't executed - for t in c.tasks - ] - ) - ) - - # resume the thread - async with AIOKafkaProducer(value_serializer=serde.dumps) as producer: - await producer.send_and_wait( - topics.orchestrator, - MessageToOrchestrator(input=None, config=config), - ) - - orch_msgs, exec_msgs = await drain_topics_async(topics, graph) - - # check final state - state = await graph.aget_state(config) - assert state.next == () - assert state.values == { - "messages": [ - _AnyIdHumanMessage(content="what's the weather in sf"), - _AnyIdAIMessage(content="I'ts sunny in San Francisco!"), - ], - "route": "weather", - } - - # check outer history - history = [c async for c in graph.aget_state_history(config)] - assert len(history) == 4 - - # check child history - # accessing second to last checkpoint, since that's the one w/ subgraph task - child_history = [ - c async for c in graph.aget_state_history(history[1].tasks[0].state) - ] - assert len(child_history) == 4 - - # check messages - assert ( - orch_msgs - == ( - # initial message to outer graph - [MessageToOrchestrator(input=None, config=config)] - # initial message to child graph - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_checkpointer": None, - "__pregel_delegate": False, - "__pregel_read": None, - "__pregel_send": None, - "__pregel_call": None, - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": True, - "__pregel_previous": None, - "__pregel_store": None, - "__pregel_task_id": history[1].tasks[0].id, - "__pregel_scratchpad": { - "subgraph_counter": None, - "call_counter": None, - "interrupt_counter": None, - "null_resume": None, - "resume": [], - }, - "checkpoint_id": None, - "checkpoint_map": { - "": history[1].config["configurable"]["checkpoint_id"] - }, - "checkpoint_ns": history[1] - .tasks[0] - .state["configurable"]["checkpoint_ns"], - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": None, - "finally_send": [ - { - "topic": topics.executor, - "value": { - "config": { - "callbacks": None, - "configurable": { - "__pregel_dedupe_tasks": True, - "__pregel_ensure_latest": True, - "__pregel_resuming": True, - "checkpoint_id": history[1].config[ - "configurable" - ]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "finally_send": None, - "task": { - "id": history[1].tasks[0].id, - "path": list(history[1].tasks[0].path), - }, - }, - } - ], - } - ] - # child graph messages, from previous last checkpoint onwards - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_checkpointer": None, - "__pregel_delegate": False, - "__pregel_read": None, - "__pregel_send": None, - "__pregel_call": None, - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": True, - "__pregel_previous": None, - "__pregel_store": None, - "__pregel_task_id": history[1].tasks[0].id, - "__pregel_scratchpad": { - "subgraph_counter": None, - "call_counter": None, - "interrupt_counter": None, - "null_resume": None, - "resume": [], - }, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_map": { - "": history[1].config["configurable"]["checkpoint_id"] - }, - "checkpoint_ns": history[1] - .tasks[0] - .state["configurable"]["checkpoint_ns"], - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": None, - "finally_send": [ - { - "topic": topics.executor, - "value": { - "config": { - "callbacks": None, - "configurable": { - "__pregel_dedupe_tasks": True, - "__pregel_ensure_latest": True, - "__pregel_resuming": True, - "checkpoint_id": history[1].config[ - "configurable" - ]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "finally_send": None, - "task": { - "id": history[1].tasks[0].id, - "path": list(history[1].tasks[0].path), - }, - }, - } - ], - } - for c in reversed(child_history[:2]) - for _ in c.tasks - ] - # outer graph messages, from previous last checkpoint onwards - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": True, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": None, - "finally_send": None, - } - for c in reversed(history[:2]) - for _ in c.tasks - ] - ) - ) - assert ( - exec_msgs - == ( - # outer graph tasks - [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": True, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "task": { - "id": t.id, - "path": list(t.path), - }, - "finally_send": None, - } - for c in reversed(history[:2]) - for t in c.tasks - ] - # child graph tasks - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_checkpointer": None, - "__pregel_delegate": False, - "__pregel_read": None, - "__pregel_send": None, - "__pregel_call": None, - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": True, - "__pregel_previous": None, - "__pregel_store": None, - "__pregel_task_id": history[1].tasks[0].id, - "__pregel_scratchpad": { - "subgraph_counter": None, - "call_counter": None, - "interrupt_counter": None, - "null_resume": None, - "resume": [], - }, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_map": { - "": history[1].config["configurable"]["checkpoint_id"] - }, - "checkpoint_ns": history[1] - .tasks[0] - .state["configurable"]["checkpoint_ns"], - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "task": { - "id": t.id, - "path": list(t.path), - }, - "finally_send": [ - { - "topic": topics.executor, - "value": { - "config": { - "callbacks": None, - "configurable": { - "__pregel_dedupe_tasks": True, - "__pregel_ensure_latest": True, - "__pregel_resuming": True, - "checkpoint_id": history[1].config[ - "configurable" - ]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "finally_send": None, - "task": { - "id": history[1].tasks[0].id, - "path": list(history[1].tasks[0].path), - }, - }, - } - ], - } - for c in reversed(child_history[:2]) - for t in c.tasks - ] - # "finally" tasks - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_dedupe_tasks": True, - "__pregel_ensure_latest": True, - "__pregel_resuming": True, - "checkpoint_id": history[1].config["configurable"][ - "checkpoint_id" - ], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "finally_send": None, - "task": { - "id": history[1].tasks[0].id, - "path": list(history[1].tasks[0].path), - }, - } - ] - ) - ) diff --git a/libs/scheduler-kafka/tests/test_subgraph_sync.py b/libs/scheduler-kafka/tests/test_subgraph_sync.py deleted file mode 100644 index 50b33a39d..000000000 --- a/libs/scheduler-kafka/tests/test_subgraph_sync.py +++ /dev/null @@ -1,763 +0,0 @@ -from typing import Literal, cast - -import pytest -from langchain_core.language_models.fake_chat_models import ( - FakeMessagesListChatModel, -) -from langchain_core.messages import AIMessage, ToolCall -from langchain_core.tools import tool - -from langgraph.checkpoint.base import BaseCheckpointSaver -from langgraph.constants import END, START -from langgraph.graph import MessagesState -from langgraph.graph.state import StateGraph -from langgraph.pregel import Pregel -from langgraph.scheduler.kafka import serde -from langgraph.scheduler.kafka.default_sync import DefaultProducer -from langgraph.scheduler.kafka.types import MessageToOrchestrator, Topics -from tests.any import AnyDict -from tests.drain import drain_topics -from tests.messages import _AnyIdAIMessage, _AnyIdHumanMessage - -pytestmark = pytest.mark.anyio - - -def mk_weather_graph(checkpointer: BaseCheckpointSaver) -> Pregel: - # copied from test_weather_subgraph - - # setup subgraph - - @tool - def get_weather(city: str): - """Get the weather for a specific city""" - return f"I'ts sunny in {city}!" - - weather_model = FakeMessagesListChatModel( - responses=[ - AIMessage( - content="", - tool_calls=[ - ToolCall( - id="tool_call123", - name="get_weather", - args={"city": "San Francisco"}, - ) - ], - ) - ] - ) - - class SubGraphState(MessagesState): - city: str - - def model_node(state: SubGraphState): - result = weather_model.invoke(state["messages"]) - return {"city": cast(AIMessage, result).tool_calls[0]["args"]["city"]} - - def weather_node(state: SubGraphState): - result = get_weather.invoke({"city": state["city"]}) - return {"messages": [{"role": "assistant", "content": result}]} - - subgraph = StateGraph(SubGraphState) - subgraph.add_node(model_node) - subgraph.add_node(weather_node) - subgraph.add_edge(START, "model_node") - subgraph.add_edge("model_node", "weather_node") - subgraph.add_edge("weather_node", END) - subgraph = subgraph.compile(interrupt_before=["weather_node"]) - - # setup main graph - - class RouterState(MessagesState): - route: Literal["weather", "other"] - - router_model = FakeMessagesListChatModel( - responses=[ - AIMessage( - content="", - tool_calls=[ - ToolCall( - id="tool_call123", - name="router", - args={"dest": "weather"}, - ) - ], - ) - ] - ) - - def router_node(state: RouterState): - system_message = "Classify the incoming query as either about weather or not." - messages = [{"role": "system", "content": system_message}] + state["messages"] - route = router_model.invoke(messages) - return {"route": cast(AIMessage, route).tool_calls[0]["args"]["dest"]} - - def normal_llm_node(state: RouterState): - return {"messages": [AIMessage("Hello!")]} - - def route_after_prediction(state: RouterState): - if state["route"] == "weather": - return "weather_graph" - else: - return "normal_llm_node" - - def weather_graph(state: RouterState): - return subgraph.invoke(state) - - graph = StateGraph(RouterState) - graph.add_node(router_node) - graph.add_node(normal_llm_node) - graph.add_node("weather_graph", weather_graph) - graph.add_edge(START, "router_node") - graph.add_conditional_edges("router_node", route_after_prediction) - graph.add_edge("normal_llm_node", END) - graph.add_edge("weather_graph", END) - - return graph.compile(checkpointer=checkpointer) - - -def test_subgraph_w_interrupt( - topics: Topics, checkpointer: BaseCheckpointSaver -) -> None: - input = {"messages": [{"role": "user", "content": "what's the weather in sf"}]} - config = {"configurable": {"thread_id": "1"}} - graph = mk_weather_graph(checkpointer) - - # start a new run - with DefaultProducer() as producer: - producer.send( - topics.orchestrator, - value=serde.dumps(MessageToOrchestrator(input=input, config=config)), - ) - producer.flush() - - orch_msgs, exec_msgs = drain_topics(topics, graph) - - # check interrupted state - state = graph.get_state(config) - assert state.next == ("weather_graph",) - assert state.values == { - "messages": [_AnyIdHumanMessage(content="what's the weather in sf")], - "route": "weather", - } - - # check outer history - history = [c for c in graph.get_state_history(config)] - assert len(history) == 3 - - # check child history - child_history = [c for c in graph.get_state_history(history[0].tasks[0].state)] - assert len(child_history) == 3 - - # check messages - assert ( - orch_msgs - == ( - # initial message to outer graph - [MessageToOrchestrator(input=input, config=config)] - # outer graph messages, until interrupted - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": None, - "finally_send": None, - } - for c in reversed(history[1:]) # the last one wasn't executed - # orchestrator messages appear only after tasks for that checkpoint - # finish executing, ie. after executor sends message to resume checkpoint - for _ in c.tasks - ] - # initial message to child graph - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_checkpointer": None, - "__pregel_delegate": False, - "__pregel_read": None, - "__pregel_send": None, - "__pregel_call": None, - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "__pregel_previous": None, - "__pregel_store": None, - "__pregel_task_id": history[0].tasks[0].id, - "__pregel_scratchpad": { - "subgraph_counter": None, - "call_counter": None, - "interrupt_counter": None, - "null_resume": None, - "resume": [], - }, - "checkpoint_id": None, - "checkpoint_map": { - "": history[0].config["configurable"]["checkpoint_id"] - }, - "checkpoint_ns": history[0] - .tasks[0] - .state["configurable"]["checkpoint_ns"], - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": { - "messages": [ - _AnyIdHumanMessage(content="what's the weather in sf") - ], - "route": "weather", - }, - "finally_send": [ - { - "topic": topics.executor, - "value": { - "config": { - "callbacks": None, - "configurable": { - "__pregel_dedupe_tasks": True, - "__pregel_ensure_latest": True, - "__pregel_resuming": False, - "checkpoint_id": history[0].config[ - "configurable" - ]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "finally_send": None, - "task": { - "id": history[0].tasks[0].id, - "path": list(history[0].tasks[0].path), - }, - }, - } - ], - } - ] - # child graph messages, until interrupted - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_checkpointer": None, - "__pregel_delegate": False, - "__pregel_read": None, - "__pregel_send": None, - "__pregel_call": None, - "__pregel_ensure_latest": True, - "__pregel_store": None, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "__pregel_previous": None, - "__pregel_task_id": history[0].tasks[0].id, - "__pregel_scratchpad": { - "subgraph_counter": None, - "call_counter": None, - "interrupt_counter": None, - "null_resume": None, - "resume": [], - }, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_map": { - "": history[0].config["configurable"]["checkpoint_id"] - }, - "checkpoint_ns": history[0] - .tasks[0] - .state["configurable"]["checkpoint_ns"], - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": None, - "finally_send": [ - { - "topic": topics.executor, - "value": { - "config": { - "callbacks": None, - "configurable": { - "__pregel_dedupe_tasks": True, - "__pregel_ensure_latest": True, - "__pregel_resuming": False, - "checkpoint_id": history[0].config[ - "configurable" - ]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "finally_send": None, - "task": { - "id": history[0].tasks[0].id, - "path": list(history[0].tasks[0].path), - }, - }, - } - ], - } - for c in reversed(child_history[1:]) # the last one wasn't executed - # orchestrator messages appear only after tasks for that checkpoint - # finish executing, ie. after executor sends message to resume checkpoint - for _ in c.tasks - ] - ) - ) - assert ( - exec_msgs - == ( - # outer graph tasks - [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": False, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "task": { - "id": t.id, - "path": list(t.path), - }, - "finally_send": None, - } - for c in reversed(history) - for t in c.tasks - ] - # child graph tasks - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_checkpointer": None, - "__pregel_delegate": False, - "__pregel_read": None, - "__pregel_send": None, - "__pregel_call": None, - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_store": None, - "__pregel_resuming": False, - "__pregel_task_id": history[0].tasks[0].id, - "__pregel_previous": None, - "__pregel_scratchpad": { - "subgraph_counter": None, - "call_counter": None, - "interrupt_counter": None, - "null_resume": None, - "resume": [], - }, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_map": { - "": history[0].config["configurable"]["checkpoint_id"] - }, - "checkpoint_ns": history[0] - .tasks[0] - .state["configurable"]["checkpoint_ns"], - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "task": { - "id": t.id, - "path": list(t.path), - }, - "finally_send": [ - { - "topic": topics.executor, - "value": { - "config": { - "callbacks": None, - "configurable": { - "__pregel_dedupe_tasks": True, - "__pregel_ensure_latest": True, - "__pregel_resuming": False, - "checkpoint_id": history[0].config[ - "configurable" - ]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "finally_send": None, - "task": { - "id": history[0].tasks[0].id, - "path": list(history[0].tasks[0].path), - }, - }, - } - ], - } - for c in reversed(child_history[1:]) # the last one wasn't executed - for t in c.tasks - ] - ) - ) - - # resume the thread - with DefaultProducer() as producer: - producer.send( - topics.orchestrator, - value=serde.dumps(MessageToOrchestrator(input=None, config=config)), - ) - producer.flush() - - orch_msgs, exec_msgs = drain_topics(topics, graph) - - # check final state - state = graph.get_state(config) - assert state.next == () - assert state.values == { - "messages": [ - _AnyIdHumanMessage(content="what's the weather in sf"), - _AnyIdAIMessage(content="I'ts sunny in San Francisco!"), - ], - "route": "weather", - } - - # check outer history - history = [c for c in graph.get_state_history(config)] - assert len(history) == 4 - - # check child history - # accessing second to last checkpoint, since that's the one w/ subgraph task - child_history = [c for c in graph.get_state_history(history[1].tasks[0].state)] - assert len(child_history) == 4 - - # check messages - assert ( - orch_msgs - == ( - # initial message to outer graph - [MessageToOrchestrator(input=None, config=config)] - # initial message to child graph - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_checkpointer": None, - "__pregel_delegate": False, - "__pregel_read": None, - "__pregel_send": None, - "__pregel_call": None, - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_store": None, - "__pregel_resuming": True, - "__pregel_previous": None, - "__pregel_task_id": history[1].tasks[0].id, - "__pregel_scratchpad": { - "subgraph_counter": None, - "call_counter": None, - "interrupt_counter": None, - "null_resume": None, - "resume": [], - }, - "checkpoint_id": None, - "checkpoint_map": { - "": history[1].config["configurable"]["checkpoint_id"] - }, - "checkpoint_ns": history[1] - .tasks[0] - .state["configurable"]["checkpoint_ns"], - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": None, - "finally_send": [ - { - "topic": topics.executor, - "value": { - "config": { - "callbacks": None, - "configurable": { - "__pregel_dedupe_tasks": True, - "__pregel_ensure_latest": True, - "__pregel_resuming": True, - "checkpoint_id": history[1].config[ - "configurable" - ]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "finally_send": None, - "task": { - "id": history[1].tasks[0].id, - "path": list(history[1].tasks[0].path), - }, - }, - } - ], - } - ] - # child graph messages, from previous last checkpoint onwards - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_checkpointer": None, - "__pregel_delegate": False, - "__pregel_read": None, - "__pregel_send": None, - "__pregel_call": None, - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_store": None, - "__pregel_resuming": True, - "__pregel_previous": None, - "__pregel_task_id": history[1].tasks[0].id, - "__pregel_scratchpad": { - "subgraph_counter": None, - "call_counter": None, - "interrupt_counter": None, - "null_resume": None, - "resume": [], - }, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_map": { - "": history[1].config["configurable"]["checkpoint_id"] - }, - "checkpoint_ns": history[1] - .tasks[0] - .state["configurable"]["checkpoint_ns"], - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": None, - "finally_send": [ - { - "topic": topics.executor, - "value": { - "config": { - "callbacks": None, - "configurable": { - "__pregel_dedupe_tasks": True, - "__pregel_ensure_latest": True, - "__pregel_resuming": True, - "checkpoint_id": history[1].config[ - "configurable" - ]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "finally_send": None, - "task": { - "id": history[1].tasks[0].id, - "path": list(history[1].tasks[0].path), - }, - }, - } - ], - } - for c in reversed(child_history[:2]) - for _ in c.tasks - ] - # outer graph messages, from previous last checkpoint onwards - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": True, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "input": None, - "finally_send": None, - } - for c in reversed(history[:2]) - for _ in c.tasks - ] - ) - ) - assert ( - exec_msgs - == ( - # outer graph tasks - [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": True, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "task": { - "id": t.id, - "path": list(t.path), - }, - "finally_send": None, - } - for c in reversed(history[:2]) - for t in c.tasks - ] - # child graph tasks - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_checkpointer": None, - "__pregel_delegate": False, - "__pregel_read": None, - "__pregel_send": None, - "__pregel_call": None, - "__pregel_ensure_latest": True, - "__pregel_dedupe_tasks": True, - "__pregel_resuming": True, - "__pregel_previous": None, - "__pregel_store": None, - "__pregel_task_id": history[1].tasks[0].id, - "__pregel_scratchpad": { - "subgraph_counter": None, - "call_counter": None, - "interrupt_counter": None, - "null_resume": None, - "resume": [], - }, - "checkpoint_id": c.config["configurable"]["checkpoint_id"], - "checkpoint_map": { - "": history[1].config["configurable"]["checkpoint_id"] - }, - "checkpoint_ns": history[1] - .tasks[0] - .state["configurable"]["checkpoint_ns"], - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "task": { - "id": t.id, - "path": list(t.path), - }, - "finally_send": [ - { - "topic": topics.executor, - "value": { - "config": { - "callbacks": None, - "configurable": { - "__pregel_dedupe_tasks": True, - "__pregel_ensure_latest": True, - "__pregel_resuming": True, - "checkpoint_id": history[1].config[ - "configurable" - ]["checkpoint_id"], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "finally_send": None, - "task": { - "id": history[1].tasks[0].id, - "path": list(history[1].tasks[0].path), - }, - }, - } - ], - } - for c in reversed(child_history[:2]) - for t in c.tasks - ] - # "finally" tasks - + [ - { - "config": { - "callbacks": None, - "configurable": { - "__pregel_dedupe_tasks": True, - "__pregel_ensure_latest": True, - "__pregel_resuming": True, - "checkpoint_id": history[1].config["configurable"][ - "checkpoint_id" - ], - "checkpoint_ns": "", - "thread_id": "1", - }, - "metadata": AnyDict(), - "recursion_limit": 25, - "tags": [], - }, - "finally_send": None, - "task": { - "id": history[1].tasks[0].id, - "path": list(history[1].tasks[0].path), - }, - } - ] - ) - )