diff --git a/docs/api_docs/docs/api_reference/context.md b/docs/api_docs/docs/api_reference/context.md index 3a4d5b8f..39b81f8e 100644 --- a/docs/api_docs/docs/api_reference/context.md +++ b/docs/api_docs/docs/api_reference/context.md @@ -3,7 +3,6 @@ show_root_heading: true show_root_full_path: false members: - - __init__ - collect_events - from_dict - get_result diff --git a/docs/src/content/docs/workflows/managing_events.md b/docs/src/content/docs/workflows/managing_events.md index cc7cf680..25e22d93 100644 --- a/docs/src/content/docs/workflows/managing_events.md +++ b/docs/src/content/docs/workflows/managing_events.md @@ -117,12 +117,11 @@ async for event in handler.stream_events(): result = await handler ``` -There is also the possibility of streaming internal events (such as changes occurring while the step is running, modifications of the workflow state or variations in the size of the internal queues). In order to do so, you need to pass `expose_internal = True` to `stream_events`. +There is also the possibility of streaming internal events (such as changes occurring while the step is running and modifications of the workflow state). In order to do so, you need to pass `expose_internal = True` to `stream_events`. -All the internal events are instances of `InternalDispatchEvent`, but they can be divided into two sub-classes: +All the internal events are instances of `InternalDispatchEvent`. The primary internal event is: -- `StepStateChanged`: exposes internal changes in the state of the event, including whether the step is running or in progress, what worker it is running on and what events it takes as input and output, as well as changes in the workflow state. -- `EventsQueueChanged`: reports the state of the internal queues. +- `StepStateChanged`: exposes internal changes in the state of the event, including whether the step is PREPARING (queued), RUNNING, or NOT_RUNNING, what worker it is running on and what events it takes as input and output. Here is how you can stream these internal events: @@ -138,9 +137,6 @@ async for event in handler.stream_events(expose_internal=True): print("Input event for current step:", event.input_event_name) print("Workflow state at current step:", event.context_state or "No state reported") print("Output event of current step:", event.output_event_name or "No output event yet") - elif isinstance(event, EventsQueueChanged): - print("Queue name:", event.name) - print("Queue size:", event.size) # other event streaming logic here result = await handler diff --git a/examples/streaming_internal_events.ipynb b/examples/streaming_internal_events.ipynb index f552291d..4abf4b73 100644 --- a/examples/streaming_internal_events.ipynb +++ b/examples/streaming_internal_events.ipynb @@ -149,12 +149,13 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "id": "393a057a", "metadata": {}, "outputs": [], "source": [ "from llama_cloud_services import LlamaParse\n", + "from llama_cloud_services.parse import ResultType\n", "from llama_index.llms.openai import OpenAIResponses\n", "\n", "\n", @@ -167,7 +168,7 @@ " adaptive_long_table=True,\n", " outlined_table_extraction=True,\n", " output_tables_as_HTML=True,\n", - " result_type=\"markdown\",\n", + " result_type=ResultType.MD,\n", " )\n", "\n", "\n", @@ -185,7 +186,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 4, "id": "08d5d329", "metadata": {}, "outputs": [], @@ -245,27 +246,17 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "11957bdb", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " % Total % Received % Xferd Average Speed Time Time Time Current\n", - " Dload Upload Total Spent Left Speed\n", - "100 764k 100 764k 0 0 3779k 0 --:--:-- --:--:-- --:--:-- 3764k\n" - ] - } - ], + "outputs": [], "source": [ "!curl https://arxiv.org/pdf/2506.05176 -L -o qwen3_embed_paper.pdf" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 5, "id": "28ae264f", "metadata": {}, "outputs": [ @@ -273,155 +264,64 @@ "name": "stdout", "output_type": "stream", "text": [ - "Queue name: _done\n", - "Queue size: 1\n", - "Queue name: get_document_content\n", - "Queue size: 1\n", - "Queue name: summarize_document\n", - "Queue size: 1\n", - "Name of current step: get_document_content\n", - "State of current step: preparing\n", - "Input event for current step: \n", - "Workflow state at current step: {'state_data': {'summary_prompt': '\"\"'}, 'state_type': 'WorkflowState', 'state_module': '__main__'}\n", - "Output event of current step: No output event yet\n", - "Name of current step: get_document_content\n", - "State of current step: in_progress\n", - "Input event for current step: \n", - "Workflow state at current step: No state reported\n", - "Output event of current step: No output event yet\n", "Name of current step: get_document_content\n", "State of current step: running\n", - "Input event for current step: \n", - "Workflow state at current step: No state reported\n", + "Input event for current step: InputDocumentEvent\n", "Output event of current step: No output event yet\n", - "Started parsing the file under job_id 1bb03bab-9b6c-4919-a290-33e84ccedd95\n", - ".Document has been successfully parsed!\n", + "Started parsing the file under job_id 995119d9-d55f-4872-aa2a-bded8ab3daf9\n", + "..Document has been successfully parsed!\n", "Name of current step: get_document_content\n", "State of current step: not_running\n", "Input event for current step: \n", - "Workflow state at current step: No state reported\n", - "Output event of current step: No output event yet\n", - "Name of current step: get_document_content\n", - "State of current step: not_in_progress\n", - "Input event for current step: \n", - "Workflow state at current step: {'state_data': {'summary_prompt': '\"This is a paper, so you should summarize it while still maintaining a scientific tone and its core concepts and findings\"'}, 'state_type': 'WorkflowState', 'state_module': '__main__'}\n", - "Output event of current step: No output event yet\n", - "Name of current step: get_document_content\n", - "State of current step: not_in_progress\n", - "Input event for current step: \n", - "Workflow state at current step: No state reported\n", - "Output event of current step: No output event yet\n", - "Name of current step: get_document_content\n", - "State of current step: exited\n", - "Input event for current step: \n", - "Workflow state at current step: No state reported\n", "Output event of current step: \n", - "Queue name: _done\n", - "Queue size: 1\n", - "Queue name: get_document_content\n", - "Queue size: 1\n", - "Queue name: summarize_document\n", - "Queue size: 1\n", - "Name of current step: summarize_document\n", - "State of current step: preparing\n", - "Input event for current step: \n", - "Workflow state at current step: {'state_data': {'summary_prompt': '\"This is a paper, so you should summarize it while still maintaining a scientific tone and its core concepts and findings\"'}, 'state_type': 'WorkflowState', 'state_module': '__main__'}\n", - "Output event of current step: No output event yet\n", - "Name of current step: summarize_document\n", - "State of current step: in_progress\n", - "Input event for current step: \n", - "Workflow state at current step: No state reported\n", - "Output event of current step: No output event yet\n", "Name of current step: summarize_document\n", "State of current step: running\n", - "Input event for current step: \n", - "Workflow state at current step: No state reported\n", + "Input event for current step: ParsedDocumentEvent\n", "Output event of current step: No output event yet\n", "Name of current step: summarize_document\n", "State of current step: not_running\n", "Input event for current step: \n", - "Workflow state at current step: No state reported\n", - "Output event of current step: No output event yet\n", - "Name of current step: summarize_document\n", - "State of current step: not_in_progress\n", - "Input event for current step: \n", - "Workflow state at current step: {'state_data': {'summary_prompt': '\"This is a paper, so you should summarize it while still maintaining a scientific tone and its core concepts and findings\"'}, 'state_type': 'WorkflowState', 'state_module': '__main__'}\n", - "Output event of current step: No output event yet\n", - "Name of current step: summarize_document\n", - "State of current step: not_in_progress\n", - "Input event for current step: \n", - "Workflow state at current step: No state reported\n", - "Output event of current step: No output event yet\n", - "Name of current step: summarize_document\n", - "State of current step: exited\n", - "Input event for current step: \n", - "Workflow state at current step: No state reported\n", "Output event of current step: \n", - "Queue name: _done\n", - "Queue size: 1\n", - "Queue name: get_document_content\n", - "Queue size: 1\n", - "Queue name: summarize_document\n", - "Queue size: 1\n", - "Name of current step: _done\n", - "State of current step: preparing\n", - "Input event for current step: \n", - "Workflow state at current step: {'state_data': {'summary_prompt': '\"This is a paper, so you should summarize it while still maintaining a scientific tone and its core concepts and findings\"'}, 'state_type': 'WorkflowState', 'state_module': '__main__'}\n", - "Output event of current step: No output event yet\n", - "Name of current step: _done\n", - "State of current step: in_progress\n", - "Input event for current step: \n", - "Workflow state at current step: No state reported\n", - "Output event of current step: No output event yet\n", - "Name of current step: _done\n", - "State of current step: running\n", - "Input event for current step: \n", - "Workflow state at current step: No state reported\n", - "Output event of current step: No output event yet\n", - "Here is a concise scientific summary of the Qwen3 Embedding technical report, preserving its core concepts, methods, and results.\n", + "Here is a concise scientific summary of the Qwen3 Embedding technical report.\n", "\n", - "Key contributions\n", - "- Introduces the Qwen3 Embedding series (text embedding and reranking) built on Qwen3 foundation LLMs, providing three sizes for each task: 0.6B, 4B and 8B parameters.\n", - "- Proposes a multi-stage training pipeline that leverages large-scale synthetic data generated by Qwen3 LLMs, supervised fine-tuning on high-quality labeled + filtered synthetic data, and model merging (slerp) of fine-tuning checkpoints to improve robustness and generalization.\n", - "- Provides architecture and training details for both embedding (LLM causal model; final embedding = last-layer hidden state at [EOS]; instruction-aware inputs) and reranking (point-wise, chat-template framing; binary “yes/no” SFT objective producing a relevance probability).\n", - "- Publishes models under an Apache 2.0 license to enable reproducibility and community use.\n", + "Overview\n", + "- The authors introduce the Qwen3 Embedding series: instruction-aware text embedding and point-wise reranking models built on the Qwen3 foundation LLMs. Models are provided at three sizes (0.6B, 4B, 8B parameters) and released under Apache 2.0.\n", + "- Goals: produce high-quality, multilingual embeddings and rerankers that perform well across retrieval, STS, classification, code retrieval, long documents and instruction-following retrieval tasks.\n", "\n", - "Model architecture and training objectives\n", - "- Embedding models: causal-attention LLMs initialized from Qwen3; instruction and query concatenated in the input context; use cosine similarity and an improved InfoNCE contrastive loss with a masking term m_ij to reduce false-negative effects and include multiple types of negatives (hard negatives, in-batch queries and documents).\n", - "- Reranker models: single-context point-wise classification using an LLM chat prompt. Relevance score = softmax over model probabilities for “yes” vs “no”; optimized with supervised fine-tuning (cross-entropy on yes/no labels).\n", - "- Model merging: spherical linear interpolation (slerp) across checkpoints from supervised fine-tuning to improve stability and generalization.\n", + "Model design and inference\n", + "- Backbone: dense (causal-attention) Qwen3 LLMs (0.6B / 4B / 8B), long context (32K), embedding dimensions: 1024 / 2560 / 4096 respectively; embedding models support customizable output dimension (MRL).\n", + "- Embedding inference: instruction + query placed in input; embedding taken from final-layer hidden state at the appended [EOS] token.\n", + "- Reranking inference: instruction + query + candidate document presented in a chat template; model produces a binary “yes” / “no” distribution and the relevance score is computed from the probability of “yes”.\n", "\n", - "Synthetic data and multi-stage pipeline\n", - "- Stage 1 (weak supervision): ~150M synthetic multi-task pairs generated by Qwen3-32B (retrieval, bitext mining, STS, classification). Generation is controllable (task type, language, length, difficulty, persona/role) using a two-stage generation pipeline (configuration → query generation).\n", - "- Stage 2 (supervised fine-tuning): mixture of labeled datasets (MS MARCO, Natural Questions, HotpotQA, NLI, Dureader, CodeSearchNet, etc.; ~7M labeled examples) plus a filtered high-quality subset of synthetic pairs (≈12M selected by cosine-similarity > 0.7).\n", - "- Stage 3: model merging across sampled supervised checkpoints.\n", - "- Rerankers are trained with supervised fine-tuning (no weak pretraining stage).\n", + "Training recipe\n", + "- Multi-stage pipeline for embedding models:\n", + " 1. Large-scale weakly supervised pre-training on synthetic pair data (∼150M pairs) synthesized by Qwen3-32B with prompts controlling task, language, query type, length, difficulty and persona/role.\n", + " 2. Supervised fine-tuning on high-quality labeled data (∼7M) combined with filtered synthetic examples (~12M pairs selected by cosine similarity > 0.7).\n", + " 3. Model merging: spherical linear interpolation (slerp) across multiple fine-tuning checkpoints to improve robustness and generalization.\n", + "- Reranker training skips the weak-supervision pretraining and uses supervised fine-tuning plus model merging.\n", + "- Objectives: embeddings optimized with an InfoNCE-style contrastive loss (cosine similarity, temperature, in-batch negatives with a mask to mitigate false negatives). Rerankers optimized with a supervised log-loss on the yes/no label.\n", "\n", - "Evaluation and main results\n", - "- Evaluated extensively on MMTEB (Massive Multilingual Text Embedding Benchmark; 216 tasks across ~250 languages), MTEB (English v2), CMTEB (Chinese), MTEB-Code, and various retrieval/reranking benchmarks (MTEB-R, CMTEB-R, MLDR, FollowIR).\n", - "- Embedding performance highlights:\n", - " - Qwen3-Embedding-8B: top-tier results (e.g., MMTEB / MTEB Multilingual mean ≈ 70.58 in reported summary; MTEB English mean 75.22; MTEB Code ≈ 80.68), outperforming many open-source baselines and surpassing prior proprietary baselines on several code and multilingual tasks (reported comparisons to Gemini Embedding, NV-Embed-v2, GTE family, E5, BGE).\n", - " - Qwen3-Embedding-4B also attains very strong results, close to the 8B model; Qwen3-Embedding-0.6B is competitive despite its small size.\n", - "- Reranker performance:\n", - " - Qwen3-Reranker models improve top-100 candidate rankings produced by the embedding retriever. Qwen3-Reranker-0.6B already surpasses earlier rerankers; the 4B and 8B rerankers provide further gains (e.g., 4B/8B deliver the highest scores on many retrieval/reranking tasks; 8B improves ranking results by ≈3 points over the 0.6B reranker across multiple tasks in aggregate).\n", - "- Detailed tables show consistent gains across retrieval, classification, clustering, STS and code retrieval subbenchmarks.\n", + "Synthetic data generation\n", + "- Two-stage generation per document: (1) configure query type / difficulty / persona via an LLM-assisted selection, (2) generate queries from that configuration. This enables controlled, diverse multilingual synthetic pairs across retrieval, bitext mining, STS and classification tasks.\n", "\n", - "Ablations and analysis\n", - "- Synthetic weakly supervised pre-training is critical: models trained only with supervised data (no synthetic pretraining) exhibit clear performance drops; models trained only on synthetic data still achieve reasonable performance but benefit from supervised fine-tuning and merging.\n", - "- Model merging yields measurable improvements: removing model merging reduces performance across benchmarks, showing its value for mitigating task/data imbalance and improving generalization.\n", - "- The controllable synthetic-data generation (task, language, persona, difficulty) is emphasized as a key factor enabling high-quality multilingual and task-diverse pretraining.\n", + "Evaluation and results\n", + "- Benchmarks: MMTEB (massive multilingual extension of MTEB), MTEB (English), CMTEB (Chinese), MTEB-Code, FollowIR and other retrieval/reranking sets.\n", + "- Embeddings: Qwen3-Embedding-4B and -8B achieve state-of-the-art scores across MMTEB/MTEB/CMTEB and code retrieval. Representative numbers: Qwen3-Embedding-8B attains 70.58 on the MTEB Multilingual benchmark and 80.68 on MTEB Code (reported comparisons to leading open-source and commercial baselines, including Gemini Embedding).\n", + "- Rerankers: all Qwen3-Reranker models improve over baseline rerankers and over raw embedding retrieval. The larger rerankers (4B/8B) deliver the best ranking performance; the 8B reranker improves ranking performance by multiple points versus the 0.6B variant (the report cites ~+3.0 points over many tasks).\n", + "- Practical pipeline: retrieval is performed with embeddings to produce a top-100 list, then rerankers refine ordering; this was used for fair reranker comparisons.\n", "\n", - "Practical features and release\n", - "- Embedding models support flexible embedding dimensions (MRL support) and are instruction-aware (instructions can be customized for downstream tasks).\n", - "- All Qwen3-Embedding and Qwen3-Reranker models (0.6B, 4B, 8B) are open-sourced under Apache 2.0 along with model details and evaluation results.\n", + "Ablations and analysis\n", + "- Synthetic pretraining is important: training only on synthetic data yields reasonable performance, but removing the weak-supervision stage degrades final results. Example (0.6B model): MMTEB mean(task) drops substantially when synthetic pretraining is removed.\n", + "- Model merging helps: disabling model merging reduces performance compared to the merged model, confirming merging’s role in robustness/generalization.\n", "\n", - "Summary statement\n", - "Qwen3 Embedding demonstrates that foundation LLMs can be used both as backbones and as controlled data generators to produce high-quality, multilingual, multi-task synthetic training data. Combined with multi-stage training, selective supervised fine-tuning, and model merging, this yields a family of embedding and reranking models that set or match state-of-the-art performance across broad multilingual, retrieval, and code benchmarks while offering practical, instruction-aware features across model sizes.\n" + "Conclusions and release\n", + "- The Qwen3 Embedding series demonstrates that large LLMs can be effectively used both as backbones and as controllable generators of large-scale, high-quality synthetic training data, producing state-of-the-art multilingual and code embeddings and strong rerankers.\n", + "- Models (0.6B / 4B / 8B for both embedding and reranking) and code are publicly available to encourage reproducibility and community use.\n" ] } ], "source": [ - "from workflows.events import StepStateChanged, EventsQueueChanged\n", + "from workflows.events import StepStateChanged\n", "\n", "wf = SummaryWorkflow(timeout=600)\n", "handler = wf.run(\n", @@ -437,16 +337,9 @@ " print(\"State of current step:\", event.step_state.value)\n", " print(\"Input event for current step:\", event.input_event_name)\n", " print(\n", - " \"Workflow state at current step:\",\n", - " event.context_state or \"No state reported\",\n", - " )\n", - " print(\n", " \"Output event of current step:\",\n", " event.output_event_name or \"No output event yet\",\n", " )\n", - " elif isinstance(event, EventsQueueChanged):\n", - " print(\"Queue name:\", event.name)\n", - " print(\"Queue size:\", event.size)\n", " elif isinstance(event, ParsedDocumentEvent):\n", " print(\"Document has been successfully parsed!\")\n", " else:\n", @@ -459,7 +352,7 @@ ], "metadata": { "kernelspec": { - "display_name": "llama-index-work", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -473,7 +366,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.11" + "version": "3.11.10" } }, "nbformat": 4, diff --git a/pyproject.toml b/pyproject.toml index 317048c2..d6aca1ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,8 @@ dev = [ "hatch>=1.14.1", "pyyaml>=6.0.2", "packaging>=21.0", - "pytest-xdist>=3.8.0" + "pytest-xdist>=3.8.0", + "pytest-timeout>=2.4.0" ] [project] @@ -32,6 +33,7 @@ dependencies = [ [project.optional-dependencies] server = ["starlette>=0.39.0", "uvicorn>=0.32.0"] client = ["httpx>=0.28.1,<1"] +dbos = ["dbos>=1.14.0"] [tool.basedpyright] typeCheckingMode = "standard" diff --git a/src/workflows/context/context.py b/src/workflows/context/context.py index 4fa0706c..3d96cc7d 100644 --- a/src/workflows/context/context.py +++ b/src/workflows/context/context.py @@ -5,45 +5,46 @@ import asyncio import functools -import json -import time +import uuid import warnings -from collections import defaultdict from typing import ( TYPE_CHECKING, Any, - Callable, - DefaultDict, + AsyncGenerator, Generic, - Tuple, Type, TypeVar, cast, ) +from pydantic import BaseModel, ValidationError + +from workflows.context.context_types import SerializedContext from workflows.decorators import StepConfig from workflows.errors import ( ContextSerdeError, - WorkflowCancelledByUser, - WorkflowDone, WorkflowRuntimeError, ) from workflows.events import ( Event, - InputRequiredEvent, - EventsQueueChanged, - StepStateChanged, - StepState, + StartEvent, + StopEvent, ) -from workflows.resource import ResourceManager +from workflows.runtime.types.internal_state import BrokerState +from workflows.runtime.broker import WorkflowBroker +from workflows.plugins.basic import basic_runtime +from workflows.runtime.types.plugin import Plugin, WorkflowRuntime from workflows.types import RunResultT +from workflows.handler import WorkflowHandler from .serializers import BaseSerializer, JsonSerializer from .state_store import MODEL_T, DictState, InMemoryStateStore + if TYPE_CHECKING: # pragma: no cover from workflows import Workflow + T = TypeVar("T", bound=Event) EventBuffer = dict[str, list[Event]] @@ -58,7 +59,9 @@ class UnserializableKeyWarning(Warning): class Context(Generic[MODEL_T]): """ - Global, per-run context and event broker for a `Workflow`. + Global, per-run context for a `Workflow`. Provides an interface into the + underlying broker run, for both external (workflow run oberservers) and + internal consumption by workflow steps. The `Context` coordinates event delivery between steps, tracks in-flight work, exposes a global state store, and provides utilities for streaming and @@ -68,6 +71,8 @@ class Context(Generic[MODEL_T]): Args: workflow (Workflow): The owning workflow instance. Used to infer step configuration and instrumentation. + previous_context: A previous context snapshot to resume from. + serializer: A serializer to use for serializing and deserializing the current and previous context snapshots. Attributes: is_running (bool): Whether the workflow is currently running. @@ -116,39 +121,161 @@ async def start(self, ctx: Context, ev: StartEvent) -> StopEvent: # are known to be unserializable in some cases. known_unserializable_keys = ("memory",) - def __init__(self, workflow: "Workflow") -> None: - self.is_running = False - # Store the step configs of this workflow, to be used in send_event - self._step_configs: dict[str, StepConfig | None] = {} - for step_name, step_func in workflow._get_steps().items(): - self._step_configs[step_name] = getattr(step_func, "__step_config", None) - - # Init broker machinery - self._init_broker_data() - - # Global data storage - self._lock = asyncio.Lock() - self._state_store: InMemoryStateStore[MODEL_T] | None = None - self._waiting_ids: set[str] = set() + # Backing state store; serialized as `state` + _state_store: InMemoryStateStore[MODEL_T] + _broker_run: WorkflowBroker[MODEL_T] | None + _plugin: Plugin + _workflow: Workflow - # instrumentation - self._dispatcher = workflow._dispatcher + def __init__( + self, + workflow: Workflow, + previous_context: dict[str, Any] | None = None, + serializer: BaseSerializer | None = None, + plugin: Plugin = basic_runtime, + ) -> None: + self._serializer = serializer or JsonSerializer() + self._broker_run = None + self._plugin = plugin + self._workflow = workflow - async def _init_state_store(self, state_class: MODEL_T) -> None: - # If a state manager already exists, ensure the requested state type is compatible - if self._state_store is not None: - existing_state = await self._state_store.get_state() - if type(state_class) is not type(existing_state): - # Existing state type differs from the requested one – this is not allowed + # parse the serialized context + serializer = serializer or JsonSerializer() + if previous_context is not None: + try: + # Auto-detect and convert V0 to V1 if needed + previous_context_parsed = SerializedContext.from_dict_auto( + previous_context + ) + # validate it fully parses synchronously to avoid delayed validation errors + BrokerState.from_serialized( + previous_context_parsed, workflow, serializer + ) + except ValidationError as e: + raise ContextSerdeError( + f"Context dict specified in an invalid format: {e}" + ) from e + else: + previous_context_parsed = SerializedContext() + + self._init_snapshot = previous_context_parsed + + # initialization of the state store is a bit complex, due to inferring and validating its type from the + # provided workflow context args + + state_types: set[Type[BaseModel]] = set() + for _, step_func in workflow._get_steps().items(): + step_config: StepConfig = step_func._step_config + if ( + step_config.context_state_type is not None + and step_config.context_state_type != DictState + and issubclass(step_config.context_state_type, BaseModel) + ): + state_type = step_config.context_state_type + state_types.add(state_type) + + if len(state_types) > 1: + raise ValueError( + "Multiple state types are not supported. Make sure that each Context[...] has the same generic state type. Found: " + + ", ".join([state_type.__name__ for state_type in state_types]) + ) + state_type = state_types.pop() if state_types else DictState + if previous_context_parsed.state: + # perhaps offer a way to clear on invalid + store_state = InMemoryStateStore.from_dict( + previous_context_parsed.state, serializer + ) + if store_state.state_type != state_type: raise ValueError( - f"Cannot initialize with state class {type(state_class)} because it already has a state class {type(existing_state)}" + f"State type mismatch. Workflow context expected {state_type.__name__}, got {store_state.state_type.__name__}" ) + self._state_store = cast(InMemoryStateStore[MODEL_T], store_state) + else: + try: + state_instance = cast(MODEL_T, state_type()) + self._state_store = InMemoryStateStore(state_instance) + except Exception as e: + raise WorkflowRuntimeError( + f"Failed to initialize state of type {state_type}. Does your state define defaults for all fields? Original error:\n{e}" + ) from e - # State manager already initialised and compatible – nothing to do - return + @property + def is_running(self) -> bool: + """Whether the workflow is currently running.""" + if self._broker_run is None: + return self._init_snapshot.is_running + else: + return self._broker_run.is_running + + def _init_broker( + self, workflow: Workflow, plugin: WorkflowRuntime | None = None + ) -> WorkflowBroker[MODEL_T]: + if self._broker_run is not None: + raise WorkflowRuntimeError("Broker already initialized") + # Initialize a runtime plugin (asyncio-based by default) + runtime: WorkflowRuntime = plugin or self._plugin.new_runtime(str(uuid.uuid4())) + # Initialize the new broker implementation (broker2) + self._broker_run = WorkflowBroker( + workflow=workflow, + context=self, + runtime=runtime, + plugin=self._plugin, + ) + return self._broker_run + + def _workflow_run( + self, + workflow: Workflow, + start_event: StartEvent | None = None, + semaphore: asyncio.Semaphore | None = None, + ) -> WorkflowHandler: + """ + called by package internally from the workflow to run it + """ + prev_broker: WorkflowBroker[MODEL_T] | None = None + if self._broker_run is not None: + prev_broker = self._broker_run + self._broker_run = None + + self._broker_run = self._init_broker(workflow) - # First-time initialisation - self._state_store = InMemoryStateStore(state_class) + async def before_start() -> None: + if prev_broker is not None: + try: + await prev_broker.shutdown() + except Exception: + pass + if semaphore is not None: + await semaphore.acquire() + + async def after_complete() -> None: + if semaphore is not None: + semaphore.release() + + state = BrokerState.from_serialized( + self._init_snapshot, workflow, self._serializer + ) + return self._broker_run.start( + workflow=workflow, + previous=state, + start_event=start_event, + before_start=before_start, + after_complete=after_complete, + ) + + def _workflow_cancel_run(self) -> None: + """ + Called internally from the handler to cancel a context's run + """ + self._running_broker.cancel_run() + + @property + def _running_broker(self) -> WorkflowBroker[MODEL_T]: + if self._broker_run is None: + raise WorkflowRuntimeError( + "Workflow run is not yet running. Make sure to only call this method after the context has been passed to a workflow.run call." + ) + return self._broker_run @property def store(self) -> InMemoryStateStore[MODEL_T]: @@ -160,62 +287,8 @@ def store(self) -> InMemoryStateStore[MODEL_T]: Returns: InMemoryStateStore[MODEL_T]: The state store instance. """ - # Default to DictState if no state manager is initialized - if self._state_store is None: - # DictState is designed to be compatible with any MODEL_T as the default fallback - default_store = InMemoryStateStore(DictState()) - self._state_store = cast(InMemoryStateStore[MODEL_T], default_store) - return self._state_store - def _init_broker_data(self) -> None: - self._queues: dict[str, asyncio.Queue] = {} - self._tasks: set[asyncio.Task] = set() - self._broker_log: list[Event] = [] - self._cancel_flag: asyncio.Event = asyncio.Event() - self._step_flags: dict[str, asyncio.Event] = {} - self._step_events_holding: list[Event] | None = None - self._step_lock: asyncio.Lock = asyncio.Lock() - self._step_condition: asyncio.Condition = asyncio.Condition( - lock=self._step_lock - ) - self._step_event_written: asyncio.Condition = asyncio.Condition( - lock=self._step_lock - ) - self._accepted_events: list[Tuple[str, str]] = [] - self._retval: RunResultT = None - # Map the step names that were executed to a list of events they received. - # This will be serialized, and is needed to resume a Workflow run passing - # an existing context. - self._in_progress: dict[str, list[Event]] = defaultdict(list) - # Keep track of the steps currently running. This is only valid when a - # workflow is running and won't be serialized. Note that a single step - # might have multiple workers, so we keep a counter. - self._currently_running_steps: DefaultDict[str, int] = defaultdict(int) - # Streaming machinery - self._streaming_queue: asyncio.Queue = asyncio.Queue() - # Step-specific instance - self._event_buffers: dict[str, EventBuffer] = {} - - def _serialize_queue(self, queue: asyncio.Queue, serializer: BaseSerializer) -> str: - queue_items = list(queue._queue) # type: ignore - queue_objs = [serializer.serialize(obj) for obj in queue_items] - return json.dumps(queue_objs) - - def _deserialize_queue( - self, - queue_str: str, - serializer: BaseSerializer, - prefix_queue_objs: list[Any] = [], - ) -> asyncio.Queue: - queue_objs = json.loads(queue_str) - queue_objs = prefix_queue_objs + queue_objs - queue: asyncio.Queue = asyncio.Queue() - for obj in queue_objs: - event_obj = serializer.deserialize(obj) - queue.put_nowait(event_obj) - return queue - def to_dict(self, serializer: BaseSerializer | None = None) -> dict[str, Any]: """Serialize the context to a JSON-serializable dict. @@ -246,35 +319,27 @@ def to_dict(self, serializer: BaseSerializer | None = None) -> dict[str, Any]: result = await my_workflow.run(..., ctx=restored_ctx) ``` """ - serializer = serializer or JsonSerializer() + serializer = serializer or self._serializer # Serialize state using the state manager's method state_data = {} if self._state_store is not None: state_data = self._state_store.to_dict(serializer) - return { - "state": state_data, # Use state manager's serialize method - "streaming_queue": self._serialize_queue(self._streaming_queue, serializer), - "queues": { - k: self._serialize_queue(v, serializer) for k, v in self._queues.items() - }, - "event_buffers": { - k: { - inner_k: [serializer.serialize(ev) for ev in inner_v] - for inner_k, inner_v in v.items() - } - for k, v in self._event_buffers.items() - }, - "in_progress": { - k: [serializer.serialize(ev) for ev in v] - for k, v in self._in_progress.items() - }, - "accepted_events": self._accepted_events, - "broker_log": [serializer.serialize(ev) for ev in self._broker_log], - "is_running": self.is_running, - "waiting_ids": list(self._waiting_ids), - } + # Get the broker state - either from the running broker or from the init snapshot + if self._broker_run is not None: + broker_state = self._broker_run._state + else: + # Deserialize the init snapshot to get a BrokerState, then re-serialize it + # This ensures we always output the current format + broker_state = BrokerState.from_serialized( + self._init_snapshot, self._workflow, serializer + ) + + context = broker_state.to_serialized(serializer) + context.state = state_data + # mode="python" to support pickling over json if one so chooses. This should perhaps be moved into the serializers + return context.model_dump(mode="python") @classmethod def from_dict( @@ -311,138 +376,19 @@ def from_dict( result = await my_workflow.run(..., ctx=restored_ctx) ``` """ - serializer = serializer or JsonSerializer() - try: - context = cls(workflow) - - # Deserialize state manager using the state manager's method - if "state" in data: - context._state_store = cast( - InMemoryStateStore[MODEL_T], - InMemoryStateStore.from_dict(data["state"], serializer), - ) - - context._streaming_queue = context._deserialize_queue( - data["streaming_queue"], serializer - ) - - context._event_buffers = {} - for buffer_id, type_events_map in data["event_buffers"].items(): - context._event_buffers[buffer_id] = {} - for event_type, events_list in type_events_map.items(): - context._event_buffers[buffer_id][event_type] = [ - serializer.deserialize(ev) for ev in events_list - ] - - context._accepted_events = data["accepted_events"] - context._broker_log = [ - serializer.deserialize(ev) for ev in data["broker_log"] - ] - context.is_running = data["is_running"] - # load back up whatever was in the queue as well as the events whose steps - # were in progress when the serialization of the Context took place - context._queues = { - k: context._deserialize_queue( - v, serializer, prefix_queue_objs=data["in_progress"].get(k, []) - ) - for k, v in data["queues"].items() - } - context._in_progress = defaultdict(list) - - # restore waiting ids for hitl - context._waiting_ids = set(data["waiting_ids"]) - - return context + return cls(workflow, previous_context=data, serializer=serializer) except KeyError as e: msg = "Error creating a Context instance: the provided payload has a wrong or old format." raise ContextSerdeError(msg) from e - async def mark_in_progress(self, name: str, ev: Event, worker_id: str = "") -> None: - """ - Add input event to in_progress dict. - - Args: - name (str): The name of the step that is in progress. - ev (Event): The input event that kicked off this step. - - """ - async with self.lock: - self.write_event_to_stream( - StepStateChanged( - step_state=StepState.IN_PROGRESS, - name=name, - input_event_name=(str(type(ev))), - worker_id=worker_id, - ) - ) - self._in_progress[name].append(ev) - - async def remove_from_in_progress( - self, name: str, ev: Event, worker_id: str = "" - ) -> None: - """ - Remove input event from active steps. - - Args: - name (str): The name of the step that has been completed. - ev (Event): The associated input event that kicked off this completed step. - - """ - async with self.lock: - self.write_event_to_stream( - StepStateChanged( - step_state=StepState.NOT_IN_PROGRESS, - name=name, - input_event_name=(str(type(ev))), - worker_id=worker_id, - ) - ) - events = [e for e in self._in_progress[name] if e != ev] - self._in_progress[name] = events - - async def add_running_step(self, name: str) -> None: - async with self.lock: - self._currently_running_steps[name] += 1 - - async def remove_running_step(self, name: str) -> None: - async with self.lock: - self._currently_running_steps[name] -= 1 - if self._currently_running_steps[name] == 0: - del self._currently_running_steps[name] - async def running_steps(self) -> list[str]: """Return the list of currently running step names. Returns: list[str]: Names of steps that have at least one active worker. """ - async with self.lock: - return list(self._currently_running_steps) - - @property - def lock(self) -> asyncio.Lock: - """Returns a mutex to lock the Context.""" - return self._lock - - def _get_full_path(self, ev_type: Type[Event]) -> str: - return f"{ev_type.__module__}.{ev_type.__name__}" - - def _get_event_buffer_id(self, events: list[Type[Event]]) -> str: - # Try getting the current task name - try: - current_task = asyncio.current_task() - if current_task: - t_name = current_task.get_name() - # Do not use the default value 'Task' - if t_name != "Task": - return t_name - except RuntimeError: - # This is a sync step, fallback to using events list - pass - - # Fall back to creating a stable identifier from expected events - return ":".join(sorted(self._get_full_path(e_type) for e_type in events)) + return await self._running_broker.running_steps() def collect_events( self, ev: Event, expected: list[Type[Event]], buffer_id: str | None = None @@ -481,34 +427,7 @@ async def synthesize( See Also: - [Event][workflows.events.Event] """ - buffer_id = buffer_id or self._get_event_buffer_id(expected) - - if buffer_id not in self._event_buffers: - self._event_buffers[buffer_id] = defaultdict(list) - - event_type_path = self._get_full_path(type(ev)) - self._event_buffers[buffer_id][event_type_path].append(ev) - - retval: list[Event] = [] - for e_type in expected: - e_type_path = self._get_full_path(e_type) - e_instance_list = self._event_buffers[buffer_id].get(e_type_path, []) - if e_instance_list: - retval.append(e_instance_list.pop(0)) - else: - # We already know we don't have all the events - break - - if len(retval) == len(expected): - return retval - - # put back the events if unable to collect all - for i, ev_to_restore in enumerate(retval): - e_type = type(retval[i]) - e_type_path = self._get_full_path(e_type) - self._event_buffers[buffer_id][e_type_path].append(ev_to_restore) - - return None + return self._running_broker.collect_events(ev, expected, buffer_id) def send_event(self, message: Event, step: str | None = None) -> None: """Dispatch an event to one or all workflow steps. @@ -548,28 +467,7 @@ async def my_step(self, ctx: Context, ev: StartEvent) -> WorkerEvent | GatherEve result = await handler ``` """ - if step is None: - for name, queue in self._queues.items(): - queue.put_nowait(message) - self.write_event_to_stream( - EventsQueueChanged(name=name, size=queue.qsize()) - ) - else: - if step not in self._step_configs: - raise WorkflowRuntimeError(f"Step {step} does not exist") - - step_config = self._step_configs[step] - if step_config and type(message) in step_config.accepted_events: - self._queues[step].put_nowait(message) - self.write_event_to_stream( - EventsQueueChanged(name=step, size=self._queues[step].qsize()) - ) - else: - raise WorkflowRuntimeError( - f"Step {step} does not accept event of type {type(message)}" - ) - - self._broker_log.append(message) + return self._running_broker.send_event(message, step) async def wait_for_event( self, @@ -615,43 +513,9 @@ async def my_step(self, ctx: Context, ev: StartEvent) -> StopEvent: return StopEvent(result=response.response) ``` """ - requirements = requirements or {} - - # Generate a unique key for the waiter - event_str = self._get_full_path(event_type) - requirements_str = str(requirements) - waiter_id = waiter_id or f"waiter_{event_str}_{requirements_str}" - - if waiter_id not in self._queues: - self._queues[waiter_id] = asyncio.Queue() - self.write_event_to_stream( - EventsQueueChanged(name=waiter_id, size=self._queues[waiter_id].qsize()) - ) - - # send the waiter event if it's not already sent - if waiter_event is not None: - is_waiting = waiter_id in self._waiting_ids - if not is_waiting: - self._waiting_ids.add(waiter_id) - self.write_event_to_stream(waiter_event) - - while True: - event = await asyncio.wait_for( - self._queues[waiter_id].get(), timeout=timeout - ) - if type(event) is event_type: - if all(getattr(event, k, None) == v for k, v in requirements.items()): - if waiter_id in self._waiting_ids: - self._waiting_ids.remove(waiter_id) - return event - else: - continue - self.write_event_to_stream( - EventsQueueChanged( - name=waiter_id, - size=self._queues[waiter_id].qsize(), - ) - ) + return await self._running_broker.wait_for_event( + event_type, waiter_event, waiter_id, requirements, timeout + ) def write_event_to_stream(self, ev: Event | None) -> None: """Enqueue an event for streaming to [WorkflowHandler]](workflows.handler.WorkflowHandler). @@ -668,263 +532,82 @@ async def my_step(self, ctx: Context, ev: StartEvent) -> StopEvent: return StopEvent(result="ok") ``` """ - self._streaming_queue.put_nowait(ev) + self._running_broker.write_event_to_stream(ev) def get_result(self) -> RunResultT: """Return the final result of the workflow run. + Deprecated: + This method is deprecated and will be removed in a future release. + Prefer awaiting the handler returned by `Workflow.run`, e.g.: + `result = await workflow.run(..., ctx=ctx)`. + Examples: ```python + # Preferred result = await my_workflow.run(..., ctx=ctx) + + # Deprecated result_agent = ctx.get_result() ``` Returns: RunResultT: The value provided via a `StopEvent`. """ - return self._retval + _warn_get_result() + if self._running_broker._handler is None: + raise WorkflowRuntimeError("Workflow handler is not set") + return self._running_broker._handler.result() - @property - def streaming_queue(self) -> asyncio.Queue: + def stream_events(self) -> AsyncGenerator[Event, None]: """The internal queue used for streaming events to callers.""" - return self._streaming_queue - - async def shutdown(self) -> None: - """Shut down the workflow run and clean up background tasks. + return self._running_broker.stream_published_events() - Cancels all outstanding workers, waits for them to finish, and marks the - context as not running. Queues and state remain available so callers can - inspect or drain leftover events. - """ - self.is_running = False - # Cancel all running tasks - for task in self._tasks: - task.cancel() - # Wait for all tasks to complete - await asyncio.gather(*self._tasks, return_exceptions=True) - self._tasks.clear() - - def add_step_worker( - self, - name: str, - step: Callable, - config: StepConfig, - verbose: bool, - run_id: str, - worker_id: str, - resource_manager: ResourceManager, - ) -> None: - """Spawn a background worker task to process events for a step. + @property + def streaming_queue(self) -> asyncio.Queue: + """Deprecated queue-based event stream. - Args: - name (str): Step name. - step (Callable): Step function (sync or async). - config (StepConfig): Resolved configuration for the step. - verbose (bool): If True, print step activity. - run_id (str): Run identifier for instrumentation. - worker_id (str): ID of the worker running the step - resource_manager (ResourceManager): Resource injector for the step. + Returns an asyncio.Queue that is populated by iterating this context's + stream_events(). A deprecation warning is emitted once per process. """ - self._tasks.add( - asyncio.create_task( - self._step_worker( - name=name, - step=step, - config=config, - verbose=verbose, - run_id=run_id, - worker_id=worker_id, - resource_manager=resource_manager, - ), - name=name, - ) - ) + _warn_streaming_queue() + q: asyncio.Queue[Event] = asyncio.Queue() - async def _step_worker( - self, - name: str, - step: Callable, - config: StepConfig, - verbose: bool, - run_id: str, - worker_id: str, - resource_manager: ResourceManager, - ) -> None: - while True: - ev = await self._queues[name].get() - if type(ev) not in config.accepted_events: - continue - - if verbose and name != "_done": - print(f"Running step {name}") - - # run step - # Initialize state manager if needed - if self._state_store is None: - if ( - hasattr(config, "context_state_type") - and config.context_state_type is not None - ): - # Instantiate the state class and initialize the state manager - try: - # Try to instantiate the state class - state_instance = cast(MODEL_T, config.context_state_type()) - await self._init_state_store(state_instance) - except Exception as e: - raise WorkflowRuntimeError( - f"Failed to initialize state of type {config.context_state_type}. " - "Does your state define defaults for all fields? Original error:\n" - f"{e}" - ) from e - else: - # Initialize state manager with DictState by default - dict_state = cast(MODEL_T, DictState()) - await self._init_state_store(dict_state) - - kwargs: dict[str, Any] = {} - if config.context_parameter: - kwargs[config.context_parameter] = self - for resource in config.resources: - kwargs[resource.name] = await resource_manager.get( - resource=resource.resource - ) - kwargs[config.event_name] = ev - - # wrap the step with instrumentation - instrumented_step = self._dispatcher.span(step) - - # - check if its async or not - # - if not async, run it in an executor - self.write_event_to_stream( - StepStateChanged( - name=name, - step_state=StepState.PREPARING, - worker_id=worker_id, - input_event_name=str(type(ev)), - context_state=self.store.to_dict_snapshot(JsonSerializer()), - ) - ) - if asyncio.iscoroutinefunction(step): - retry_start_at = time.time() - attempts = 0 - while True: - await self.mark_in_progress(name=name, ev=ev, worker_id=worker_id) - await self.add_running_step(name) - self.write_event_to_stream( - StepStateChanged( - name=name, - step_state=StepState.RUNNING, - worker_id=worker_id, - input_event_name=str(type(ev)), - ) - ) - try: - new_ev = await instrumented_step(**kwargs) - kwargs.clear() - break # exit the retrying loop - - except WorkflowDone: - await self.remove_from_in_progress( - name=name, ev=ev, worker_id=worker_id - ) - raise - except Exception as e: - if config.retry_policy is None: - raise - - delay = config.retry_policy.next( - retry_start_at + time.time(), attempts, e - ) - if delay is None: - raise - - attempts += 1 - if verbose: - print( - f"Step {name} produced an error, retry in {delay} seconds" - ) - await asyncio.sleep(delay) - finally: - await self.remove_running_step(name) - self.write_event_to_stream( - StepStateChanged( - name=name, - step_state=StepState.NOT_RUNNING, - worker_id=worker_id, - input_event_name=str(type(ev)), - ) - ) - - else: - try: - run_task = functools.partial(instrumented_step, **kwargs) - kwargs.clear() - new_ev = await asyncio.get_event_loop().run_in_executor( - None, run_task - ) - except WorkflowDone: - await self.remove_from_in_progress( - name=name, ev=ev, worker_id=worker_id - ) - raise - except Exception as e: - raise WorkflowRuntimeError(f"Error in step '{name}': {e!s}") from e - - self.write_event_to_stream( - StepStateChanged( - name=name, - step_state=StepState.NOT_IN_PROGRESS, - worker_id=worker_id, - input_event_name=str(type(ev)), - context_state=self.store.to_dict_snapshot(JsonSerializer()), - ) - ) - if verbose and name != "_done": - if new_ev is not None: - print(f"Step {name} produced event {type(new_ev).__name__}") - else: - print(f"Step {name} produced no event") - - # Store the accepted event for the drawing operations - if new_ev is not None: - self._accepted_events.append((name, type(ev).__name__)) - - # Fail if the step returned something that's not an event - if new_ev is not None and not isinstance(new_ev, Event): - msg = f"Step function {name} returned {type(new_ev).__name__} instead of an Event instance." - raise WorkflowRuntimeError(msg) - - await self.remove_from_in_progress(name=name, ev=ev, worker_id=worker_id) - self.write_event_to_stream( - StepStateChanged( - name=name, - step_state=StepState.EXITED, - worker_id=worker_id, - input_event_name=str(type(ev)), - output_event_name=str(type(new_ev)), - ) - ) - # InputRequiredEvent's are special case and need to be written to the stream - # this way, the user can access and respond to the event - if isinstance(new_ev, InputRequiredEvent): - self.write_event_to_stream(new_ev) - elif new_ev is not None: - self.send_event(new_ev) - - def add_cancel_worker(self) -> None: - """Install a worker that turns a cancel flag into an exception. - - When the cancel flag is set, a `WorkflowCancelledByUser` will be raised - internally to abort the run. + async def _pump() -> None: + async for ev in self.stream_events(): + await q.put(ev) + if isinstance(ev, StopEvent): + break - See Also: - - [WorkflowCancelledByUser][workflows.errors.WorkflowCancelledByUser] - """ - self._tasks.add(asyncio.create_task(self._cancel_worker())) - - async def _cancel_worker(self) -> None: try: - await self._cancel_flag.wait() - raise WorkflowCancelledByUser - except asyncio.CancelledError: - return + asyncio.create_task(_pump()) + except RuntimeError: + loop = asyncio.get_event_loop() + loop.create_task(_pump()) + return q + + +@functools.lru_cache(maxsize=1) +def _warn_get_result() -> None: + warnings.warn( + ( + "Context.get_result() is deprecated and will be removed in a future " + "release. Prefer awaiting the WorkflowHandler returned by " + "Workflow.run: `result = await workflow.run(..., ctx=ctx)`." + ), + DeprecationWarning, + stacklevel=2, + ) + + +@functools.lru_cache(maxsize=1) +def _warn_streaming_queue() -> None: + warnings.warn( + ( + "Context.streaming_queue is deprecated and will be removed in a future " + "release. Prefer iterating Context.stream_events(): " + "`async for ev in ctx.stream_events(): ...`" + ), + DeprecationWarning, + stacklevel=2, + ) diff --git a/src/workflows/context/context_types.py b/src/workflows/context/context_types.py new file mode 100644 index 00000000..e7c1089a --- /dev/null +++ b/src/workflows/context/context_types.py @@ -0,0 +1,192 @@ +from typing import Any, Optional +import json +from pydantic import BaseModel, Field + + +class SerializedContextV0(BaseModel): + """ + Legacy format for serialized context (V0). Supported for backwards compatibility, but does not + include all currently required runtime state. + """ + + # Serialized state store payload produced by InMemoryStateStore.to_dict(serializer). + # Shape: + # { + # "state_type": str, # class name of the model (e.g. "DictState" or custom model) + # "state_module": str, # module path of the model + # "state_data": ... # see below + # } + # For DictState: state_data = {"_data": {key: serialized_value_str}}, where each value is the + # serializer-encoded string (e.g. JSON string from JsonSerializer.serialize). + # For typed Pydantic models: state_data is a serializer-encoded string containing JSON for a dict with + # discriminator fields (e.g. {"__is_pydantic": true, "value": , "qualified_name": }). + state: dict[str, Any] = Field(default_factory=dict) + + # Streaming queue contents used by the event stream. This is a JSON string representing a list + # of serializer-encoded events (each element is a string as returned by BaseSerializer.serialize). + # Example: '["", ""]'. + streaming_queue: str = Field(default="[]") + + # Per-step (and waiter) inbound event queues. Maps queue name -> JSON string representing a list + # of serializer-encoded events (same format as streaming_queue). + queues: dict[str, str] = Field(default_factory=dict) + + # Buffered events used by Context.collect_events. Maps buffer_id -> { fully.qualified.EventType: [serialized_event_str, ...] }. + # Each inner list element is a serializer-encoded string for an Event. + event_buffers: dict[str, dict[str, list[str]]] = Field(default_factory=dict) + + # Events that were in-flight for each step at serialization time. Maps step_name -> [serialized_event_str, ...]. + in_progress: dict[str, list[str]] = Field(default_factory=dict) + + # Pairs recorded when a step produced an output event: (step_name, input_event_class_name). + # Note: stored as Python tuples here; if JSON-encoded externally they become 2-element lists. + accepted_events: list[tuple[str, str]] = Field(default_factory=list) + + # Broker log of all dispatched events in order, as serializer-encoded strings. + broker_log: list[str] = Field(default_factory=list) + + # Whether the workflow was running when serialized. + is_running: bool = Field(default=False) + + # IDs currently waiting in wait_for_event to suppress duplicate waiter events. These IDs may appear + # as keys in `queues` (they are used as queue names for waiter-specific queues). + waiting_ids: list[str] = Field(default_factory=list) + + +class SerializedEventAttempt(BaseModel): + """Serialized representation of an EventAttempt with retry information.""" + + # The event being processed (as serializer-encoded string) + event: str + # Number of times this event has been attempted (0 for first attempt) + attempts: int = 0 + # Unix timestamp of first attempt, or None if not yet attempted + first_attempt_at: Optional[float] = None + + +class SerializedWaiter(BaseModel): + """Serialized representation of a waiter created by wait_for_event.""" + + # Unique waiter ID + waiter_id: str + # The original event that triggered the wait (serialized) + event: str + # Fully qualified name of the event type being waited for (e.g. "mymodule.MyEvent") + waiting_for_event: str + # Requirements dict for matching the waited-for event + requirements: dict[str, Any] = Field(default_factory=dict) + # Resolved event if available (serialized), None otherwise + resolved_event: Optional[str] = None + + +class SerializedStepWorkerState(BaseModel): + """Serialized representation of a step worker's state.""" + + # Queue of events waiting to be processed (with retry info) + queue: list[SerializedEventAttempt] = Field(default_factory=list) + # Events currently being processed (no retry info needed, will be re-queued on failure) + in_progress: list[str] = Field(default_factory=list) + # Collected events for ctx.collect_events(), keyed by buffer_id -> [event, ...] + # Events are serialized strings + collected_events: dict[str, list[str]] = Field(default_factory=dict) + # Active waiters created by ctx.wait_for_event() + collected_waiters: list[SerializedWaiter] = Field(default_factory=list) + + +class SerializedContext(BaseModel): + """ + Current format for serialized context. Uses proper JSON structures instead of nested JSON strings. + This format better represents BrokerState needs including retry information and waiter state. + """ + + # Version marker to distinguish from V0 + version: int = Field(default=1) + + # Serialized state store payload (same format as V0) + state: dict[str, Any] = Field(default_factory=dict) + + # Whether the workflow was running when serialized + is_running: bool = Field(default=False) + + # Per-step worker state with queues, in-progress events, collected events, and waiters + # Maps step_name -> SerializedStepWorkerState + workers: dict[str, SerializedStepWorkerState] = Field(default_factory=dict) + + @staticmethod + def from_v0(v0: SerializedContextV0) -> "SerializedContext": + """Convert V0 format to current format. + + Note: V0 doesn't store retry information or waiter state, so these will be lost. + V0 also doesn't properly separate collected_events by buffer_id per step. + """ + workers: dict[str, SerializedStepWorkerState] = {} + + # Convert queues and in_progress per step + all_step_names = ( + set(v0.queues.keys()) + | set(v0.in_progress.keys()) + | set(v0.event_buffers.keys()) + ) + + for step_name in all_step_names: + # Skip waiter-specific queues (identified by waiter IDs) + if step_name in v0.waiting_ids: + continue + + queue_events: list[SerializedEventAttempt] = [] + + # Convert in_progress events to queue entries with no retry info + if step_name in v0.in_progress: + for event_str in v0.in_progress[step_name]: + queue_events.append( + SerializedEventAttempt( + event=event_str, attempts=0, first_attempt_at=None + ) + ) + + # Convert queued events + if step_name in v0.queues: + queue_str = v0.queues[step_name] + queue_list = json.loads(queue_str) + for event_str in queue_list: + queue_events.append( + SerializedEventAttempt( + event=event_str, attempts=0, first_attempt_at=None + ) + ) + + # Convert collected events (V0 doesn't track buffer_id properly, so we use "default") + collected: dict[str, list[str]] = {} + if step_name in v0.event_buffers: + # V0 format: step_name -> { event_type -> [event_str, ...] } + # We flatten this into a single "default" buffer + all_events = [] + for event_list in v0.event_buffers[step_name].values(): + all_events.extend(event_list) + if all_events: + collected["default"] = all_events + + workers[step_name] = SerializedStepWorkerState( + queue=queue_events, + in_progress=[], # V0 in_progress are moved to queue + collected_events=collected, + collected_waiters=[], # V0 doesn't store waiter state + ) + + return SerializedContext( + version=1, + state=v0.state, + is_running=v0.is_running, + workers=workers, + ) + + @staticmethod + def from_dict_auto(data: dict[str, Any]) -> "SerializedContext": + """Parse a dict as either V0 or V1 format and return V1.""" + # Check if it has version field + if "version" in data and data["version"] == 1: + return SerializedContext.model_validate(data) + else: + # Assume V0 format + v0 = SerializedContextV0.model_validate(data) + return SerializedContext.from_v0(v0) diff --git a/src/workflows/context/state_store.py b/src/workflows/context/state_store.py index c083012e..4bbf0b21 100644 --- a/src/workflows/context/state_store.py +++ b/src/workflows/context/state_store.py @@ -1,7 +1,7 @@ import asyncio import warnings from contextlib import asynccontextmanager -from typing import Any, AsyncGenerator, Generic, Optional +from typing import Any, AsyncGenerator, Generic, Optional, Type from pydantic import BaseModel, ValidationError from typing_extensions import TypeVar @@ -46,7 +46,7 @@ def __init__(self, **params: Any): # Default state type is DictState for the generic type -MODEL_T = TypeVar("MODEL_T", bound=BaseModel, default=DictState) +MODEL_T = TypeVar("MODEL_T", bound=BaseModel, default=DictState) # type: ignore class InMemoryStateStore(Generic[MODEL_T]): @@ -95,9 +95,12 @@ class MyState(BaseModel): # are known to be unserializable in some cases. known_unserializable_keys = ("memory",) + state_type: Type[MODEL_T] + def __init__(self, initial_state: MODEL_T): self._state = initial_state self._lock = asyncio.Lock() + self.state_type = type(initial_state) async def get_state(self) -> MODEL_T: """Return a shallow copy of the current state model. @@ -170,46 +173,6 @@ def to_dict(self, serializer: "BaseSerializer") -> dict[str, Any]: "state_module": type(self._state).__module__, } - def to_dict_snapshot(self, serializer: "BaseSerializer") -> dict[str, Any]: - """Serialize the state and model metadata for exposure as a state snapshot. - - This method uses a serializer to encode serializable values and, when this strategy produces errors, it falls back to casting the unserializable value to a string. - - Note: - This method should **not** be used for persistency purposes. - - Args: - serializer (BaseSerializer): Strategy used to encode values. - - Returns: - dict[str, Any]: A payload suitable for - [StateSnapshot][workflows.events.StateSnapshot]. - """ - serialized_data = {} - state_dictionary_items = ( - self._state.items() - if isinstance(self._state, DictState) - else self._state.model_dump().items() - ) - for key, value in state_dictionary_items: - try: - serialized_data[key] = serializer.serialize(value) - except Exception: - try: - serialized_data[key] = str(value) - except Exception: - warnings.warn( - f"Skipping safe serialization for key: {key} -- " - "Impossible to cast the value to string.", - category=UnserializableKeyWarning, - ) - continue - return { - "state_data": serialized_data, - "state_type": type(self._state).__name__, - "state_module": type(self._state).__module__, - } - @classmethod def from_dict( cls, serialized_state: dict[str, Any], serializer: "BaseSerializer" diff --git a/src/workflows/decorators.py b/src/workflows/decorators.py index aaf99b3d..60236521 100644 --- a/src/workflows/decorators.py +++ b/src/workflows/decorators.py @@ -3,7 +3,24 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Type +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Generic, + Protocol, + Type, + TypeVar, + cast, + overload, +) + +import sys + +if sys.version_info >= (3, 10): + from typing import ParamSpec +else: + from typing_extensions import ParamSpec from pydantic import BaseModel, ConfigDict, Field @@ -33,12 +50,42 @@ class StepConfig(BaseModel): context_state_type: Type[BaseModel] | None = Field(default=None) +P = ParamSpec("P") +R = TypeVar("R") +R_co = TypeVar("R_co", covariant=True) + + +class StepFunction(Protocol, Generic[P, R_co]): + """A decorated function, that has some _step_config metadata from the @step decorator""" + + _step_config: StepConfig + + __name__: str + __qualname__: str + + def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R_co: ... + + +@overload +def step(func: Callable[P, R]) -> StepFunction[P, R]: ... + + +@overload +def step( + *, + workflow: Type["Workflow"] | None = None, + num_workers: int = 4, + retry_policy: RetryPolicy | None = None, +) -> Callable[[Callable[P, R]], StepFunction[P, R]]: ... + + def step( - *args: Any, + func: Callable[P, R] | None = None, + *, workflow: Type["Workflow"] | None = None, num_workers: int = 4, retry_policy: RetryPolicy | None = None, -) -> Callable: +) -> Callable[[Callable[P, R]], StepFunction[P, R]] | StepFunction[P, R]: """ Decorate a callable to declare it as a workflow step. @@ -83,28 +130,13 @@ async def generate(ev: StartEvent) -> NextEvent: ... ``` """ - def decorator(func: Callable) -> Callable: + def decorator(func: Callable[P, R]) -> StepFunction[P, R]: if not isinstance(num_workers, int) or num_workers <= 0: raise WorkflowValidationError( "num_workers must be an integer greater than 0" ) - # This will raise providing a message with the specific validation failure - spec = inspect_signature(func) - validate_step_signature(spec) - event_name, accepted_events = next(iter(spec.accepted_events.items())) - - # store the configuration in the function object - func.__step_config = StepConfig( # type: ignore[attr-defined] - accepted_events=accepted_events, - event_name=event_name, - return_types=spec.return_types, - context_parameter=spec.context_parameter, - context_state_type=spec.context_state_type, - num_workers=num_workers, - retry_policy=retry_policy, - resources=spec.resources, - ) + func = make_step_function(func, num_workers, retry_policy) # If this is a free function, call add_step() explicitly. if is_free_function(func.__qualname__): @@ -115,9 +147,31 @@ def decorator(func: Callable) -> Callable: return func - if len(args): + if func is not None: # The decorator was used without parentheses, like `@step` - func = args[0] - decorator(func) - return func + return decorator(func) return decorator + + +def make_step_function( + func: Callable[P, R], num_workers: int = 4, retry_policy: RetryPolicy | None = None +) -> StepFunction[P, R]: + # This will raise providing a message with the specific validation failure + spec = inspect_signature(func) + validate_step_signature(spec) + + event_name, accepted_events = next(iter(spec.accepted_events.items())) + + casted = cast(StepFunction[P, R], func) + casted._step_config = StepConfig( + accepted_events=accepted_events, + event_name=event_name, + return_types=spec.return_types, + context_parameter=spec.context_parameter, + context_state_type=spec.context_state_type, + num_workers=num_workers, + retry_policy=retry_policy, + resources=spec.resources, + ) + + return casted diff --git a/src/workflows/events.py b/src/workflows/events.py index 1b3c2416..8da87701 100644 --- a/src/workflows/events.py +++ b/src/workflows/events.py @@ -271,12 +271,12 @@ class InternalDispatchEvent(Event): class StepState(Enum): + # is enqueued, but no capacity yet available to run PREPARING = "preparing" + # is running now on a worker. Skips PREPARING if there is capacity available. RUNNING = "running" - IN_PROGRESS = "in_progress" + # is no longer running. NOT_RUNNING = "not_running" - NOT_IN_PROGRESS = "not_in_progress" - EXITED = "exited" class StepStateChanged(InternalDispatchEvent): @@ -301,22 +301,6 @@ class StepStateChanged(InternalDispatchEvent): output_event_name: Optional[str] = Field( description="Name of the output event", default=None ) - context_state: Optional[dict[str, Any]] = Field( - description="Snapshot of the current workflow state", default=None - ) - - -class EventsQueueChanged(InternalDispatchEvent): - """ - A special event that reports the state of internal queues. - - Attributes: - name (str): Name of the queue - size (int): Size of the queue - """ - - name: str = Field(description="Name of the queue") - size: int = Field(description="Size of the queue") EventType = Type[Event] diff --git a/src/workflows/handler.py b/src/workflows/handler.py index b0cac3c9..d044ead0 100644 --- a/src/workflows/handler.py +++ b/src/workflows/handler.py @@ -4,14 +4,18 @@ from __future__ import annotations import asyncio -from typing import Any, AsyncGenerator +from typing import Any, AsyncGenerator, TYPE_CHECKING + -from .context import Context from .errors import WorkflowRuntimeError from .events import Event, StopEvent, InternalDispatchEvent from .types import RunResultT +if TYPE_CHECKING: + from .context import Context + + class WorkflowHandler(asyncio.Future[RunResultT]): """ Handle a running workflow: await results, stream events, access context, or cancel. @@ -104,9 +108,7 @@ async def stream_events( msg = "All the streamed events have already been consumed." raise WorkflowRuntimeError(msg) - while True: - ev = await self.ctx.streaming_queue.get() - + async for ev in self.ctx.stream_events(): if isinstance(ev, InternalDispatchEvent) and not expose_internal: continue yield ev @@ -129,5 +131,9 @@ async def cancel_run(self) -> None: ``` """ if self.ctx: - self.ctx._cancel_flag.set() - await asyncio.sleep(0) + self.ctx._workflow_cancel_run() + if self._run_task is not None: + try: + await self._run_task + except Exception: + pass diff --git a/src/workflows/plugins/basic.py b/src/workflows/plugins/basic.py new file mode 100644 index 00000000..6d7ae774 --- /dev/null +++ b/src/workflows/plugins/basic.py @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2025 LlamaIndex Inc. + +from __future__ import annotations + +import asyncio +import time +from typing import AsyncGenerator, Callable + +from workflows.events import Event, StopEvent +from workflows.runtime.types.plugin import Plugin, SnapshottableRuntime, WorkflowRuntime +from workflows.runtime.types.step_function import StepWorkerFunction +from workflows.runtime.types.ticks import WorkflowTick +from workflows.decorators import P, R +from workflows.workflow import Workflow + + +class BasicRuntime: + def register( + self, + workflow: Workflow, + workflow_function: Callable[P, R], + steps: dict[str, StepWorkerFunction[R]], + ) -> None: + return + + def new_runtime(self, run_id: str) -> WorkflowRuntime: + snapshottable: SnapshottableRuntime = AsyncioWorkflowRuntime(run_id) + return snapshottable + + +basic_runtime: Plugin = BasicRuntime() + + +class AsyncioWorkflowRuntime: + """ + A plugin interface to switch out a broker runtime (external library or service that manages durable/distributed step execution) + """ + + def __init__( + self, + run_id: str, + ) -> None: + self.run_id = run_id + self.receive_queue: asyncio.Queue[WorkflowTick] = asyncio.Queue() + self.publish_queue: asyncio.Queue[Event] = asyncio.Queue() + self.ticks: list[WorkflowTick] = [] + + def on_tick(self, tick: WorkflowTick) -> None: + self.ticks.append(tick) + + def replay(self) -> list[WorkflowTick]: + return self.ticks + + async def wait_receive(self) -> WorkflowTick: + return await self.receive_queue.get() + + async def write_to_event_stream(self, event: Event) -> None: + self.publish_queue.put_nowait(event) + + async def stream_published_events(self) -> AsyncGenerator[Event, None]: + while True: + item = await self.publish_queue.get() + yield item + if isinstance(item, StopEvent): + break + + async def send_event(self, tick: WorkflowTick) -> None: + self.receive_queue.put_nowait(tick) + + async def register_step_worker( + self, step_name: str, step_worker: StepWorkerFunction[R] + ) -> StepWorkerFunction[R]: + return step_worker + + async def register_workflow_function( + self, workflow_function: Callable[P, R] + ) -> Callable[P, R]: + return workflow_function + + async def get_now(self) -> float: + return time.monotonic() + + async def sleep(self, seconds: float) -> None: + await asyncio.sleep(seconds) + + async def close(self) -> None: + pass diff --git a/src/workflows/plugins/dbos.py b/src/workflows/plugins/dbos.py new file mode 100644 index 00000000..7b5cde23 --- /dev/null +++ b/src/workflows/plugins/dbos.py @@ -0,0 +1,107 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2025 LlamaIndex Inc. + +from __future__ import annotations + +import time +from typing import AsyncGenerator + +from dbos import DBOS, SetWorkflowID # required extra, import must succeed + +from workflows.events import Event, StopEvent +from workflows.runtime.types.plugin import ( + ControlLoopFunction, + Plugin, + WorkflowRuntime, + RegisteredWorkflow, +) +from workflows.runtime.types.internal_state import BrokerState +from workflows.runtime.types.step_function import StepWorkerFunction +from workflows.runtime.types.ticks import WorkflowTick + +from workflows.workflow import Workflow + + +@DBOS.step() +async def _durable_time() -> float: + return time.time() + + +class DBOSRuntime: + def register( + self, + workflow: Workflow, + workflow_function: ControlLoopFunction, + steps: dict[str, StepWorkerFunction], + ) -> RegisteredWorkflow | None: + """ + Wrap the workflow control loop in a DBOS workflow so ticks are received via DBOS.recv + and sent via DBOS.send, enabling durable orchestration. + """ + + @DBOS.workflow() + async def _dbos_control_loop( + start_event: Event | None, + init_state: BrokerState | None, + run_id: str, + ) -> StopEvent: + with SetWorkflowID(run_id): + return await workflow_function(start_event, init_state, run_id) + + wrapped_steps = {name: DBOS.step()(step) for name, step in steps.items()} + + return RegisteredWorkflow( + workflow_function=_dbos_control_loop, steps=wrapped_steps + ) + + def new_runtime(self, run_id: str) -> WorkflowRuntime: + runtime: WorkflowRuntime = DBOSWorkflowRuntime(run_id) + return runtime + + +dbos_runtime: Plugin = DBOSRuntime() + + +class DBOSWorkflowRuntime: + """ + Workflow runtime backed by asyncio mailboxes, with durable timing via DBOS when available. + + - send_event/wait_receive implement the tick mailbox used by the control loop + - write_to_event_stream/stream_published_events expose published events to callers + - get_now returns a stable value on first call within a run (durable if DBOS is installed) + - sleep uses DBOS durable sleep when available, otherwise asyncio.sleep + - on_tick/replay provide a lightweight snapshot for debug/replay via the broker + """ + + def __init__( + self, + run_id: str, + ) -> None: + self.run_id = run_id + + # Mailbox used by control loop and broker + async def wait_receive(self) -> WorkflowTick: + # Receive next tick via DBOS durable notification + tick = await DBOS.recv_async() + return tick # type: ignore[return-value] + + async def send_event(self, tick: WorkflowTick) -> None: + await DBOS.send_async(self.run_id, tick) + + # Event stream used by handlers/observers + async def write_to_event_stream(self, event: Event) -> None: + await DBOS.write_stream_async("published_events", event) + + async def stream_published_events(self) -> AsyncGenerator[Event, None]: + async for event in DBOS.read_stream_async(self.run_id, "published_events"): + yield event + + # Timing utilities + async def get_now(self) -> float: + return await _durable_time() + + async def sleep(self, seconds: float) -> None: + await DBOS.sleep_async(seconds) + + async def close(self) -> None: + pass diff --git a/src/workflows/runtime/broker.py b/src/workflows/runtime/broker.py new file mode 100644 index 00000000..60e7213b --- /dev/null +++ b/src/workflows/runtime/broker.py @@ -0,0 +1,341 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2025 LlamaIndex Inc. + +from __future__ import annotations + +import asyncio +import logging +import uuid +from collections import Counter, defaultdict +from typing import ( + TYPE_CHECKING, + Any, + AsyncGenerator, + Awaitable, + Callable, + Coroutine, + Generic, + Type, + TypeVar, + cast, +) + + +from workflows.errors import WorkflowRuntimeError +from workflows.events import ( + Event, + StartEvent, + StopEvent, +) +from workflows.runtime.control_loop import control_loop, rebuild_state_from_ticks +from workflows.runtime.types.internal_state import BrokerState +from workflows.runtime.types.plugin import Plugin, WorkflowRuntime, as_snapshottable +from workflows.runtime.types.results import ( + AddCollectedEvent, + AddWaiter, + DeleteCollectedEvent, + DeleteWaiter, + StepWorkerContext, + StepWorkerStateContextVar, + WaitingForEvent, +) +from workflows.runtime.types.step_function import ( + StepWorkerFunction, + as_step_worker_function, +) +from workflows.runtime.types.ticks import TickAddEvent, TickCancelRun, WorkflowTick +from workflows.runtime.workflow_registry import workflow_registry + +from ..context.state_store import MODEL_T + +from workflows.handler import WorkflowHandler + +if TYPE_CHECKING: + from workflows import Workflow + from workflows.context.context import Context + + +T = TypeVar("T", bound=Event) +EventBuffer = dict[str, list[Event]] + +logger = logging.getLogger() + + +# Only warn once about unserializable keys +class UnserializableKeyWarning(Warning): + pass + + +class WorkflowBroker(Generic[MODEL_T]): + """ + The workflow broker manages starting up and connecting a workflow handler, a runtime, and triggering the + execution of the workflow. From there it manages communication between the workflow and the outside world. + """ + + _context: Context[MODEL_T] + _runtime: WorkflowRuntime + _plugin: Plugin + _is_running: bool + _handler: WorkflowHandler | None + _workflow: Workflow + # transient tasks to run async ops in background, exposing sync interfaces + _workers: list[asyncio.Task] + _init_state: BrokerState | None + + def __init__( + self, + workflow: Workflow, + context: Context[MODEL_T], + runtime: WorkflowRuntime, + plugin: Plugin, + ) -> None: + self._context = context + self._runtime = runtime + self._plugin = plugin + self._is_running = False + self._handler = None + self._workflow = workflow + self._workers = [] + self._init_state = None + + def _execute_task(self, coro: Coroutine[Any, Any, Any]) -> asyncio.Task[Any]: + task = asyncio.create_task(coro) + self._workers.append(task) + task.add_done_callback(lambda _: self._workers.remove(task)) + return task + + # context API only + def start( + self, + workflow: Workflow, + previous: BrokerState | None = None, + start_event: StartEvent | None = None, + before_start: Callable[[], Awaitable[None]] | None = None, + after_complete: Callable[[], Awaitable[None]] | None = None, + ) -> WorkflowHandler: + """Start the workflow run. Can only be called once.""" + if self._handler is not None: + raise WorkflowRuntimeError( + "this WorkflowBroker already run or running. Cannot start again." + ) + self._init_state = previous + + async def _run_workflow() -> None: + # defer execution to make sure the task can be captured and passed + # to the handler as async exception, protecting against exceptions from before_start + self._is_running = True + await asyncio.sleep(0) + if before_start is not None: + await before_start() + try: + init_state = previous or BrokerState.from_workflow(workflow) + + try: + exception_raised = None + + step_workers: dict[str, StepWorkerFunction] = {} + for name, step_func in workflow._get_steps().items(): + # Avoid capturing a bound method (which retains the instance). + # If it's a bound method, extract the unbound function from the class. + unbound = getattr(step_func, "__func__", step_func) + step_workers[name] = as_step_worker_function(unbound) + + registered = workflow_registry.get_registered_workflow( + workflow, self._plugin, control_loop, step_workers + ) + + # Register run context prior to invoking control loop + workflow_registry.register_run( + run_id=run_id, + workflow=workflow, + plugin=self._runtime, + context=self._context, # type: ignore + steps=registered.steps, + ) + + try: + workflow_result = await registered.workflow_function( + start_event, + init_state, + run_id, + ) + finally: + # ensure run context is cleaned up even on failure + workflow_registry.delete_run(run_id) + result.set_result( + workflow_result.result + if type(workflow_result) is StopEvent + else workflow_result + ) + except Exception as e: + exception_raised = e + + if exception_raised: + # cancel the stream + if not result.done(): + result.set_exception(exception_raised) + finally: + if after_complete is not None: + await after_complete() + self._is_running = False + + # Start the machinery in a new Context or use the provided one + run_id = str(uuid.uuid4()) + + # If a previous context is provided, pass its serialized form + + run_task = self._execute_task(_run_workflow()) + result = WorkflowHandler( + ctx=self._context, # type: ignore + run_id=run_id, + run_task=run_task, + ) + self._handler = result + return result + + # outer handler API to cancel the workflow run + def cancel_run(self) -> None: + self._execute_task(self._runtime.send_event(TickCancelRun())) + + @property + def is_running(self) -> bool: + return self._is_running + + @property + def _state(self) -> BrokerState: + ticks = self._replay_ticks + state = self._init_state or BrokerState.from_workflow(self._workflow) + new_state = rebuild_state_from_ticks(state, ticks) + return new_state + + @property + def _replay_ticks(self) -> list[WorkflowTick]: + snapshottable = as_snapshottable(self._runtime) + if snapshottable is None: + raise WorkflowRuntimeError("Plugin is not snapshottable") + return snapshottable.replay() + + # mostly a debug API. May be removed in the future. + async def running_steps(self) -> list[str]: + return [ + step + for step in self._state.workers.keys() + if self._state.workers[step].in_progress + ] + + # step api only + def collect_events( + self, ev: Event, expected: list[Type[Event]], buffer_id: str | None = None + ) -> list[Event] | None: + step_ctx = self._get_step_ctx(fn="collect_events") + + buffer_id = buffer_id or "default" + + collected_events = step_ctx.state.collected_events.get(buffer_id, []) + + remaining_event_types = Counter(expected) - Counter( + [type(e) for e in collected_events] + ) + + if remaining_event_types != Counter([type(ev)]): + if type(ev) in remaining_event_types: + step_ctx.returns.return_values.append( + AddCollectedEvent(event_id=buffer_id, event=ev) + ) + return None + + total = [] + by_type = defaultdict(list) + for e in collected_events + [ev]: + by_type[type(e)].append(e) + # order by expected type + for e_type in expected: + total.append(by_type[e_type].pop(0)) + # if we got here, it means the collection is fulfilled. Clear the collected events when the step is complete + step_ctx.returns.return_values.append(DeleteCollectedEvent(event_id=buffer_id)) + return total + + # may be called from both step API and outer handler API + def send_event(self, message: Event, step: str | None = None) -> None: + if step is not None: + if step not in self._workflow._get_steps(): + raise WorkflowRuntimeError(f"Step {step} does not exist") + + # Validate that the step accepts this event type + step_func = self._workflow._get_steps()[step] + step_config = step_func._step_config + if type(message) not in step_config.accepted_events: + raise WorkflowRuntimeError( + f"Step {step} does not accept event of type {type(message)}" + ) + + self._execute_task( + self._runtime.send_event(TickAddEvent(event=message, step_name=step)) + ) + + def _get_step_ctx(self, fn: str) -> StepWorkerContext: + try: + return StepWorkerStateContextVar.get() + except LookupError: + raise WorkflowRuntimeError( + f"{fn} may only be called from within a step function" + ) + + # step api only + async def wait_for_event( + self, + event_type: Type[T], + waiter_event: Event | None = None, + waiter_id: str | None = None, + requirements: dict[str, Any] | None = None, + timeout: float | None = 2000, + ) -> T: + step_ctx = self._get_step_ctx(fn="wait_for_event") + + collected_waiters = step_ctx.state.collected_waiters + requirements = requirements or {} + + # Generate a unique key for the waiter + event_str = self._get_full_path(event_type) + requirements_str = str(requirements) + waiter_id = waiter_id or f"waiter_{event_str}_{requirements_str}" + + waiter = next((w for w in collected_waiters if w.waiter_id == waiter_id), None) + if waiter is None or waiter.resolved_event is None: + raise WaitingForEvent( + AddWaiter( + waiter_id=waiter_id, + requirements=requirements, + timeout=timeout, + event_type=event_type, + waiter_event=waiter_event, + ) + ) + else: + step_ctx.returns.return_values.append(DeleteWaiter(waiter_id=waiter_id)) + return cast(T, waiter.resolved_event) + + def _get_full_path(self, ev_type: Type[Event]) -> str: + return f"{ev_type.__module__}.{ev_type.__name__}" + + def stream_published_events(self) -> AsyncGenerator[Event, None]: + """The internal queue used for streaming events to callers.""" + return self._runtime.stream_published_events() + + # step API only + def write_event_to_stream(self, ev: Event | None) -> None: + if ev is not None: + self._execute_task(self._runtime.write_to_event_stream(ev)) + + async def shutdown(self) -> None: + """Cancels the running workflow loop + + Cancels all outstanding workers, waits for them to finish, and marks the + broker as not running. Queues and state remain available so callers can + inspect or drain leftover events. + """ + await self._runtime.send_event(TickCancelRun()) + for worker in self._workers: + worker.cancel() + self._workers.clear() + await self._runtime.close() diff --git a/src/workflows/runtime/control_loop.py b/src/workflows/runtime/control_loop.py new file mode 100644 index 00000000..f065515e --- /dev/null +++ b/src/workflows/runtime/control_loop.py @@ -0,0 +1,672 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2025 LlamaIndex Inc. + +from __future__ import annotations + +import asyncio +from dataclasses import replace +from copy import deepcopy +import time +from typing import TYPE_CHECKING + + +from workflows.decorators import R +from workflows.errors import ( + WorkflowCancelledByUser, + WorkflowRuntimeError, + WorkflowTimeoutError, +) +from workflows.events import ( + Event, + InputRequiredEvent, + StartEvent, + StepState, + StepStateChanged, + StopEvent, +) +from workflows.runtime.types.commands import ( + CommandCompleteRun, + CommandFailWorkflow, + CommandHalt, + CommandPublishEvent, + CommandQueueEvent, + CommandRunWorker, + WorkflowCommand, + indicates_exit, +) +from workflows.runtime.types.internal_state import ( + EventAttempt, + InProgressState, + BrokerState, + InternalStepWorkerState, +) +from workflows.runtime.types.plugin import ( + WorkflowRuntime, + as_snapshottable, +) +from workflows.runtime.types.results import ( + AddCollectedEvent, + AddWaiter, + DeleteCollectedEvent, + DeleteWaiter, + StepWorkerFailed, + StepWorkerResult, + StepWorkerState, + StepWorkerWaiter, +) +from workflows.runtime.types.step_function import ( + StepWorkerFunction, +) +from workflows.runtime.types.ticks import ( + TickAddEvent, + TickCancelRun, + TickPublishEvent, + TickStepResult, + TickTimeout, + WorkflowTick, +) +import logging + +from workflows.workflow import Workflow +from workflows.runtime.workflow_registry import workflow_registry + +if TYPE_CHECKING: + from workflows.context.context import Context + + +logger = logging.getLogger() + + +class _ControlLoopRunner: + """ + Private class to encapsulate the async control loop runtime state and behavior. + Keeps the pure transformation functions at module level for testability. + """ + + def __init__( + self, + workflow: Workflow, + plugin: WorkflowRuntime, + context: Context, + step_workers: dict[str, StepWorkerFunction], + init_state: BrokerState, + ): + self.workflow = workflow + self.plugin = plugin + self.context = context + self.step_workers = step_workers + self.state = init_state + self.workers: list[asyncio.Task] = [] + self.queue: asyncio.Queue[WorkflowTick] = asyncio.Queue() + self.snapshot_plugin = as_snapshottable(plugin) + + async def wait_for_tick(self) -> WorkflowTick: + """Wait for the next tick from the internal queue.""" + return await self.queue.get() + + def queue_event(self, tick: WorkflowTick, delay: float | None = None) -> None: + """Queue a tick event for processing, optionally after a delay.""" + if delay: + + async def _delayed_queue() -> None: + await self.plugin.sleep(delay) + self.queue.put_nowait(tick) + + task = asyncio.create_task(_delayed_queue()) + self.workers.append(task) + else: + self.queue.put_nowait(tick) + + def run_worker(self, command: CommandRunWorker) -> None: + """Run a worker for a step function.""" + + async def _run_worker() -> None: + try: + worker = next( + ( + w + for w in self.state.workers[command.step_name].in_progress + if w.worker_id == command.id + ), + None, + ) + if worker is None: + raise WorkflowRuntimeError( + f"Worker {command.id} not found in in_progress. This should not happen." + ) + snapshot = worker.shared_state + step_fn: StepWorkerFunction = self.step_workers[command.step_name] + + result = await step_fn( + state=snapshot, + step_name=command.step_name, + event=command.event, + context=self.context, + workflow=self.workflow, + ) + self.queue_event( + TickStepResult( + step_name=command.step_name, + worker_id=command.id, + event=command.event, + result=result, + ) + ) + except Exception as e: + logger.error("error running step worker function: ", e, exc_info=True) + self.queue_event( + TickStepResult( + step_name=command.step_name, + worker_id=command.id, + event=command.event, + result=[ + StepWorkerFailed( + exception=e, failed_at=await self.plugin.get_now() + ) + ], + ) + ) + raise e + + task = asyncio.create_task(_run_worker()) + task.add_done_callback(lambda _: self.workers.remove(task)) + self.workers.append(task) + + async def process_command(self, command: WorkflowCommand) -> None | StopEvent: + """Process a single command returned from tick reduction.""" + if isinstance(command, CommandQueueEvent): + self.queue_event( + TickAddEvent( + event=command.event, + step_name=command.step_name, + attempts=command.attempts, + first_attempt_at=command.first_attempt_at, + ) + ) + return None + elif isinstance(command, CommandRunWorker): + self.run_worker(command) + return None + elif isinstance(command, CommandHalt): + await self.cleanup_tasks() + if command.exception is not None: + raise command.exception + elif isinstance(command, CommandCompleteRun): + return command.result + elif isinstance(command, CommandPublishEvent): + await self.plugin.write_to_event_stream(command.event) + return None + elif isinstance(command, CommandFailWorkflow): + await self.cleanup_tasks() + raise command.exception + else: + raise ValueError(f"Unknown command type: {type(command)}") + + async def cleanup_tasks(self) -> None: + """Cancel and cleanup all running worker tasks.""" + try: + for worker in self.workers: + worker.cancel() + except Exception: + pass + try: + await asyncio.wait_for( + asyncio.gather(*self.workers, return_exceptions=True), + timeout=0.5, + ) + except Exception: + pass + + async def run( + self, start_event: Event | None = None, start_with_timeout: bool = True + ) -> StopEvent: + """ + Run the control loop until completion. + + Args: + start_event: Optional initial event to process + start_with_timeout: Whether to start the timeout timer + + Returns: + The final StopEvent from the workflow + """ + + # Start external event listener + async def _pull() -> None: + while True: + tick = await self.plugin.wait_receive() + self.queue_event(tick) + + self.workers.append(asyncio.create_task(_pull())) + + # Queue initial event and timeout + if start_event is not None: + self.queue_event(TickAddEvent(event=start_event)) + + if start_with_timeout and self.workflow._timeout is not None: + self.queue_event( + TickTimeout(timeout=self.workflow._timeout), + delay=self.workflow._timeout, + ) + + # Resume any in-progress work + self.state, commands = rewind_in_progress( + self.state, await self.plugin.get_now() + ) + for command in commands: + try: + await self.process_command(command) + except Exception: + await self.cleanup_tasks() + raise + + # Main event loop + try: + while True: + tick = await self.wait_for_tick() + self.state, commands = _reduce_tick( + tick, self.state, await self.plugin.get_now() + ) + if self.snapshot_plugin is not None: + self.snapshot_plugin.on_tick(tick) + for command in commands: + try: + result = await self.process_command(command) + except Exception: + await self.cleanup_tasks() + raise + + if result is not None: + return result + finally: + await self.cleanup_tasks() + + +async def control_loop( + start_event: Event | None, + init_state: BrokerState | None, + run_id: str, +) -> StopEvent: + """ + The main async control loop for a workflow run. + """ + # Prefer run-scoped context if available (set by broker) + current = workflow_registry.get_run(run_id) + if current is None: + raise WorkflowRuntimeError("Run context not found for control loop") + state = init_state or BrokerState.from_workflow(current.workflow) + runner = _ControlLoopRunner( + current.workflow, current.plugin, current.context, current.steps, state + ) + return await runner.run(start_event=start_event) + + +def rebuild_state_from_ticks( + state: BrokerState, + ticks: list[WorkflowTick], +) -> BrokerState: + """Rebuild the state from a list of ticks""" + for tick in ticks: + state, _ = _reduce_tick( + tick, state, time.time() + ) # somewhat broken kludge on the timestamps, need to move these to ticks + return state + + +def _reduce_tick( + tick: WorkflowTick, init: BrokerState, now_seconds: float +) -> tuple[BrokerState, list[WorkflowCommand]]: + if isinstance(tick, TickStepResult): + return _process_step_result_tick(tick, init, now_seconds) + elif isinstance(tick, TickAddEvent): + return _process_add_event_tick(tick, init, now_seconds) + elif isinstance(tick, TickCancelRun): + return _process_cancel_run_tick(tick, init) + elif isinstance(tick, TickPublishEvent): + return _process_publish_event_tick(tick, init) + elif isinstance(tick, TickTimeout): + return _process_timeout_tick(tick, init) + else: + raise ValueError(f"Unknown tick type: {type(tick)}") + + +def rewind_in_progress( + state: BrokerState, + now_seconds: float, +) -> tuple[BrokerState, list[WorkflowCommand]]: + """Rewind the in_progress state, extracting commands to re-initiate the workers""" + state = deepcopy(state) + commands: list[WorkflowCommand] = [] + for step_name, step_state in sorted(state.workers.items(), key=lambda x: x[0]): + for in_progress in step_state.in_progress: + step_state.queue.insert( + 0, + EventAttempt( + event=in_progress.event, + attempts=in_progress.attempts, + first_attempt_at=in_progress.first_attempt_at, + ), + ) + step_state.in_progress = [] + while ( + len(step_state.queue) > 0 + and len(step_state.in_progress) < step_state.config.num_workers + ): + event = step_state.queue.pop(0) + commands.extend( + _add_or_enqueue_event(event, step_name, step_state, now_seconds) + ) + return state, commands + + +def _process_step_result_tick( + tick: TickStepResult[R], init: BrokerState, now_seconds: float +) -> tuple[BrokerState, list[WorkflowCommand]]: + """ + processes the results from a step function execution + """ + state = deepcopy(init) + commands: list[WorkflowCommand] = [] + worker_state = state.workers[tick.step_name] + # get the current execution details and mark it as no longer in progress + this_execution = next( + (w for w in worker_state.in_progress if w.worker_id == tick.worker_id), None + ) + if this_execution is None: + # this should not happen unless there's a logic bug in the control loop + raise ValueError(f"Worker {tick.worker_id} not found in in_progress") + output_event_name: str | None = None + + did_complete_step = bool( + [x for x in tick.result if isinstance(x, StepWorkerResult)] + ) + step_no_longer_in_progress = True + + for result in tick.result: + if isinstance(result, StepWorkerResult): + output_event_name = str(type(result.result)) + if isinstance(result.result, StopEvent): + # huzzah! The workflow has completed + commands.append( + CommandPublishEvent(event=result.result) + ) # stop event always published to the stream + state.is_running = False + commands.append(CommandCompleteRun(result=result.result)) + elif isinstance(result.result, Event): + # queue any subsequent events + # human input required are automatically published to the stream + if isinstance(result.result, InputRequiredEvent): + commands.append(CommandPublishEvent(event=result.result)) + commands.append(CommandQueueEvent(event=result.result)) + elif result.result is None: + # None means skip + pass + else: + logger.warning( + f"Unknown result type returned from step function ({tick.step_name}): {type(result.result)}" + ) + elif isinstance(result, StepWorkerFailed): + # Schedulea a retry if permitted, otherwise fail the workflow + retries = worker_state.config.retry_policy + failures = this_execution.attempts + 1 + elapsed_time = result.failed_at - this_execution.first_attempt_at + delay = ( + retries.next(elapsed_time, failures, result.exception) + if retries is not None + else None + ) + if delay is not None: + commands.append( + CommandQueueEvent( + event=tick.event, + delay=delay, + step_name=tick.step_name, + attempts=this_execution.attempts + 1, + first_attempt_at=this_execution.first_attempt_at, + ) + ) + else: + # used as a sentinel to end the stream. Perhaps reconsider this and have an alternate failure stop event + state.is_running = False + commands.append(CommandPublishEvent(event=StopEvent())) + commands.append( + CommandFailWorkflow( + step_name=tick.step_name, exception=result.exception + ) + ) + elif isinstance(result, AddCollectedEvent): + # The current state of collected events. + collected_events = state.workers[ + tick.step_name + ].collected_events.setdefault(result.event_id, []) + # the events snapshot that was sent with the step function execution that yielded this result + sent_events = this_execution.shared_state.collected_events.get( + result.event_id, [] + ) + if len(collected_events) > len(sent_events): + # rerun it, and don't append now to ensure serializability + # updating the run state + step_no_longer_in_progress = False + updated_state = replace( + this_execution.shared_state, + collected_events=deepcopy( + state.workers[tick.step_name].collected_events + ), + ) + this_execution.shared_state = updated_state + commands.append( + CommandRunWorker( + step_name=tick.step_name, + event=result.event, + id=this_execution.worker_id, + ) + ) + else: + collected_events.append(result.event) + elif isinstance(result, DeleteCollectedEvent): + if did_complete_step: # allow retries to grab the events + # indicates that a run has successfully collected its events, and they can be deleted from the collected events state + state.workers[tick.step_name].collected_events.pop( + result.event_id, None + ) + elif isinstance(result, AddWaiter): + # indicates that a run has added a waiter to the collected waiters state + existing = [ + x + for x in worker_state.collected_waiters + if x.waiter_id == result.waiter_id + ] + if not existing: + # somewhat arbitrary, just retain the first one + worker_state.collected_waiters.append( + StepWorkerWaiter( + waiter_id=result.waiter_id, + event=this_execution.event, + waiting_for_event=result.event_type, + requirements=result.requirements, + resolved_event=None, + ) + ) + if result.waiter_event: + commands.append(CommandPublishEvent(event=result.waiter_event)) + + elif isinstance(result, DeleteWaiter): + if did_complete_step: # allow retries to grab the waiter events + # indicates that a run has obtained the waiting event, and it can be deleted from the collected waiters state + to_remove = result.waiter_id + waiters = state.workers[tick.step_name].collected_waiters + item = next(filter(lambda w: w.waiter_id == to_remove, waiters), None) + if item is not None: + waiters.remove(item) + else: + raise ValueError(f"Unknown result type: {type(result)}") + + is_completed = len([x for x in commands if indicates_exit(x)]) > 0 + if step_no_longer_in_progress: + commands.insert( + 0, + CommandPublishEvent( + StepStateChanged( + step_state=StepState.NOT_RUNNING, + name=tick.step_name, + input_event_name=str(type(tick.event)), + output_event_name=output_event_name, + worker_id=str(tick.worker_id), + ) + ), + ) + worker_state.in_progress.remove(this_execution) + # enqueue next events if there are any + if not is_completed: + while ( + len(worker_state.queue) > 0 + and len(worker_state.in_progress) < worker_state.config.num_workers + ): + event = worker_state.queue.pop(0) + subcommands = _add_or_enqueue_event( + event, tick.step_name, worker_state, now_seconds + ) + commands.extend(subcommands) + return state, commands + + +def _add_or_enqueue_event( + event: EventAttempt, + step_name: str, + state: InternalStepWorkerState, + now_seconds: float, +) -> list[WorkflowCommand]: + """ + Small helper to assist in adding an event to a step worker state, or enqueuing it if it's not accepted. + Note! This mutates the state, assuming that its already been deepcopied in an outer scope. + """ + commands: list[WorkflowCommand] = [] + # Determine if there is available capacity based on in_progress workers + has_space = len(state.in_progress) < state.config.num_workers + if has_space: + # Assign the smallest available worker id + used = set(x.worker_id for x in state.in_progress) + id_candidates = [i for i in range(state.config.num_workers) if i not in used] + id = id_candidates[0] + shared_state: StepWorkerState = StepWorkerState( + step_name=step_name, + collected_events=deepcopy(state.collected_events), + collected_waiters=deepcopy(state.collected_waiters), + ) + state.in_progress.append( + InProgressState( + event=event.event, + worker_id=id, + shared_state=shared_state, + attempts=event.attempts or 0, + first_attempt_at=event.first_attempt_at or now_seconds, + ) + ) + commands.append(CommandRunWorker(step_name=step_name, event=event.event, id=id)) + commands.append( + CommandPublishEvent( + StepStateChanged( + step_state=StepState.RUNNING, + name=step_name, + input_event_name=type(event.event).__name__, + worker_id=str(id), + ) + ) + ) + else: + commands.append( + CommandPublishEvent( + StepStateChanged( + step_state=StepState.PREPARING, + name=step_name, + input_event_name=type(event.event).__name__, + worker_id="", + ) + ) + ) + state.queue.append(event) + return commands + + +def _process_add_event_tick( + tick: TickAddEvent, init: BrokerState, now_seconds: float +) -> tuple[BrokerState, list[WorkflowCommand]]: + state = deepcopy(init) + # iterate through the steps, and add to steps work queue if it's accepted. + commands: list[WorkflowCommand] = [] + if isinstance(tick.event, StartEvent): + state.is_running = True + for step_name, step_config in state.config.steps.items(): + is_accepted = type(tick.event) in step_config.accepted_events + if is_accepted and (tick.step_name is None or tick.step_name == step_name): + subcommands = _add_or_enqueue_event( + EventAttempt(event=tick.event), + step_name, + state.workers[step_name], + now_seconds, + ) + commands.extend(subcommands) + + # separately, check if the event is a waiting event, and if so, update the waiting event state + # and set the resolved event. Add the original event as a command + for step_name, step_config in state.config.steps.items(): + wait_conditions = state.workers[step_name].collected_waiters + for wait_condition in wait_conditions: + is_match = type(tick.event) is wait_condition.waiting_for_event + is_match = is_match and all( + getattr(tick.event, k, None) == v + for k, v in wait_condition.requirements.items() + ) + if is_match: + wait_condition.resolved_event = tick.event + subcommands = _add_or_enqueue_event( + EventAttempt(event=wait_condition.event), + step_name, + state.workers[step_name], + now_seconds, + ) + commands.extend(subcommands) + return state, commands + + +def _process_cancel_run_tick( + tick: TickCancelRun, init: BrokerState +) -> tuple[BrokerState, list[WorkflowCommand]]: + state = deepcopy(init) + state.is_running = False + return state, [ + CommandPublishEvent(event=StopEvent()), + CommandHalt(exception=WorkflowCancelledByUser()), + ] + + +def _process_publish_event_tick( + tick: TickPublishEvent, init: BrokerState +) -> tuple[BrokerState, list[WorkflowCommand]]: + # doesn't affect state. Pass through as publish command + return init, [CommandPublishEvent(event=tick.event)] + + +def _process_timeout_tick( + tick: TickTimeout, init: BrokerState +) -> tuple[BrokerState, list[WorkflowCommand]]: + state = deepcopy(init) + state.is_running = False + active_steps = [ + step_name + for step_name, worker_state in init.workers.items() + if len(worker_state.in_progress) > 0 + ] + steps_info = ( + "Currently active steps: " + ", ".join(active_steps) + if active_steps + else "No steps active" + ) + return state, [ + CommandPublishEvent(event=StopEvent()), + CommandHalt( + exception=WorkflowTimeoutError( + f"Operation timed out after {tick.timeout} seconds. {steps_info}" + ) + ), + ] diff --git a/src/workflows/runtime/types/commands.py b/src/workflows/runtime/types/commands.py new file mode 100644 index 00000000..d17805db --- /dev/null +++ b/src/workflows/runtime/types/commands.py @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2025 LlamaIndex Inc. + +""" +Commands returned by the control loop's tick reducer. + +The control loop follows a reducer pattern: + 1. Wait for a tick (event, step result, timeout, etc.) + 2. Reduce the tick with current state -> (new_state, commands) + 3. Execute commands (which may spawn async tasks or queue new ticks) + 4. Repeat + +Commands represent imperative actions to take after processing a tick, +such as starting workers, queuing events, or completing the workflow. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Union + +from workflows.events import Event, StopEvent + + +@dataclass(frozen=True) +class CommandRunWorker: + step_name: str + event: Event + id: int + + +@dataclass(frozen=True) +class CommandQueueEvent: + event: Event + step_name: str | None = None + delay: float | None = None + attempts: int | None = None + first_attempt_at: float | None = None + + +@dataclass(frozen=True) +class CommandHalt: + exception: Exception + + +@dataclass(frozen=True) +class CommandCompleteRun: + result: StopEvent + + +@dataclass(frozen=True) +class CommandFailWorkflow: + step_name: str + exception: Exception + + +@dataclass(frozen=True) +class CommandPublishEvent: + event: Event + + +WorkflowCommand = Union[ + CommandRunWorker, + CommandQueueEvent, + CommandHalt, + CommandCompleteRun, + CommandFailWorkflow, + CommandPublishEvent, +] + + +def indicates_exit(command: WorkflowCommand) -> bool: + return ( + isinstance(command, CommandCompleteRun) + or isinstance(command, CommandFailWorkflow) + or isinstance(command, CommandHalt) + ) diff --git a/src/workflows/runtime/types/internal_state.py b/src/workflows/runtime/types/internal_state.py new file mode 100644 index 00000000..0f8fb833 --- /dev/null +++ b/src/workflows/runtime/types/internal_state.py @@ -0,0 +1,307 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2025 LlamaIndex Inc. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, TYPE_CHECKING + +from workflows.events import Event +from workflows.retry_policy import RetryPolicy +from workflows.decorators import StepConfig +from workflows.runtime.types.results import StepWorkerState, StepWorkerWaiter +from workflows.workflow import Workflow +from workflows.context.context_types import ( + SerializedContext, + SerializedStepWorkerState, + SerializedEventAttempt, + SerializedWaiter, +) + +from workflows.context.serializers import JsonSerializer +import importlib + +if TYPE_CHECKING: + from workflows.context.serializers import BaseSerializer + from workflows.context.context_types import SerializedContext + + +@dataclass() +class BrokerState: + """ + Complete state of the workflow broker at a given point in time. + + This is the primary state object passed through the control loop's reducer pattern. + Each tick processes this state and returns an updated copy along with commands to execute. + + Attributes: + config: Immutable configuration for the workflow and all steps + workers: Mutable state for each step's worker pool, queues, and in-progress executions + """ + + is_running: bool + config: BrokerConfig + workers: dict[str, InternalStepWorkerState] + + @staticmethod + def from_workflow(workflow: Workflow) -> BrokerState: + return BrokerState( + is_running=False, + config=BrokerConfig( + steps={ + name: InternalStepConfig( + accepted_events=step_func._step_config.accepted_events, + retry_policy=step_func._step_config.retry_policy, + num_workers=step_func._step_config.num_workers, + ) + for name, step_func in workflow._get_steps().items() + }, + timeout=workflow._timeout, + ), + workers={ + name: InternalStepWorkerState( + queue=[], + config=step_func._step_config, + in_progress=[], + collected_events={}, + collected_waiters=[], + ) + for name, step_func in workflow._get_steps().items() + }, + ) + + def to_serialized(self, serializer: BaseSerializer) -> SerializedContext: + """Serialize the broker state to a SerializedContext.""" + + workers_dict = {} + for step_name, worker_state in self.workers.items(): + # Serialize queue with retry info + queue = [ + SerializedEventAttempt( + event=serializer.serialize(attempt.event), + attempts=attempt.attempts or 0, + first_attempt_at=attempt.first_attempt_at, + ) + for attempt in worker_state.queue + ] + + # Serialize in-progress events (just the events, retry info tracked separately) + in_progress = [ + serializer.serialize(ip.event) for ip in worker_state.in_progress + ] + + # Serialize collected events + collected_events = { + buffer_id: [serializer.serialize(ev) for ev in events] + for buffer_id, events in worker_state.collected_events.items() + } + + # Serialize waiters + waiters = [ + SerializedWaiter( + waiter_id=waiter.waiter_id, + event=serializer.serialize(waiter.event), + waiting_for_event=f"{waiter.waiting_for_event.__module__}.{waiter.waiting_for_event.__name__}", + requirements=waiter.requirements, + resolved_event=serializer.serialize(waiter.resolved_event) + if waiter.resolved_event + else None, + ) + for waiter in worker_state.collected_waiters + ] + + workers_dict[step_name] = SerializedStepWorkerState( + queue=queue, + in_progress=in_progress, + collected_events=collected_events, + collected_waiters=waiters, + ) + + return SerializedContext( + version=1, + state={}, # State is filled separately by the state store + is_running=self.is_running, + workers=workers_dict, + ) + + @staticmethod + def from_serialized( + serialized: SerializedContext, + workflow: Workflow, + serializer: BaseSerializer, + ) -> BrokerState: + """Deserialize a SerializedContext into a BrokerState.""" + + serializer = serializer or JsonSerializer() + + # Start with a base state from the workflow + base_state = BrokerState.from_workflow(workflow) + # Always set is_running to False on deserialization - the workflow will set it to True when it starts + base_state.is_running = False + + # Restore worker state (queues, collected events, waiters) + # We do this regardless of is_running state so workflows can resume from where they left off + for step_name, worker_data in serialized.workers.items(): + if step_name not in base_state.workers: + continue + + worker = base_state.workers[step_name] + + # Restore queue with retry info + worker.queue = [ + EventAttempt( + event=serializer.deserialize(attempt.event), + attempts=attempt.attempts, + first_attempt_at=attempt.first_attempt_at, + ) + for attempt in worker_data.queue + ] + + # in_progress events are moved to the queue on deserialization + # They will be restarted when the workflow runs + for event_str in worker_data.in_progress: + worker.queue.append( + EventAttempt( + event=serializer.deserialize(event_str), + attempts=0, + first_attempt_at=None, + ) + ) + + # Restore collected events + worker.collected_events = { + buffer_id: [serializer.deserialize(ev) for ev in events] + for buffer_id, events in worker_data.collected_events.items() + } + + # Restore waiters + worker.collected_waiters = [] + for waiter_data in worker_data.collected_waiters: + # Import the event type + waiting_for_event = _import_event_type(waiter_data.waiting_for_event) + + worker.collected_waiters.append( + StepWorkerWaiter( + waiter_id=waiter_data.waiter_id, + event=serializer.deserialize(waiter_data.event), + waiting_for_event=waiting_for_event, + requirements=waiter_data.requirements, + resolved_event=serializer.deserialize( + waiter_data.resolved_event + ) + if waiter_data.resolved_event + else None, + ) + ) + + return base_state + + +def _import_event_type(qualified_name: str) -> type[Event]: + """Import an event type from a fully qualified name like 'mymodule.MyEvent'.""" + parts = qualified_name.rsplit(".", 1) + if len(parts) != 2: + raise ValueError(f"Invalid qualified name: {qualified_name}") + + module_name, class_name = parts + + module = importlib.import_module(module_name) + return getattr(module, class_name) + + +@dataclass() +class BrokerConfig: + """ + Immutable configuration for a workflow run. + + This contains all the static configuration that doesn't change during workflow execution. + + Attributes: + steps: Configuration for each step indexed by step name + timeout: Maximum seconds before the workflow times out, or None for no timeout + """ + + steps: dict[str, InternalStepConfig] + timeout: float | None + + +@dataclass() +class InternalStepConfig: + """ + Configuration for a single step in the workflow. + + Attributes: + accepted_events: List of Event type classes this step can handle + retry_policy: Policy for retrying failed executions, or None for no retries + num_workers: Maximum number of concurrent executions of this step + """ + + accepted_events: list[Any] + retry_policy: RetryPolicy | None + num_workers: int + + +@dataclass() +class EventAttempt: + """ + Represents an event that is being or will be processed by a step. + + Tracks retry information for events that have failed and are being retried. + + Attributes: + event: The event to process + attempts: Number of times this event has been attempted (0 for first attempt), or None if not yet attempted + first_attempt_at: Unix timestamp of first attempt, or None if not yet attempted + """ + + event: Event + attempts: int | None = None + first_attempt_at: float | None = None + + +@dataclass() +class InternalStepWorkerState: + """ + Runtime state for a single step's worker pool. + + This manages the queue of pending events, currently executing workers, and any + state needed for ctx.collect_events() and ctx.wait_for_event() operations. + + Attributes: + queue: Events waiting to be processed by this step + config: Step configuration (includes retry policy, num_workers, etc.) + in_progress: Currently executing workers for this step + collected_events: Events being collected via ctx.collect_events(), keyed by buffer_id + collected_waiters: Active waiters created by ctx.wait_for_event() + """ + + queue: list[EventAttempt] + config: StepConfig + in_progress: list[InProgressState] + collected_events: dict[str, list[Event]] + collected_waiters: list[StepWorkerWaiter] + + +@dataclass() +class InProgressState: + """ + Represents a single worker execution that is currently in progress. + + Each worker gets a snapshot of the step's shared state at the time it starts. + This enables optimistic execution - if the shared state changes during execution + (e.g., new collected events arrive), the control loop can detect this and retry + the worker with the updated state. + + Attributes: + event: The event being processed by this worker + worker_id: Numeric ID (0 to num_workers-1) identifying this worker slot + shared_state: Snapshot of collected_events and collected_waiters at worker start time + attempts: Number of times this event has been attempted (including current attempt) + first_attempt_at: Unix timestamp when this event was first attempted + """ + + event: Event + worker_id: int + shared_state: StepWorkerState + attempts: int + first_attempt_at: float diff --git a/src/workflows/runtime/types/plugin.py b/src/workflows/runtime/types/plugin.py new file mode 100644 index 00000000..1ae01c6f --- /dev/null +++ b/src/workflows/runtime/types/plugin.py @@ -0,0 +1,133 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2025 LlamaIndex Inc. +""" +A plugin interface to switch out a broker runtime (external library or service that manages durable/distributed step execution). +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import ( + AsyncGenerator, + Callable, + Coroutine, + Generic, + Protocol, + TYPE_CHECKING, + cast, +) + + +from workflows.decorators import P, R +from workflows.events import Event, StopEvent + +from workflows.runtime.types.internal_state import BrokerState +from workflows.runtime.types.step_function import StepWorkerFunction +from workflows.runtime.types.ticks import WorkflowTick + +if TYPE_CHECKING: + from workflows.workflow import Workflow + + +@dataclass +class RegisteredWorkflow(Generic[P, R]): + workflow_function: Callable[P, R] + steps: dict[str, StepWorkerFunction] + + +class Plugin(Protocol): + def register( + self, + workflow: Workflow, + workflow_function: ControlLoopFunction, + steps: dict[str, StepWorkerFunction], + ) -> None | RegisteredWorkflow: + """ + Called on a workflow before the first time each workflow instance is run, in order to register it within the plugin's runtime. + + Provides an opportunity to modify the workflow function and steps, e.g. to wrap the workflow_function, or StepWorkerFunction's within the steps a decorator. + """ + ... + + def new_runtime(self, run_id: str) -> WorkflowRuntime: + """ + Called on each workflow run, in order to create a new runtime instance for driving the workflow via the plugin's runtime. + """ + ... + + +class WorkflowRuntime(Protocol): + """ + A LlamaIndex workflow's internal state is managed via an event-sourced reducer that triggers step executions. It communicates + with the outside world via messages. Messages may be sent to it to update its event log, and it in turn publishes messages that are made + available via the event stream. + """ + + async def send_event(self, tick: WorkflowTick) -> None: + """Called from outside of the workflow to modify the workflow execution. WorkflowTick events are appended to a mailbox and processed sequentially""" + ... + + async def wait_receive(self) -> WorkflowTick: + """called from inside of the workflow control loop function to add a tick event from `send_event` to the mailbox. Function waits until a tick event is received.""" + ... + + async def write_to_event_stream(self, event: Event) -> None: + """Called from inside of a workflow function to write / emit events to listeners outside of the workflow""" + ... + + def stream_published_events(self) -> AsyncGenerator[Event, None]: + """Called from outside of a workflow, reads event stream published by the workflow""" + ... + + async def get_now(self) -> float: + """Called from within the workflow control loop function to get the current time in seconds since epoch. If workflow is durable via replay, it should return a cached value from the first call. (e.g. this should be implemented similar to a regular durable step)""" + ... + + async def sleep(self, seconds: float) -> None: + """Called from within the workflow control loop function to sleep for a given number of seconds. This should integrate with the host plugin for cases where an inactive workflow may be paused, and awoken later via memoized replay. Note that other tasks in the control loop may still be running simultaneously.""" + ... + + async def close(self) -> None: + """API that the broker calls to close the plugin after a workflow run is fully complete""" + ... + + +class SnapshottableRuntime(WorkflowRuntime, Protocol): + """ + Snapshot API. Optional mix in to a WorkflowRuntime. When implemented, plugin should offer a replay function to return the recorded + ticks so that callers can debug or replay the workflow. `on_tick` is called whenever a tick event is received externally OR as a result + from an internal command (e.g. a step function completing, a timeout occurring, etc.) + + """ + + def on_tick(self, tick: WorkflowTick) -> None: + """Called whenever a tick event is received""" + ... + + def replay(self) -> list[WorkflowTick]: + """return the recorded ticks for replay""" + ... + + +def as_snapshottable(runtime: WorkflowRuntime) -> SnapshottableRuntime | None: + """Check if a runtime is snapshottable.""" + if ( + getattr(runtime, "on_tick", None) is not None + and getattr(runtime, "replay", None) is not None + ): + return cast(SnapshottableRuntime, runtime) + return None + + +class ControlLoopFunction(Protocol): + """ + Protocol for a function that starts and runs the internal control loop for a workflow run. + Plugin decorators to the control loop function must maintain this signature. + """ + + def __call__( + self, + start_event: Event | None, + init_state: BrokerState | None, + run_id: str, + ) -> Coroutine[None, None, StopEvent]: ... diff --git a/src/workflows/runtime/types/results.py b/src/workflows/runtime/types/results.py new file mode 100644 index 00000000..165b6f34 --- /dev/null +++ b/src/workflows/runtime/types/results.py @@ -0,0 +1,172 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2025 LlamaIndex Inc. + +from __future__ import annotations + +from contextvars import ContextVar +from dataclasses import dataclass +from typing import ( + TYPE_CHECKING, + Any, + Generic, + TypeVar, + Union, +) + +from workflows.events import Event +from workflows.decorators import R + +if TYPE_CHECKING: + pass + + +EventType = TypeVar("EventType", bound=Event) + +################################################################# +# State Passed to step functions and returned by step functions # +################################################################# + + +@dataclass(frozen=True) +class StepWorkerContext(Generic[R]): + """ + Base state passed to step functions and returned by step functions. + """ + + # immutable state of the step events at start of the step function execution + state: StepWorkerState + # add commands here to mutate the internal worker state after step execution + returns: Returns[R] + + +@dataclass(frozen=True) +class StepWorkerState: + """ + State passed to step functions and returned by step functions. + """ + + step_name: str + collected_events: dict[str, list[Event]] + collected_waiters: list[StepWorkerWaiter] + + +@dataclass() +class StepWorkerWaiter(Generic[EventType]): + """ + Any current waiters for events that are or are not resolved. Upon resolution, step should provide a delete waiter command. + """ + + # the waiter id + waiter_id: str + # original event to replay once the condition is met + event: Event + # the type of event that is being waited for + waiting_for_event: type[EventType] + # the requirements for the waiting event to consider it met + requirements: dict[str, Any] + # set to true when the waiting event has been resolved, such that the step can retrieve it + resolved_event: EventType | None + + +@dataclass() +class Returns(Generic[R]): + """ + Mutate to add return values to the step function. These are only executed after the + step function has completed (including errors!) + """ + + return_values: list[StepFunctionResult[R, Any]] + + +class WaitingForEvent(Exception, Generic[EventType]): + """ + Raised when a step function is called, waiting for an event, but the event is not yet available. + Handled by the step worker to instead add a waiter rather than failing. Step is retried with the original event + once the waiting event is available. + """ + + def __init__(self, add: AddWaiter[EventType]): + self.add = add + super().__init__(f"Waiting for event {add.event_type}") + + add: AddWaiter[EventType] + + +StepWorkerStateContextVar = ContextVar[StepWorkerContext]("step_worker") + + +################################### +# Data returned by step functions # +################################### + + +@dataclass +class StepWorkerResult(Generic[R]): + """ + Returned after a step function has been successfully executed. + """ + + result: R + + +@dataclass +class StepWorkerFailed(Generic[R]): + """ + Returned after a step function has failed + """ + + exception: Exception + failed_at: float + + +@dataclass +class DeleteWaiter: + """ + Returned after a waiter condition has been successfully resolved. + """ + + waiter_id: str + + +@dataclass +class DeleteCollectedEvent: + """ + Returned after a collected event has been successfully resolved. + """ + + event_id: str + + +@dataclass +class AddCollectedEvent: + """ + Returned after a collected event has been added, and is not yet resolved. + """ + + event_id: str + event: Event + + +@dataclass +class AddWaiter(Generic[EventType]): + """ + Returned after a waiter has been added, and is not yet resolved. + """ + + waiter_id: str + waiter_event: Event | None + requirements: dict[str, Any] + timeout: float | None + event_type: type[EventType] + + +# A step function result "command" communicates back to the workflow how the step function was resolved +# e.g. are we collecting events, waiting for an event, or just returning a result? +StepFunctionResult = Union[ + StepWorkerResult[R], + StepWorkerFailed[R], + AddCollectedEvent, + DeleteCollectedEvent, + AddWaiter[EventType], + DeleteWaiter, +] diff --git a/src/workflows/runtime/types/step_function.py b/src/workflows/runtime/types/step_function.py new file mode 100644 index 00000000..5fcc7086 --- /dev/null +++ b/src/workflows/runtime/types/step_function.py @@ -0,0 +1,151 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2025 LlamaIndex Inc. + +from __future__ import annotations + +import asyncio +from contextvars import copy_context +import functools +import time +from typing import Any, Awaitable, Callable, TYPE_CHECKING, Generic, Protocol + + +from workflows.decorators import P, R, StepConfig +from workflows.errors import WorkflowRuntimeError +from workflows.events import ( + Event, +) +from workflows.runtime.types.results import ( + Returns, + StepFunctionResult, + StepWorkerContext, + StepWorkerFailed, + StepWorkerResult, + StepWorkerState, + StepWorkerStateContextVar, + WaitingForEvent, +) + +from workflows.workflow import Workflow + +if TYPE_CHECKING: + from workflows.context.context import Context + + +class StepWorkerFunction(Protocol, Generic[R]): + def __call__( + self, + state: StepWorkerState, + step_name: str, + event: Event, + context: Context, # TODO - pass an identifier and re-hydrate from the plugin for distributed step workers + workflow: Workflow, + ) -> Awaitable[list[StepFunctionResult[R, Any]]]: ... + + +async def partial( + func: Callable[..., R], + step_config: StepConfig, + event: Event, + context: Context, + workflow: Workflow, +) -> Callable[[], R]: + kwargs: dict[str, Any] = {} + kwargs[step_config.event_name] = event + if step_config.context_parameter: + kwargs[step_config.context_parameter] = context + for resource in step_config.resources: + resource_value = await workflow._resource_manager.get( + resource=resource.resource + ) + kwargs[resource.name] = resource_value + return functools.partial(func, **kwargs) + + +def as_step_worker_function(func: Callable[P, Awaitable[R]]) -> StepWorkerFunction[R]: + """ + Wrap a step function, setting context variables and handling exceptions to instead + return the appropriate StepFunctionResult. + """ + + # Keep original function reference for free-function steps; for methods we + # will resolve the currently-bound method from the provided workflow at call time. + original_func: Callable[..., Awaitable[R]] = func + + # Avoid functools.wraps here because it would set __wrapped__ to the bound + # method (when present), which would strongly reference the workflow + # instance and prevent garbage collection under high churn. + async def wrapper( + state: StepWorkerState, + step_name: str, + event: Event, + context: Context, + workflow: Workflow, + ) -> list[StepFunctionResult[R, Any]]: + returns = Returns[R](return_values=[]) + + token = StepWorkerStateContextVar.set( + StepWorkerContext(state=state, returns=returns) + ) + + try: + config = workflow._get_steps()[step_name]._step_config + # Resolve callable at call time: + # - If the workflow has an attribute with the step name, use it + # (this yields a bound method for instance-defined steps). + # - Otherwise, fall back to the original function (free function step). + try: + call_func = getattr(workflow, step_name) + except AttributeError: + call_func = original_func + partial_func = await partial( + func=workflow._dispatcher.span(call_func), + step_config=config, + event=event, + context=context, + workflow=workflow, + ) + + try: + # coerce to coroutine function + if not asyncio.iscoroutinefunction(call_func): + # run_in_executor doesn't accept **kwargs, so we need to use partial + copy = copy_context() + + result: R = await asyncio.get_event_loop().run_in_executor( + None, + lambda: copy.run(partial_func), # type: ignore + ) + else: + result = await partial_func() + if result is not None and not isinstance(result, Event): + msg = f"Step function {step_name} returned {type(result).__name__} instead of an Event instance." + raise WorkflowRuntimeError(msg) + returns.return_values.append(StepWorkerResult(result=result)) + except WaitingForEvent as e: + await asyncio.sleep(0) + returns.return_values.append(e.add) + except Exception as e: + returns.return_values.append( + StepWorkerFailed(exception=e, failed_at=time.monotonic()) + ) + return returns.return_values + finally: + try: + StepWorkerStateContextVar.reset(token) + except Exception: + pass + + # Manually set minimal metadata without retaining bound instance references. + try: + unbound_for_wrapped = getattr(func, "__func__", func) + wrapper.__name__ = getattr(func, "__name__", wrapper.__name__) + wrapper.__qualname__ = getattr(func, "__qualname__", wrapper.__qualname__) + # Point __wrapped__ to the unbound function when available to avoid + # strong refs to the instance via a bound method object. + setattr(wrapper, "__wrapped__", unbound_for_wrapped) + except Exception: + # Best-effort; lack of these attributes is non-fatal. + pass + + return wrapper diff --git a/src/workflows/runtime/types/ticks.py b/src/workflows/runtime/types/ticks.py new file mode 100644 index 00000000..c7c13235 --- /dev/null +++ b/src/workflows/runtime/types/ticks.py @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2025 LlamaIndex Inc. + +""" +Ticks (events) that drive the control loop. + +The control loop waits for ticks to arrive, then processes them through a reducer +to produce updated state and commands. Ticks represent all the different kinds of +events that can occur during workflow execution: + - New events added to the workflow + - Step function execution completing + - Timeout occurring + - User cancellation + - External event publishing requests +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Generic, Union + +from workflows.events import Event +from workflows.decorators import R +from workflows.runtime.types.results import StepFunctionResult + + +@dataclass(frozen=True) +class TickStepResult(Generic[R]): + """When processed, executes a step function and publishes the result""" + + step_name: str + worker_id: int + event: Event + result: list[StepFunctionResult[R, Any]] + + +@dataclass(frozen=True) +class TickAddEvent: + """When sent, adds an event to the workflow's event queue""" + + event: Event + step_name: str | None = None + attempts: int | None = None + first_attempt_at: float | None = None + + +@dataclass(frozen=True) +class TickCancelRun: + """When processed, cancels the workflow run""" + + pass + + +@dataclass(frozen=True) +class TickPublishEvent: + """When sent, publishes an event to workflow consumers, e.g. a UI or a callback""" + + event: Event + + +@dataclass(frozen=True) +class TickTimeout: + """When processed, times the workflow out, cancelling it""" + + timeout: float + + +WorkflowTick = Union[ + TickStepResult[R], TickAddEvent, TickCancelRun, TickPublishEvent, TickTimeout +] diff --git a/src/workflows/runtime/workflow_registry.py b/src/workflows/runtime/workflow_registry.py new file mode 100644 index 00000000..f67aaa7b --- /dev/null +++ b/src/workflows/runtime/workflow_registry.py @@ -0,0 +1,93 @@ +from threading import Lock +from weakref import WeakKeyDictionary +from dataclasses import dataclass +from typing import Optional +from workflows.runtime.types.plugin import ( + ControlLoopFunction, + Plugin, + RegisteredWorkflow, + WorkflowRuntime, +) +from workflows.workflow import Workflow +from workflows.runtime.types.step_function import StepWorkerFunction +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from workflows.context.context import Context + + +class WorkflowPluginRegistry: + """ + Ensures that plugins register each workflow once and only once for each plugin. + """ + + def __init__(self) -> None: + # Map each workflow instance to its plugin registrations. + # Weakly references workflow keys so entries are GC'd when workflows are. + self.workflows: WeakKeyDictionary[ + Workflow, dict[type[Plugin], RegisteredWorkflow] + ] = WeakKeyDictionary() + self.lock = Lock() + self.run_contexts: dict[str, RegisteredRunContext] = {} + + def get_registered_workflow( + self, + workflow: Workflow, + plugin: Plugin, + workflow_function: ControlLoopFunction, + steps: dict[str, StepWorkerFunction], + ) -> RegisteredWorkflow: + plugin_type = type(plugin) + + # Fast path without lock + plugin_map = self.workflows.get(workflow) + if plugin_map is not None and plugin_type in plugin_map: + return plugin_map[plugin_type] + with self.lock: + # Double-check after acquiring lock + plugin_map = self.workflows.get(workflow) + if plugin_map is not None and plugin_type in plugin_map: + return plugin_map[plugin_type] + + registered_workflow = plugin.register(workflow, workflow_function, steps) + if registered_workflow is None: + registered_workflow = RegisteredWorkflow(workflow_function, steps) + if plugin_map is None: + plugin_map = {} + self.workflows[workflow] = plugin_map + plugin_map[plugin_type] = registered_workflow + return registered_workflow + + def register_run( + self, + run_id: str, + workflow: Workflow, + plugin: WorkflowRuntime, + context: "Context", + steps: dict[str, StepWorkerFunction], + ) -> None: + self.run_contexts[run_id] = RegisteredRunContext( + run_id=run_id, + workflow=workflow, + plugin=plugin, + context=context, + steps=steps, + ) + + def get_run(self, run_id: str) -> Optional["RegisteredRunContext"]: + return self.run_contexts.get(run_id) + + def delete_run(self, run_id: str) -> None: + self.run_contexts.pop(run_id, None) + + +workflow_registry = WorkflowPluginRegistry() + + +@dataclass +class RegisteredRunContext: + run_id: str + workflow: Workflow + plugin: WorkflowRuntime + context: "Context" + steps: dict[str, StepWorkerFunction] diff --git a/src/workflows/server/representation_utils.py b/src/workflows/server/representation_utils.py index e1264836..3cdc9115 100644 --- a/src/workflows/server/representation_utils.py +++ b/src/workflows/server/representation_utils.py @@ -6,7 +6,7 @@ InputRequiredEvent, HumanResponseEvent, ) -from workflows.decorators import StepConfig +from workflows.decorators import StepConfig, StepFunction from workflows.protocol import ( WorkflowGraphEdge, WorkflowGraphNode, @@ -79,7 +79,7 @@ def _extract_workflow_structure( ) -> DrawWorkflowGraph: """Extract workflow structure into an intermediate representation.""" # Get workflow steps - steps = get_steps_from_class(workflow) + steps: dict[str, StepFunction] = get_steps_from_class(workflow) if not steps: steps = get_steps_from_instance(workflow) @@ -93,9 +93,7 @@ def _extract_workflow_structure( # Assuming that `Workflow` is validated before drawing, it's enough to find the first one. current_stop_event = None for step_name, step_func in steps.items(): - step_config = getattr(step_func, "__step_config", None) - if step_config is None: - continue + step_config = step_func._step_config for return_type in step_config.return_types: if issubclass(return_type, StopEvent): @@ -107,9 +105,7 @@ def _extract_workflow_structure( # First pass: Add all nodes for step_name, step_func in steps.items(): - step_config = getattr(step_func, "__step_config", None) - if step_config is None: - continue + step_config = step_func._step_config # Add step node step_label = ( @@ -206,9 +202,7 @@ def _extract_workflow_structure( # Second pass: Add edges for step_name, step_func in steps.items(): - step_config = getattr(step_func, "__step_config", None) - if step_config is None: - continue + step_config = step_func._step_config # Edges from steps to return types for return_type in step_config.return_types: diff --git a/src/workflows/server/server.py b/src/workflows/server/server.py index a83a34b0..23c78767 100644 --- a/src/workflows/server/server.py +++ b/src/workflows/server/server.py @@ -231,8 +231,9 @@ async def stop(self) -> None: logger.info( f"Shutting down Workflow server. Cancelling {len(self._handlers)} handlers." ) - for handler in list(self._handlers.values()): - await self._close_handler(handler) + await asyncio.gather( + *[self._close_handler(handler) for handler in list(self._handlers.values())] + ) self._handlers.clear() self._results.clear() diff --git a/src/workflows/testing/runner.py b/src/workflows/testing/runner.py index ddf59549..1f0fcdc7 100644 --- a/src/workflows/testing/runner.py +++ b/src/workflows/testing/runner.py @@ -1,9 +1,8 @@ -from typing import Any, Optional, TYPE_CHECKING +from typing import Any, Optional from collections import Counter from dataclasses import dataclass -if TYPE_CHECKING: - from workflows import Workflow, Context +from workflows import Workflow, Context from workflows.events import StartEvent, Event, EventType @@ -21,6 +20,7 @@ class WorkflowTestResult: collected: list[Event] event_types: dict[EventType, int] result: Any + ctx: Context class WorkflowTestRunner: @@ -59,7 +59,7 @@ async def run( ``` wf = GreetingWorkflow() runner = WorkflowTestRunner(wf) - test_result = runner.run(start_even=StartEvent(message="hello"), expose_internal = True, exclude_events = [EventsQueueChanged]) + test_result = runner.run(start_even=StartEvent(message="hello"), expose_internal = True, exclude_events = [StepStateChanged]) assert test_result.collected == 22 assert test_result.event_types.get(StepStateChanged, 0) == 8 assert str(test_result.result) == "hello Adam!" @@ -75,6 +75,10 @@ async def run( event_freqs: dict[EventType, int] = dict( Counter([type(ev) for ev in collected_events]) ) + assert handler.ctx is not None return WorkflowTestResult( - collected=collected_events, result=result, event_types=event_freqs + collected=collected_events, + result=result, + event_types=event_freqs, + ctx=handler.ctx, ) diff --git a/src/workflows/utils.py b/src/workflows/utils.py index d494b908..3a0482df 100644 --- a/src/workflows/utils.py +++ b/src/workflows/utils.py @@ -5,15 +5,20 @@ import inspect from typing import ( + TYPE_CHECKING, Annotated, Any, Callable, Optional, + cast, get_args, get_origin, get_type_hints, ) +if TYPE_CHECKING: + from workflows.decorators import StepFunction + try: from typing import Union except ImportError: # pragma: no cover @@ -147,7 +152,7 @@ def validate_step_signature(spec: StepSignatureSpec) -> None: raise WorkflowValidationError(msg) -def get_steps_from_class(_class: object) -> dict[str, Callable]: +def get_steps_from_class(_class: object) -> dict[str, StepFunction]: """ Given a class, return the list of its methods that were defined as steps. @@ -158,17 +163,19 @@ def get_steps_from_class(_class: object) -> dict[str, Callable]: dict[str, Callable]: A dictionary mapping step names to their corresponding methods. """ - step_methods: dict[str, Callable] = {} + from workflows.decorators import StepFunction + + step_methods: dict[str, StepFunction] = {} all_methods = inspect.getmembers(_class, predicate=inspect.isfunction) for name, method in all_methods: - if hasattr(method, "__step_config"): - step_methods[name] = method + if hasattr(method, "_step_config"): + step_methods[name] = cast(StepFunction, method) return step_methods -def get_steps_from_instance(workflow: object) -> dict[str, Callable]: +def get_steps_from_instance(workflow: object) -> dict[str, StepFunction]: """ Given a workflow instance, return the list of its methods that were defined as steps. @@ -179,12 +186,14 @@ def get_steps_from_instance(workflow: object) -> dict[str, Callable]: dict[str, Callable]: A dictionary mapping step names to their corresponding methods. """ - step_methods: dict[str, Callable] = {} + from workflows.decorators import StepFunction + + step_methods: dict[str, StepFunction] = {} all_methods = inspect.getmembers(workflow, predicate=inspect.ismethod) for name, method in all_methods: - if hasattr(method, "__step_config"): - step_methods[name] = method + if hasattr(method, "_step_config"): + step_methods[name] = cast(StepFunction, method) return step_methods diff --git a/src/workflows/workflow.py b/src/workflows/workflow.py index 704fb1a4..756c973d 100644 --- a/src/workflows/workflow.py +++ b/src/workflows/workflow.py @@ -5,24 +5,22 @@ import asyncio import logging -import uuid from typing import ( Any, - Callable, Tuple, ) -from weakref import WeakSet from llama_index_instrumentation import get_dispatcher from pydantic import ValidationError -from .context import Context -from .decorators import StepConfig, step +from typing import TYPE_CHECKING + +if TYPE_CHECKING: # pragma: no cover + from .context import Context +from .decorators import StepConfig, StepFunction from .errors import ( WorkflowConfigurationError, - WorkflowDone, WorkflowRuntimeError, - WorkflowTimeoutError, WorkflowValidationError, ) from .events import ( @@ -44,7 +42,7 @@ class WorkflowMeta(type): def __init__(cls, name: str, bases: Tuple[type, ...], dct: dict[str, Any]) -> None: super().__init__(name, bases, dct) - cls._step_functions: dict[str, Callable] = {} + cls._step_functions: dict[str, StepFunction] = {} class Workflow(metaclass=WorkflowMeta): @@ -127,8 +125,6 @@ def __init__( self._sem = ( asyncio.Semaphore(num_concurrent_runs) if num_concurrent_runs else None ) - # Broker machinery - self._contexts: WeakSet[Context] = WeakSet() # Resource management self._resource_manager = resource_manager or ResourceManager() # Instrumentation @@ -142,7 +138,7 @@ def _ensure_start_event_class(self) -> type[StartEvent]: """ start_events_found: set[type[StartEvent]] = set() for step_func in self._get_steps().values(): - step_config: StepConfig = getattr(step_func, "__step_config") + step_config: StepConfig = step_func._step_config for event_type in step_config.accepted_events: if issubclass(event_type, StartEvent): start_events_found.add(event_type) @@ -180,7 +176,7 @@ def _ensure_events_collected(self) -> list[type[Event]]: """ events_found: set[type[Event]] = set() for step_func in self._get_steps().values(): - step_config: StepConfig = getattr(step_func, "__step_config") + step_config: StepConfig = step_func._step_config # Do not collect events from the done step if step_func.__name__ == "_done": @@ -203,7 +199,7 @@ def _ensure_stop_event_class(self) -> type[RunResultT]: """ stop_events_found: set[type[StopEvent]] = set() for step_func in self._get_steps().values(): - step_config: StepConfig = getattr(step_func, "__step_config") + step_config: StepConfig = step_func._step_config for event_type in step_config.return_types: if issubclass(event_type, StopEvent): stop_events_found.add(event_type) @@ -227,13 +223,13 @@ def stop_event_class(self) -> type[RunResultT]: return self._stop_event_class @classmethod - def add_step(cls, func: Callable) -> None: + def add_step(cls, func: StepFunction) -> None: """ Adds a free function as step for this workflow instance. It raises an exception if a step with the same name was already added to the workflow. """ - step_config: StepConfig | None = getattr(func, "__step_config", None) + step_config: StepConfig | None = getattr(func, "_step_config", None) if not step_config: msg = f"Step function {func.__name__} is missing the `@step` decorator." raise WorkflowValidationError(msg) @@ -244,62 +240,9 @@ def add_step(cls, func: Callable) -> None: cls._step_functions[func.__name__] = func - def _get_steps(self) -> dict[str, Callable]: + def _get_steps(self) -> dict[str, StepFunction]: """Returns all the steps, whether defined as methods or free functions.""" - return {**get_steps_from_instance(self), **self._step_functions} # type: ignore[attr-defined] - - def _start( - self, - ctx: Context | None = None, - ) -> Tuple[Context, str]: - """ - sets up the queues and tasks for each declared step. - - This method also launches each step as an async task. - """ - run_id = str(uuid.uuid4()) - if ctx is None: - ctx = Context(self) - self._contexts.add(ctx) - else: - # clean up the context from the previous run - ctx._tasks = set() - ctx._retval = None - ctx._step_events_holding = None - ctx._cancel_flag.clear() - - for name, step_func in self._get_steps().items(): - if name not in ctx._queues: - ctx._queues[name] = asyncio.Queue() - - if name not in ctx._step_flags: - ctx._step_flags[name] = asyncio.Event() - - # At this point, step_func is guaranteed to have the `__step_config` attribute - step_config: StepConfig = getattr(step_func, "__step_config") - - # Make the system step "_done" accept custom stop events - if ( - name == "_done" - and self._stop_event_class not in step_config.accepted_events - ): - step_config.accepted_events.append(self._stop_event_class) - - for _ in range(step_config.num_workers): - ctx.add_step_worker( - name=name, - step=step_func, - config=step_config, - verbose=self._verbose, - run_id=run_id, - worker_id=str(uuid.uuid4()), - resource_manager=self._resource_manager, - ) - - # add dedicated cancel task - ctx.add_cancel_worker() - - return ctx, run_id + return {**get_steps_from_instance(self), **self.__class__._step_functions} def _get_start_event_instance( self, start_event: StartEvent | None, **kwargs: Any @@ -381,99 +324,21 @@ def run( result = await my_workflow.run(start_event=MyStartEvent(topic="Pirates")) ``` """ + from workflows.context import Context # Validate the workflow self._validate() - async def _run_workflow(ctx: Context) -> None: - if self._sem: - await self._sem.acquire() - try: - if not ctx.is_running: - # Send the first event - start_event_instance = self._get_start_event_instance( - start_event, **kwargs - ) - ctx.send_event(start_event_instance) - - # the context is now running - ctx.is_running = True - - done, unfinished = await asyncio.wait( - ctx._tasks, - timeout=self._timeout, - return_when=asyncio.FIRST_EXCEPTION, - ) - - we_done = False - exception_raised = None - for task in done: - e = task.exception() - if type(e) is WorkflowDone: - we_done = True - elif e is not None: - exception_raised = e - break - - # Cancel any pending tasks - for t in unfinished: - t.cancel() - - # wait for cancelled tasks to cleanup - # prevents any tasks from being stuck - try: - await asyncio.wait_for( - asyncio.gather(*unfinished, return_exceptions=True), - timeout=0.5, - ) - except asyncio.TimeoutError: - logger.warning("Some tasks did not clean up within timeout") - - # the context is no longer running - ctx.is_running = False - - if exception_raised: - # cancel the stream - ctx.write_event_to_stream(StopEvent()) - - raise exception_raised - - if not we_done: - # cancel the stream - ctx.write_event_to_stream(StopEvent()) - - msg = f"Operation timed out after {self._timeout} seconds" - raise WorkflowTimeoutError(msg) - - result.set_result(ctx._retval) - except Exception as e: - if not result.done(): - result.set_exception(e) - finally: - if self._sem: - self._sem.release() - await ctx.shutdown() - - # Start the machinery in a new Context or use the provided one - started_ctx, run_id = self._start(ctx=ctx) - run_task = asyncio.create_task(_run_workflow(started_ctx)) - result = WorkflowHandler(ctx=started_ctx, run_id=run_id, run_task=run_task) - return result - - @step(num_workers=1) - async def _done(self, ctx: Context, ev: StopEvent) -> None: - """Tears down the whole workflow and stop execution.""" - if self._stop_event_class is StopEvent: - ctx._retval = ev.result - else: - ctx._retval = ev - - ctx.write_event_to_stream(ev) - - # Signal we want to stop the workflow. Since we're about to raise, delete - # the reference to ctx explicitly to avoid it becoming dangling - del ctx - raise WorkflowDone + # If a previous context is provided, pass its serialized form + ctx = ctx if ctx is not None else Context(self) + start_event_instance: StartEvent | None = ( + None + if ctx.is_running + else self._get_start_event_instance(start_event, **kwargs) + ) + return ctx._workflow_run( + workflow=self, start_event=start_event_instance, semaphore=self._sem + ) def _validate(self) -> bool: """ @@ -491,9 +356,7 @@ def _validate(self) -> bool: steps_accepting_stop_event: list[str] = [] for name, step_func in self._get_steps().items(): - step_config: StepConfig | None = getattr(step_func, "__step_config") - # At this point we know step config is not None, let's make the checker happy - assert step_config is not None + step_config: StepConfig = step_func._step_config # Check that no user-defined step accepts StopEvent (only _done step should) if name != "_done": diff --git a/tests/conftest.py b/tests/conftest.py index c11f7161..3d1908f8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2025 LlamaIndex Inc. +from typing import AsyncGenerator import pytest from pydantic import Field @@ -47,5 +48,10 @@ def events() -> list: @pytest.fixture() -def ctx() -> Context: - return Context(workflow=DummyWorkflow()) +async def ctx(workflow: Workflow) -> AsyncGenerator[Context, None]: + ctx = Context(workflow=workflow) + broker = ctx._init_broker(workflow) + try: + yield ctx + finally: + await broker.shutdown() diff --git a/tests/context/test_context.py b/tests/context/test_context.py index d6ef9434..0c5413f6 100644 --- a/tests/context/test_context.py +++ b/tests/context/test_context.py @@ -4,8 +4,8 @@ from __future__ import annotations import asyncio -import json -import sys + +from workflows.runtime.types.ticks import TickAddEvent try: from typing import Union @@ -13,14 +13,13 @@ from typing_extensions import Union from typing import Optional -from unittest import mock import pytest from pydantic import BaseModel from workflows.context import Context from workflows.context.state_store import DictState -from workflows.decorators import StepConfig, step +from workflows.decorators import step from workflows.errors import WorkflowRuntimeError from workflows.events import ( Event, @@ -32,7 +31,7 @@ from workflows.testing import WorkflowTestRunner from workflows.workflow import Workflow -from ..conftest import AnotherTestEvent, OneTestEvent +from ..conftest import AnotherTestEvent, LastEvent, OneTestEvent @pytest.mark.asyncio @@ -62,6 +61,66 @@ async def step3( assert r.result == [ev1, ev2] +@pytest.mark.asyncio +async def test_collect_events_with_extra_event_type() -> None: + """ + Test that collect_events properly handles when an event of a different type + arrives first, before the expected events. + + This validates that when collect_events is called with an event that's NOT + in the expected types list, it returns None and waits for matching events. + """ + + class TestWorkflow(Workflow): + @step + async def start_step( + self, ctx: Context, ev: StartEvent + ) -> Union[OneTestEvent, AnotherTestEvent, LastEvent]: + await ctx.store.set("num_to_collect", 2) + await ctx.store.set("calls", 0) + # Send a LastEvent first (not in the expected collection types) + ctx.send_event(LastEvent()) + # Then send the events we want to collect + ctx.send_event(OneTestEvent(test_param="first")) + ctx.send_event(AnotherTestEvent(another_test_param="second")) + return None # type: ignore + + @step + async def collector( + self, ctx: Context, ev: Union[OneTestEvent, AnotherTestEvent, LastEvent] + ) -> Optional[StopEvent]: + # Track how many times this step is called + calls = await ctx.store.get("calls") + await ctx.store.set("calls", calls + 1) + + # Try to collect OneTestEvent and AnotherTestEvent + # LastEvent is NOT in this list + events = ctx.collect_events(ev, [OneTestEvent, AnotherTestEvent]) + if events is None: + # This happens when we receive LastEvent or haven't received all events yet + return None + + # Verify we got the right events + assert len(events) == 2 + assert isinstance(events[0], OneTestEvent) + assert isinstance(events[1], AnotherTestEvent) + assert events[0].test_param == "first" + assert events[1].another_test_param == "second" + + return StopEvent(result="collected") + + r = await WorkflowTestRunner(TestWorkflow()).run() + assert r.result == "collected" + + # Verify the collector was called multiple times (once for each event) + ctx = r.ctx + assert ctx is not None + calls = await ctx.store.get("calls") + # Should be called at least 3 times: once for LastEvent (returns None), + # once for OneTestEvent (returns None), once for AnotherTestEvent (returns result) + assert calls >= 3 + + @pytest.mark.asyncio async def test_get_default(workflow: Workflow) -> None: c1: Context[DictState] = Context(workflow) @@ -80,106 +139,48 @@ async def test_get_not_found(ctx: Context) -> None: await ctx.store.get("foo") -def test_send_event_step_is_none(ctx: Context) -> None: - ctx._queues = {"step1": mock.MagicMock(), "step2": mock.MagicMock()} +@pytest.mark.asyncio +async def test_send_event_step_is_none(workflow: Workflow, ctx: Context) -> None: ev = Event(foo="bar") + ctx._workflow_run(workflow, start_event=StartEvent()) ctx.send_event(ev) - for q in ctx._queues.values(): - q.put_nowait.assert_called_with(ev) # type: ignore - assert ctx._broker_log == [ev] + await asyncio.sleep(0.01) + assert ctx._broker_run is not None + replay = ctx._broker_run._replay_ticks + assert TickAddEvent(event=ev, step_name=None) in replay -def test_send_event_to_non_existent_step(ctx: Context) -> None: +@pytest.mark.asyncio +async def test_send_event_to_non_existent_step(ctx: Context) -> None: with pytest.raises( WorkflowRuntimeError, match="Step does_not_exist does not exist" ): ctx.send_event(Event(), "does_not_exist") -def test_send_event_to_wrong_step(ctx: Context) -> None: - ctx._step_configs["step"] = StepConfig( # type: ignore[attr-defined] - accepted_events=[], - event_name="test_event", - return_types=[], - context_parameter="", - num_workers=99, - retry_policy=None, - resources=[], - ) - +@pytest.mark.asyncio +async def test_send_event_to_wrong_step(ctx: Context) -> None: with pytest.raises( WorkflowRuntimeError, - match="Step step does not accept event of type ", + match="Step middle_step does not accept event of type ", ): - ctx.send_event(Event(), "step") - - -def test_send_event_to_step(workflow: Workflow) -> None: - step2 = mock.MagicMock() - step2.__step_config.accepted_events = [Event] - - workflow._get_steps = mock.MagicMock( # type: ignore - return_value={"step1": mock.MagicMock(), "step2": step2} - ) - - ctx: Context[DictState] = Context(workflow=workflow) - ctx._queues = {"step1": mock.MagicMock(), "step2": mock.MagicMock()} - - ev = Event(foo="bar") - ctx.send_event(ev, "step2") - - ctx._queues["step1"].put_nowait.assert_not_called() # type: ignore - ctx._queues["step2"].put_nowait.assert_called_with(ev) # type: ignore - - -def test_get_result(ctx: Context) -> None: - ctx._retval = 42 - assert ctx.get_result() == 42 - - -def test_to_dict_with_events_buffer(ctx: Context) -> None: - ctx.collect_events(OneTestEvent(), [OneTestEvent, AnotherTestEvent]) - assert json.dumps(ctx.to_dict()) + ctx.send_event(Event(), "middle_step") @pytest.mark.asyncio async def test_empty_inprogress_when_workflow_done(workflow: Workflow) -> None: - await WorkflowTestRunner(workflow).run() - ctx = workflow._contexts.pop() + result = await WorkflowTestRunner(workflow).run() + ctx = result.ctx # there shouldn't be any in progress events assert ctx is not None - for inprogress_list in ctx._in_progress.values(): - assert len(inprogress_list) == 0 - - -@pytest.mark.asyncio -async def test_wait_for_event(ctx: Context) -> None: - # skip test if python version is 3.9 or lower - if sys.version_info < (3, 10): - pytest.skip("Skipping test for Python 3.9 or lower") - - wait_job = asyncio.create_task(ctx.wait_for_event(Event)) - await asyncio.sleep(0.01) - ctx.send_event(Event(msg="foo")) - ev = await wait_job - assert ev.msg == "foo" - - -@pytest.mark.asyncio -async def test_wait_for_event_with_requirements(ctx: Context) -> None: - # skip test if python version is 3.9 or lower - if sys.version_info < (3, 10): - pytest.skip("Skipping test for Python 3.9 or lower") - - wait_job = asyncio.create_task( - ctx.wait_for_event(Event, requirements={"msg": "foo"}) - ) - await asyncio.sleep(0.01) - ctx.send_event(Event(msg="bar")) - ctx.send_event(Event(msg="foo")) - ev = await wait_job - assert ev.msg == "foo" + assert ctx._broker_run is not None + # After workflow completion, in_progress should be empty for all steps + state = ctx._broker_run._state + for step_name, worker_state in state.workers.items(): + assert len(worker_state.in_progress) == 0, ( + f"Step {step_name} has {len(worker_state.in_progress)} in-progress events" + ) @pytest.mark.asyncio @@ -232,47 +233,41 @@ async def step1(self, ctx: Context[CustomState], ev: StartEvent) -> StopEvent: async for ev in handler.stream_events(): if isinstance(ev, Event) and ev.msg == "foo": ctx_dict = handler.ctx.to_dict() - assert len(ctx_dict["waiting_ids"]) == 1 + # Check that at least one worker has waiters + assert ctx_dict["version"] == 1 + total_waiters = sum( + len(worker_data["collected_waiters"]) + for worker_data in ctx_dict["workers"].values() + ) + assert total_waiters == 1 await handler.cancel_run() break # Roundtrip the context assert ctx_dict is not None + # verify creating a new context has the correct state new_ctx = Context.from_dict(workflow, ctx_dict) - assert len(new_ctx._waiting_ids) == 1 new_handler = workflow.run(ctx=new_ctx) + assert new_ctx._broker_run + # Check that the waiters are properly restored + state = new_ctx._broker_run._state + total_waiters = sum( + len(worker.collected_waiters) for worker in state.workers.values() + ) + assert total_waiters == 1 # Continue the workflow assert new_handler.ctx new_handler.ctx.send_event(Event(msg="bar")) - result = await new_handler assert result == "bar" - assert len(new_handler.ctx._waiting_ids) == 0 - - -@pytest.mark.asyncio -async def test_prompt_and_wait(ctx: Context) -> None: - prompt_id = "test_prompt_and_wait" - prompt_event = InputRequiredEvent(prefix="test_prompt_and_wait") # type: ignore - expected_event = HumanResponseEvent - requirements = {"waiter_id": "test_prompt_and_wait"} - timeout = 10 - - waiting_task = asyncio.create_task( - ctx.wait_for_event( - expected_event, - waiter_id=prompt_id, - waiter_event=prompt_event, - timeout=timeout, - requirements=requirements, - ) + assert new_handler.ctx._broker_run + # After workflow completion, there should be no more waiters + state = new_handler.ctx._broker_run._state + total_waiters = sum( + len(worker.collected_waiters) for worker in state.workers.values() ) - await asyncio.sleep(0.01) - ctx.send_event(HumanResponseEvent(response="foo", waiter_id="test_prompt_and_wait")) # type: ignore - - result = await waiting_task - assert result.response == "foo" + assert total_waiters == 0 class Waiter1(Event): @@ -302,7 +297,7 @@ async def waiter_one(self, ctx: Context, ev: Waiter1) -> ResultEvent: new_ev: HumanResponseEvent = await ctx.wait_for_event( HumanResponseEvent, - {"waiter_id": "waiter_one"}, # type: ignore + requirements={"waiter_id": "waiter_one"}, ) return ResultEvent(result=new_ev.response) @@ -312,7 +307,7 @@ async def waiter_two(self, ctx: Context, ev: Waiter2) -> ResultEvent: new_ev: HumanResponseEvent = await ctx.wait_for_event( HumanResponseEvent, - {"waiter_id": "waiter_two"}, # type: ignore + requirements={"waiter_id": "waiter_two"}, ) return ResultEvent(result=new_ev.response) @@ -345,6 +340,7 @@ async def test_wait_for_multiple_events_in_workflow() -> None: result = await handler assert result == ["foo", "bar"] + assert not handler.ctx.is_running # serialize and resume ctx_dict = handler.ctx.to_dict() diff --git a/tests/context/test_serializers.py b/tests/context/test_serializers.py index d61c9649..67627cfe 100644 --- a/tests/context/test_serializers.py +++ b/tests/context/test_serializers.py @@ -14,13 +14,13 @@ def test_serialization_roundtrip(ctx: Context, workflow: Workflow) -> None: assert Context.from_dict(workflow, ctx.to_dict()) -def test_old_serialization(ctx: Context, workflow: Workflow) -> None: +def test_deserialization_invalid(ctx: Context, workflow: Workflow) -> None: old_payload = { "globals": {}, "streaming_queue": "[]", "queues": {"test_id": "[]"}, "events_buffer": {}, - "in_progress": {}, + "in_progress": "This should be a dict", "accepted_events": [], "broker_log": [], "waiter_id": "test_id", diff --git a/tests/plugins/test_dbos_plugin.py b/tests/plugins/test_dbos_plugin.py new file mode 100644 index 00000000..47d97e17 --- /dev/null +++ b/tests/plugins/test_dbos_plugin.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +from typing import Generator, Any +from pathlib import Path + +import pytest + +# Require Python 3.10+ for dbos typing +# if sys.version_info < (3, 10): # pragma: no cover - environment guard +# pytest.skip("Requires Python 3.10 or higher", allow_module_level=True) + +from dbos import DBOS, DBOSConfig +from workflows.plugins.dbos import dbos_runtime + +from workflows.workflow import Workflow +from workflows.decorators import step +from workflows.events import StartEvent, StopEvent +from workflows.context.context import Context + + +@pytest.fixture() +def dbos(tmp_path: Path) -> Generator[None, Any, None]: + # Use a file-based SQLite DB so the schema persists across connections/threads + db_file: Path = tmp_path / "dbos_test.sqlite3" + # Allow usage across threads in tests + system_db_url: str = f"sqlite+pysqlite:///{db_file}?check_same_thread=false" + + config: DBOSConfig = { + "name": "workflows-py-dbostest", + "system_database_url": system_db_url, + "run_admin_server": False, + } + # DBOS.reset_system_database() + DBOS(config=config) + DBOS.launch() + try: + yield None + finally: + DBOS.destroy() + + +class SimpleWorkflow(Workflow): + @step + async def start(self, ev: StartEvent) -> StopEvent: + return StopEvent(result="ok") + + +@pytest.mark.asyncio +async def test_dbos_plugin_simple_run(dbos: None) -> None: + wf = SimpleWorkflow() + ctx: Context = Context(wf, plugin=dbos_runtime) + handler = wf.run(ctx=ctx) + result = await handler + assert result == "ok" diff --git a/tests/runtime/__init__.py b/tests/runtime/__init__.py new file mode 100644 index 00000000..b64876d9 --- /dev/null +++ b/tests/runtime/__init__.py @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2025 LlamaIndex Inc. diff --git a/tests/runtime/conftest.py b/tests/runtime/conftest.py new file mode 100644 index 00000000..56340036 --- /dev/null +++ b/tests/runtime/conftest.py @@ -0,0 +1,177 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2025 LlamaIndex Inc. + +"""Test fixtures and utilities for runtime tests.""" + +import asyncio +import time +from typing import Any, AsyncGenerator + +import pytest + +from workflows.events import Event, StopEvent +from workflows.runtime.types.plugin import ControlLoopFunction, Plugin, WorkflowRuntime +from workflows.runtime.types.step_function import StepWorkerFunction +from workflows.runtime.types.ticks import WorkflowTick +from workflows.workflow import Workflow + + +class MockPlugin(Plugin): + def register( + self, + workflow: Workflow, + workflow_function: ControlLoopFunction, + steps: dict[str, StepWorkerFunction], + ) -> None: + return + + def new_runtime(self, run_id: str) -> WorkflowRuntime: + return MockRuntimePlugin(run_id) + + +class MockRuntimePlugin(WorkflowRuntime): + """ + Mock implementation of BrokerRuntimePlugin for unit testing control loops. + + This plugin uses asyncio queues to simulate external event sending and receiving, + allowing tests to control the flow of events in a deterministic way. + """ + + def __init__(self, run_id: str) -> None: + self.run_id = run_id + # Queue for events sent from external sources (e.g., via send_event) + self._external_queue: asyncio.Queue[WorkflowTick] = asyncio.Queue() + # Queue for events published to the event stream (e.g., for UI/callbacks) + self._event_stream: asyncio.Queue[Event] = asyncio.Queue() + # Current time in seconds, can be advanced manually for testing + self._current_time: float = time.time() + + async def close(self) -> None: + """ + Close the plugin. + """ + pass + + async def wait_receive(self) -> WorkflowTick: + """ + Waits until an external event is sent via send_event. + + Returns: + The next external event in the queue + """ + return await self._external_queue.get() + + async def write_to_event_stream(self, event: Event) -> None: + """ + Publishes an event to the event stream for external consumers. + + Args: + event: The event to publish + """ + await self._event_stream.put(event) + + async def stream_published_events(self) -> AsyncGenerator[Event, None]: + """ + Streams published events from the workflow. + """ + while True: + item = await self._event_stream.get() + yield item + if isinstance(item, StopEvent): + break + + async def send_event(self, tick: WorkflowTick) -> None: + """ + Sends an event from the external world into the workflow. + + Args: + tick: The tick/event to send to the workflow + """ + await self._external_queue.put(tick) + + async def register_step_worker(self, step_name: str, step_worker: Any) -> Any: + """ + No-op registration for testing - returns the step worker unchanged. + + Args: + step_name: Name of the step + step_worker: The step worker function + + Returns: + The unmodified step worker + """ + return step_worker + + async def register_workflow_function(self, workflow_function: Any) -> Any: + """ + No-op registration for testing - returns the workflow function unchanged. + + Args: + workflow_function: The workflow function + + Returns: + The unmodified workflow function + """ + return workflow_function + + async def get_now(self) -> float: + """ + Returns the current time for the test. + + Returns: + Current time in seconds since epoch + """ + return self._current_time + + async def sleep(self, seconds: float) -> None: + """ + Simulates sleeping for a given duration. + + Args: + seconds: Number of seconds to sleep + """ + await asyncio.sleep(seconds) + + def advance_time(self, seconds: float) -> None: + """ + Advances the mock time for testing purposes. + + Args: + seconds: Number of seconds to advance + """ + self._current_time += seconds + + async def get_stream_event(self, timeout: float = 1.0) -> Event: + """ + Helper to get the next event from the event stream. + + Args: + timeout: Maximum time to wait for an event + + Returns: + The next event from the stream + + Raises: + asyncio.TimeoutError: If no event is received within timeout + """ + return await asyncio.wait_for(self._event_stream.get(), timeout=timeout) + + def has_stream_events(self) -> bool: + """ + Check if there are any events in the stream queue. + + Returns: + True if there are events waiting + """ + return not self._event_stream.empty() + + +@pytest.fixture +async def test_plugin() -> MockRuntimePlugin: + """ + Provides a test runtime plugin for control loop tests. + + Returns: + A fresh MockRuntimePlugin instance + """ + return MockRuntimePlugin(run_id="test") diff --git a/tests/runtime/test_control_loop.py b/tests/runtime/test_control_loop.py new file mode 100644 index 00000000..2eddefd0 --- /dev/null +++ b/tests/runtime/test_control_loop.py @@ -0,0 +1,655 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2025 LlamaIndex Inc. + +""" +Tests for the control_loop function in the runtime module. + +The control loop is the core event processing engine that: +- Processes workflow ticks (events, step results, timeouts, cancellations) +- Manages step worker state and execution +- Coordinates event routing between steps +- Handles retries, timeouts, and failures +""" + +import asyncio +import uuid +from typing import Coroutine, Optional, Union + +import pytest + +from workflows.decorators import step +from workflows.events import ( + Event, + HumanResponseEvent, + InputRequiredEvent, + StartEvent, + StopEvent, + StepStateChanged, +) +from workflows.errors import WorkflowCancelledByUser, WorkflowTimeoutError +from workflows.runtime.types.internal_state import BrokerState +from workflows.runtime.types.step_function import as_step_worker_function +from workflows.workflow import Workflow +from workflows.context import Context +from workflows.runtime.control_loop import control_loop +from workflows.runtime.workflow_registry import workflow_registry +from workflows.runtime.types.ticks import TickAddEvent, TickCancelRun +from workflows.retry_policy import ConstantDelayRetryPolicy + +from .conftest import MockRuntimePlugin + + +class IntermediateEvent(Event): + """Test event passed between workflow steps.""" + + value: int + + +class FinalEvent(Event): + """Test event indicating workflow completion.""" + + final_value: str + + +class SimpleWorkflow(Workflow): + """ + A simple three-step workflow for testing the happy path. + + Flow: + StartEvent -> IntermediateEvent -> FinalEvent -> StopEvent + """ + + @step + async def start_step(self, ev: StartEvent) -> IntermediateEvent: + """First step: receives start event and produces intermediate event.""" + return IntermediateEvent(value=42) + + @step + async def middle_step(self, ev: IntermediateEvent) -> FinalEvent: + """Second step: processes intermediate event and produces final event.""" + return FinalEvent(final_value=f"processed_{ev.value}") + + @step + async def end_step(self, ev: FinalEvent) -> StopEvent: + """Final step: receives final event and returns stop event.""" + return StopEvent(result=ev.final_value) + + +class CollectEv(Event): + i: int + + +class CollectEv2(Event): + j: int + + +class CollectMultipleEventTypesWorkflow(Workflow): + @step + async def accept_start( + self, ev: StartEvent, context: Context + ) -> Optional[CollectEv]: + for i in range(2): + context.send_event(CollectEv(i=i + 1)) + return None + + @step + async def accept_collect1(self, ev: CollectEv, context: Context) -> CollectEv2: + return CollectEv2(j=ev.i * 10) + + @step + async def collector( + self, ev: Union[CollectEv, CollectEv2], context: Context + ) -> Optional[StopEvent]: + events = context.collect_events(ev, [CollectEv, CollectEv2] * 2) + if events is None: + return None + assert [type(x) for x in events] == [ + CollectEv, + CollectEv2, + ] * 2 # same order as expected + events = sum( + [ + e.i + if isinstance(e, CollectEv) + else e.j + if isinstance(e, CollectEv2) + else 0 + for e in events + ] + ) + return StopEvent(result=f"sum_{events}") + + +class CollectWorkflow(Workflow): + @step + async def accept_start( + self, ev: StartEvent, context: Context + ) -> Optional[CollectEv]: + for i in range(4): + context.send_event(CollectEv(i=i + 1)) + return None + + @step + async def collector(self, ev: CollectEv, context: Context) -> Optional[StopEvent]: + events = context.collect_events(ev, [CollectEv] * 4) + if events is None: + return None + events = sum([e.i for e in events]) + return StopEvent(result=f"sum_{events}") + + +def run_control_loop( + workflow: Workflow, start_event: Optional[StartEvent], plugin: MockRuntimePlugin +) -> Coroutine[None, None, StopEvent]: + step_workers = {} + for name, step_func in workflow._get_steps().items(): + unbound = getattr(step_func, "__func__", step_func) + step_workers[name] = as_step_worker_function(unbound) + ctx = Context(workflow=workflow) + ctx._broker_run = ctx._init_broker(workflow, plugin=plugin) + run_id = str(uuid.uuid4()) + workflow_registry.register_run( + run_id=run_id, + workflow=workflow, + plugin=plugin, + context=ctx, + steps=step_workers, + ) + + async def _run() -> StopEvent: + try: + return await control_loop( + start_event=start_event, + init_state=BrokerState.from_workflow(workflow), + run_id=run_id, + ) + finally: + workflow_registry.delete_run(run_id) + + return _run() + + +async def wait_for_stop_event( + plugin: MockRuntimePlugin, timeout: float = 1.0 +) -> Optional[StopEvent]: + """ + Helper to wait for a StopEvent in the event stream. + + Args: + plugin: The MockRuntimePlugin to read events from + timeout: Maximum time to wait for StopEvent (default: 1.0 seconds) + + Returns: + The StopEvent if found, None if timeout occurs + """ + try: + while True: + try: + ev = await asyncio.wait_for( + plugin.get_stream_event(timeout=timeout), timeout=timeout + ) + if isinstance(ev, StopEvent): + return ev + except asyncio.TimeoutError: + return None + except Exception: + return None + + +@pytest.mark.asyncio +async def test_control_loop_happy_path(test_plugin: MockRuntimePlugin) -> None: + """ + Test the happy path through the control loop. + + This test validates that: + 1. The control loop properly initializes with workflow state + 2. Events flow through the workflow steps in order + 3. Each step executes and produces the correct output event + 4. The workflow completes with the expected StopEvent result + 5. Step state changes are published to the event stream + """ + + result = await run_control_loop( + workflow=SimpleWorkflow(timeout=1.0), + start_event=StartEvent(), + plugin=test_plugin, + ) + + # Verify the workflow completed with expected result + assert isinstance(result, StopEvent) + assert result.result == "processed_42" + + +@pytest.mark.asyncio +async def test_control_loop_with_external_event(test_plugin: MockRuntimePlugin) -> None: + """ + Test that external events can be sent to a running workflow. + + This validates that the control loop can receive events from outside + during execution, useful for human-in-the-loop or webhook scenarios. + + The workflow starts with no initial event, and we inject a StartEvent + externally using the plugin's send_event method. + """ + + class ExternalTriggerWorkflow(Workflow): + """Workflow that waits for an external event.""" + + @step + async def start_step(self, ev: StartEvent) -> StopEvent: + """Step that processes the externally sent start event.""" + return StopEvent(result="received_external_event") + + # Setup + workflow = ExternalTriggerWorkflow(timeout=1.0) + + result_task = asyncio.create_task( + run_control_loop( + workflow=workflow, + start_event=None, + plugin=test_plugin, + ) + ) + + # Now send an external event to trigger the workflow + await test_plugin.send_event(TickAddEvent(event=StartEvent())) + + # Wait for completion + result = await asyncio.wait_for(result_task, timeout=5.0) + + # Verify + assert isinstance(result, StopEvent) + assert result.result == "received_external_event" + + +@pytest.mark.asyncio +async def test_control_loop_timeout(test_plugin: MockRuntimePlugin) -> None: + """ + Test that workflow timeout raises WorkflowTimeoutError and publishes StopEvent. + + When a workflow times out, an empty StopEvent should be published to the stream + to signal stream closure before the exception is raised. + """ + + class SlowWorkflow(Workflow): + @step + async def slow(self, ev: StartEvent) -> StopEvent: + await asyncio.sleep(0.5) + return StopEvent(result="never") + + wf = SlowWorkflow(timeout=0.01) + + task = asyncio.create_task( + run_control_loop( + workflow=wf, + start_event=StartEvent(), + plugin=test_plugin, + ) + ) + + # Wait for the StopEvent to be published + stop_event = await wait_for_stop_event(test_plugin) + + # Verify that the timeout exception is raised + with pytest.raises(WorkflowTimeoutError): + await asyncio.wait_for(task, timeout=1.0) + + # Verify an empty StopEvent was published to the stream + assert stop_event is not None, ( + "Timeout should publish empty StopEvent to stream before raising exception" + ) + assert stop_event.result is None, "Timeout StopEvent should have None result" + + +@pytest.mark.asyncio +async def test_control_loop_retry_policy(test_plugin: MockRuntimePlugin) -> None: + """ + Test that retry policy works correctly when a step fails initially but succeeds on retry. + """ + + class RetryWorkflow(Workflow): + def __init__(self) -> None: + super().__init__(timeout=1.0) + self.attempts = 0 + + @step(retry_policy=ConstantDelayRetryPolicy(maximum_attempts=2, delay=0)) + async def flaky(self, ev: StartEvent) -> StopEvent: + self.attempts += 1 + if self.attempts == 1: + raise RuntimeError("fail once") + return StopEvent(result=f"ok_{self.attempts}") + + wf = RetryWorkflow() + + result = await run_control_loop( + workflow=wf, + start_event=StartEvent(), + plugin=test_plugin, + ) + + assert isinstance(result, StopEvent) + assert result.result == "ok_2" + + +@pytest.mark.asyncio +async def test_control_loop_step_failure_publishes_stop_event( + test_plugin: MockRuntimePlugin, +) -> None: + """ + Test that when a step fails permanently (retries exhausted), + an empty StopEvent is published to the stream before raising the exception. + + This allows external consumers to know the workflow stream has ended. + """ + + class FailingWorkflow(Workflow): + @step(retry_policy=ConstantDelayRetryPolicy(maximum_attempts=1, delay=0)) + async def always_fails(self, ev: StartEvent) -> StopEvent: + raise ValueError("intentional failure") + + wf = FailingWorkflow(timeout=1.0) + task = asyncio.create_task( + run_control_loop( + workflow=wf, + start_event=StartEvent(), + plugin=test_plugin, + ) + ) + + # Wait for the StopEvent to be published + stop_event = await wait_for_stop_event(test_plugin) + + # Now verify the workflow raised an exception + with pytest.raises(ValueError, match="intentional failure"): + await asyncio.wait_for(task, timeout=1.0) + + # Verify that an empty StopEvent was published before the exception + assert stop_event is not None, ( + "Empty StopEvent should be published to stream when step fails permanently" + ) + assert stop_event.result is None, "Failure StopEvent should have None result" + + +@pytest.mark.asyncio +async def test_control_loop_waiter_resolution(test_plugin: MockRuntimePlugin) -> None: + class Awaited(Event): + tag: str + + class WaiterWorkflow(Workflow): + @step + async def start(self, ev: StartEvent, ctx: Context) -> StopEvent: + print("waiting for event") + awaited = await ctx.wait_for_event( + Awaited, + waiter_event=InputRequiredEvent(), + requirements={"tag": "go"}, + ) + return StopEvent(result=f"got_{awaited.tag}") + + wf = WaiterWorkflow(timeout=1.0) + task = asyncio.create_task( + run_control_loop( + workflow=wf, + start_event=StartEvent(), + plugin=test_plugin, + ) + ) + + # Let first run add the waiter + async def wait_input_required() -> InputRequiredEvent: + async for event in test_plugin.stream_published_events(): + if isinstance(event, InputRequiredEvent): + return event + raise TimeoutError("InputRequiredEvent not found") + + await asyncio.wait_for(wait_input_required(), timeout=1.0) + + # Send the awaited event that satisfies requirements + await test_plugin.send_event(TickAddEvent(event=Awaited(tag="go"))) + + result = await asyncio.wait_for(task, timeout=2.0) + assert isinstance(result, StopEvent) + assert result.result == "got_go" + + +@pytest.mark.asyncio +async def test_control_loop_input_required_published_to_stream( + test_plugin: MockRuntimePlugin, +) -> None: + """ + Test that InputRequiredEvent is automatically published to the outward stream. + + When a workflow step calls wait_for_event, an InputRequiredEvent should be + automatically published to the event stream so that external consumers + (like UIs or monitoring systems) can be notified that the workflow is + waiting for input. + """ + + class AwaitedEvent(Event): + value: str + + class WaitingWorkflow(Workflow): + @step + async def waiter(self, ev: StartEvent, ctx: Context) -> StopEvent: + # This should cause an InputRequiredEvent to be published + awaited = await ctx.wait_for_event( + AwaitedEvent, + waiter_event=InputRequiredEvent(), + ) + return StopEvent(result=f"received_{awaited.value}") + + wf = WaitingWorkflow(timeout=2.0) + task = asyncio.create_task( + run_control_loop( + workflow=wf, + start_event=StartEvent(), + plugin=test_plugin, + ) + ) + + # Wait for the InputRequiredEvent to appear in the stream + input_required_found = False + while True: + ev = await test_plugin.get_stream_event(timeout=1.0) + if isinstance(ev, InputRequiredEvent): + input_required_found = True + break + # Skip StepStateChanged events + if isinstance(ev, StopEvent): + break + + assert input_required_found, "InputRequiredEvent should be published to stream" + + # Now send the awaited event to complete the workflow + await test_plugin.send_event(TickAddEvent(event=AwaitedEvent(value="test_data"))) + + # Verify workflow completes successfully + result = await asyncio.wait_for(task, timeout=1.0) + assert isinstance(result, StopEvent) + assert result.result == "received_test_data" + + +@pytest.mark.asyncio +async def test_control_loop_collect_events_same_type( + test_plugin: MockRuntimePlugin, +) -> None: + wf = CollectWorkflow(timeout=1.0) + result = await asyncio.create_task( + run_control_loop( + workflow=wf, + start_event=StartEvent(), + plugin=test_plugin, + ) + ) + + assert isinstance(result, StopEvent) + assert result.result == "sum_10" + + +@pytest.mark.asyncio +async def test_control_loop_collect_events_multiple_types( + test_plugin: MockRuntimePlugin, +) -> None: + wf = CollectMultipleEventTypesWorkflow(timeout=1.0) + result = await run_control_loop( + workflow=wf, + start_event=StartEvent(), + plugin=test_plugin, + ) + assert isinstance(result, StopEvent) + assert result.result == "sum_33" + + +@pytest.mark.asyncio +async def test_control_loop_stream_events(test_plugin: MockRuntimePlugin) -> None: + wf = SimpleWorkflow(timeout=5.0) + task = asyncio.create_task( + run_control_loop( + workflow=wf, + start_event=StartEvent(), + plugin=test_plugin, + ) + ) + + # Expect at least one StepStateChanged event while running + stream_events: list[Event] = [] + while True: + ev = await test_plugin.get_stream_event(timeout=1.0) + stream_events.append(ev) + if isinstance(ev, StopEvent): + break + + result = await asyncio.wait_for(task, timeout=2.0) + assert isinstance(result, StopEvent) + # Ensure at least one StepStateChanged observed + assert len(stream_events) == 7 + assert [type(x) for x in stream_events] == [StepStateChanged] * 6 + [StopEvent] + change_events = [x for x in stream_events if isinstance(x, StepStateChanged)] + assert [x.step_state.name + " - " + x.name for x in change_events] == [ + "RUNNING - start_step", + "NOT_RUNNING - start_step", + "RUNNING - middle_step", + "NOT_RUNNING - middle_step", + "RUNNING - end_step", + "NOT_RUNNING - end_step", + ] + + +class SomeEvent(HumanResponseEvent): + pass + + +@pytest.mark.asyncio +async def test_control_loop_per_step_routing(test_plugin: MockRuntimePlugin) -> None: + class RouteWorkflow(Workflow): + @step + async def starter(self, ev: StartEvent) -> Optional[StopEvent]: + return None + + @step + async def first(self, ev: SomeEvent) -> StopEvent: + return StopEvent(result="first") + + @step + async def second(self, ev: SomeEvent) -> StopEvent: + return StopEvent(result="second") + + wf = RouteWorkflow(timeout=1.0) + task = asyncio.create_task( + run_control_loop( + workflow=wf, + start_event=StartEvent(), + plugin=test_plugin, + ) + ) + + # Route explicitly to the 'second' step with an accepted event type + await test_plugin.send_event(TickAddEvent(event=SomeEvent(), step_name="second")) + + result = await asyncio.wait_for(task, timeout=2.0) + assert isinstance(result, StopEvent) + assert result.result == "second" + + +@pytest.mark.asyncio +async def test_control_loop_concurrency_queueing( + test_plugin: MockRuntimePlugin, +) -> None: + class LimitedWorkflow(Workflow): + @step(num_workers=1) + async def only_one(self, ev: StartEvent) -> StopEvent: + # Hold to simulate long work + await asyncio.sleep(0.01) + return StopEvent(result="done") + + wf = LimitedWorkflow(timeout=5.0) + + task = asyncio.create_task( + run_control_loop( + workflow=wf, + plugin=test_plugin, + start_event=None, + ) + ) + + await asyncio.sleep(0) + # Send two events quickly; with num_workers=1, second should queue (PREPARING) + await asyncio.gather( + *[test_plugin.send_event(TickAddEvent(event=StartEvent())) for _ in range(10)] + ) + + # Observe stream for PREPARING signal + saw_preparing = False + for _ in range(5): + ev = await test_plugin.get_stream_event(timeout=1.0) + if isinstance(ev, StepStateChanged) and ev.step_state.name == "PREPARING": + saw_preparing = True + break + + # Drain to completion + result = await asyncio.wait_for(task, timeout=2.0) + assert isinstance(result, StopEvent) + assert saw_preparing + + +@pytest.mark.asyncio +async def test_control_loop_user_cancellation(test_plugin: MockRuntimePlugin) -> None: + """ + Test that user cancellation raises WorkflowCancelledByUser and publishes StopEvent. + + When a workflow is cancelled, an empty StopEvent should be published to the stream + to signal stream closure before the exception is raised. + """ + + class CancelWorkflow(Workflow): + @step + async def slow(self, ev: StartEvent) -> StopEvent: + await asyncio.sleep(1.0) + return StopEvent(result="never") + + wf = CancelWorkflow(timeout=5.0) + + task = asyncio.create_task( + run_control_loop( + workflow=wf, + plugin=test_plugin, + start_event=StartEvent(), + ) + ) + + # Cancel the run externally + await asyncio.sleep(0) + await test_plugin.send_event(TickCancelRun()) + + # Wait for the StopEvent to be published + stop_event = await wait_for_stop_event(test_plugin) + + # Verify that the cancellation exception is raised + with pytest.raises(WorkflowCancelledByUser): + await asyncio.wait_for(task, timeout=1.0) + + # Verify an empty StopEvent was published to the stream + assert stop_event is not None, ( + "Cancellation should publish empty StopEvent to stream before raising exception" + ) + assert stop_event.result is None, "Cancellation StopEvent should have None result" diff --git a/tests/runtime/test_control_loop_transformations.py b/tests/runtime/test_control_loop_transformations.py new file mode 100644 index 00000000..aeb305fb --- /dev/null +++ b/tests/runtime/test_control_loop_transformations.py @@ -0,0 +1,504 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2025 LlamaIndex Inc. + +""" +Unit tests for control loop transformation functions. + +These tests focus on the pure transformation functions in the control loop, +testing them in isolation without running the full async control loop. +""" + +from __future__ import annotations +from typing import Any, Union + +import pytest + +from workflows.events import ( + Event, + InputRequiredEvent, + StartEvent, + StepState, + StepStateChanged, + StopEvent, +) +from workflows.errors import WorkflowTimeoutError +from workflows.retry_policy import ConstantDelayRetryPolicy +from workflows.runtime.control_loop import ( + _add_or_enqueue_event, + _process_add_event_tick, + _process_cancel_run_tick, + _process_publish_event_tick, + _process_step_result_tick, + _process_timeout_tick, + rewind_in_progress, +) +from workflows.runtime.types.commands import ( + CommandCompleteRun, + CommandFailWorkflow, + CommandHalt, + CommandPublishEvent, + CommandQueueEvent, + CommandRunWorker, +) +from workflows.runtime.types.internal_state import ( + BrokerConfig, + BrokerState, + EventAttempt, + InProgressState, + InternalStepConfig, + InternalStepWorkerState, +) +from workflows.runtime.types.results import ( + AddCollectedEvent, + AddWaiter, + DeleteCollectedEvent, + DeleteWaiter, + StepWorkerFailed, + StepWorkerResult, + StepWorkerState, + StepWorkerWaiter, +) +from workflows.runtime.types.ticks import ( + TickAddEvent, + TickCancelRun, + TickPublishEvent, + TickStepResult, + TickTimeout, +) +from workflows.decorators import StepConfig + + +class MyTestEvent(Event): + value: int + + +class OtherEvent(Event): + data: str + + +@pytest.fixture +def base_state() -> BrokerState: + """Create a minimal BrokerState for testing.""" + step_config = StepConfig( + accepted_events=[MyTestEvent, StartEvent], + event_name="ev", + return_types=[StopEvent, OtherEvent, type(None)], + context_parameter="ctx", + retry_policy=None, + num_workers=1, + resources=[], + ) + return BrokerState( + is_running=True, + config=BrokerConfig( + steps={ + "test_step": InternalStepConfig( + accepted_events=[MyTestEvent, StartEvent], + retry_policy=None, + num_workers=1, + ) + }, + timeout=None, + ), + workers={ + "test_step": InternalStepWorkerState( + queue=[], + config=step_config, + in_progress=[], + collected_events={}, + collected_waiters=[], + ) + }, + ) + + +def add_worker(state: BrokerState, event: Event, worker_id: int = 0) -> None: + """Helper to add an in-progress worker to state.""" + state.workers["test_step"].in_progress.append( + InProgressState( + event=event, + worker_id=worker_id, + shared_state=StepWorkerState( + step_name="test_step", + collected_events={}, + collected_waiters=[], + ), + attempts=0, + first_attempt_at=100.0, + ) + ) + + +@pytest.mark.parametrize( + "result,expected_commands", + [ + (StopEvent(result="done"), [StepStateChanged, StopEvent, CommandCompleteRun]), + (OtherEvent(data="next"), [StepStateChanged, CommandQueueEvent]), + ( + InputRequiredEvent(), + [StepStateChanged, InputRequiredEvent, CommandQueueEvent], + ), + (None, [StepStateChanged]), + ], +) +def test_step_worker_results( + base_state: BrokerState, result: Union[Event, None], expected_commands: list +) -> None: + """Test different step worker result types.""" + event = MyTestEvent(value=42) + add_worker(base_state, event) + + tick = TickStepResult( + step_name="test_step", + worker_id=0, + event=event, + result=[StepWorkerResult(result=result)], + ) + + new_state, commands = _process_step_result_tick(tick, base_state, now_seconds=110.0) + + # Check expected command types + for i, expected_type in enumerate(expected_commands): + if isinstance(expected_type, type) and issubclass(expected_type, Event): + command = commands[i] + assert isinstance(command, CommandPublishEvent) + assert isinstance(command.event, expected_type) + else: + assert isinstance(commands[i], expected_type) + + # Worker should be removed from in_progress + assert len(new_state.workers["test_step"].in_progress) == 0 + + +def test_step_worker_failed_with_retry(base_state: BrokerState) -> None: + """Test that failures with retry policy queue a retry.""" + base_state.workers["test_step"].config.retry_policy = ConstantDelayRetryPolicy( + maximum_attempts=3, delay=1.0 + ) + event = MyTestEvent(value=42) + add_worker(base_state, event) + + tick: TickStepResult[Any] = TickStepResult( + step_name="test_step", + worker_id=0, + event=event, + result=[StepWorkerFailed(exception=ValueError("test"), failed_at=110.0)], + ) + + _, commands = _process_step_result_tick(tick, base_state, now_seconds=110.0) + + # Should queue retry + queue_cmds = [c for c in commands if isinstance(c, CommandQueueEvent)] + assert len(queue_cmds) == 1 + assert queue_cmds[0].attempts == 1 + + # First command should be NOT_RUNNING state change before re-queue + assert isinstance(commands[0], CommandPublishEvent) + assert isinstance(commands[0].event, StepStateChanged) + assert commands[0].event.step_state == StepState.NOT_RUNNING + + +def test_step_worker_failed_without_retry(base_state: BrokerState) -> None: + """Test that failures without retry fail the workflow.""" + event = MyTestEvent(value=42) + add_worker(base_state, event) + + tick: TickStepResult[Any] = TickStepResult( + step_name="test_step", + worker_id=0, + event=event, + result=[StepWorkerFailed(exception=ValueError("test"), failed_at=110.0)], + ) + + new_state, commands = _process_step_result_tick(tick, base_state, now_seconds=110.0) + + assert new_state.is_running is False + assert any(isinstance(c, CommandFailWorkflow) for c in commands) + + +def test_collected_events(base_state: BrokerState) -> None: + """Test AddCollectedEvent and DeleteCollectedEvent.""" + event = MyTestEvent(value=42) + add_worker(base_state, event) + + # Add event + tick: TickStepResult[Any] = TickStepResult( + step_name="test_step", + worker_id=0, + event=event, + result=[AddCollectedEvent(event_id="buf1", event=OtherEvent(data="e1"))], + ) + new_state, _ = _process_step_result_tick(tick, base_state, now_seconds=110.0) + assert "buf1" in new_state.workers["test_step"].collected_events + + # Delete event + add_worker(new_state, event) + tick = TickStepResult( + step_name="test_step", + worker_id=0, + event=event, + result=[ + StepWorkerResult(result=StopEvent()), + DeleteCollectedEvent(event_id="buf1"), + ], + ) + new_state, _ = _process_step_result_tick(tick, new_state, now_seconds=110.0) + assert "buf1" not in new_state.workers["test_step"].collected_events + + +def test_waiters(base_state: BrokerState) -> None: + """Test AddWaiter and DeleteWaiter.""" + event = MyTestEvent(value=42) + add_worker(base_state, event) + + # Add waiter + tick: TickStepResult[Any] = TickStepResult( + step_name="test_step", + worker_id=0, + event=event, + result=[ + AddWaiter( + waiter_id="w1", + waiter_event=InputRequiredEvent(), + requirements={}, + timeout=None, + event_type=OtherEvent, + ) + ], + ) + new_state, _ = _process_step_result_tick(tick, base_state, now_seconds=110.0) + assert len(new_state.workers["test_step"].collected_waiters) == 1 + + # Delete waiter + add_worker(new_state, event) + tick = TickStepResult( + step_name="test_step", + worker_id=0, + event=event, + result=[ + StepWorkerResult(result=StopEvent()), + DeleteWaiter(waiter_id="w1"), + ], + ) + new_state, _ = _process_step_result_tick(tick, new_state, now_seconds=110.0) + assert len(new_state.workers["test_step"].collected_waiters) == 0 + + +def test_start_event_sets_running(base_state: BrokerState) -> None: + """Test that StartEvent sets is_running to True.""" + base_state.is_running = False + tick = TickAddEvent(event=StartEvent()) + new_state, _ = _process_add_event_tick(tick, base_state, now_seconds=100.0) + assert new_state.is_running is True + + +def test_event_routing(base_state: BrokerState) -> None: + """Test that events are routed to accepting steps.""" + tick = TickAddEvent(event=MyTestEvent(value=42)) + new_state, commands = _process_add_event_tick(tick, base_state, now_seconds=100.0) + + run_cmds = [c for c in commands if isinstance(c, CommandRunWorker)] + assert len(run_cmds) == 1 + assert run_cmds[0].step_name == "test_step" + + +def test_per_step_explicit_routing_accepts_only_matching_types( + base_state: BrokerState, +) -> None: + """Explicit routing with step_name must still satisfy accepted event types.""" + # base_state only has test_step that accepts MyTestEvent and StartEvent + # Explicitly target test_step with MyTestEvent → should run + tick_ok = TickAddEvent(event=MyTestEvent(value=1), step_name="test_step") + _, cmds_ok = _process_add_event_tick(tick_ok, base_state, now_seconds=100.0) + assert any(isinstance(c, CommandRunWorker) for c in cmds_ok) + + # Explicitly target an unknown step → should not run anything + tick_bad = TickAddEvent(event=MyTestEvent(value=1), step_name="unknown") + _, cmds_bad = _process_add_event_tick(tick_bad, base_state, now_seconds=100.0) + assert not any(isinstance(c, CommandRunWorker) for c in cmds_bad) + + +def test_explicit_routing_requires_acceptance(base_state: BrokerState) -> None: + """Explicit step routing should still require accepted event types.""" + # Add a second step that does NOT accept MyTestEvent + other_step_cfg = StepConfig( + accepted_events=[StartEvent], + event_name="ev", + return_types=[StopEvent, OtherEvent, type(None)], + context_parameter="ctx", + retry_policy=None, + num_workers=1, + resources=[], + ) + base_state.config.steps["other_step"] = InternalStepConfig( + accepted_events=[StartEvent], retry_policy=None, num_workers=1 + ) + base_state.workers["other_step"] = InternalStepWorkerState( + queue=[], + config=other_step_cfg, + in_progress=[], + collected_events={}, + collected_waiters=[], + ) + + # Try to route MyTestEvent explicitly to non-accepting step → should not start + tick = TickAddEvent(event=MyTestEvent(value=1), step_name="other_step") + _, commands = _process_add_event_tick(tick, base_state, now_seconds=100.0) + assert not any( + isinstance(c, CommandRunWorker) and c.step_name == "other_step" + for c in commands + ) + + # Explicitly route to accepting step → should start + tick_ok = TickAddEvent(event=MyTestEvent(value=2), step_name="test_step") + _, commands_ok = _process_add_event_tick(tick_ok, base_state, now_seconds=100.0) + assert any( + isinstance(c, CommandRunWorker) and c.step_name == "test_step" + for c in commands_ok + ) + + +def test_waiter_resolution(base_state: BrokerState) -> None: + """Test that events matching waiters trigger step re-execution.""" + original_event = MyTestEvent(value=1) + waiter = StepWorkerWaiter( + waiter_id="w1", + event=original_event, + waiting_for_event=OtherEvent, + requirements={"data": "expected"}, + resolved_event=None, + ) + base_state.workers["test_step"].collected_waiters.append(waiter) + + tick = TickAddEvent(event=OtherEvent(data="expected")) + new_state, commands = _process_add_event_tick(tick, base_state, now_seconds=100.0) + + assert ( + new_state.workers["test_step"].collected_waiters[0].resolved_event is not None + ) + run_cmds = [c for c in commands if isinstance(c, CommandRunWorker)] + assert any(c.event == original_event for c in run_cmds) + + +def test_step_state_changed_names(base_state: BrokerState) -> None: + """Verify input/output event names on StepStateChanged use actual event types.""" + input_ev = MyTestEvent(value=7) + add_worker(base_state, input_ev) + + # Return a regular Event → output_event_name should be its type, and input_event_name should be str(type(input)) + tick = TickStepResult( + step_name="test_step", + worker_id=0, + event=input_ev, + result=[StepWorkerResult(result=OtherEvent(data="x"))], + ) + _, commands = _process_step_result_tick(tick, base_state, now_seconds=110.0) + assert isinstance(commands[0], CommandPublishEvent) + assert isinstance(commands[0].event, StepStateChanged) + ev = commands[0].event + assert ev.input_event_name == str(type(input_ev)) + assert ev.output_event_name == str(type(OtherEvent(data="x"))) + + # Return StopEvent → output_event_name should be None + add_worker(base_state, input_ev) + tick2 = TickStepResult( + step_name="test_step", + worker_id=0, + event=input_ev, + result=[StepWorkerResult(result=StopEvent(result="done"))], + ) + _, commands2 = _process_step_result_tick(tick2, base_state, now_seconds=110.0) + assert isinstance(commands2[0], CommandPublishEvent) + assert isinstance(commands2[0].event, StepStateChanged) + ev2 = commands2[0].event + assert ev2.input_event_name == str(type(input_ev)) + assert ev2.output_event_name == "" + + +def test_cancel_run(base_state: BrokerState) -> None: + """Test that cancel sets not running and halts.""" + tick = TickCancelRun() + new_state, commands = _process_cancel_run_tick(tick, base_state) + + assert new_state.is_running is False + assert len(commands) == 2 + assert isinstance(commands[0], CommandPublishEvent) + assert isinstance(commands[1], CommandHalt) + + +def test_publish_event(base_state: BrokerState) -> None: + """Test that publish events pass through without state changes.""" + event = MyTestEvent(value=42) + tick = TickPublishEvent(event=event) + new_state, commands = _process_publish_event_tick(tick, base_state) + + assert new_state is base_state + assert len(commands) == 1 + assert isinstance(commands[0], CommandPublishEvent) + + +def test_timeout(base_state: BrokerState) -> None: + """Test that timeout sets not running and halts with error.""" + tick = TickTimeout(timeout=10.0) + new_state, commands = _process_timeout_tick(tick, base_state) + + assert new_state.is_running is False + assert isinstance(commands[1], CommandHalt) + assert isinstance(commands[1].exception, WorkflowTimeoutError) + + +def test_add_when_capacity_available(base_state: BrokerState) -> None: + """Test that events start immediately when capacity available.""" + event = MyTestEvent(value=42) + commands = _add_or_enqueue_event( + EventAttempt(event=event), + "test_step", + base_state.workers["test_step"], + now_seconds=100.0, + ) + + assert len(base_state.workers["test_step"].in_progress) == 1 + assert any(isinstance(c, CommandRunWorker) for c in commands) + assert any( + isinstance(c, CommandPublishEvent) + and isinstance(c.event, StepStateChanged) + and c.event.step_state == StepState.RUNNING + for c in commands + ) + + +def test_enqueue_when_no_capacity(base_state: BrokerState) -> None: + """Test that events queue when no capacity available.""" + # Fill capacity + add_worker(base_state, MyTestEvent(value=1)) + + # Try to add another + event = MyTestEvent(value=42) + commands = _add_or_enqueue_event( + EventAttempt(event=event), + "test_step", + base_state.workers["test_step"], + now_seconds=100.0, + ) + + assert len(base_state.workers["test_step"].queue) == 1 + # PREPARING should be published when we enqueue + assert isinstance(commands[0], CommandPublishEvent) + assert isinstance(commands[0].event, StepStateChanged) + assert commands[0].event.step_state == StepState.PREPARING + + +def test_rewind_restarts_workers(base_state: BrokerState) -> None: + """Test that in_progress workers are restarted.""" + base_state.workers["test_step"].config.num_workers = 2 + base_state.config.steps["test_step"].num_workers = 2 + + add_worker(base_state, MyTestEvent(value=1), worker_id=0) + add_worker(base_state, MyTestEvent(value=2), worker_id=1) + + new_state, commands = rewind_in_progress(base_state, now_seconds=120.0) + + # Both should be restarted + run_cmds = [c for c in commands if isinstance(c, CommandRunWorker)] + assert len(run_cmds) == 2 + assert len(new_state.workers["test_step"].in_progress) == 2 diff --git a/tests/runtime/test_state.py b/tests/runtime/test_state.py new file mode 100644 index 00000000..a37dcc14 --- /dev/null +++ b/tests/runtime/test_state.py @@ -0,0 +1,27 @@ +from json import JSONDecodeError +import pytest +from workflows.context.context_types import SerializedContext, SerializedContextV0 +from workflows.workflow import Workflow + + +def test_deserialize_broken_state_raises_validation_error(workflow: Workflow) -> None: + """Test that broken V0 state raises an error when deserializing.""" + broken_state = { + "state": {}, + "streaming_queue": "[]", + "queues": {"middle_step": "not-deserializable-as-a-queue"}, + "event_buffers": {}, + "in_progress": {}, + "accepted_events": [], + "broker_log": [], + "is_running": True, + "waiting_ids": [], + } + + # This is V0 format (no version field) + serialized_v0 = SerializedContextV0.model_validate(broken_state) + + # The broken queue string should cause an error during V0->V1 conversion + # because the queue value is not valid JSON + with pytest.raises(JSONDecodeError): + SerializedContext.from_v0(serialized_v0) diff --git a/tests/server/conftest.py b/tests/server/conftest.py index 6454eefe..f9823615 100644 --- a/tests/server/conftest.py +++ b/tests/server/conftest.py @@ -22,6 +22,7 @@ async def process(self, ctx: Context, ev: StartEvent) -> StopEvent: message = await ctx.store.get("test_param", None) if message is None: message = getattr(ev, "message", "default") + return StopEvent(result=f"processed: {message}") diff --git a/tests/server/test_server_endpoints.py b/tests/server/test_server_endpoints.py index f5e04873..e21d84c0 100644 --- a/tests/server/test_server_endpoints.py +++ b/tests/server/test_server_endpoints.py @@ -582,7 +582,6 @@ async def test_stream_events_include_internal_true(client: AsyncClient) -> None: event_types = [e["qualified_name"] for e in events] # Expect internal event types to be present along with StopEvent assert "workflows.events.StopEvent" in event_types - assert "workflows.events.EventsQueueChanged" in event_types assert "workflows.events.StepStateChanged" in event_types @@ -950,11 +949,9 @@ async def test_cancel_handler_persists_cancelled_status( json={}, ) handler_id = response.json()["handler_id"] - resp_cancel = await client.post(f"/handlers/{handler_id}/cancel?purge=false") assert resp_cancel.status_code == 200 assert resp_cancel.json() == {"status": "cancelled"} - persisted_cancelled = await store.query( HandlerQuery(handler_id_in=[handler_id]) ) diff --git a/tests/server/test_server_persistence.py b/tests/server/test_server_persistence.py index 6a6c42fa..4fff477b 100644 --- a/tests/server/test_server_persistence.py +++ b/tests/server/test_server_persistence.py @@ -5,6 +5,7 @@ from typing import AsyncGenerator from httpx import AsyncClient, ASGITransport + from .conftest import ExternalEvent, RequestedExternalEvent from workflows.events import Event, InternalDispatchEvent from workflows.server import WorkflowServer @@ -123,13 +124,13 @@ async def test_startup_marks_invalid_persisted_context_as_failed( # Make the context structurally valid but with an invalid streaming_queue JSON invalid_ctx = { "state": {}, - "streaming_queue": "not-json", - "queues": {}, + "streaming_queue": "[]", + "queues": {"process": "not-deserializable-as-a-queue"}, "event_buffers": {}, "in_progress": {}, "accepted_events": [], "broker_log": [], - "is_running": False, + "is_running": True, "waiting_ids": [], } @@ -336,7 +337,6 @@ async def test_workflow_cancelled_after_all_retries_fail( task.cancel() except Exception: pass - await asyncio.sleep(0) @pytest.mark.asyncio diff --git a/tests/test_decorator.py b/tests/test_decorator.py index edca95f3..89989f38 100644 --- a/tests/test_decorator.py +++ b/tests/test_decorator.py @@ -16,7 +16,7 @@ def f(self, ev: Event) -> Event: # type: ignore return Event() res = step(workflow=workflow.__class__)(f) - config = getattr(res, "__step_config") + config = res._step_config assert config.accepted_events == [Event] assert config.event_name == "ev" assert config.return_types == [Event] @@ -33,8 +33,8 @@ def f2(self, ev: Event) -> StopEvent: return StopEvent() wf = TestWorkflow() - assert getattr(wf.f1, "__step_config") - assert getattr(wf.f2, "__step_config") + assert wf.f1._step_config + assert wf.f2._step_config def test_decorate_wrong_signature() -> None: diff --git a/tests/test_handler.py b/tests/test_handler.py index 0ce1ae2a..82ccb33e 100644 --- a/tests/test_handler.py +++ b/tests/test_handler.py @@ -10,7 +10,8 @@ from workflows.handler import WorkflowHandler -def test_str() -> None: +@pytest.mark.asyncio +async def test_str() -> None: h = WorkflowHandler() h.set_result([]) assert str(h) == "[]" diff --git a/tests/test_state_manager.py b/tests/test_state_manager.py index 9d20bea1..2d412114 100644 --- a/tests/test_state_manager.py +++ b/tests/test_state_manager.py @@ -128,63 +128,6 @@ async def test_default_state_manager_serialization( assert await new_state_manager.get("age") == 30 -@pytest.mark.asyncio -async def test_custom_state_manager_snapshot( - default_state_manager: InMemoryStateStore[DictState], -) -> None: - await default_state_manager.set("serializer", type(JsonSerializer())) - await default_state_manager.set("test_data", {"test": 1, "data": 2}) - - assert await default_state_manager.get("serializer") is type(JsonSerializer()) - assert await default_state_manager.get("test_data") == {"test": 1, "data": 2} - - # prove that to_dict throws error when called on this object - with pytest.raises(ValueError): - default_state_manager.to_dict(JsonSerializer()) - - dict_snapshot = default_state_manager.to_dict_snapshot(JsonSerializer()) - assert isinstance(dict_snapshot, dict) - assert ( - "state_data" in dict_snapshot - and "state_type" in dict_snapshot - and "state_module" in dict_snapshot - ) - assert dict_snapshot["state_data"] == { - "serializer": "", - "test_data": '{"test": 1, "data": 2}', - } - assert dict_snapshot["state_type"] == "DictState" - assert dict_snapshot["state_module"] == "workflows.context.state_store" - - -@pytest.mark.asyncio -async def test_default_state_manager_snapshot( - unser_custom_state_manager: InMemoryStateStore[MyUnserializableState], -) -> None: - assert await unser_custom_state_manager.get("serializer_type") is type( - JsonSerializer() - ) - assert await unser_custom_state_manager.get("test_data") == {"test": 1, "data": 2} - - # prove that to_dict throws error when called on this object - with pytest.raises(ValueError): - unser_custom_state_manager.to_dict(JsonSerializer()) - - dict_snapshot = unser_custom_state_manager.to_dict_snapshot(JsonSerializer()) - assert isinstance(dict_snapshot, dict) - assert ( - "state_data" in dict_snapshot - and "state_type" in dict_snapshot - and "state_module" in dict_snapshot - ) - assert dict_snapshot["state_data"] == { - "serializer_type": "", - "test_data": '{"test": 1, "data": 2}', - } - assert dict_snapshot["state_type"] == "MyUnserializableState" - assert dict_snapshot["state_module"] == "tests.test_state_manager" - - @pytest.mark.asyncio async def test_custom_state_manager( custom_state_manager: InMemoryStateStore[MyState], diff --git a/tests/test_streaming.py b/tests/test_streaming.py index f460354d..26ad46d9 100644 --- a/tests/test_streaming.py +++ b/tests/test_streaming.py @@ -68,7 +68,7 @@ async def step(self, ctx: Context, ev: StartEvent) -> StopEvent: await asyncio.sleep(2) return StopEvent() - wf = DummyWorkflow(timeout=1) + wf = DummyWorkflow(timeout=0.1) r = wf.run() # Make sure we don't block indefinitely here because the step raised @@ -137,13 +137,13 @@ async def count(self, ctx: Context, ev: StartEvent) -> StopEvent: return StopEvent(result="done") wf = CounterWorkflow() - await WorkflowTestRunner(wf).run() - ctx1 = wf._contexts.copy().pop() + result = await WorkflowTestRunner(wf).run() + ctx1 = result.ctx assert ctx1 - await WorkflowTestRunner(wf).run(ctx=ctx1) + result2 = await WorkflowTestRunner(wf).run(ctx=ctx1) - ctx2 = wf._contexts.pop() + ctx2 = result2.ctx assert ctx2 assert await ctx2.store.get("cur_count") == 2 diff --git a/tests/test_testing_utils.py b/tests/test_testing_utils.py index 19daef7d..129e869c 100644 --- a/tests/test_testing_utils.py +++ b/tests/test_testing_utils.py @@ -5,7 +5,6 @@ StopEvent, Event, StepStateChanged, - EventsQueueChanged, ) from workflows import Context, Workflow, step from workflows.testing import WorkflowTestRunner @@ -37,13 +36,11 @@ async def test_testing_utils() -> None: runner = WorkflowTestRunner(wf) wf_test_run = await runner.run( start_event=StartEvent(message="hi"), # type: ignore - exclude_events=[EventsQueueChanged], ) assert isinstance(wf_test_run, WorkflowTestResult) assert len(wf_test_run.collected) == sum( [wf_test_run.event_types[k] for k in wf_test_run.event_types] ) - assert wf_test_run.event_types.get(EventsQueueChanged, 0) == 0 assert wf_test_run.event_types.get(SecondEvent, 0) == 1 assert wf_test_run.event_types.get(StopEvent, 0) == 1 assert wf_test_run.event_types.get(StepStateChanged, 0) == len( diff --git a/tests/test_workflow.py b/tests/test_workflow.py index dbe7b766..9a57b3a7 100644 --- a/tests/test_workflow.py +++ b/tests/test_workflow.py @@ -1,11 +1,13 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2025 LlamaIndex Inc. +from __future__ import annotations + import asyncio +import gc import logging -import sys -import time -from typing import Any, Type, Union +import weakref +from typing import Any, Callable, Union from unittest import mock import pytest @@ -26,8 +28,9 @@ StopEvent, ) from workflows.handler import WorkflowHandler -from workflows.workflow import Workflow +from workflows.runtime.types.ticks import TickAddEvent from workflows.testing import WorkflowTestRunner +from workflows.workflow import Workflow from .conftest import ( AnotherTestEvent, @@ -75,7 +78,7 @@ async def slow_step(self, ev: StartEvent) -> StopEvent: return StopEvent(result="Done") with pytest.raises(WorkflowTimeoutError): - await WorkflowTestRunner(SlowWorkflow(timeout=1)).run() + await WorkflowTestRunner(SlowWorkflow(timeout=0.1)).run() @pytest.mark.asyncio @@ -179,6 +182,19 @@ def step_two(self, ctx: Context, ev: OneTestEvent) -> StopEvent: @pytest.mark.asyncio async def test_workflow_num_workers() -> None: + signal = asyncio.Event() + lock = asyncio.Lock() + counter = 0 + + async def await_count(count: int) -> None: + nonlocal counter + async with lock: + counter += 1 + if counter == count: + signal.set() + return + await signal.wait() + class NumWorkersWorkflow(Workflow): @step async def original_step( @@ -196,7 +212,8 @@ async def original_step( @step(num_workers=3) async def test_step(self, ev: OneTestEvent) -> AnotherTestEvent: - await asyncio.sleep(1.0) + await await_count(3) # wait for all 3 to be waiting + return AnotherTestEvent(another_test_param=ev.test_param) @step @@ -209,29 +226,18 @@ async def final_step( return None # type: ignore return StopEvent(result=[ev.another_test_param for ev in events]) - workflow = NumWorkersWorkflow() - start_time = time.time() + workflow = NumWorkersWorkflow(timeout=1) r = await WorkflowTestRunner(workflow).run() - end_time = time.time() - assert set(r.result) == {"test1", "test2", "test4"} + assert "test4" in set(r.result) + assert len({"test1", "test2", "test3"} - set(r.result)) == 1 - # ctx should have 1 extra event - ctx = workflow._contexts.pop() + # Ensure ctx is serializable + ctx = r.ctx assert ctx - assert "final_step" in ctx._event_buffers - event_buffer = ctx._event_buffers["final_step"] - assert len(event_buffer["tests.conftest.AnotherTestEvent"]) == 1 - - # ensure ctx is serializable + assert ctx._broker_run is not None ctx.to_dict() - # Check if the execution time is close to 1 second (with some tolerance) - execution_time = end_time - start_time - assert 1.0 <= execution_time < 1.1, ( - f"Execution time was {execution_time:.2f} seconds" - ) - @pytest.mark.asyncio async def test_workflow_step_send_event() -> None: @@ -252,9 +258,9 @@ async def step3(self, ev: OneTestEvent) -> StopEvent: workflow = StepSendEventWorkflow() r = await WorkflowTestRunner(workflow).run() assert r.result == "step2" - ctx = workflow._contexts.pop() - assert ("step2", "OneTestEvent") in ctx._accepted_events - assert ("step3", "OneTestEvent") not in ctx._accepted_events + ctx = r.ctx + replay = ctx._broker_run._runtime.replay() # type:ignore + assert TickAddEvent(OneTestEvent(), step_name="step2") in replay @pytest.mark.asyncio @@ -270,8 +276,10 @@ async def step2(self, ev: OneTestEvent) -> StopEvent: return StopEvent(result="step2") workflow = StepSendEventToNoneWorkflow(verbose=True) - await WorkflowTestRunner(workflow).run() - assert ("step2", "OneTestEvent") in workflow._contexts.pop()._accepted_events + result = await WorkflowTestRunner(workflow).run() + assert result.ctx._broker_run is not None + replay = result.ctx._broker_run._runtime.replay() # type:ignore + assert TickAddEvent(OneTestEvent()) in replay @pytest.mark.asyncio @@ -341,7 +349,7 @@ def another_step(ev: StartEvent) -> None: WorkflowValidationError, match="Step function another_step is missing the `@step` decorator.", ): - TestWorkflow.add_step(another_step) + TestWorkflow.add_step(another_step) # type: ignore @pytest.mark.asyncio @@ -381,14 +389,14 @@ async def step(self, ctx: Context, ev: StartEvent) -> StopEvent: # first run r = await WorkflowTestRunner(wf).run() assert r.result == "Done" - ctx = wf._contexts.pop() + ctx = r.ctx assert ctx assert await ctx.store.get("number") == 1 # second run -- independent from the first r = await WorkflowTestRunner(wf).run() assert r.result == "Done" - ctx = wf._contexts.pop() + ctx = r.ctx assert ctx assert await ctx.store.get("number") == 1 @@ -410,9 +418,9 @@ async def step(self, ctx: Context, ev: StartEvent) -> StopEvent: await ctx.store.set("test_fn", test_fn) return StopEvent(result="Done") - wf = DummyWorkflow() - await WorkflowTestRunner(wf).run() - ctx = wf._contexts.copy().pop() + wf = DummyWorkflow(timeout=1) + r = await WorkflowTestRunner(wf).run() + ctx = r.ctx assert ctx # by default, we can't pickle the LLM/embedding object @@ -421,16 +429,16 @@ async def step(self, ctx: Context, ev: StartEvent) -> StopEvent: # if we allow pickle, then we can pickle the LLM/embedding object state_dict = ctx.to_dict(serializer=PickleSerializer()) - new_handler = WorkflowHandler( - ctx=Context.from_dict(wf, state_dict, serializer=PickleSerializer()) - ) + new_ctx = Context.from_dict(wf, state_dict, serializer=PickleSerializer()) + assert await new_ctx.store.get("step") == 1 + new_handler = WorkflowHandler(ctx=new_ctx) assert new_handler.ctx + assert await new_handler.ctx.store.get("step") == 1 # check that the step count is the same cur_step = await ctx.store.get("step") new_step = await new_handler.ctx.store.get("step") assert new_step == cur_step - await WorkflowTestRunner(wf).run(ctx=new_handler.ctx) # check that the step count is incremented @@ -438,22 +446,47 @@ async def step(self, ctx: Context, ev: StartEvent) -> StopEvent: @pytest.mark.asyncio -async def test_workflow_context_to_dict(workflow: Workflow) -> None: - handler = workflow.run() - ctx = handler.ctx - - ctx.send_event(EventWithName(name="test")) # type:ignore - - # get the context dict - data = ctx.to_dict() # type:ignore +async def test_workflow_context_to_dict() -> None: + ctx: Union[Context, None] = None + new_ctx: Union[Context, None] = None + signal_continue = asyncio.Event() + signal_ready = asyncio.Event() + run_count = 0 + + class StallableWorkflow(Workflow): + @step + async def step1(self, ev: StartEvent) -> EventWithName: + nonlocal run_count + run_count += 1 + if run_count > 1: + raise ValueError("Start ran more than once") + return EventWithName(name="test") + + @step + async def step2(self, ev: EventWithName) -> StopEvent: + signal_ready.set() + await signal_continue.wait() + return StopEvent(result="Done") - # finish workflow - await handler + workflow = StallableWorkflow() + try: + handler = workflow.run() + ctx = handler.ctx + await signal_ready.wait() + # get the context dict + data = ctx.to_dict() # type:ignore - new_ctx = Context.from_dict(workflow, data) + await handler.cancel_run() - print(new_ctx._queues) - assert new_ctx._queues["start_step"].get_nowait().name == "test" + new_ctx = Context.from_dict(workflow, data) + handler2 = workflow.run(ctx=new_ctx) + signal_continue.set() + await handler2 + finally: + if ctx is not None and ctx._broker_run is not None: + await ctx._broker_run.shutdown() + if new_ctx is not None and new_ctx._broker_run is not None: + await new_ctx._broker_run.shutdown() class HumanInTheLoopWorkflow(Workflow): @@ -474,7 +507,7 @@ async def step2(self, ctx: Context, ev: HumanResponseEvent) -> StopEvent: async def test_human_in_the_loop() -> None: # workflow should raise a timeout error because hitl only works with streaming with pytest.raises(WorkflowTimeoutError): - await WorkflowTestRunner(HumanInTheLoopWorkflow(timeout=1)).run() + await WorkflowTestRunner(HumanInTheLoopWorkflow(timeout=0.01)).run() # workflow should work with streaming workflow = HumanInTheLoopWorkflow() @@ -507,7 +540,6 @@ async def test_human_in_the_loop_with_resume() -> None: break assert handler.exception() - new_handler = workflow.run(ctx=Context.from_dict(workflow, ctx_dict)) # type:ignore new_handler.ctx.send_event(HumanResponseEvent(response="42")) # type:ignore @@ -526,87 +558,49 @@ def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) self._lock = asyncio.Lock() self.num_active_runs = 0 + self.num_active_runs_history: list[int] = [] @step async def step_one(self, ev: StartEvent) -> StopEvent: run_num = ev.get("run_num") async with self._lock: self.num_active_runs += 1 + self.num_active_runs_history.append(self.num_active_runs) await asyncio.sleep(0.01) - return StopEvent(result=f"Run {run_num}: Done") - - @step - async def _done(self, ctx: Context, ev: StopEvent) -> None: async with self._lock: self.num_active_runs -= 1 - await super()._done(ctx, ev) - - async def get_active_runs(self) -> Any: - async with self._lock: - return self.num_active_runs - + return StopEvent(result=f"Run {run_num}: Done") -class NumConcurrentRunsException(Exception): - pass + async def get_active_runs(self) -> list[int]: + return self.num_active_runs_history @pytest.mark.asyncio @pytest.mark.parametrize( ( - "workflow", - "desired_max_concurrent_runs", - "expected_exception", + "workflow_factory", + "validate_max_concurrent_runs", ), [ ( - DummyWorkflowForConcurrentRunsTest(num_concurrent_runs=1), - 1, - type(None), + lambda: DummyWorkflowForConcurrentRunsTest(num_concurrent_runs=1), + lambda actual_max_concurrent_runs: actual_max_concurrent_runs == 1, ), # This workflow is not protected, and so NumConcurrentRunsException is raised ( - DummyWorkflowForConcurrentRunsTest(), - 1, - NumConcurrentRunsException, + lambda: DummyWorkflowForConcurrentRunsTest(), + lambda actual_max_concurrent_runs: actual_max_concurrent_runs > 1, ), ], ) async def test_workflow_run_num_concurrent( - workflow: DummyWorkflowForConcurrentRunsTest, - desired_max_concurrent_runs: int, - expected_exception: Type, + workflow_factory: Callable[[], DummyWorkflowForConcurrentRunsTest], + validate_max_concurrent_runs: Callable[[int], bool], ) -> None: - # skip test if python version is 3.9 or lower - if sys.version_info < (3, 10): - pytest.skip("Skipping test for Python 3.9 or lower") - - async def _poll_workflow( - wf: DummyWorkflowForConcurrentRunsTest, desired_max_concurrent_runs: int - ) -> None: - """Check that number of concurrent runs is less than desired max amount.""" - for _ in range(100): - num_active_runs = await wf.get_active_runs() - if num_active_runs > desired_max_concurrent_runs: - raise NumConcurrentRunsException - await asyncio.sleep(0.01) - - poll_task = asyncio.create_task( - _poll_workflow( - wf=workflow, desired_max_concurrent_runs=desired_max_concurrent_runs - ), - ) - - tasks = [] - for ix in range(1, 5): - tasks.append(workflow.run(run_num=ix)) - - results = await asyncio.gather(*tasks) - - if not poll_task.done(): - await poll_task - e = poll_task.exception() - - assert type(e) is expected_exception + workflow = workflow_factory() + results = await asyncio.gather(*[workflow.run(run_num=ix) for ix in range(1, 5)]) + max_concurrent_runs = max(workflow.num_active_runs_history) + assert validate_max_concurrent_runs(max_concurrent_runs) assert results == [f"Run {ix}: Done" for ix in range(1, 5)] @@ -678,26 +672,29 @@ async def _stream_events() -> Any: assert result.outcome == "Workflow completed" -def test_wrong_event_types() -> None: - class RandomEvent(Event): - pass +class RandomEvent(Event): + pass - class InvalidStopWorkflow(Workflow): - @step - async def a_step(self, ev: MyStart) -> RandomEvent: - return RandomEvent() +class InvalidStopWorkflow(Workflow): + @step + async def a_step(self, ev: MyStart) -> RandomEvent: + return RandomEvent() + + +class InvalidStartWorkflow(Workflow): + @step + async def a_step(self, ev: RandomEvent) -> StopEvent: + return StopEvent() + + +def test_wrong_event_types() -> None: with pytest.raises( WorkflowConfigurationError, match="At least one Event of type StopEvent must be returned by any step.", ): InvalidStopWorkflow() - class InvalidStartWorkflow(Workflow): - @step - async def a_step(self, ev: RandomEvent) -> StopEvent: - return StopEvent() - with pytest.raises( WorkflowConfigurationError, match="At least one Event of type StartEvent must be received by any step.", @@ -794,3 +791,28 @@ def one(self, ev: MyStart) -> MyStop: event_names = [e.__name__ for e in events] assert "MyStop" in event_names assert "MyStart" in event_names + + +@pytest.mark.asyncio +async def test_workflow_instances_garbage_collected_after_completion() -> None: + class TinyWorkflow(Workflow): + @step + async def only(self, ev: StartEvent) -> StopEvent: + return StopEvent(result="done") + + refs: list[weakref.ReferenceType[Workflow]] = [] + + for _ in range(10): + wf = TinyWorkflow() + refs.append(weakref.ref(wf)) + await WorkflowTestRunner(wf).run() + # Drop strong reference before next iteration + del wf + + # Force GC to clear weakly-referenced registry entries + for _ in range(3): + gc.collect() + await asyncio.sleep(0) + + # All weakrefs should be cleared + assert all([r() is None for r in refs]) diff --git a/tests/test_workflow_internal_events.py b/tests/test_workflow_internal_events.py index 10c130a6..f2e3fcd5 100644 --- a/tests/test_workflow_internal_events.py +++ b/tests/test_workflow_internal_events.py @@ -5,7 +5,6 @@ from workflows.testing import WorkflowTestRunner from typing import Union from workflows.events import ( - EventsQueueChanged, StepStateChanged, StepState, Event, @@ -101,68 +100,26 @@ async def test_internal_events(wf: ExampleWorkflow) -> None: exclude_events=[StopEvent], ) assert len(result.collected) > 0 - assert all( - isinstance(ev, StepStateChanged) or isinstance(ev, EventsQueueChanged) - for ev in result.collected - ) + assert all(isinstance(ev, StepStateChanged) for ev in result.collected) @pytest.mark.asyncio -async def test_internal_events_state(wf_state: ExampleWorkflowState) -> None: +async def test_internal_events_sequence(wf_state: ExampleWorkflowState) -> None: test_runner = WorkflowTestRunner(wf_state) result = await test_runner.run( start_event=StartEvent(message="hello"), # type: ignore - exclude_events=[StopEvent, EventsQueueChanged], + exclude_events=[StopEvent], ) assert all(isinstance(ev, StepStateChanged) for ev in result.collected) filtered_events = [ - r for r in result.collected if r.context_state is not None and r.name != "_done" + {"name": x.name, "step_state": x.step_state} for x in result.collected ] - assert len(filtered_events) == 4 - for i, ev in enumerate(filtered_events): - if i < 2: - assert ev.name == "first_step" - else: - assert ev.name == "second_step" - if i % 2 == 0: - assert ev.step_state == StepState.PREPARING - else: - assert ev.step_state == StepState.NOT_IN_PROGRESS - assert filtered_events[0].context_state["state_data"] == {"test": '""'} - assert all( - ev.context_state["state_data"] == {"test": '"Test"'} - for ev in filtered_events[1:] - ) - - -@pytest.mark.asyncio -async def test_internal_events_dict_state( - wf_dict_state: ExampleWorkflowDictState, -) -> None: - # prove that state modification works also with DictState - test_runner = WorkflowTestRunner(wf_dict_state) - result = await test_runner.run( - start_event=StartEvent(message="hello"), # type: ignore - exclude_events=[StopEvent, EventsQueueChanged], - ) - assert all(isinstance(ev, StepStateChanged) for ev in result.collected) - filtered_events = [ - r for r in result.collected if r.context_state is not None and r.name != "_done" + assert filtered_events == [ + dict(name="first_step", step_state=StepState.RUNNING), + dict(name="first_step", step_state=StepState.NOT_RUNNING), + dict(name="second_step", step_state=StepState.RUNNING), + dict(name="second_step", step_state=StepState.NOT_RUNNING), ] - assert len(filtered_events) == 4 - for i, ev in enumerate(filtered_events): - if i < 2: - assert ev.name == "first_step" - else: - assert ev.name == "second_step" - if i % 2 == 0: - assert ev.step_state == StepState.PREPARING - else: - assert ev.step_state == StepState.NOT_IN_PROGRESS - assert filtered_events[0].context_state["state_data"] == {} - assert filtered_events[1].context_state["state_data"] == {"test": '"Test"'} - assert filtered_events[2].context_state["state_data"] == {"test": '"Test"'} - assert filtered_events[3].context_state["state_data"] == {} @pytest.mark.asyncio @@ -172,13 +129,14 @@ async def test_internal_events_multiple_workers( test_runner = WorkflowTestRunner(wf_workers) result = await test_runner.run( start_event=StartEvent(message="hello"), # type: ignore - exclude_events=[StopEvent, EventsQueueChanged], + exclude_events=[StopEvent], ) assert all(isinstance(ev, StepStateChanged) for ev in result.collected) + collected = [ev for ev in result.collected if isinstance(ev, StepStateChanged)] run_ids = [ - r.worker_id - for r in result.collected - if r.step_state == StepState.RUNNING and r.name != "_done" + str(r.worker_id) + r.name + for r in collected + if r.step_state == StepState.RUNNING ] assert len(run_ids) == 11 assert ( diff --git a/tests/test_workflow_typed_state.py b/tests/test_workflow_typed_state.py index c1a2261a..30fa0d9b 100644 --- a/tests/test_workflow_typed_state.py +++ b/tests/test_workflow_typed_state.py @@ -33,10 +33,10 @@ async def step(self, ctx: Context[MyState], ev: StartEvent) -> StopEvent: async def test_typed_state() -> None: test_runner = WorkflowTestRunner(MyWorkflow()) - await test_runner.run() + result = await test_runner.run() # Check final state - ctx = test_runner._workflow._contexts.pop() + ctx = result.ctx assert ctx is not None state = await ctx.store.get_state() assert state.model_dump() == MyState(name="John", age=31).model_dump() diff --git a/uv.lock b/uv.lock index 7a9f2eeb..6b807b24 100644 --- a/uv.lock +++ b/uv.lock @@ -6,6 +6,21 @@ resolution-markers = [ "python_full_version < '3.10'", ] +[[package]] +name = "alembic" +version = "1.16.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako", marker = "python_full_version < '3.10'" }, + { name = "sqlalchemy", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/ca/4dc52902cf3491892d464f5265a81e9dff094692c8a049a3ed6a05fe7ee8/alembic-1.16.5.tar.gz", hash = "sha256:a88bb7f6e513bd4301ecf4c7f2206fe93f9913f9b48dac3b78babde2d6fe765e", size = 1969868, upload-time = "2025-08-27T18:02:05.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/4a/4c61d4c84cfd9befb6fa08a702535b27b21fff08c946bc2f6139decbf7f7/alembic-1.16.5-py3-none-any.whl", hash = "sha256:e845dfe090c5ffa7b92593ae6687c5cb1a101e91fa53868497dbd79847f9dbe3", size = 247355, upload-time = "2025-08-27T18:02:07.37Z" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -30,6 +45,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, ] +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + [[package]] name = "backports-tarfile" version = "1.2.0" @@ -57,6 +81,8 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191, upload-time = "2024-09-04T20:43:30.027Z" }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592, upload-time = "2024-09-04T20:43:32.108Z" }, { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024, upload-time = "2024-09-04T20:43:34.186Z" }, { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188, upload-time = "2024-09-04T20:43:36.286Z" }, { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571, upload-time = "2024-09-04T20:43:38.586Z" }, @@ -65,6 +91,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325, upload-time = "2024-09-04T20:43:43.117Z" }, { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784, upload-time = "2024-09-04T20:43:45.256Z" }, { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564, upload-time = "2024-09-04T20:43:46.779Z" }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804, upload-time = "2024-09-04T20:43:48.186Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299, upload-time = "2024-09-04T20:43:49.812Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, @@ -73,6 +103,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, @@ -80,6 +114,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, @@ -87,6 +125,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ea/8bb50596b8ffbc49ddd7a1ad305035daa770202a6b782fc164647c2673ad/cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", size = 182220, upload-time = "2024-09-04T20:45:01.577Z" }, + { url = "https://files.pythonhosted.org/packages/ae/11/e77c8cd24f58285a82c23af484cf5b124a376b32644e445960d1a4654c3a/cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", size = 178605, upload-time = "2024-09-04T20:45:03.837Z" }, { url = "https://files.pythonhosted.org/packages/ed/65/25a8dc32c53bf5b7b6c2686b42ae2ad58743f7ff644844af7cdb29b49361/cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", size = 424910, upload-time = "2024-09-04T20:45:05.315Z" }, { url = "https://files.pythonhosted.org/packages/42/7a/9d086fab7c66bd7c4d0f27c57a1b6b068ced810afc498cc8c49e0088661c/cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", size = 447200, upload-time = "2024-09-04T20:45:06.903Z" }, { url = "https://files.pythonhosted.org/packages/da/63/1785ced118ce92a993b0ec9e0d0ac8dc3e5dbfbcaa81135be56c69cabbb6/cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", size = 454565, upload-time = "2024-09-04T20:45:08.975Z" }, @@ -95,6 +137,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/95/b34462f3ccb09c2594aa782d90a90b045de4ff1f70148ee79c69d37a0a5a/cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", size = 460486, upload-time = "2024-09-04T20:45:13.935Z" }, { url = "https://files.pythonhosted.org/packages/fc/fc/a1e4bebd8d680febd29cf6c8a40067182b64f00c7d105f8f26b5bc54317b/cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", size = 437911, upload-time = "2024-09-04T20:45:15.696Z" }, { url = "https://files.pythonhosted.org/packages/e6/c3/21cab7a6154b6a5ea330ae80de386e7665254835b9e98ecc1340b3a7de9a/cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", size = 460632, upload-time = "2024-09-04T20:45:17.284Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b5/fd9f8b5a84010ca169ee49f4e4ad6f8c05f4e3545b72ee041dbbcb159882/cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", size = 171820, upload-time = "2024-09-04T20:45:18.762Z" }, + { url = "https://files.pythonhosted.org/packages/8c/52/b08750ce0bce45c143e1b5d7357ee8c55341b52bdef4b0f081af1eb248c2/cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", size = 181290, upload-time = "2024-09-04T20:45:20.226Z" }, ] [[package]] @@ -106,6 +150,111 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, ] +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/46/7c/0c4760bccf082737ca7ab84a4c2034fcc06b1f21cf3032ea98bd6feb1725/charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", size = 209609, upload-time = "2025-10-14T04:42:10.922Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a4/69719daef2f3d7f1819de60c9a6be981b8eeead7542d5ec4440f3c80e111/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", size = 149029, upload-time = "2025-10-14T04:42:12.38Z" }, + { url = "https://files.pythonhosted.org/packages/e6/21/8d4e1d6c1e6070d3672908b8e4533a71b5b53e71d16828cc24d0efec564c/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608", size = 144580, upload-time = "2025-10-14T04:42:13.549Z" }, + { url = "https://files.pythonhosted.org/packages/a7/0a/a616d001b3f25647a9068e0b9199f697ce507ec898cacb06a0d5a1617c99/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", size = 162340, upload-time = "2025-10-14T04:42:14.892Z" }, + { url = "https://files.pythonhosted.org/packages/85/93/060b52deb249a5450460e0585c88a904a83aec474ab8e7aba787f45e79f2/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", size = 159619, upload-time = "2025-10-14T04:42:16.676Z" }, + { url = "https://files.pythonhosted.org/packages/dd/21/0274deb1cc0632cd587a9a0ec6b4674d9108e461cb4cd40d457adaeb0564/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", size = 153980, upload-time = "2025-10-14T04:42:17.917Z" }, + { url = "https://files.pythonhosted.org/packages/28/2b/e3d7d982858dccc11b31906976323d790dded2017a0572f093ff982d692f/charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", size = 152174, upload-time = "2025-10-14T04:42:19.018Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ff/4a269f8e35f1e58b2df52c131a1fa019acb7ef3f8697b7d464b07e9b492d/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", size = 151666, upload-time = "2025-10-14T04:42:20.171Z" }, + { url = "https://files.pythonhosted.org/packages/da/c9/ec39870f0b330d58486001dd8e532c6b9a905f5765f58a6f8204926b4a93/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", size = 145550, upload-time = "2025-10-14T04:42:21.324Z" }, + { url = "https://files.pythonhosted.org/packages/75/8f/d186ab99e40e0ed9f82f033d6e49001701c81244d01905dd4a6924191a30/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", size = 163721, upload-time = "2025-10-14T04:42:22.46Z" }, + { url = "https://files.pythonhosted.org/packages/96/b1/6047663b9744df26a7e479ac1e77af7134b1fcf9026243bb48ee2d18810f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", size = 152127, upload-time = "2025-10-14T04:42:23.712Z" }, + { url = "https://files.pythonhosted.org/packages/59/78/e5a6eac9179f24f704d1be67d08704c3c6ab9f00963963524be27c18ed87/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", size = 161175, upload-time = "2025-10-14T04:42:24.87Z" }, + { url = "https://files.pythonhosted.org/packages/e5/43/0e626e42d54dd2f8dd6fc5e1c5ff00f05fbca17cb699bedead2cae69c62f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", size = 155375, upload-time = "2025-10-14T04:42:27.246Z" }, + { url = "https://files.pythonhosted.org/packages/e9/91/d9615bf2e06f35e4997616ff31248c3657ed649c5ab9d35ea12fce54e380/charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", size = 99692, upload-time = "2025-10-14T04:42:28.425Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a9/6c040053909d9d1ef4fcab45fddec083aedc9052c10078339b47c8573ea8/charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", size = 107192, upload-time = "2025-10-14T04:42:29.482Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c6/4fa536b2c0cd3edfb7ccf8469fa0f363ea67b7213a842b90909ca33dd851/charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", size = 100220, upload-time = "2025-10-14T04:42:30.632Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + [[package]] name = "click" version = "8.1.8" @@ -233,6 +382,7 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/a7/35/c495bffc2056f2dadb32434f1feedd79abde2a7f8363e1974afa9c33c7e2/cryptography-45.0.7.tar.gz", hash = "sha256:4b1654dfc64ea479c242508eb8c724044f1e964a47d1d1cacc5132292d851971", size = 744980, upload-time = "2025-09-01T11:15:03.146Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/91/925c0ac74362172ae4516000fe877912e33b5983df735ff290c653de4913/cryptography-45.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:3be4f21c6245930688bd9e162829480de027f8bf962ede33d4f8ba7d67a00cee", size = 7041105, upload-time = "2025-09-01T11:13:59.684Z" }, { url = "https://files.pythonhosted.org/packages/fc/63/43641c5acce3a6105cf8bd5baeceeb1846bb63067d26dae3e5db59f1513a/cryptography-45.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:67285f8a611b0ebc0857ced2081e30302909f571a46bfa7a3cc0ad303fe015c6", size = 4205799, upload-time = "2025-09-01T11:14:02.517Z" }, { url = "https://files.pythonhosted.org/packages/bc/29/c238dd9107f10bfde09a4d1c52fd38828b1aa353ced11f358b5dd2507d24/cryptography-45.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:577470e39e60a6cd7780793202e63536026d9b8641de011ed9d8174da9ca5339", size = 4430504, upload-time = "2025-09-01T11:14:04.522Z" }, { url = "https://files.pythonhosted.org/packages/62/62/24203e7cbcc9bd7c94739428cd30680b18ae6b18377ae66075c8e4771b1b/cryptography-45.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4bd3e5c4b9682bc112d634f2c6ccc6736ed3635fc3319ac2bb11d768cc5a00d8", size = 4209542, upload-time = "2025-09-01T11:14:06.309Z" }, @@ -242,6 +392,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/e4/b3e68a4ac363406a56cf7b741eeb80d05284d8c60ee1a55cdc7587e2a553/cryptography-45.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b6a0e535baec27b528cb07a119f321ac024592388c5681a5ced167ae98e9fff3", size = 4460397, upload-time = "2025-09-01T11:14:12.924Z" }, { url = "https://files.pythonhosted.org/packages/22/49/2c93f3cd4e3efc8cb22b02678c1fad691cff9dd71bb889e030d100acbfe0/cryptography-45.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a24ee598d10befaec178efdff6054bc4d7e883f615bfbcd08126a0f4931c83a6", size = 4337244, upload-time = "2025-09-01T11:14:14.431Z" }, { url = "https://files.pythonhosted.org/packages/04/19/030f400de0bccccc09aa262706d90f2ec23d56bc4eb4f4e8268d0ddf3fb8/cryptography-45.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa26fa54c0a9384c27fcdc905a2fb7d60ac6e47d14bc2692145f2b3b1e2cfdbd", size = 4568862, upload-time = "2025-09-01T11:14:16.185Z" }, + { url = "https://files.pythonhosted.org/packages/29/56/3034a3a353efa65116fa20eb3c990a8c9f0d3db4085429040a7eef9ada5f/cryptography-45.0.7-cp311-abi3-win32.whl", hash = "sha256:bef32a5e327bd8e5af915d3416ffefdbe65ed975b646b3805be81b23580b57b8", size = 2936578, upload-time = "2025-09-01T11:14:17.638Z" }, + { url = "https://files.pythonhosted.org/packages/b3/61/0ab90f421c6194705a99d0fa9f6ee2045d916e4455fdbb095a9c2c9a520f/cryptography-45.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:3808e6b2e5f0b46d981c24d79648e5c25c35e59902ea4391a0dcb3e667bf7443", size = 3405400, upload-time = "2025-09-01T11:14:18.958Z" }, + { url = "https://files.pythonhosted.org/packages/63/e8/c436233ddf19c5f15b25ace33979a9dd2e7aa1a59209a0ee8554179f1cc0/cryptography-45.0.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bfb4c801f65dd61cedfc61a83732327fafbac55a47282e6f26f073ca7a41c3b2", size = 7021824, upload-time = "2025-09-01T11:14:20.954Z" }, { url = "https://files.pythonhosted.org/packages/bc/4c/8f57f2500d0ccd2675c5d0cc462095adf3faa8c52294ba085c036befb901/cryptography-45.0.7-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:81823935e2f8d476707e85a78a405953a03ef7b7b4f55f93f7c2d9680e5e0691", size = 4202233, upload-time = "2025-09-01T11:14:22.454Z" }, { url = "https://files.pythonhosted.org/packages/eb/ac/59b7790b4ccaed739fc44775ce4645c9b8ce54cbec53edf16c74fd80cb2b/cryptography-45.0.7-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3994c809c17fc570c2af12c9b840d7cea85a9fd3e5c0e0491f4fa3c029216d59", size = 4423075, upload-time = "2025-09-01T11:14:24.287Z" }, { url = "https://files.pythonhosted.org/packages/b8/56/d4f07ea21434bf891faa088a6ac15d6d98093a66e75e30ad08e88aa2b9ba/cryptography-45.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dad43797959a74103cb59c5dac71409f9c27d34c8a05921341fb64ea8ccb1dd4", size = 4204517, upload-time = "2025-09-01T11:14:25.679Z" }, @@ -251,14 +404,72 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/fa/1d5745d878048699b8eb87c984d4ccc5da4f5008dfd3ad7a94040caca23a/cryptography-45.0.7-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f3df7b3d0f91b88b2106031fd995802a2e9ae13e02c36c1fc075b43f420f3a17", size = 4449383, upload-time = "2025-09-01T11:14:32.046Z" }, { url = "https://files.pythonhosted.org/packages/36/8b/fc61f87931bc030598e1876c45b936867bb72777eac693e905ab89832670/cryptography-45.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd342f085542f6eb894ca00ef70236ea46070c8a13824c6bde0dfdcd36065b9b", size = 4332186, upload-time = "2025-09-01T11:14:33.95Z" }, { url = "https://files.pythonhosted.org/packages/0b/11/09700ddad7443ccb11d674efdbe9a832b4455dc1f16566d9bd3834922ce5/cryptography-45.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1993a1bb7e4eccfb922b6cd414f072e08ff5816702a0bdb8941c247a6b1b287c", size = 4561639, upload-time = "2025-09-01T11:14:35.343Z" }, + { url = "https://files.pythonhosted.org/packages/71/ed/8f4c1337e9d3b94d8e50ae0b08ad0304a5709d483bfcadfcc77a23dbcb52/cryptography-45.0.7-cp37-abi3-win32.whl", hash = "sha256:18fcf70f243fe07252dcb1b268a687f2358025ce32f9f88028ca5c364b123ef5", size = 2926552, upload-time = "2025-09-01T11:14:36.929Z" }, + { url = "https://files.pythonhosted.org/packages/bc/ff/026513ecad58dacd45d1d24ebe52b852165a26e287177de1d545325c0c25/cryptography-45.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:7285a89df4900ed3bfaad5679b1e668cb4b38a8de1ccbfc84b05f34512da0a90", size = 3392742, upload-time = "2025-09-01T11:14:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/13/3e/e42f1528ca1ea82256b835191eab1be014e0f9f934b60d98b0be8a38ed70/cryptography-45.0.7-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:de58755d723e86175756f463f2f0bddd45cc36fbd62601228a3f8761c9f58252", size = 3572442, upload-time = "2025-09-01T11:14:39.836Z" }, { url = "https://files.pythonhosted.org/packages/59/aa/e947693ab08674a2663ed2534cd8d345cf17bf6a1facf99273e8ec8986dc/cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a20e442e917889d1a6b3c570c9e3fa2fdc398c20868abcea268ea33c024c4083", size = 4142233, upload-time = "2025-09-01T11:14:41.305Z" }, { url = "https://files.pythonhosted.org/packages/24/06/09b6f6a2fc43474a32b8fe259038eef1500ee3d3c141599b57ac6c57612c/cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:258e0dff86d1d891169b5af222d362468a9570e2532923088658aa866eb11130", size = 4376202, upload-time = "2025-09-01T11:14:43.047Z" }, { url = "https://files.pythonhosted.org/packages/00/f2/c166af87e95ce6ae6d38471a7e039d3a0549c2d55d74e059680162052824/cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d97cf502abe2ab9eff8bd5e4aca274da8d06dd3ef08b759a8d6143f4ad65d4b4", size = 4141900, upload-time = "2025-09-01T11:14:45.089Z" }, { url = "https://files.pythonhosted.org/packages/16/b9/e96e0b6cb86eae27ea51fa8a3151535a18e66fe7c451fa90f7f89c85f541/cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:c987dad82e8c65ebc985f5dae5e74a3beda9d0a2a4daf8a1115f3772b59e5141", size = 4375562, upload-time = "2025-09-01T11:14:47.166Z" }, + { url = "https://files.pythonhosted.org/packages/36/d0/36e8ee39274e9d77baf7d0dafda680cba6e52f3936b846f0d56d64fec915/cryptography-45.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c13b1e3afd29a5b3b2656257f14669ca8fa8d7956d509926f0b130b600b50ab7", size = 3322781, upload-time = "2025-09-01T11:14:48.747Z" }, + { url = "https://files.pythonhosted.org/packages/99/4e/49199a4c82946938a3e05d2e8ad9482484ba48bbc1e809e3d506c686d051/cryptography-45.0.7-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a862753b36620af6fc54209264f92c716367f2f0ff4624952276a6bbd18cbde", size = 3584634, upload-time = "2025-09-01T11:14:50.593Z" }, { url = "https://files.pythonhosted.org/packages/16/ce/5f6ff59ea9c7779dba51b84871c19962529bdcc12e1a6ea172664916c550/cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:06ce84dc14df0bf6ea84666f958e6080cdb6fe1231be2a51f3fc1267d9f3fb34", size = 4149533, upload-time = "2025-09-01T11:14:52.091Z" }, { url = "https://files.pythonhosted.org/packages/ce/13/b3cfbd257ac96da4b88b46372e662009b7a16833bfc5da33bb97dd5631ae/cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d0c5c6bac22b177bf8da7435d9d27a6834ee130309749d162b26c3105c0795a9", size = 4385557, upload-time = "2025-09-01T11:14:53.551Z" }, { url = "https://files.pythonhosted.org/packages/1c/c5/8c59d6b7c7b439ba4fc8d0cab868027fd095f215031bc123c3a070962912/cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:2f641b64acc00811da98df63df7d59fd4706c0df449da71cb7ac39a0732b40ae", size = 4149023, upload-time = "2025-09-01T11:14:55.022Z" }, { url = "https://files.pythonhosted.org/packages/55/32/05385c86d6ca9ab0b4d5bb442d2e3d85e727939a11f3e163fc776ce5eb40/cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:f5414a788ecc6ee6bc58560e85ca624258a55ca434884445440a810796ea0e0b", size = 4385722, upload-time = "2025-09-01T11:14:57.319Z" }, + { url = "https://files.pythonhosted.org/packages/23/87/7ce86f3fa14bc11a5a48c30d8103c26e09b6465f8d8e9d74cf7a0714f043/cryptography-45.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f3d56f73595376f4244646dd5c5870c14c196949807be39e79e7bd9bac3da63", size = 3332908, upload-time = "2025-09-01T11:14:58.78Z" }, +] + +[[package]] +name = "dbos" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "alembic", marker = "python_full_version < '3.10'" }, + { name = "cryptography", marker = "python_full_version < '3.10'" }, + { name = "docker", marker = "python_full_version < '3.10'" }, + { name = "fastapi", extra = ["standard"], marker = "python_full_version < '3.10'" }, + { name = "jsonpickle", marker = "python_full_version < '3.10'" }, + { name = "jsonschema", marker = "python_full_version < '3.10'" }, + { name = "opentelemetry-api", marker = "python_full_version < '3.10'" }, + { name = "opentelemetry-exporter-otlp-proto-http", marker = "python_full_version < '3.10'" }, + { name = "opentelemetry-sdk", marker = "python_full_version < '3.10'" }, + { name = "psycopg", extra = ["binary"], marker = "python_full_version < '3.10'" }, + { name = "pyjwt", marker = "python_full_version < '3.10'" }, + { name = "python-dateutil", marker = "python_full_version < '3.10'" }, + { name = "pyyaml", marker = "python_full_version < '3.10'" }, + { name = "rich", marker = "python_full_version < '3.10'" }, + { name = "tomlkit", marker = "python_full_version < '3.10'" }, + { name = "typer", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, + { name = "websockets", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/b7/2cfefeb96fe67481ec2cf009c427ccbb32c82e2026cf2c259acbe6b22045/dbos-1.14.0.tar.gz", hash = "sha256:16052bd8ad004c7dce6b9cd2c10b282c2c4bb3b1bd7c5f6714e4bc437b7ca6f4", size = 216135, upload-time = "2025-09-16T16:49:08.741Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/7f/2461485e608594a4c43e65dac7a5c6614256f2ca9aeeddcd080e4fb52939/dbos-1.14.0-py3-none-any.whl", hash = "sha256:544c99a641636f6b9f528f64ae92430c444bc68486fc2511ab5415f61304e2bb", size = 151550, upload-time = "2025-09-16T16:49:05.806Z" }, +] + +[[package]] +name = "dbos" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "psycopg", extra = ["binary"], marker = "python_full_version >= '3.10'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, + { name = "pyyaml", marker = "python_full_version >= '3.10'" }, + { name = "sqlalchemy", marker = "python_full_version >= '3.10'" }, + { name = "typer-slim", marker = "python_full_version >= '3.10'" }, + { name = "websockets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/b4/b5082fe26f64d1a4d0856e6777897f0c685fe9d862cb4b638e6b82b31cb2/dbos-2.2.0.tar.gz", hash = "sha256:85089526874eb3dd3b32fa16b1860f068b2f752877b2a88de33b4ff72ed2cd04", size = 210807, upload-time = "2025-10-14T19:11:52.136Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/50/018aa40908cc2b4f137b15d4548966c333318784f15184c3e9ab58ad421e/dbos-2.2.0-py3-none-any.whl", hash = "sha256:35953e5675b0656535a0458e3c459e9c1f56664eefa87fdb8f4a2f54ee4ff54a", size = 135471, upload-time = "2025-10-14T19:11:50.514Z" }, ] [[package]] @@ -282,6 +493,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload-time = "2024-10-09T18:35:44.272Z" }, ] +[[package]] +name = "dnspython" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, +] + +[[package]] +name = "docker" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "requests", marker = "python_full_version < '3.10'" }, + { name = "urllib3", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834, upload-time = "2024-05-23T11:13:57.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, +] + +[[package]] +name = "email-validator" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython", marker = "python_full_version < '3.10'" }, + { name = "idna", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, +] + [[package]] name = "eval-type-backport" version = "0.2.2" @@ -312,6 +559,68 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612, upload-time = "2024-04-08T09:04:17.414Z" }, ] +[[package]] +name = "fastapi" +version = "0.119.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic", marker = "python_full_version < '3.10'" }, + { name = "starlette", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/f4/152127681182e6413e7a89684c434e19e7414ed7ac0c632999c3c6980640/fastapi-0.119.1.tar.gz", hash = "sha256:a5e3426edce3fe221af4e1992c6d79011b247e3b03cc57999d697fe76cbf8ae0", size = 338616, upload-time = "2025-10-20T11:30:27.734Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/26/e6d959b4ac959fdb3e9c4154656fc160794db6af8e64673d52759456bf07/fastapi-0.119.1-py3-none-any.whl", hash = "sha256:0b8c2a2cce853216e150e9bd4faaed88227f8eb37de21cb200771f491586a27f", size = 108123, upload-time = "2025-10-20T11:30:26.185Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "email-validator", marker = "python_full_version < '3.10'" }, + { name = "fastapi-cli", extra = ["standard"], marker = "python_full_version < '3.10'" }, + { name = "httpx", marker = "python_full_version < '3.10'" }, + { name = "jinja2", marker = "python_full_version < '3.10'" }, + { name = "python-multipart", marker = "python_full_version < '3.10'" }, + { name = "uvicorn", extra = ["standard"], marker = "python_full_version < '3.10'" }, +] + +[[package]] +name = "fastapi-cli" +version = "0.0.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rich-toolkit", marker = "python_full_version < '3.10'" }, + { name = "typer", marker = "python_full_version < '3.10'" }, + { name = "uvicorn", extra = ["standard"], marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/13/11e43d630be84e51ba5510a6da6a11eb93b44b72caa796137c5dddda937b/fastapi_cli-0.0.14.tar.gz", hash = "sha256:ddfb5de0a67f77a8b3271af1460489bd4d7f4add73d11fbfac613827b0275274", size = 17994, upload-time = "2025-10-20T16:33:21.054Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/e8/bc8bbfd93dcc8e347ce98a3e654fb0d2e5f2739afb46b98f41a30c339269/fastapi_cli-0.0.14-py3-none-any.whl", hash = "sha256:e66b9ad499ee77a4e6007545cde6de1459b7f21df199d7f29aad2adaab168eca", size = 11151, upload-time = "2025-10-20T16:33:19.318Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "fastapi-cloud-cli", marker = "python_full_version < '3.10'" }, + { name = "uvicorn", extra = ["standard"], marker = "python_full_version < '3.10'" }, +] + +[[package]] +name = "fastapi-cloud-cli" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx", marker = "python_full_version < '3.10'" }, + { name = "pydantic", extra = ["email"], marker = "python_full_version < '3.10'" }, + { name = "rich-toolkit", marker = "python_full_version < '3.10'" }, + { name = "rignore", marker = "python_full_version < '3.10'" }, + { name = "sentry-sdk", marker = "python_full_version < '3.10'" }, + { name = "typer", marker = "python_full_version < '3.10'" }, + { name = "uvicorn", extra = ["standard"], marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/48/0f14d8555b750dc8c04382804e4214f1d7f55298127f3a0237ba566e69dd/fastapi_cloud_cli-0.3.1.tar.gz", hash = "sha256:8c7226c36e92e92d0c89827e8f56dbf164ab2de4444bd33aa26b6c3f7675db69", size = 24080, upload-time = "2025-10-09T11:32:58.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/79/7f5a5e5513e6a737e5fb089d9c59c74d4d24dc24d581d3aa519b326bedda/fastapi_cloud_cli-0.3.1-py3-none-any.whl", hash = "sha256:7d1a98a77791a9d0757886b2ffbf11bcc6b3be93210dd15064be10b216bf7e00", size = 19711, upload-time = "2025-10-09T11:32:57.118Z" }, +] + [[package]] name = "filelock" version = "3.18.0" @@ -321,6 +630,79 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, ] +[[package]] +name = "googleapis-common-protos" +version = "1.71.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/43/b25abe02db2911397819003029bef768f68a974f2ece483e6084d1a5f754/googleapis_common_protos-1.71.0.tar.gz", hash = "sha256:1aec01e574e29da63c80ba9f7bbf1ccfaacf1da877f23609fe236ca7c72a2e2e", size = 146454, upload-time = "2025-10-20T14:58:08.732Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/e8/eba9fece11d57a71e3e22ea672742c8f3cf23b35730c9e96db768b295216/googleapis_common_protos-1.71.0-py3-none-any.whl", hash = "sha256:59034a1d849dc4d18971997a72ac56246570afdd17f9369a0ff68218d50ab78c", size = 294576, upload-time = "2025-10-20T14:56:21.295Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/ed/6bfa4109fcb23a58819600392564fea69cdc6551ffd5e69ccf1d52a40cbc/greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c", size = 271061, upload-time = "2025-08-07T13:17:15.373Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fc/102ec1a2fc015b3a7652abab7acf3541d58c04d3d17a8d3d6a44adae1eb1/greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590", size = 629475, upload-time = "2025-08-07T13:42:54.009Z" }, + { url = "https://files.pythonhosted.org/packages/c5/26/80383131d55a4ac0fb08d71660fd77e7660b9db6bdb4e8884f46d9f2cc04/greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c", size = 640802, upload-time = "2025-08-07T13:45:25.52Z" }, + { url = "https://files.pythonhosted.org/packages/9f/7c/e7833dbcd8f376f3326bd728c845d31dcde4c84268d3921afcae77d90d08/greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b", size = 636703, upload-time = "2025-08-07T13:53:12.622Z" }, + { url = "https://files.pythonhosted.org/packages/e9/49/547b93b7c0428ede7b3f309bc965986874759f7d89e4e04aeddbc9699acb/greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31", size = 635417, upload-time = "2025-08-07T13:18:25.189Z" }, + { url = "https://files.pythonhosted.org/packages/7f/91/ae2eb6b7979e2f9b035a9f612cf70f1bf54aad4e1d125129bef1eae96f19/greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d", size = 584358, upload-time = "2025-08-07T13:18:23.708Z" }, + { url = "https://files.pythonhosted.org/packages/f7/85/433de0c9c0252b22b16d413c9407e6cb3b41df7389afc366ca204dbc1393/greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5", size = 1113550, upload-time = "2025-08-07T13:42:37.467Z" }, + { url = "https://files.pythonhosted.org/packages/a1/8d/88f3ebd2bc96bf7747093696f4335a0a8a4c5acfcf1b757717c0d2474ba3/greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f", size = 1137126, upload-time = "2025-08-07T13:18:20.239Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6f/b60b0291d9623c496638c582297ead61f43c4b72eef5e9c926ef4565ec13/greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c", size = 298654, upload-time = "2025-08-07T13:50:00.469Z" }, + { url = "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305, upload-time = "2025-08-07T13:15:41.288Z" }, + { url = "https://files.pythonhosted.org/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472, upload-time = "2025-08-07T13:42:55.044Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/95d48d7e3d433e6dae5b1682e4292242a53f22df82e6d3dda81b1701a960/greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3", size = 644646, upload-time = "2025-08-07T13:45:26.523Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5e/405965351aef8c76b8ef7ad370e5da58d57ef6068df197548b015464001a/greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", size = 640519, upload-time = "2025-08-07T13:53:13.928Z" }, + { url = "https://files.pythonhosted.org/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", size = 639707, upload-time = "2025-08-07T13:18:27.146Z" }, + { url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" }, + { url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" }, + { url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" }, + { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, + { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, + { url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" }, + { url = "https://files.pythonhosted.org/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926, upload-time = "2025-08-07T13:53:15.251Z" }, + { url = "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839, upload-time = "2025-08-07T13:18:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" }, + { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, + { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, + { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, + { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, + { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, + { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, + { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, + { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, + { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c0/93885c4106d2626bf51fdec377d6aef740dfa5c4877461889a7cf8e565cc/greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c", size = 269859, upload-time = "2025-08-07T13:16:16.003Z" }, + { url = "https://files.pythonhosted.org/packages/4d/f5/33f05dc3ba10a02dedb1485870cf81c109227d3d3aa280f0e48486cac248/greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d", size = 627610, upload-time = "2025-08-07T13:43:01.345Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a7/9476decef51a0844195f99ed5dc611d212e9b3515512ecdf7321543a7225/greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58", size = 639417, upload-time = "2025-08-07T13:45:32.094Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e0/849b9159cbb176f8c0af5caaff1faffdece7a8417fcc6fe1869770e33e21/greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4", size = 634751, upload-time = "2025-08-07T13:53:18.848Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d3/844e714a9bbd39034144dca8b658dcd01839b72bb0ec7d8014e33e3705f0/greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433", size = 634020, upload-time = "2025-08-07T13:18:36.841Z" }, + { url = "https://files.pythonhosted.org/packages/6b/4c/f3de2a8de0e840ecb0253ad0dc7e2bb3747348e798ec7e397d783a3cb380/greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df", size = 582817, upload-time = "2025-08-07T13:18:35.48Z" }, + { url = "https://files.pythonhosted.org/packages/89/80/7332915adc766035c8980b161c2e5d50b2f941f453af232c164cff5e0aeb/greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594", size = 1111985, upload-time = "2025-08-07T13:42:42.425Z" }, + { url = "https://files.pythonhosted.org/packages/66/71/1928e2c80197353bcb9b50aa19c4d8e26ee6d7a900c564907665cf4b9a41/greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98", size = 1136137, upload-time = "2025-08-07T13:18:26.168Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/a5dc74dde38aeb2b15d418cec76ed50e1dd3d620ccda84d8199703248968/greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b", size = 281400, upload-time = "2025-08-07T14:02:20.263Z" }, + { url = "https://files.pythonhosted.org/packages/e5/44/342c4591db50db1076b8bda86ed0ad59240e3e1da17806a4cf10a6d0e447/greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb", size = 298533, upload-time = "2025-08-07T13:56:34.168Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -387,6 +769,56 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] +[[package]] +name = "httptools" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/e5/c07e0bcf4ec8db8164e9f6738c048b2e66aabf30e7506f440c4cc6953f60/httptools-0.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:11d01b0ff1fe02c4c32d60af61a4d613b74fad069e47e06e9067758c01e9ac78", size = 204531, upload-time = "2025-10-10T03:54:20.887Z" }, + { url = "https://files.pythonhosted.org/packages/7e/4f/35e3a63f863a659f92ffd92bef131f3e81cf849af26e6435b49bd9f6f751/httptools-0.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d86c1e5afdc479a6fdabf570be0d3eb791df0ae727e8dbc0259ed1249998d4", size = 109408, upload-time = "2025-10-10T03:54:22.455Z" }, + { url = "https://files.pythonhosted.org/packages/f5/71/b0a9193641d9e2471ac541d3b1b869538a5fb6419d52fd2669fa9c79e4b8/httptools-0.7.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c8c751014e13d88d2be5f5f14fc8b89612fcfa92a9cc480f2bc1598357a23a05", size = 440889, upload-time = "2025-10-10T03:54:23.753Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d9/2e34811397b76718750fea44658cb0205b84566e895192115252e008b152/httptools-0.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:654968cb6b6c77e37b832a9be3d3ecabb243bbe7a0b8f65fbc5b6b04c8fcabed", size = 440460, upload-time = "2025-10-10T03:54:25.313Z" }, + { url = "https://files.pythonhosted.org/packages/01/3f/a04626ebeacc489866bb4d82362c0657b2262bef381d68310134be7f40bb/httptools-0.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b580968316348b474b020edf3988eecd5d6eec4634ee6561e72ae3a2a0e00a8a", size = 425267, upload-time = "2025-10-10T03:54:26.81Z" }, + { url = "https://files.pythonhosted.org/packages/a5/99/adcd4f66614db627b587627c8ad6f4c55f18881549bab10ecf180562e7b9/httptools-0.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d496e2f5245319da9d764296e86c5bb6fcf0cf7a8806d3d000717a889c8c0b7b", size = 424429, upload-time = "2025-10-10T03:54:28.174Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/ec8fc904a8fd30ba022dfa85f3bbc64c3c7cd75b669e24242c0658e22f3c/httptools-0.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cbf8317bfccf0fed3b5680c559d3459cccf1abe9039bfa159e62e391c7270568", size = 86173, upload-time = "2025-10-10T03:54:29.5Z" }, + { url = "https://files.pythonhosted.org/packages/9c/08/17e07e8d89ab8f343c134616d72eebfe03798835058e2ab579dcc8353c06/httptools-0.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:474d3b7ab469fefcca3697a10d11a32ee2b9573250206ba1e50d5980910da657", size = 206521, upload-time = "2025-10-10T03:54:31.002Z" }, + { url = "https://files.pythonhosted.org/packages/aa/06/c9c1b41ff52f16aee526fd10fbda99fa4787938aa776858ddc4a1ea825ec/httptools-0.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3c3b7366bb6c7b96bd72d0dbe7f7d5eead261361f013be5f6d9590465ea1c70", size = 110375, upload-time = "2025-10-10T03:54:31.941Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cc/10935db22fda0ee34c76f047590ca0a8bd9de531406a3ccb10a90e12ea21/httptools-0.7.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:379b479408b8747f47f3b253326183d7c009a3936518cdb70db58cffd369d9df", size = 456621, upload-time = "2025-10-10T03:54:33.176Z" }, + { url = "https://files.pythonhosted.org/packages/0e/84/875382b10d271b0c11aa5d414b44f92f8dd53e9b658aec338a79164fa548/httptools-0.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cad6b591a682dcc6cf1397c3900527f9affef1e55a06c4547264796bbd17cf5e", size = 454954, upload-time = "2025-10-10T03:54:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/30/e1/44f89b280f7e46c0b1b2ccee5737d46b3bb13136383958f20b580a821ca0/httptools-0.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eb844698d11433d2139bbeeb56499102143beb582bd6c194e3ba69c22f25c274", size = 440175, upload-time = "2025-10-10T03:54:35.942Z" }, + { url = "https://files.pythonhosted.org/packages/6f/7e/b9287763159e700e335028bc1824359dc736fa9b829dacedace91a39b37e/httptools-0.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f65744d7a8bdb4bda5e1fa23e4ba16832860606fcc09d674d56e425e991539ec", size = 440310, upload-time = "2025-10-10T03:54:37.1Z" }, + { url = "https://files.pythonhosted.org/packages/b3/07/5b614f592868e07f5c94b1f301b5e14a21df4e8076215a3bccb830a687d8/httptools-0.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:135fbe974b3718eada677229312e97f3b31f8a9c8ffa3ae6f565bf808d5b6bcb", size = 86875, upload-time = "2025-10-10T03:54:38.421Z" }, + { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280, upload-time = "2025-10-10T03:54:39.274Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5", size = 110004, upload-time = "2025-10-10T03:54:40.403Z" }, + { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03", size = 517655, upload-time = "2025-10-10T03:54:41.347Z" }, + { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2", size = 511440, upload-time = "2025-10-10T03:54:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362", size = 495186, upload-time = "2025-10-10T03:54:43.937Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c", size = 499192, upload-time = "2025-10-10T03:54:45.003Z" }, + { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321", size = 86694, upload-time = "2025-10-10T03:54:45.923Z" }, + { url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889, upload-time = "2025-10-10T03:54:47.089Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180, upload-time = "2025-10-10T03:54:48.052Z" }, + { url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596, upload-time = "2025-10-10T03:54:48.919Z" }, + { url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268, upload-time = "2025-10-10T03:54:49.993Z" }, + { url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517, upload-time = "2025-10-10T03:54:51.066Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337, upload-time = "2025-10-10T03:54:52.196Z" }, + { url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6", size = 85743, upload-time = "2025-10-10T03:54:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" }, + { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" }, + { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" }, + { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" }, + { url = "https://files.pythonhosted.org/packages/90/de/b1fe0e8890f0292c266117d4cd268186758a9c34e576fbd573fdf3beacff/httptools-0.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ac50afa68945df63ec7a2707c506bd02239272288add34539a2ef527254626a4", size = 206454, upload-time = "2025-10-10T03:55:01.528Z" }, + { url = "https://files.pythonhosted.org/packages/57/a7/a675c90b49e550c7635ce209c01bc61daa5b08aef17da27ef4e0e78fcf3f/httptools-0.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de987bb4e7ac95b99b805b99e0aae0ad51ae61df4263459d36e07cf4052d8b3a", size = 110260, upload-time = "2025-10-10T03:55:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/03/44/fb5ef8136e6e97f7b020e97e40c03a999f97e68574d4998fa52b0a62b01b/httptools-0.7.1-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d169162803a24425eb5e4d51d79cbf429fd7a491b9e570a55f495ea55b26f0bf", size = 441524, upload-time = "2025-10-10T03:55:03.292Z" }, + { url = "https://files.pythonhosted.org/packages/b4/62/8496a5425341867796d7e2419695f74a74607054e227bbaeabec8323e87f/httptools-0.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49794f9250188a57fa73c706b46cb21a313edb00d337ca4ce1a011fe3c760b28", size = 440877, upload-time = "2025-10-10T03:55:04.282Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f1/26c2e5214106bf6ed04d03e518ff28ca0c6b5390c5da7b12bbf94b40ae43/httptools-0.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aeefa0648362bb97a7d6b5ff770bfb774930a327d7f65f8208394856862de517", size = 425775, upload-time = "2025-10-10T03:55:05.341Z" }, + { url = "https://files.pythonhosted.org/packages/3a/34/7500a19257139725281f7939a7d1aa3701cf1ac4601a1690f9ab6f510e15/httptools-0.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0d92b10dbf0b3da4823cde6a96d18e6ae358a9daa741c71448975f6a2c339cad", size = 425001, upload-time = "2025-10-10T03:55:06.389Z" }, + { url = "https://files.pythonhosted.org/packages/71/04/31a7949d645ebf33a67f56a0024109444a52a271735e0647a210264f3e61/httptools-0.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:5ddbd045cfcb073db2449563dd479057f2c2b681ebc232380e63ef15edc9c023", size = 86818, upload-time = "2025-10-10T03:55:07.316Z" }, +] + [[package]] name = "httpx" version = "0.28.1" @@ -498,6 +930,54 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, ] +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jsonpickle" +version = "4.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/a6/d07afcfdef402900229bcca795f80506b207af13a838d4d99ad45abf530c/jsonpickle-4.1.1.tar.gz", hash = "sha256:f86e18f13e2b96c1c1eede0b7b90095bbb61d99fedc14813c44dc2f361dbbae1", size = 316885, upload-time = "2025-06-02T20:36:11.57Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/73/04df8a6fa66d43a9fd45c30f283cc4afff17da671886e451d52af60bdc7e/jsonpickle-4.1.1-py3-none-any.whl", hash = "sha256:bb141da6057898aa2438ff268362b126826c812a1721e31cf08a6e142910dc91", size = 47125, upload-time = "2025-06-02T20:36:08.647Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs", marker = "python_full_version < '3.10'" }, + { name = "jsonschema-specifications", marker = "python_full_version < '3.10'" }, + { name = "referencing", marker = "python_full_version < '3.10'" }, + { name = "rpds-py", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + [[package]] name = "keyring" version = "25.6.0" @@ -544,6 +1024,10 @@ dependencies = [ client = [ { name = "httpx" }, ] +dbos = [ + { name = "dbos", version = "1.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "dbos", version = "2.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] server = [ { name = "starlette" }, { name = "uvicorn" }, @@ -558,12 +1042,14 @@ dev = [ { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, + { name = "pytest-timeout" }, { name = "pytest-xdist" }, { name = "pyyaml" }, ] [package.metadata] requires-dist = [ + { name = "dbos", marker = "extra == 'dbos'", specifier = ">=1.14.0" }, { name = "eval-type-backport", marker = "python_full_version < '3.10'", specifier = ">=0.2.2" }, { name = "httpx", marker = "extra == 'client'", specifier = ">=0.28.1,<1" }, { name = "llama-index-instrumentation", specifier = ">=0.1.0" }, @@ -572,7 +1058,7 @@ requires-dist = [ { name = "typing-extensions", specifier = ">=4.6.0" }, { name = "uvicorn", marker = "extra == 'server'", specifier = ">=0.32.0" }, ] -provides-extras = ["server", "client"] +provides-extras = ["server", "client", "dbos"] [package.metadata.requires-dev] dev = [ @@ -583,10 +1069,23 @@ dev = [ { name = "pytest", specifier = ">=8.4.0" }, { name = "pytest-asyncio", specifier = ">=1.0.0" }, { name = "pytest-cov", specifier = ">=6.1.1" }, + { name = "pytest-timeout", specifier = ">=2.4.0" }, { name = "pytest-xdist", specifier = ">=3.8.0" }, { name = "pyyaml", specifier = ">=6.0.2" }, ] +[[package]] +name = "mako" +version = "1.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, +] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -617,6 +1116,102 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, + { url = "https://files.pythonhosted.org/packages/56/23/0d8c13a44bde9154821586520840643467aee574d8ce79a17da539ee7fed/markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", size = 11623, upload-time = "2025-09-27T18:37:29.296Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/07a2cb9a8045d5f3f0890a8c3bc0859d7a47bfd9a560b563899bec7b72ed/markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", size = 12049, upload-time = "2025-09-27T18:37:30.234Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e4/6be85eb81503f8e11b61c0b6369b6e077dcf0a74adbd9ebf6b349937b4e9/markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", size = 21923, upload-time = "2025-09-27T18:37:31.177Z" }, + { url = "https://files.pythonhosted.org/packages/6f/bc/4dc914ead3fe6ddaef035341fee0fc956949bbd27335b611829292b89ee2/markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", size = 20543, upload-time = "2025-09-27T18:37:32.168Z" }, + { url = "https://files.pythonhosted.org/packages/89/6e/5fe81fbcfba4aef4093d5f856e5c774ec2057946052d18d168219b7bd9f9/markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", size = 20585, upload-time = "2025-09-27T18:37:33.166Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f6/e0e5a3d3ae9c4020f696cd055f940ef86b64fe88de26f3a0308b9d3d048c/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", size = 21387, upload-time = "2025-09-27T18:37:34.185Z" }, + { url = "https://files.pythonhosted.org/packages/c8/25/651753ef4dea08ea790f4fbb65146a9a44a014986996ca40102e237aa49a/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", size = 20133, upload-time = "2025-09-27T18:37:35.138Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0a/c3cf2b4fef5f0426e8a6d7fce3cb966a17817c568ce59d76b92a233fdbec/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", size = 20588, upload-time = "2025-09-27T18:37:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/cd/1b/a7782984844bd519ad4ffdbebbba2671ec5d0ebbeac34736c15fb86399e8/markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", size = 14566, upload-time = "2025-09-27T18:37:37.09Z" }, + { url = "https://files.pythonhosted.org/packages/18/1f/8d9c20e1c9440e215a44be5ab64359e207fcb4f675543f1cf9a2a7f648d0/markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", size = 15053, upload-time = "2025-09-27T18:37:38.054Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d3/fe08482b5cd995033556d45041a4f4e76e7f0521112a9c9991d40d39825f/markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", size = 13928, upload-time = "2025-09-27T18:37:39.037Z" }, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -644,6 +1239,88 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] +[[package]] +name = "opentelemetry-api" +version = "1.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/d8/0f354c375628e048bd0570645b310797299754730079853095bf000fba69/opentelemetry_api-1.38.0.tar.gz", hash = "sha256:f4c193b5e8acb0912b06ac5b16321908dd0843d75049c091487322284a3eea12", size = 65242, upload-time = "2025-10-16T08:35:50.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/a2/d86e01c28300bd41bab8f18afd613676e2bd63515417b77636fc1add426f/opentelemetry_api-1.38.0-py3-none-any.whl", hash = "sha256:2891b0197f47124454ab9f0cf58f3be33faca394457ac3e09daba13ff50aa582", size = 65947, upload-time = "2025-10-16T08:35:30.23Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-proto", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/83/dd4660f2956ff88ed071e9e0e36e830df14b8c5dc06722dbde1841accbe8/opentelemetry_exporter_otlp_proto_common-1.38.0.tar.gz", hash = "sha256:e333278afab4695aa8114eeb7bf4e44e65c6607d54968271a249c180b2cb605c", size = 20431, upload-time = "2025-10-16T08:35:53.285Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/9e/55a41c9601191e8cd8eb626b54ee6827b9c9d4a46d736f32abc80d8039fc/opentelemetry_exporter_otlp_proto_common-1.38.0-py3-none-any.whl", hash = "sha256:03cb76ab213300fe4f4c62b7d8f17d97fcfd21b89f0b5ce38ea156327ddda74a", size = 18359, upload-time = "2025-10-16T08:35:34.099Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos", marker = "python_full_version < '3.10'" }, + { name = "opentelemetry-api", marker = "python_full_version < '3.10'" }, + { name = "opentelemetry-exporter-otlp-proto-common", marker = "python_full_version < '3.10'" }, + { name = "opentelemetry-proto", marker = "python_full_version < '3.10'" }, + { name = "opentelemetry-sdk", marker = "python_full_version < '3.10'" }, + { name = "requests", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/0a/debcdfb029fbd1ccd1563f7c287b89a6f7bef3b2902ade56797bfd020854/opentelemetry_exporter_otlp_proto_http-1.38.0.tar.gz", hash = "sha256:f16bd44baf15cbe07633c5112ffc68229d0edbeac7b37610be0b2def4e21e90b", size = 17282, upload-time = "2025-10-16T08:35:54.422Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/77/154004c99fb9f291f74aa0822a2f5bbf565a72d8126b3a1b63ed8e5f83c7/opentelemetry_exporter_otlp_proto_http-1.38.0-py3-none-any.whl", hash = "sha256:84b937305edfc563f08ec69b9cb2298be8188371217e867c1854d77198d0825b", size = 19579, upload-time = "2025-10-16T08:35:36.269Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/14/f0c4f0f6371b9cb7f9fa9ee8918bfd59ac7040c7791f1e6da32a1839780d/opentelemetry_proto-1.38.0.tar.gz", hash = "sha256:88b161e89d9d372ce723da289b7da74c3a8354a8e5359992be813942969ed468", size = 46152, upload-time = "2025-10-16T08:36:01.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/6a/82b68b14efca5150b2632f3692d627afa76b77378c4999f2648979409528/opentelemetry_proto-1.38.0-py3-none-any.whl", hash = "sha256:b6ebe54d3217c42e45462e2a1ae28c3e2bf2ec5a5645236a490f55f45f1a0a18", size = 72535, upload-time = "2025-10-16T08:35:45.749Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api", marker = "python_full_version < '3.10'" }, + { name = "opentelemetry-semantic-conventions", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/cb/f0eee1445161faf4c9af3ba7b848cc22a50a3d3e2515051ad8628c35ff80/opentelemetry_sdk-1.38.0.tar.gz", hash = "sha256:93df5d4d871ed09cb4272305be4d996236eedb232253e3ab864c8620f051cebe", size = 171942, upload-time = "2025-10-16T08:36:02.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/2e/e93777a95d7d9c40d270a371392b6d6f1ff170c2a3cb32d6176741b5b723/opentelemetry_sdk-1.38.0-py3-none-any.whl", hash = "sha256:1c66af6564ecc1553d72d811a01df063ff097cdc82ce188da9951f93b8d10f6b", size = 132349, upload-time = "2025-10-16T08:35:46.995Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.59b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/bc/8b9ad3802cd8ac6583a4eb7de7e5d7db004e89cb7efe7008f9c8a537ee75/opentelemetry_semantic_conventions-0.59b0.tar.gz", hash = "sha256:7a6db3f30d70202d5bf9fa4b69bc866ca6a30437287de6c510fb594878aed6b0", size = 129861, upload-time = "2025-10-16T08:36:03.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/7d/c88d7b15ba8fe5c6b8f93be50fc11795e9fc05386c44afaf6b76fe191f9b/opentelemetry_semantic_conventions-0.59b0-py3-none-any.whl", hash = "sha256:35d3b8833ef97d614136e253c1da9342b4c3c083bbaf29ce31d572a1c3825eed", size = 207954, upload-time = "2025-10-16T08:35:48.054Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -708,6 +1385,102 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, ] +[[package]] +name = "protobuf" +version = "6.33.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/ff/64a6c8f420818bb873713988ca5492cba3a7946be57e027ac63495157d97/protobuf-6.33.0.tar.gz", hash = "sha256:140303d5c8d2037730c548f8c7b93b20bb1dc301be280c378b82b8894589c954", size = 443463, upload-time = "2025-10-15T20:39:52.159Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/ee/52b3fa8feb6db4a833dfea4943e175ce645144532e8a90f72571ad85df4e/protobuf-6.33.0-cp310-abi3-win32.whl", hash = "sha256:d6101ded078042a8f17959eccd9236fb7a9ca20d3b0098bbcb91533a5680d035", size = 425593, upload-time = "2025-10-15T20:39:40.29Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c6/7a465f1825872c55e0341ff4a80198743f73b69ce5d43ab18043699d1d81/protobuf-6.33.0-cp310-abi3-win_amd64.whl", hash = "sha256:9a031d10f703f03768f2743a1c403af050b6ae1f3480e9c140f39c45f81b13ee", size = 436882, upload-time = "2025-10-15T20:39:42.841Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a9/b6eee662a6951b9c3640e8e452ab3e09f117d99fc10baa32d1581a0d4099/protobuf-6.33.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:905b07a65f1a4b72412314082c7dbfae91a9e8b68a0cc1577515f8df58ecf455", size = 427521, upload-time = "2025-10-15T20:39:43.803Z" }, + { url = "https://files.pythonhosted.org/packages/10/35/16d31e0f92c6d2f0e77c2a3ba93185130ea13053dd16200a57434c882f2b/protobuf-6.33.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e0697ece353e6239b90ee43a9231318302ad8353c70e6e45499fa52396debf90", size = 324445, upload-time = "2025-10-15T20:39:44.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/2a981a13e35cda8b75b5585aaffae2eb904f8f351bdd3870769692acbd8a/protobuf-6.33.0-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:e0a1715e4f27355afd9570f3ea369735afc853a6c3951a6afe1f80d8569ad298", size = 339159, upload-time = "2025-10-15T20:39:46.186Z" }, + { url = "https://files.pythonhosted.org/packages/21/51/0b1cbad62074439b867b4e04cc09b93f6699d78fd191bed2bbb44562e077/protobuf-6.33.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:35be49fd3f4fefa4e6e2aacc35e8b837d6703c37a2168a55ac21e9b1bc7559ef", size = 323172, upload-time = "2025-10-15T20:39:47.465Z" }, + { url = "https://files.pythonhosted.org/packages/57/33/fbe61bbe91a656619f107b9dfd84b16e1438766bd62157b8d1c1214491fd/protobuf-6.33.0-cp39-cp39-win32.whl", hash = "sha256:cd33a8e38ea3e39df66e1bbc462b076d6e5ba3a4ebbde58219d777223a7873d3", size = 425690, upload-time = "2025-10-15T20:39:48.909Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e4/ccc4814ad9d12fa404f7e5ce1983a2403644b0ed2588678c762b7a26ed92/protobuf-6.33.0-cp39-cp39-win_amd64.whl", hash = "sha256:c963e86c3655af3a917962c9619e1a6b9670540351d7af9439d06064e3317cc9", size = 436876, upload-time = "2025-10-15T20:39:50.009Z" }, + { url = "https://files.pythonhosted.org/packages/07/d1/0a28c21707807c6aacd5dc9c3704b2aa1effbf37adebd8caeaf68b17a636/protobuf-6.33.0-py3-none-any.whl", hash = "sha256:25c9e1963c6734448ea2d308cfa610e692b801304ba0908d7bfa564ac5132995", size = 170477, upload-time = "2025-10-15T20:39:51.311Z" }, +] + +[[package]] +name = "psycopg" +version = "3.2.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/02/9fdfc018c026df2bcf9c11480c1014f9b90c6d801e5f929408cbfbf94cc0/psycopg-3.2.11.tar.gz", hash = "sha256:398bb484ed44361e041c8f804ed7af3d2fcefbffdace1d905b7446c319321706", size = 160644, upload-time = "2025-10-18T22:48:28.136Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/1b/96ee90ed0007d64936d9bd1bb3108d0af3cf762b4f11dbd73359f0687c3d/psycopg-3.2.11-py3-none-any.whl", hash = "sha256:217231b2b6b72fba88281b94241b2f16043ee67f81def47c52a01b72ff0c086a", size = 206766, upload-time = "2025-10-18T22:43:32.114Z" }, +] + +[package.optional-dependencies] +binary = [ + { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, +] + +[[package]] +name = "psycopg-binary" +version = "3.2.11" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/96/9fe31ef61b311c697a98709a31b875d152e4f67924dd2cb94a4de0396d74/psycopg_binary-3.2.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f72146ad5b69ea177c2707578e5a4a9422b79e50d5a80992dabc5619b0929771", size = 4031016, upload-time = "2025-10-18T22:43:35.867Z" }, + { url = "https://files.pythonhosted.org/packages/55/fe/3ae6be34bfda1ba6dfd4e3b5c1d68bc51d4593399b5a10faaff68937c9a1/psycopg_binary-3.2.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b051aa1e67f0d03ccdb4503d716f22da56229896526f0aa721e5a199baa9e5d4", size = 4090430, upload-time = "2025-10-18T22:43:41.154Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ea/7aa84f6bb64f94bfbe7d494d384a0d2bc66ba66e8607f5e9b515aa6af627/psycopg_binary-3.2.11-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49d76391b225f72dd63fcab87937ccf307ae0f093b5a382eeacf05f19a57c176", size = 4641307, upload-time = "2025-10-18T22:43:45.959Z" }, + { url = "https://files.pythonhosted.org/packages/9a/67/ef12ff8a530230824965668b44ccd58a88dae40511f7bbd125defb7972c4/psycopg_binary-3.2.11-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:58997db1aa48a1119e26c1c2f893d1c92339bd3be5d1f25334f22eaeaeeca90e", size = 4742204, upload-time = "2025-10-18T22:43:50.702Z" }, + { url = "https://files.pythonhosted.org/packages/f7/9c/8f35345fe22a0e5997cbfba0b7e1a58f26b290400cb9a6cb67e72e503331/psycopg_binary-3.2.11-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e3b6328bc2f3ca233f9a5f08d266089b96a534eca9ee4e45cb92d0a8d4629d9c", size = 4425352, upload-time = "2025-10-18T22:43:55.039Z" }, + { url = "https://files.pythonhosted.org/packages/f1/29/f0c585c6b48526f0ecf179e13ea2b6d8fed0dbba8c1a0d61da8ece149b0e/psycopg_binary-3.2.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5bc571786a256a2fa2d8f13b5ecf714020b753bc76c2fa6d308e46751946dc31", size = 3885019, upload-time = "2025-10-18T22:43:59.256Z" }, + { url = "https://files.pythonhosted.org/packages/20/6d/a139e1c7e9840491d9ad3c837264a900ac95ba35e5f83fd5715b1ce7a729/psycopg_binary-3.2.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:766089fdaa8af1b5f7e2ec9fd7ad190c865e226b4fb0e7b1bd8dbcd62b5b923e", size = 3568192, upload-time = "2025-10-18T22:44:03.915Z" }, + { url = "https://files.pythonhosted.org/packages/74/ea/43a2b6fcfa816797dc6d2ac67e9cd09b3a7e4da0a29467a8b5940e7a1312/psycopg_binary-3.2.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5fb27dd9c52ae13cb4de90244207155b694f76a75a816115ead2d573f40e1e36", size = 3609300, upload-time = "2025-10-18T22:44:09.168Z" }, + { url = "https://files.pythonhosted.org/packages/77/d5/c9d46e626528a44b0feb881064e8018107b603ac683a658be3ee9ca00222/psycopg_binary-3.2.11-cp310-cp310-win_amd64.whl", hash = "sha256:3f32b09fba85d9e239229bdc5b6254420c02054f6954fe7fbd1ecf1ca93009ed", size = 2918105, upload-time = "2025-10-18T22:44:14.203Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c4/350473820759d7e599e68bd79c88d32376353ceb0f764db05de8f13ff421/psycopg_binary-3.2.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6688807ed07436c18e9946d01372bc80b9d20b7732cde27de9313e0860910c84", size = 4037740, upload-time = "2025-10-18T22:44:21.344Z" }, + { url = "https://files.pythonhosted.org/packages/50/e0/00bf3e207676bbe6e9f32c0f924f0e5be1efcd1a9fb2fd84d1c3d9958a96/psycopg_binary-3.2.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:478a68d50f34f6203642d245e2046d266c719ab4e593a1bb94c3be5f82e1aee1", size = 4098558, upload-time = "2025-10-18T22:44:26.948Z" }, + { url = "https://files.pythonhosted.org/packages/1e/db/bc1d22fe57b01fa76b02943e1034cb59070bf906e982ebc507d079998b5b/psycopg_binary-3.2.11-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7575ca710277cc3e9257ff803a3e0e3cb7cc1b7851639cb783a7cd55ebfc815", size = 4646689, upload-time = "2025-10-18T22:44:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/e9/6e/b1234e784af5c999ca4bd2e3a8673c58e941926dc4a53b9196d00929f7c9/psycopg_binary-3.2.11-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:110a2036007230416fcc2c17bfe7aaa2c1fa9b6e9d21e2cd551523e3f6489759", size = 4749164, upload-time = "2025-10-18T22:44:39.529Z" }, + { url = "https://files.pythonhosted.org/packages/ee/77/98c2e6c683941e54560ef3449fbc97b7ca31318436576e0c9d92c1dc875d/psycopg_binary-3.2.11-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:31f1d5630afa673c37a6327f8e3efa1f17d4e4e42972643b3478b52275233529", size = 4432473, upload-time = "2025-10-18T22:44:45.323Z" }, + { url = "https://files.pythonhosted.org/packages/94/1d/73e72427152c03f61b75c14642a7187b16be0e03480f7329ab5cf618fdac/psycopg_binary-3.2.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9f12a34bddaeffa7840a61163595ec0d70a9db855896865dcfbb731510014484", size = 3890114, upload-time = "2025-10-18T22:44:49.161Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ed/08a6b135ece52bb4024e19d03a294f002992d2f0c60fccdc35f245801d9c/psycopg_binary-3.2.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:82fe30afbdd66fbdad583b02baad5c15930a3dc8a3756d2ae15fc874e9be8ec8", size = 3571474, upload-time = "2025-10-18T22:44:52.476Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/b0c857cd0718b1a8af86a24e61deeb9643e9e4731f732a9b7cab280b1323/psycopg_binary-3.2.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:592fb928efe0674a7400af914bcf931eb5267d36237925947aaecf63bd9a91aa", size = 3613401, upload-time = "2025-10-18T22:44:56.473Z" }, + { url = "https://files.pythonhosted.org/packages/ec/29/437255bc149b132c63ab0279f8850648cd3ae524667f475b621a2a3d0d5b/psycopg_binary-3.2.11-cp311-cp311-win_amd64.whl", hash = "sha256:20d41bcd9ac289d44ac1f6151594f7883483b4ad14680a63e04b639dc90c3349", size = 2919850, upload-time = "2025-10-18T22:45:00.108Z" }, + { url = "https://files.pythonhosted.org/packages/f9/9e/58945c828b60820e5c192d04f238f1aa49de0fe5f3b9883e277f33c17c0a/psycopg_binary-3.2.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4cae9bdc482e36e825d5102a9f3010e729f33a4ca83fc8a1f439ba16eb61e1f1", size = 4019920, upload-time = "2025-10-18T22:45:05.023Z" }, + { url = "https://files.pythonhosted.org/packages/73/c4/ac7f600ae5d8fb7a89c2712163b642d88739b3bb4c8d0fb3178c084dc521/psycopg_binary-3.2.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:749d23fbfd642a7abfef5fc0f6ca185fa82a2c0f895e6eab42c3f2a5d88f6011", size = 4092123, upload-time = "2025-10-18T22:45:09.763Z" }, + { url = "https://files.pythonhosted.org/packages/39/aa/866c8b2c83490f0d55c4a27d16c0b733744faac442adf181eb59d8d48a3d/psycopg_binary-3.2.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58d8f9f80ae79ba7f2a0509424939236220d7d66a4f8256ae999b882cc58065b", size = 4626894, upload-time = "2025-10-18T22:45:13.367Z" }, + { url = "https://files.pythonhosted.org/packages/17/a8/e7c1eba4ca230d510b76b3f8701321e0c21820953744db67ec7c8fb67537/psycopg_binary-3.2.11-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:eab6959fade522e586b8ec37d3fe337ce10861965edef3292f52e66e36dc375d", size = 4719913, upload-time = "2025-10-18T22:45:19.523Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5f/de0dea38cef6e050ff8e9acd0f7c5d956251fcfece5360973329eb10b84b/psycopg_binary-3.2.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe5e3648e855df4fba1d70c18aef18c9880ea8d123fdfae754c18787c8cb37b3", size = 4411018, upload-time = "2025-10-18T22:45:24.717Z" }, + { url = "https://files.pythonhosted.org/packages/8f/bf/2bbefb24e491f2fa4a7c627d14680429ca33092176eadae88fab4fbce8c6/psycopg_binary-3.2.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:30e2c114d26554ae677088de5d4133cc112344d7a233200fdbf4a2ca5754c7ec", size = 3861940, upload-time = "2025-10-18T22:45:28.624Z" }, + { url = "https://files.pythonhosted.org/packages/67/07/d68f78df7490fcd17eef7f138f96bf3398a961208262498cde7d30266481/psycopg_binary-3.2.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e3f5887019dfb094c60e7026968ca3a964ca16305807ba5e43f9a78483767d5f", size = 3534831, upload-time = "2025-10-18T22:45:32.089Z" }, + { url = "https://files.pythonhosted.org/packages/d0/18/fc5a881ca3d8b40b8e37a396bf14176b8439a7e4b1a29848af325009f955/psycopg_binary-3.2.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9b4b0fc4e774063ae64c92cc57e2b10160150de68c96d71743218159d953869d", size = 3583559, upload-time = "2025-10-18T22:45:36.438Z" }, + { url = "https://files.pythonhosted.org/packages/c0/98/c4418b609ffea80907861ddb01c043af860b179cb8fb41905ad2f0a4f400/psycopg_binary-3.2.11-cp312-cp312-win_amd64.whl", hash = "sha256:9bdc762600fcc8e4ad3224734a4e70cc226207fd8f2de47c36b115efeed01782", size = 2910294, upload-time = "2025-10-18T22:45:40.135Z" }, + { url = "https://files.pythonhosted.org/packages/f2/93/9cea78ed3b279909f0fd6c2badb24b2361b93c875d6a7c921e26f6254044/psycopg_binary-3.2.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47f6cf8a1d02d25238bdb8741ac641ff0ec22b1c6ff6a2acd057d0da5c712842", size = 4017939, upload-time = "2025-10-18T22:45:45.114Z" }, + { url = "https://files.pythonhosted.org/packages/58/86/fc9925f500b2c140c0bb8c1f8fcd04f8c45c76d4852e87baf4c75182de8c/psycopg_binary-3.2.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:91268f04380964a5e767f8102d05f1e23312ddbe848de1a9514b08b3fc57d354", size = 4090150, upload-time = "2025-10-18T22:45:50.214Z" }, + { url = "https://files.pythonhosted.org/packages/4e/10/752b698da1ca9e6c5f15d8798cb637c3615315fd2da17eee4a90cf20ee08/psycopg_binary-3.2.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:199f88a05dd22133eab2deb30348ef7a70c23d706c8e63fdc904234163c63517", size = 4625597, upload-time = "2025-10-18T22:45:54.638Z" }, + { url = "https://files.pythonhosted.org/packages/0a/9f/b578545c3c23484f4e234282d97ab24632a1d3cbfec64209786872e7cc8f/psycopg_binary-3.2.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7b3c5474dbad63bcccb8d14d4d4c7c19f1dc6f8e8c1914cbc771d261cf8eddca", size = 4720326, upload-time = "2025-10-18T22:45:59.266Z" }, + { url = "https://files.pythonhosted.org/packages/43/3b/ba548d3fe65a7d4c96e568c2188e4b665802e3cba41664945ed95d16eae9/psycopg_binary-3.2.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:581358e770a4536e546841b78fd0fe318added4a82443bf22d0bbe3109cf9582", size = 4411647, upload-time = "2025-10-18T22:46:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/26/65/559ab485b198600e7ff70d70786ae5c89d63475ca01d43a7dda0d7c91386/psycopg_binary-3.2.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:54a30f00a51b9043048b3e7ee806ffd31fc5fbd02a20f0e69d21306ff33dc473", size = 3863037, upload-time = "2025-10-18T22:46:08.469Z" }, + { url = "https://files.pythonhosted.org/packages/8c/29/05d0b48c8bef147e8216a36a1263a309a6240dcc09a56f5b8174fa6216d2/psycopg_binary-3.2.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2a438fad4cc081b018431fde0e791b6d50201526edf39522a85164f606c39ddb", size = 3536975, upload-time = "2025-10-18T22:46:12.982Z" }, + { url = "https://files.pythonhosted.org/packages/d4/75/304e133d3ab1a49602616192edb81f603ed574f79966449105f2e200999d/psycopg_binary-3.2.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f5e7415b5d0f58edf2708842c66605092df67f3821161d861b09695fc326c4de", size = 3586213, upload-time = "2025-10-18T22:46:19.523Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/c47cce42fa3c37d439e1400eaa5eeb2ce53dc3abc84d52c8a8a9e544d945/psycopg_binary-3.2.11-cp313-cp313-win_amd64.whl", hash = "sha256:6b9632c42f76d5349e7dd50025cff02688eb760b258e891ad2c6428e7e4917d5", size = 2912997, upload-time = "2025-10-18T22:46:24.978Z" }, + { url = "https://files.pythonhosted.org/packages/85/13/728b4763ef76a688737acebfcb5ab8696b024adc49a69c86081392b0e5ba/psycopg_binary-3.2.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:260738ae222b41dbefd0d84cb2e150a112f90b41688630f57fdac487ab6d6f38", size = 4016962, upload-time = "2025-10-18T22:46:29.207Z" }, + { url = "https://files.pythonhosted.org/packages/9f/0f/6180149621a907c5b60a2fae87d6ee10cc13e8c9f58d8250c310634ced04/psycopg_binary-3.2.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c594c199869099c59c85b9f4423370b6212491fb929e7fcda0da1768761a2c2c", size = 4090614, upload-time = "2025-10-18T22:46:33.073Z" }, + { url = "https://files.pythonhosted.org/packages/f8/97/cce19bdef510b698c9036d5573b941b539ffcaa7602450da559c8a62e0c3/psycopg_binary-3.2.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5768a9e7d393b2edd3a28de5a6d5850d054a016ed711f7044a9072f19f5e50d5", size = 4629749, upload-time = "2025-10-18T22:46:37.415Z" }, + { url = "https://files.pythonhosted.org/packages/93/9d/9bff18989fb2bf05d18c1431dd8bec4a1d90141beb11fc45d3269947ddf3/psycopg_binary-3.2.11-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:27eb6367350b75fef882c40cd6f748bfd976db2f8651f7511956f11efc15154f", size = 4724035, upload-time = "2025-10-18T22:46:42.568Z" }, + { url = "https://files.pythonhosted.org/packages/08/e5/39b930323428596990367b7953197730213d3d9d07bcedcad1d026608178/psycopg_binary-3.2.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa2aa5094dc962967ca0978c035b3ef90329b802501ef12a088d3bac6a55598e", size = 4411419, upload-time = "2025-10-18T22:46:47.745Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9c/97c25438d1e51ddc6a7f67990b4c59f94bc515114ada864804ccee27ef1b/psycopg_binary-3.2.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7744b4ed1f3b76fe37de7e9ef98014482fe74b6d3dfe1026cc4cfb4b4404e74f", size = 3867844, upload-time = "2025-10-18T22:46:53.328Z" }, + { url = "https://files.pythonhosted.org/packages/91/51/8c1e291cf4aa9982666f71a886aa782d990aa16853a42de545a0a9a871ef/psycopg_binary-3.2.11-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5f6f948ff1cd252003ff534d7b50a2b25453b4212b283a7514ff8751bdb68c37", size = 3541539, upload-time = "2025-10-18T22:46:58.993Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/e25edcdfa1111bfc5c95668b7469b5a957b40ce10cc81383688d65564826/psycopg_binary-3.2.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3bd2c8fb1dec6f93383fbaa561591fa3d676e079f9cb9889af17c3020a19715f", size = 3588090, upload-time = "2025-10-18T22:47:04.105Z" }, + { url = "https://files.pythonhosted.org/packages/a3/aa/f8c2f4b4c13d5680a20e5bfcd61f9e154bce26e7a2c70cb0abeade088d61/psycopg_binary-3.2.11-cp314-cp314-win_amd64.whl", hash = "sha256:c45f61202e5691090a697e599997eaffa3ec298209743caa4fd346145acabafe", size = 3006049, upload-time = "2025-10-18T22:47:07.923Z" }, + { url = "https://files.pythonhosted.org/packages/07/df/9b839a689f5bb9993fbfd6a2a07cfbf3e42ac3ae61997adb5283ed691af7/psycopg_binary-3.2.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1db270e6bdbd183e3908cd9bb832506b99e1f2222a2fc2145f980c3ba1c8c30f", size = 4032026, upload-time = "2025-10-18T22:47:48.785Z" }, + { url = "https://files.pythonhosted.org/packages/be/d6/3f17bfa82b372f90725391f3c5e19286dcbbfbc9e972c4523c30d66aeebb/psycopg_binary-3.2.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:00221bfeb9594ca6e01207b032c300fa6f889d918bf0de47f4571c1f9f6e1578", size = 4091477, upload-time = "2025-10-18T22:47:53.512Z" }, + { url = "https://files.pythonhosted.org/packages/0f/cb/7c5f8a6350123e1a95b4ce236f13fb26639a539b7e6543d3d4e319b97226/psycopg_binary-3.2.11-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a3a59d404e1fb8ec47116f66f5adf48a8993a8aac0dad0395a923155fd55ee38", size = 4643392, upload-time = "2025-10-18T22:47:57.803Z" }, + { url = "https://files.pythonhosted.org/packages/ac/69/74e4e92f9f4af3cbadfaa510d6b9e76d7996a8707e89fd61dd9fd5fcd02c/psycopg_binary-3.2.11-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8792e502a16a0b28d9fd23571fe492271a00c4b2b55f6c0b8377e47032758cd3", size = 4742083, upload-time = "2025-10-18T22:48:02.741Z" }, + { url = "https://files.pythonhosted.org/packages/84/34/9868abe7a34b8fe8da6c329342c207e1275117b12fdf681bf2fabf8f1189/psycopg_binary-3.2.11-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d59db908d9baaa057a43dd5aa8352f3e3de4b8c57f295172d5fe521e97d6c39d", size = 4426947, upload-time = "2025-10-18T22:48:08.421Z" }, + { url = "https://files.pythonhosted.org/packages/23/e8/410b75aa7353fe949fa75e2824c9fc5302fd909b2a027edb6c27a5ac97e6/psycopg_binary-3.2.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:566d02a0b85b994e40b4f6276b3423c59e8157f10b73bd2e634f8e0a3dfb1890", size = 3885428, upload-time = "2025-10-18T22:48:13.905Z" }, + { url = "https://files.pythonhosted.org/packages/24/a8/ccf3290d0c67bb44d5e8b87e095ba130dd35d54e2ebd9543e20f48ff4aa7/psycopg_binary-3.2.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:23c77dbbffe8ba679213877f7204f4599bd545b65d2d69982fd685a3fea35b11", size = 3567133, upload-time = "2025-10-18T22:48:18.93Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/8e62eaf2ef429f49619828f447ff31af497f3eeddfbb826701829b10d698/psycopg_binary-3.2.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b2fa94ce40bc4b408149d83a6204fc5e53c3e9d3cd5b749de2e7e9671a049cf7", size = 3610488, upload-time = "2025-10-18T22:48:23.088Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b6/725f1107ad045f466fcacc33fdde21797998bd80913e24de00e8282ad810/psycopg_binary-3.2.11-cp39-cp39-win_amd64.whl", hash = "sha256:81e57d1f00af9b7414c8d00ac77892b3786ddd69a23c27dee47cae8fd3543b07", size = 2919605, upload-time = "2025-10-18T22:48:26.581Z" }, +] + [[package]] name = "ptyprocess" version = "0.7.0" @@ -741,6 +1514,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229, upload-time = "2025-05-22T21:18:06.329Z" }, ] +[package.optional-dependencies] +email = [ + { name = "email-validator", marker = "python_full_version < '3.10'" }, +] + [[package]] name = "pydantic-core" version = "2.33.2" @@ -859,6 +1637,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, ] +[[package]] +name = "pyjwt" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, +] + [[package]] name = "pytest" version = "8.4.0" @@ -903,6 +1690,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload-time = "2025-04-05T14:07:49.641Z" }, ] +[[package]] +name = "pytest-timeout" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" }, +] + [[package]] name = "pytest-xdist" version = "3.8.0" @@ -916,6 +1715,61 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, ] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" }, + { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" }, + { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, + { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, + { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, + { url = "https://files.pythonhosted.org/packages/59/42/b86689aac0cdaee7ae1c58d464b0ff04ca909c19bb6502d4973cdd9f9544/pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b", size = 8760837, upload-time = "2025-07-14T20:12:59.59Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8a/1403d0353f8c5a2f0829d2b1c4becbf9da2f0a4d040886404fc4a5431e4d/pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91", size = 9590187, upload-time = "2025-07-14T20:13:01.419Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/e0e8d802f124772cec9c75430b01a212f86f9de7546bda715e54140d5aeb/pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d", size = 8778162, upload-time = "2025-07-14T20:13:03.544Z" }, +] + [[package]] name = "pywin32-ctypes" version = "0.2.3" @@ -978,6 +1832,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload-time = "2024-08-06T20:33:49.073Z" }, ] +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs", marker = "python_full_version < '3.10'" }, + { name = "rpds-py", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi", marker = "python_full_version < '3.10'" }, + { name = "charset-normalizer", marker = "python_full_version < '3.10'" }, + { name = "idna", marker = "python_full_version < '3.10'" }, + { name = "urllib3", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + [[package]] name = "rich" version = "14.1.0" @@ -992,6 +1875,304 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, ] +[[package]] +name = "rich-toolkit" +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "rich", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/33/1a18839aaa8feef7983590c05c22c9c09d245ada6017d118325bbfcc7651/rich_toolkit-0.15.1.tar.gz", hash = "sha256:6f9630eb29f3843d19d48c3bd5706a086d36d62016687f9d0efa027ddc2dd08a", size = 115322, upload-time = "2025-09-04T09:28:11.789Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/49/42821d55ead7b5a87c8d121edf323cb393d8579f63e933002ade900b784f/rich_toolkit-0.15.1-py3-none-any.whl", hash = "sha256:36a0b1d9a135d26776e4b78f1d5c2655da6e0ef432380b5c6b523c8d8ab97478", size = 29412, upload-time = "2025-09-04T09:28:10.587Z" }, +] + +[[package]] +name = "rignore" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/1a/4e407524cf97ed42a9c77d3cc31b12dd5fb2ce542f174ff7cf78ea0ca293/rignore-0.7.1.tar.gz", hash = "sha256:67bb99d57d0bab0c473261561f98f118f7c9838a06de222338ed8f2b95ed84b4", size = 15437, upload-time = "2025-10-15T20:59:08.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/05/608c1ec2c25195687535926686c40ee90adcf7d443b900846ab37749985f/rignore-0.7.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68bb98485fb05503305bab81a1da9645fd9863192a652f0c3d35ad8ecdac6a1c", size = 885200, upload-time = "2025-10-15T20:58:05.206Z" }, + { url = "https://files.pythonhosted.org/packages/d4/03/92c9a53f8327df33278b4b83189e39dae8350712c1c8e33d009e627ff9d8/rignore-0.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41d35f27e22342d7a30a19887ee22c1f42d7334a7d3ea0051d3ec718034b7988", size = 818078, upload-time = "2025-10-15T20:57:55.935Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4a/7ee24f8b6320cf58e51021aa52e8efaa4ca40cb05a7f9682b9464cdd7665/rignore-0.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4af8c2edc43c5f2f23847b652d154bbb56a11e41af4416ed1cf5f887baa32d91", size = 893771, upload-time = "2025-10-15T20:56:41.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/eb/7b848d2a7cffefe062a0dade23bedcb012ca9e273157af7dfedfa648056c/rignore-0.7.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:075ee67cf1800ed47c4110b30a43fc929ca6e329becb99d35b923b105ecbaa07", size = 867653, upload-time = "2025-10-15T20:56:55.132Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5f/a9ebbbb1e082cd2f57ebf7144154ca218badf5cce182dff497e1a00af55d/rignore-0.7.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51b0fb67c1e6a659d0a61bc6e94c3f988989e466865ce479d67145ee3c441a51", size = 1169351, upload-time = "2025-10-15T20:57:08.752Z" }, + { url = "https://files.pythonhosted.org/packages/51/e4/e9bd1ec204c2f3e420ec101eab13bb85ce42132aa8c7e2fb02b4c73c1c30/rignore-0.7.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2d093f183da133656d75aba7478b33a159df77d79872d9a38d29ac4e900bf32", size = 938011, upload-time = "2025-10-15T20:57:22.074Z" }, + { url = "https://files.pythonhosted.org/packages/f7/46/0afdf2baf996dda4996db9ced50cd9608a90e9ecb58a865310981346d7f0/rignore-0.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70035888dfe63068042f8f807bd5cff11d379e2f026e2bf07758cd645fc5ee38", size = 952426, upload-time = "2025-10-15T20:57:45.771Z" }, + { url = "https://files.pythonhosted.org/packages/cc/49/7efd7478eea778872125d49d71763f6a15291a1cde334e93c6d48bf8d157/rignore-0.7.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b5b0ec651213198c181a369e2f6c99792189033d2958c33c4215308e461f6e59", size = 978020, upload-time = "2025-10-15T20:57:34.723Z" }, + { url = "https://files.pythonhosted.org/packages/63/08/8b6ab31871a3176304843443f0d6b93960ed95b1af4dab4f5cfd57bc3c11/rignore-0.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9caf52e1e3dd06bea4a0e0caa79483eb969e8fbb357d232bba9f653864b479e3", size = 1074465, upload-time = "2025-10-15T20:58:14.926Z" }, + { url = "https://files.pythonhosted.org/packages/31/a5/3c1bdae5b594e5a59742e8fa1510a8aee902defd94d1b3ed46b304aa397e/rignore-0.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:5f4a9a274764bf797f2f3d0e674e6ba0103c4dd7ec8b1c90cef9772a72efd99c", size = 1131263, upload-time = "2025-10-15T20:58:28.093Z" }, + { url = "https://files.pythonhosted.org/packages/25/64/faf7e0017fffa4561d7b4a9ddd9ef8fffa70812fc60ed57d8aed7f6e5e90/rignore-0.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:664c6375ac66fb371a6931a53268a5f8398de2d306765ec2bf594602654f0332", size = 1110989, upload-time = "2025-10-15T20:58:42.02Z" }, + { url = "https://files.pythonhosted.org/packages/77/5d/6b70591a862387076e354ab93ca1b165e8c213fe0d8d59d954515d4b19a8/rignore-0.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d332d15c06eb1831cf851628ecfc152549c9b3e0ee09791f532f459c502d0975", size = 1118385, upload-time = "2025-10-15T20:58:54.981Z" }, + { url = "https://files.pythonhosted.org/packages/81/9f/d83370fbec24777b4a6e03af4d76ea4dce96df017a0c096709e6a83b4639/rignore-0.7.1-cp310-cp310-win32.whl", hash = "sha256:3a2eff764c6792cdf8e12929f8e2d73f02befb16fb7f698f2c7c2f45e15192a8", size = 636478, upload-time = "2025-10-15T20:59:24.698Z" }, + { url = "https://files.pythonhosted.org/packages/14/1e/fa13c93c5e9adb32c033719a9b091eea8324c2c69f0f3d37d2e64e74e9d2/rignore-0.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:747a9b368ffa21939dc704e103e1030bfb38316469239c7b3ad14edee6490f39", size = 717953, upload-time = "2025-10-15T20:59:14.92Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c3/4245eb9b8b3f65a2cbf70050e36870500ab1332490877b1d114dd3d9f337/rignore-0.7.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a328bd98987c11ec6a6243a348296aefd840e93639ca7c65706e12006f863e92", size = 884913, upload-time = "2025-10-15T20:58:07.028Z" }, + { url = "https://files.pythonhosted.org/packages/55/9f/1c6345944e13b0dc39f1e8be3ffdccf9c811d8ca72efef6738ca97ca7f65/rignore-0.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4beefafd3c6cb90d158bd067de446cf91e0fa127523ce9999b573bf9d7910ce1", size = 818113, upload-time = "2025-10-15T20:57:57.37Z" }, + { url = "https://files.pythonhosted.org/packages/0a/77/365f795d4996ebdfd006f20a055e1451ddbec4a4daca30c614036d2956ad/rignore-0.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abdc91baef314475c88423fc3d06509b5d5f73931e5eb7cf7026cf4bccc4c252", size = 893594, upload-time = "2025-10-15T20:56:42.783Z" }, + { url = "https://files.pythonhosted.org/packages/07/8b/44ae937da83c33e560b4cd08c0461fdc49c81dd81d3cb1abc597522508e9/rignore-0.7.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c0ca3a88c60bd4f952eb39fae64f8f1948cc9a21e430f55a20384b982971a98f", size = 867566, upload-time = "2025-10-15T20:56:56.213Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d5/9613b32ea0838ea2bc320912fe147415558c7196300e753af38bff7c70dc/rignore-0.7.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9802c188c8abdac139bdbf73e40b7725ed73c4945c7e861ab6c2fef0e0d74238", size = 1169604, upload-time = "2025-10-15T20:57:09.852Z" }, + { url = "https://files.pythonhosted.org/packages/8d/06/86f4fdfd18b1fc7e5c2780286cdd336777e942d0a2ba0a35ac5df18c706e/rignore-0.7.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fbb82c9d0f2d0ba305fcc6a5260bf38df3660d0a435acdd11e5a8a1940cba19", size = 938187, upload-time = "2025-10-15T20:57:23.233Z" }, + { url = "https://files.pythonhosted.org/packages/c4/04/54118c1d636c21640a91ec05b2784337eec3cf7cc5e37c170e3fc85fa251/rignore-0.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae45784a1639009ef5a0f59955870327206a4d13e5f59e8d5cf1e46b923a99b3", size = 952346, upload-time = "2025-10-15T20:57:46.94Z" }, + { url = "https://files.pythonhosted.org/packages/66/2a/b4312c964662a293949374e726ce22c74da8021d74fdde2fe9101ae9cc00/rignore-0.7.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c86e9e81448afcf558daf281a924fc61e368b9fcbf7b0e364d21ead6cac6b96", size = 978145, upload-time = "2025-10-15T20:57:36.247Z" }, + { url = "https://files.pythonhosted.org/packages/d4/a6/ebdd5448456b73622132acc67b58aa114e5738bc423881081f6d18bc447a/rignore-0.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ae20640392518957be9f99057a7313de49c24ceec9511e3d281e0b7644c0c331", size = 1074545, upload-time = "2025-10-15T20:58:16.134Z" }, + { url = "https://files.pythonhosted.org/packages/9a/15/e53aed04f55b741569588c0f61f4fb8c14512ffdc1d58058878721367dfc/rignore-0.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:97d34db7ae894103bbd3ed6723f295387a9167ca92ec1ae3801ba936813ed5c1", size = 1131060, upload-time = "2025-10-15T20:58:29.327Z" }, + { url = "https://files.pythonhosted.org/packages/8f/92/41ae7cc82a2fdd7774ad9f386e91a5718950eacf5e7c55b620e3b31aca62/rignore-0.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0a700b79b3d4b5d1ed30f28671042649f0dc4826c66ea5abc6cfd9420f3999a", size = 1110893, upload-time = "2025-10-15T20:58:43.292Z" }, + { url = "https://files.pythonhosted.org/packages/be/53/45de7e07bb8893424660d4c616b1247a613dc04c58989fad0a2a6eeb0a55/rignore-0.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:53209d06e6f3db46f568ea9df1da1139ac6216df82abcaa09c654efa02efd62d", size = 1118182, upload-time = "2025-10-15T20:58:56.172Z" }, + { url = "https://files.pythonhosted.org/packages/f4/e5/d274a68dae1542a59fde3ebc406cc45ae479c13bf84cdf535254bc90e254/rignore-0.7.1-cp311-cp311-win32.whl", hash = "sha256:3743bcdecfad058f17572cfc0d49715fed5c626cd9c20c784ae697bfff40250c", size = 636262, upload-time = "2025-10-15T20:59:26.324Z" }, + { url = "https://files.pythonhosted.org/packages/b5/88/5d474a48a5d52a112ed498b1e08d90bb9e690c7c7ddea5751d66e70f5da6/rignore-0.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:aacdd39bb6897444928b7fdac51f6ebedf471c2cdc26d108700fa4447502ec80", size = 718054, upload-time = "2025-10-15T20:59:16.701Z" }, + { url = "https://files.pythonhosted.org/packages/19/f4/849d2ce45003f96806079f8d37515705f629796397a5c7e01b2b7be69695/rignore-0.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:00683be573b0858e78976387d63ecccf7eae84773a85dfc83b6266a9b15f86f3", size = 649673, upload-time = "2025-10-15T20:59:09.331Z" }, + { url = "https://files.pythonhosted.org/packages/35/23/116f02af72512b45d707bf5a9d0c19c0367b112dcd04415e3bca6c23115c/rignore-0.7.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:12038950bc45f28e904b4d03c345fecb19c90236a9fe9ce4e7556712d5fa6243", size = 882111, upload-time = "2025-10-15T20:58:08.254Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/0cf24f911a9ea0ba78b58acc9969709bd94186fc9a1c06aeb75364223eae/rignore-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:926d21c39880bc52d1b0d05c06e97b9c10d011aa8d5ea32bcd2fd05677476a9e", size = 814669, upload-time = "2025-10-15T20:57:58.577Z" }, + { url = "https://files.pythonhosted.org/packages/8f/ee/9e5d50a3ac4a814caea69da7d8aa53fae793bd977763e0bdcd5247ba03d9/rignore-0.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ee1b9c17f93d74f3b79e7c6bf7359981748e7b606ae767d106067c664798f52", size = 893708, upload-time = "2025-10-15T20:56:44.027Z" }, + { url = "https://files.pythonhosted.org/packages/38/9e/3e4d1aa225d0551f54d3185d1295d92a282c249710968aace26f09cbef6c/rignore-0.7.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3eaa6c1a5d4c4da6453b73606d5f505bf98448cf64c86429f5b18e056d3e2a69", size = 867626, upload-time = "2025-10-15T20:56:57.409Z" }, + { url = "https://files.pythonhosted.org/packages/27/cd/cdf6ab4e24ec9af677969409e22f9bd2363d53c3137adca63aaa4aa9deec/rignore-0.7.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c2057d7de9e9284f2b834a9fe71eaba7c01aa46215d0ca89924f465d7572be8", size = 1166969, upload-time = "2025-10-15T20:57:10.962Z" }, + { url = "https://files.pythonhosted.org/packages/7e/64/8829ac6f4658757c9d92ad61a82b1a7f7a0168c5158badedfc37d77c0594/rignore-0.7.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a876989c63731241944190b88e7dde02ff63788e8ce95167e30e22dfb05796b", size = 937957, upload-time = "2025-10-15T20:57:24.336Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9f/190cd40b398e30a700eabdb0b4735ce872eba86c3d198adfa1239c2ee02b/rignore-0.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f37af4f75809456b56b8b41e29934f5be668d3bb069aa09fc102bc15b853c8d5", size = 951906, upload-time = "2025-10-15T20:57:48.026Z" }, + { url = "https://files.pythonhosted.org/packages/16/0b/24fe555b622ce2a26fc3a86988846b2b4f71275ee626411ed743c01cae99/rignore-0.7.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dcd0b5bcdd278135aa735d0adc8a7a7436450e5a59d2bcf6c51b27ba7ce76a3", size = 977971, upload-time = "2025-10-15T20:57:37.7Z" }, + { url = "https://files.pythonhosted.org/packages/85/8b/e2b8c269706c511e8a87d302e6217ab0b8f62db61f22662ad3989af76f03/rignore-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9342d49d273e791ce728d47d3acfd55712e75ce5d130ad1887de9b29c77c464", size = 1074578, upload-time = "2025-10-15T20:58:17.327Z" }, + { url = "https://files.pythonhosted.org/packages/73/e5/93b6221e17735275aab5dd0aee763beb566a19e85ccd4cd63f11f21f80cf/rignore-0.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ba9d70e972a40ee787c7da4f0a77785c22e5ff5ec70b61c682c7c587ff289828", size = 1131031, upload-time = "2025-10-15T20:58:30.82Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2f/2bd7873b8ba12060d381d60ef145b73ed3585d56b4fcfe47948b2c42a6cc/rignore-0.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:504ad4d6b531874f15d95b9ceda13f9057751df3e64dfb6af76262535d70fa57", size = 1109882, upload-time = "2025-10-15T20:58:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a2/aa/e935a4620621b1ba3aa711fef17cf73b2cc61ab8e5d26aacca1a6b208262/rignore-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0be80bd1f44d4eb3dfaa87ef7692a787fca7da9d10d9c8008fca9c82aa3f7491", size = 1117651, upload-time = "2025-10-15T20:58:57.855Z" }, + { url = "https://files.pythonhosted.org/packages/30/5a/2a18b6cd7f8e5bf927ef6c768877b30cc9b330d29081f25571518cb0f288/rignore-0.7.1-cp312-cp312-win32.whl", hash = "sha256:d17c99fb0e996040a44103089d8dbb1867badd66a488fefa2de99537961d0fe9", size = 635258, upload-time = "2025-10-15T20:59:27.588Z" }, + { url = "https://files.pythonhosted.org/packages/01/12/43f209b8fa1f103661fae71800c0cb351b5053b7ca0082719e038f6b3f39/rignore-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:21db6b08b0595225409eeb7fcc224cb6afbc75a4b88ecf3e2e4b3232a1958162", size = 717975, upload-time = "2025-10-15T20:59:18.026Z" }, + { url = "https://files.pythonhosted.org/packages/07/d9/72c9c2e5fda75afc1ea0b3f1ac98cfcf38b92972a4e8d3d226088cf46588/rignore-0.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:05574f4676b906e92e664ad0ff4a664f3e7b55e98d86c358d6dad4a4269f8724", size = 647968, upload-time = "2025-10-15T20:59:10.65Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f8/99145d7ee439db898709b9a7e913d42ed3a6ff679c50a163bae373f07276/rignore-0.7.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:cb6c993b22d7c88eeadc4fed2957be688b6c5f98d4a9b86d3a5057f4a17ea5bd", size = 881743, upload-time = "2025-10-15T20:58:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/fa/db/aea84354518a24578c77d8fec2f42c065520b48ba5bded9d8eca9e46fefd/rignore-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:32da28b0e0434b88134f8d97f22afe6bd1e2a103278a726809e2d8da8426b33f", size = 814397, upload-time = "2025-10-15T20:58:00.071Z" }, + { url = "https://files.pythonhosted.org/packages/12/0b/116afdee4093f0ccd3c4e7b6840d3699ea2a34c1ae6d1dd4d7d9d0adc65b/rignore-0.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:401d52a0a1c5eae342b2c7b4091206e1ce70de54e85c8c8f0ea3309765a62d60", size = 893431, upload-time = "2025-10-15T20:56:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/52/b5/66778c7cbb8e2c6f4ca6f2f59067aa01632b913741c4aa46b163dc4c8f8c/rignore-0.7.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9ffcfbef75656243cfdcdd495b0ea0b71980b76af343b1bf3aed61a78db3f145", size = 867220, upload-time = "2025-10-15T20:56:58.931Z" }, + { url = "https://files.pythonhosted.org/packages/6e/da/bdd6de52941391f0056295c6904c45e1f8667df754b17fe880d0a663d941/rignore-0.7.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0e89efa2ad36a9206ed30219eb1a8783a0722ae8b6d68390ae854e5f5ceab6ff", size = 1169076, upload-time = "2025-10-15T20:57:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/0e/8d/d7d4bfbae28e340a6afe850809a020a31c2364fc0ee8105be4ec0841b20a/rignore-0.7.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f6191d7f52894ee65a879f022329011e31cc41f98739ff184cd3f256a3f0711", size = 937738, upload-time = "2025-10-15T20:57:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/d8/b1/1d3f88aaf3cc6f4e31d1d72eb261eff3418dabd2677c83653b7574e7947a/rignore-0.7.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:873a8e84b4342534b9e283f7c17dc39c295edcdc686dfa395ddca3628316931b", size = 951791, upload-time = "2025-10-15T20:57:49.574Z" }, + { url = "https://files.pythonhosted.org/packages/90/7f/033631f29af972bc4f69e241ab188d21fbc4665ad67879c77bc984009550/rignore-0.7.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65443a6a5efd184d21538816282c78c4787a8a5f73c243ab87cbbb6f313a623d", size = 977580, upload-time = "2025-10-15T20:57:39.063Z" }, + { url = "https://files.pythonhosted.org/packages/c7/38/6f963926b769365a803ec17d448a4fc9c2dbad9c1a1bf73c28088021c2fc/rignore-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d6cafca0b422c0d57ce617fed3831e6639dc151653b98396af919f8eb3ba9e2b", size = 1074486, upload-time = "2025-10-15T20:58:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/74/d2/a1c1e2cd3e43f6433d3ecb8d947e1ed684c261fa2e7b2f6b8827c3bf18d1/rignore-0.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:1f731b018b5b5a93d7b4a0f4e43e5fcbd6cf25e97cec265392f9dd8d10916e5c", size = 1131024, upload-time = "2025-10-15T20:58:32.075Z" }, + { url = "https://files.pythonhosted.org/packages/93/22/b7dd8312aa98211df1f10a6cd2a3005e72cd4ac5c125fd064c7e58394205/rignore-0.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3be78b1ab9fa1c0eac822a69a7799a2261ce06e4d548374093c4c64d796d7d8", size = 1109625, upload-time = "2025-10-15T20:58:46.077Z" }, + { url = "https://files.pythonhosted.org/packages/f7/65/dd31859304bd71ad72f71e2bf5f18e6f0043cc75394ead8c0d752ab580ad/rignore-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d8c3b77ae1a24b09a6d38e07d180f362e47b970c767d2e22417b03d95685cb9d", size = 1117466, upload-time = "2025-10-15T20:58:59.102Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d7/e83241e1b0a6caef1e37586d5b2edb0227478d038675e4e6e1cd748c08ce/rignore-0.7.1-cp313-cp313-win32.whl", hash = "sha256:c01cc8c5d7099d35a7fd00e174948986d4f2cfb6b7fe2923b0b801b1a4741b37", size = 635266, upload-time = "2025-10-15T20:59:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/95/e5/c2ce66a71cfc44010a238a61339cae7469adc17306025796884672784b4c/rignore-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:5dd0de4a7d38a49b9d85f332d129b4ca4a29eef5667d4c7bf503e767cf9e2ec4", size = 718048, upload-time = "2025-10-15T20:59:19.312Z" }, + { url = "https://files.pythonhosted.org/packages/ba/fb/b92aa591e247f6258997163e8b1844c9b799371fbfdfd29533e203df06b9/rignore-0.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:4a4c57b75ec758fb31ad1abab4c77810ea417e9d33bdf2f38cf9e6db556eebcb", size = 647790, upload-time = "2025-10-15T20:59:12.408Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d3/b6c5764d3dcaf47de7f0e408dcb4a1a17d4ce3bb1b0aa9a346e221e3c5a1/rignore-0.7.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb7df83a41213069195436e9c1a433a6df85c089ce4be406d070a4db0ee3897", size = 892938, upload-time = "2025-10-15T20:56:46.559Z" }, + { url = "https://files.pythonhosted.org/packages/48/6a/4d8ae9af9936a061dacda0d8f638cd63571ff93e4eb28e0159db6c4dc009/rignore-0.7.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:30d9c9a93a266d1f384465d626178f49d0da4d1a0cf739f15151cdf2eb500e53", size = 867312, upload-time = "2025-10-15T20:57:00.083Z" }, + { url = "https://files.pythonhosted.org/packages/9b/88/cb243662a0b523b4350db1c7c3adee87004af90e9b26100e84c7e13b93cc/rignore-0.7.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e83c68f557d793b4cc7aac943f3b23631469e1bc5b02e63626d0b008be01cd1", size = 1166871, upload-time = "2025-10-15T20:57:13.618Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0a/da28a3f3e8ab1829180f3a7af5b601b04bab1d833e31a74fee78a2d3f5c3/rignore-0.7.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:682a6efe3f84af4b1100d4c68f0a345f490af74fd9d18346ebf67da9a3b96b08", size = 937964, upload-time = "2025-10-15T20:57:27.054Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2e/f55d0759c6cf48d8fabc62d8924ce58dca81f5c370c0abdcc7cc8176210d/rignore-0.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:736b6aa3e3dfda2b1404b6f9a9d6f67e2a89f184179e9e5b629198df7c22f9c6", size = 1073720, upload-time = "2025-10-15T20:58:20.833Z" }, + { url = "https://files.pythonhosted.org/packages/c3/aa/8698caf5eb1824f8cae08cd3a296bc7f6f46e7bb539a4dd60c6a7a9f5ca2/rignore-0.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eed55292d949e99f29cd4f1ae6ddc2562428a3e74f6f4f6b8658f1d5113ffbd5", size = 1130545, upload-time = "2025-10-15T20:58:33.709Z" }, + { url = "https://files.pythonhosted.org/packages/f5/88/89abacdc122f4a0d069d12ebbd87693253f08f19457b77f030c0c6cba316/rignore-0.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:93ce054754857e37f15fe6768fd28e5450a52c7bbdb00e215100b092281ed123", size = 1108570, upload-time = "2025-10-15T20:58:47.438Z" }, + { url = "https://files.pythonhosted.org/packages/c9/4b/a815624ff1f2420ff29be1ffa2ea5204a69d9a9738fe5a6638fcd1069347/rignore-0.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:447004c774083e4f9cddf0aefcb80b12264f23e28c37918fb709917c2aabd00d", size = 1116940, upload-time = "2025-10-15T20:59:00.581Z" }, + { url = "https://files.pythonhosted.org/packages/43/63/3464fe5855fc37689d7bdd7b4b7ea0d008a8a58738bc0d68b0b5fa6dcf28/rignore-0.7.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:322ac35f2431dd2e80518200e31af1985689dfa7658003ae40012bf3d3e9f0dd", size = 880536, upload-time = "2025-10-15T20:58:11.286Z" }, + { url = "https://files.pythonhosted.org/packages/63/c3/c37469643baeb04c58db2713dc268f582974c71f3936f7d989610b344fca/rignore-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2d38e282e4b917fb6108198564b018f90de57bb6209aadf9ff39434d4709a650", size = 814741, upload-time = "2025-10-15T20:58:01.228Z" }, + { url = "https://files.pythonhosted.org/packages/76/6c/57fa917c7515db3b72a9c3a6377dc806282e6db390ace68cda29bd73774e/rignore-0.7.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89ad7373ec1e7b519a6f07dbcfca38024ba45f5e44df79ee0da4e4c817648a50", size = 951257, upload-time = "2025-10-15T20:57:50.779Z" }, + { url = "https://files.pythonhosted.org/packages/b6/58/b64fb42d6a73937a93c5f060e2720decde4d2b4a7a27fc3b69e69c397358/rignore-0.7.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ff94b215b4fe1d81e45b29dc259145fd8aaf40e7b1057f020890cd12db566e4e", size = 977468, upload-time = "2025-10-15T20:57:40.291Z" }, + { url = "https://files.pythonhosted.org/packages/22/54/5b9e60ad6ea7ef654d2607936be312ce78615e011b3461d4b1d161f031c0/rignore-0.7.1-cp314-cp314-win32.whl", hash = "sha256:f49ecef68b5cb99d1212ebe332cbb2851fb2c93672d3b1d372b0fbf475eeb172", size = 635618, upload-time = "2025-10-15T20:59:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/50/67137617cbe3e53cbf34d21dad49e153f731797e07261f3b00572a49e69d/rignore-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:3f55593d3bbcae3c108d546e8776e51ecb61d1d79bbb02016acf29d136813835", size = 717951, upload-time = "2025-10-15T20:59:20.519Z" }, + { url = "https://files.pythonhosted.org/packages/77/19/dd556e97354ad541b4f7f113e28503865777d6edd940c147f052dc7b8f04/rignore-0.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:60745773b5278fa5f20232fbfb148d74ad9fb27ae8a5097d3cbd5d7cc922d7f7", size = 647796, upload-time = "2025-10-15T20:59:13.724Z" }, + { url = "https://files.pythonhosted.org/packages/f7/9c/e9c05e3c3a5d7cd35d1299f8b84621874581efb3cd62d9f795bd8783f86f/rignore-0.7.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d869a60c209bb25dfe8265c66c24e8681544e9e6fa5671c28b8eb393d59e99eb", size = 886859, upload-time = "2025-10-15T20:58:13.775Z" }, + { url = "https://files.pythonhosted.org/packages/2a/f2/4b5ce1207c20e2a9c9a793f0e566cfaaf762090699ae15ed30c3c7cfcca5/rignore-0.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1af4e8724a316d621c7e7a7af6be59c1da1eb774f80dbb699f52bb4c108631a1", size = 819968, upload-time = "2025-10-15T20:58:03.655Z" }, + { url = "https://files.pythonhosted.org/packages/00/0c/1cfc3f0511459d7548a5d806ac7bd250c3d955808495630c45af30a7d1af/rignore-0.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3255760eea7637d2519de0156c497d6641b5cc4388f137a896b5dd2e1a94411e", size = 895232, upload-time = "2025-10-15T20:56:49.45Z" }, + { url = "https://files.pythonhosted.org/packages/57/3a/12c602b5d42f8959226c201441bfb3fb7b62c553e443e046b816eb808476/rignore-0.7.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff9dabd29f1fb897d9d08477631a7b9ebb7a86473c22faefc39dc5b1b394a1a6", size = 868602, upload-time = "2025-10-15T20:57:02.353Z" }, + { url = "https://files.pythonhosted.org/packages/3a/6a/0710028aaadd2d433238d4b2edfe1bf4ad766efb5a4586490d13619027e2/rignore-0.7.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f95b80cda30e638ddfc0d119ca11593b056bbd3af3608c198cd8e3345fba82c", size = 1170967, upload-time = "2025-10-15T20:57:16.945Z" }, + { url = "https://files.pythonhosted.org/packages/49/cc/4c1d169d3933b31e79fa252c11508ffac615008f18d5704e60ef89b69acf/rignore-0.7.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0eed55b89e03344792bb431556ee2475b94f2577338dcfa3693968cdce0e61e", size = 939438, upload-time = "2025-10-15T20:57:29.801Z" }, + { url = "https://files.pythonhosted.org/packages/09/1c/fc7fce903c1ef03bba88c6d0d0ea7620e63044c1fe8b99743c3ba016d83a/rignore-0.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10bfefb03543277a802d6f24d569db4755428a42befb8e5a00a828bb818f5f15", size = 953387, upload-time = "2025-10-15T20:57:53.603Z" }, + { url = "https://files.pythonhosted.org/packages/99/2c/cd99310fbe02e3f6aed1b2f4729f11a45d81a037f87f4a9c4a8b54da6b0f/rignore-0.7.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e52029b0e53c8783a1d5e1d71caf63b627b34ac469abc6f48280387749683ab7", size = 980037, upload-time = "2025-10-15T20:57:43.405Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6f/2c3cd47da3de7b8791a8f03be91e63ea4baef0333b14bc9fe7b85593dea2/rignore-0.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:93ef7cd9c969acd60d983ab2d7388330d51954fe4dfca3145974ea7faa5a7f94", size = 1076064, upload-time = "2025-10-15T20:58:23.159Z" }, + { url = "https://files.pythonhosted.org/packages/66/26/64c17439e4b8ba265ec27d5d47ae7746693de0999d1ac83307db445bbb92/rignore-0.7.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:f63f6c6dbcaf98d041df165e83865d68276fe908ebacca527eed2ae9c6d574fe", size = 1132314, upload-time = "2025-10-15T20:58:36.218Z" }, + { url = "https://files.pythonhosted.org/packages/3a/00/85538e85e64f4ac39c0e3bbbb808a8bdeef8d45d28ee810583db48f5c08a/rignore-0.7.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d0f535be97902f9f615767d894d2234585b641df2dc3a411fb60d08b7c389583", size = 1112510, upload-time = "2025-10-15T20:58:49.953Z" }, + { url = "https://files.pythonhosted.org/packages/f7/2c/e717ed33b0cc74b5b38587376227c3689ab6525734b5dac42915d7d2be08/rignore-0.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0bbd3abfdda70ebcc37e1fe896f0b3da62dfe45a230c41244ce23eba7f07c057", size = 1119949, upload-time = "2025-10-15T20:59:03.364Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/59e367836f478c0b35b1334ccd794a96e07f4b6fd67b1807f5ab5d0051ed/rignore-0.7.1-cp39-cp39-win32.whl", hash = "sha256:d4b8498693b413e5293268932b14cb6f1a3082146ff4b481876b8fd083ef7824", size = 638253, upload-time = "2025-10-15T20:59:32.789Z" }, + { url = "https://files.pythonhosted.org/packages/52/f4/b35bb61f55fcf792e2e21d5e126d0511fc1f9c7316f17ae96c5c2799fa76/rignore-0.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:4cb1181850ba61515f2b6a0b0c5813b908c50559215a1efcba2af3fb4c82fac0", size = 719449, upload-time = "2025-10-15T20:59:23.397Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a9/1e0dd0c8b47c4acd9d5bc514f51fa06fbbd29bd81327a100956ed983693a/rignore-0.7.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b6e161194932c6813113397668495284fd716b7a959530777d3e77e4420ba14", size = 896300, upload-time = "2025-10-15T20:56:50.989Z" }, + { url = "https://files.pythonhosted.org/packages/72/92/13e0a4d7e0b03a9374f40e5739e6a2835458cb3b858a722a17f0399cc745/rignore-0.7.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dc36104026d5f4e3c45772d161ba1dd542d7ad9ae9af944a3df4a553690b60b0", size = 868854, upload-time = "2025-10-15T20:57:04.559Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c2/8a49671d4a5d05fd9d991b4946f84abe94b713a16aeda0e5fcac83322481/rignore-0.7.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f14bdc68b1bcc80fd7a75d32c3b611df142cfbc293fdd946133fb8a9697e5141", size = 1169511, upload-time = "2025-10-15T20:57:18.153Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/df08f56beb0cdf268d3f2774b388680d955e88fc5a8e511d29b542460df0/rignore-0.7.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0c237746acc628b4eabc9f6eaada3672be8f48f6318fda4a79fde45376f7a14", size = 940119, upload-time = "2025-10-15T20:57:30.962Z" }, + { url = "https://files.pythonhosted.org/packages/84/fd/2b8ef9b57922a40084916687966dc7289e4063dd5d21e93ea03f7907e011/rignore-0.7.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:0b8eefb5529ea91d751ab5ec62e1c23fe73ce1222c4d8754be5f525a475279cf", size = 1076839, upload-time = "2025-10-15T20:58:24.323Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9f/a50854f6d8a6997c690e157d2f049b55a84f258cc42f3ccac5e44048aa67/rignore-0.7.1-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:af628176f77acb54694afeba190b2e08c737f80448a375329faa9fe70888c85b", size = 1132575, upload-time = "2025-10-15T20:58:38.074Z" }, + { url = "https://files.pythonhosted.org/packages/cc/ac/fb8c98f1c0ed4bf3578d3fc71b22e0391df10113b7ea347938a37aa8496f/rignore-0.7.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:cbc934258fac3dc5c6c9f19d5af4bf8fbb1763d427d3ae25cbcdbc7844bc1951", size = 1113370, upload-time = "2025-10-15T20:58:51.147Z" }, + { url = "https://files.pythonhosted.org/packages/2f/0a/821eda3e6f0be6b45870f7d51cab4e2381e9f90f19c0ef28515edd8e5637/rignore-0.7.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2b84de34f24b4550a477e00c3af3f17836fadd531a4af44d810be24a7561fa56", size = 1121094, upload-time = "2025-10-15T20:59:04.636Z" }, + { url = "https://files.pythonhosted.org/packages/3b/98/033cc15bf1d2d8c1d03a1afc7b6d4b334d43fc1ddae063aae69444f07ae1/rignore-0.7.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1732baaff2b527a56037d57e5a3c3cc645d3f876dc850f57f2199233ab7636ae", size = 894739, upload-time = "2025-10-15T20:56:52.202Z" }, + { url = "https://files.pythonhosted.org/packages/a0/89/e3ea9230734f646089a70971971d71a170b175b83072d7041a12f5baef08/rignore-0.7.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9b81d18b7a9e7bae8af323daaf540e03433527b4648c56a21137cdc76f9b8b2f", size = 868279, upload-time = "2025-10-15T20:57:05.582Z" }, + { url = "https://files.pythonhosted.org/packages/3f/21/6b326cc8dca54ded71f1071acc19f6e1c32e334d40f290183efab1e8a824/rignore-0.7.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7fddac52045545d21ac6ae22dfb8a377bad67f6307251b1cb8aa5a5ec8a7a266", size = 1168216, upload-time = "2025-10-15T20:57:19.442Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/4ae5342971574f6aadb15a99b814dc3440712c143b70dbeb9080e683ffdd/rignore-0.7.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a26a8f4be7ddd02ff406a0b87632b02a270be8a2a792fc1038c1148069d931c1", size = 939474, upload-time = "2025-10-15T20:57:32.13Z" }, + { url = "https://files.pythonhosted.org/packages/e2/41/e8a55e06fe66f7bfe32b04b3f7b3055a64d37b223a8021c6e49e77a41316/rignore-0.7.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81dd8fb0356c8826862783b8bf3f404cf0f049927414522dacf2fe72850bc175", size = 952963, upload-time = "2025-10-15T20:57:54.753Z" }, + { url = "https://files.pythonhosted.org/packages/e8/4b/210d8ca1eda34e41431bc01243f615654b33d4455c3524427bc189617bed/rignore-0.7.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:23e256405db72326f6ea1b298744377e297d4e4a386b66ca922155cc85570d08", size = 979249, upload-time = "2025-10-15T20:57:44.598Z" }, + { url = "https://files.pythonhosted.org/packages/ce/82/f7925c476f4e1a2331d5ecf6a5a8c45498c2c8e6a9633e1f4becd36cbe56/rignore-0.7.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:87fe72181575e748261973b5e0aa30fc9094aaa5eba913312c80ce3515dc5011", size = 1075458, upload-time = "2025-10-15T20:58:25.534Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a7/c25e9c6e77e1ea88ef39614e008a53de7f3eaff00d7ffb8547120de50117/rignore-0.7.1-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:54d47cf63226c12b56f0d6b3b3c50ee8e945776bf8146895dc9d6b28f31c1d70", size = 1132091, upload-time = "2025-10-15T20:58:39.337Z" }, + { url = "https://files.pythonhosted.org/packages/97/99/794a2d69535a76d029b0630c2b0444edf6b7f860e3aba5b1db98e751733c/rignore-0.7.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:e572eb7ba06d70ac295c6d2d9455491d30d1d553926b1dd3801cd2ca8f1e14de", size = 1111766, upload-time = "2025-10-15T20:58:52.398Z" }, + { url = "https://files.pythonhosted.org/packages/65/73/abf94b0697d8ca7aa953dacc2378bdaffb9f20b95316f5af07fcf9c9bb0b/rignore-0.7.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dd87a68eee7aefc0d51d1a69dc8448b2ab1de8666da0bd6013e87b4a2ae71852", size = 1119460, upload-time = "2025-10-15T20:59:06.066Z" }, + { url = "https://files.pythonhosted.org/packages/41/dc/86733efa0050b2524ab231f9806fae68028c480c525daaefa959deab1c72/rignore-0.7.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:164d1b222c3dd950229c79b5b4569d037493bb0bb61bd0ab6d88bef44f6a6a4b", size = 896330, upload-time = "2025-10-15T20:56:53.973Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c8/2edb5b0e7d9544811fece89bc9d608ebcd417e4fb9bff0967899c70f2640/rignore-0.7.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9cc35babe3738efdead1976aadeb78718705a202e8c09b31382d10831ef3f33", size = 869553, upload-time = "2025-10-15T20:57:07.276Z" }, + { url = "https://files.pythonhosted.org/packages/af/c0/432477588b49b5f832e6c47dfbf0aea5b2b4bc800f6fa0e301aff09aa376/rignore-0.7.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb0135149f357517947e0d3a47af841155b6d763ecf53cd6420cb769bb923682", size = 1171372, upload-time = "2025-10-15T20:57:20.69Z" }, + { url = "https://files.pythonhosted.org/packages/4d/81/6c0e0214e427a04ab8bc8fa14213d75826183d85b7af389d21e9e2df39b8/rignore-0.7.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ead594bcb4ac4ace3015ce308cc48d54667190f6162970d970354d00b2dc4b4", size = 940106, upload-time = "2025-10-15T20:57:33.265Z" }, + { url = "https://files.pythonhosted.org/packages/6c/55/4f419680652817540aacfd5bbd59dee69c2fe9e10b052afc326d1167e36c/rignore-0.7.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d9e8ce64f1e2a0e5528f963386f70f3b2e02a38799c824378d1ae15ca0d92bdc", size = 1076786, upload-time = "2025-10-15T20:58:26.819Z" }, + { url = "https://files.pythonhosted.org/packages/e6/03/1eebb9778996848b4661aa26dbc04ade34304a34e4b01311495004f319ea/rignore-0.7.1-pp39-pypy39_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:067fe8783e7be20ab7e32579161e6ef806a34621ebaac1f8db1d11d57b1ef701", size = 1133355, upload-time = "2025-10-15T20:58:40.786Z" }, + { url = "https://files.pythonhosted.org/packages/0a/16/00d53ec03cc65bb7746b7e9654ff08d73e893f2d44c945be06ce56b5961a/rignore-0.7.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:43e705e11a1c99218f42c4279950b3e8adb0e47d6b70cbc5dc319d16a11f24df", size = 1113385, upload-time = "2025-10-15T20:58:53.738Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b4/e20488572aceee879918f43923b77a6c107f344656f191d96f3f9584d2d5/rignore-0.7.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:f8b5481be7913e53ed72924d72420eeff20c4c0ee4f946dc1c48fb893376cf08", size = 1120639, upload-time = "2025-10-15T20:59:07.35Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.27.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/dd/2c0cbe774744272b0ae725f44032c77bdcab6e8bcf544bffa3b6e70c8dba/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", size = 27479, upload-time = "2025-08-27T12:16:36.024Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/ed/3aef893e2dd30e77e35d20d4ddb45ca459db59cead748cad9796ad479411/rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef", size = 371606, upload-time = "2025-08-27T12:12:25.189Z" }, + { url = "https://files.pythonhosted.org/packages/6d/82/9818b443e5d3eb4c83c3994561387f116aae9833b35c484474769c4a8faf/rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be", size = 353452, upload-time = "2025-08-27T12:12:27.433Z" }, + { url = "https://files.pythonhosted.org/packages/99/c7/d2a110ffaaa397fc6793a83c7bd3545d9ab22658b7cdff05a24a4535cc45/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61", size = 381519, upload-time = "2025-08-27T12:12:28.719Z" }, + { url = "https://files.pythonhosted.org/packages/5a/bc/e89581d1f9d1be7d0247eaef602566869fdc0d084008ba139e27e775366c/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb", size = 394424, upload-time = "2025-08-27T12:12:30.207Z" }, + { url = "https://files.pythonhosted.org/packages/ac/2e/36a6861f797530e74bb6ed53495f8741f1ef95939eed01d761e73d559067/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657", size = 523467, upload-time = "2025-08-27T12:12:31.808Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/c1bc2be32564fa499f988f0a5c6505c2f4746ef96e58e4d7de5cf923d77e/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013", size = 402660, upload-time = "2025-08-27T12:12:33.444Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ec/ef8bf895f0628dd0a59e54d81caed6891663cb9c54a0f4bb7da918cb88cf/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a", size = 384062, upload-time = "2025-08-27T12:12:34.857Z" }, + { url = "https://files.pythonhosted.org/packages/69/f7/f47ff154be8d9a5e691c083a920bba89cef88d5247c241c10b9898f595a1/rpds_py-0.27.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1", size = 401289, upload-time = "2025-08-27T12:12:36.085Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d9/ca410363efd0615814ae579f6829cafb39225cd63e5ea5ed1404cb345293/rpds_py-0.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10", size = 417718, upload-time = "2025-08-27T12:12:37.401Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a0/8cb5c2ff38340f221cc067cc093d1270e10658ba4e8d263df923daa18e86/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808", size = 558333, upload-time = "2025-08-27T12:12:38.672Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8c/1b0de79177c5d5103843774ce12b84caa7164dfc6cd66378768d37db11bf/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8", size = 589127, upload-time = "2025-08-27T12:12:41.48Z" }, + { url = "https://files.pythonhosted.org/packages/c8/5e/26abb098d5e01266b0f3a2488d299d19ccc26849735d9d2b95c39397e945/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9", size = 554899, upload-time = "2025-08-27T12:12:42.925Z" }, + { url = "https://files.pythonhosted.org/packages/de/41/905cc90ced13550db017f8f20c6d8e8470066c5738ba480d7ba63e3d136b/rpds_py-0.27.1-cp310-cp310-win32.whl", hash = "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4", size = 217450, upload-time = "2025-08-27T12:12:44.813Z" }, + { url = "https://files.pythonhosted.org/packages/75/3d/6bef47b0e253616ccdf67c283e25f2d16e18ccddd38f92af81d5a3420206/rpds_py-0.27.1-cp310-cp310-win_amd64.whl", hash = "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1", size = 228447, upload-time = "2025-08-27T12:12:46.204Z" }, + { url = "https://files.pythonhosted.org/packages/b5/c1/7907329fbef97cbd49db6f7303893bd1dd5a4a3eae415839ffdfb0762cae/rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881", size = 371063, upload-time = "2025-08-27T12:12:47.856Z" }, + { url = "https://files.pythonhosted.org/packages/11/94/2aab4bc86228bcf7c48760990273653a4900de89c7537ffe1b0d6097ed39/rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5", size = 353210, upload-time = "2025-08-27T12:12:49.187Z" }, + { url = "https://files.pythonhosted.org/packages/3a/57/f5eb3ecf434342f4f1a46009530e93fd201a0b5b83379034ebdb1d7c1a58/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e", size = 381636, upload-time = "2025-08-27T12:12:50.492Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f4/ef95c5945e2ceb5119571b184dd5a1cc4b8541bbdf67461998cfeac9cb1e/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c", size = 394341, upload-time = "2025-08-27T12:12:52.024Z" }, + { url = "https://files.pythonhosted.org/packages/5a/7e/4bd610754bf492d398b61725eb9598ddd5eb86b07d7d9483dbcd810e20bc/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195", size = 523428, upload-time = "2025-08-27T12:12:53.779Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e5/059b9f65a8c9149361a8b75094864ab83b94718344db511fd6117936ed2a/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52", size = 402923, upload-time = "2025-08-27T12:12:55.15Z" }, + { url = "https://files.pythonhosted.org/packages/f5/48/64cabb7daced2968dd08e8a1b7988bf358d7bd5bcd5dc89a652f4668543c/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed", size = 384094, upload-time = "2025-08-27T12:12:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e1/dc9094d6ff566bff87add8a510c89b9e158ad2ecd97ee26e677da29a9e1b/rpds_py-0.27.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a", size = 401093, upload-time = "2025-08-27T12:12:58.985Z" }, + { url = "https://files.pythonhosted.org/packages/37/8e/ac8577e3ecdd5593e283d46907d7011618994e1d7ab992711ae0f78b9937/rpds_py-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde", size = 417969, upload-time = "2025-08-27T12:13:00.367Z" }, + { url = "https://files.pythonhosted.org/packages/66/6d/87507430a8f74a93556fe55c6485ba9c259949a853ce407b1e23fea5ba31/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21", size = 558302, upload-time = "2025-08-27T12:13:01.737Z" }, + { url = "https://files.pythonhosted.org/packages/3a/bb/1db4781ce1dda3eecc735e3152659a27b90a02ca62bfeea17aee45cc0fbc/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9", size = 589259, upload-time = "2025-08-27T12:13:03.127Z" }, + { url = "https://files.pythonhosted.org/packages/7b/0e/ae1c8943d11a814d01b482e1f8da903f88047a962dff9bbdadf3bd6e6fd1/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948", size = 554983, upload-time = "2025-08-27T12:13:04.516Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d5/0b2a55415931db4f112bdab072443ff76131b5ac4f4dc98d10d2d357eb03/rpds_py-0.27.1-cp311-cp311-win32.whl", hash = "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39", size = 217154, upload-time = "2025-08-27T12:13:06.278Z" }, + { url = "https://files.pythonhosted.org/packages/24/75/3b7ffe0d50dc86a6a964af0d1cc3a4a2cdf437cb7b099a4747bbb96d1819/rpds_py-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15", size = 228627, upload-time = "2025-08-27T12:13:07.625Z" }, + { url = "https://files.pythonhosted.org/packages/8d/3f/4fd04c32abc02c710f09a72a30c9a55ea3cc154ef8099078fd50a0596f8e/rpds_py-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746", size = 220998, upload-time = "2025-08-27T12:13:08.972Z" }, + { url = "https://files.pythonhosted.org/packages/bd/fe/38de28dee5df58b8198c743fe2bea0c785c6d40941b9950bac4cdb71a014/rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90", size = 361887, upload-time = "2025-08-27T12:13:10.233Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/4b6c7eedc7dd90986bf0fab6ea2a091ec11c01b15f8ba0a14d3f80450468/rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5", size = 345795, upload-time = "2025-08-27T12:13:11.65Z" }, + { url = "https://files.pythonhosted.org/packages/6f/0e/e650e1b81922847a09cca820237b0edee69416a01268b7754d506ade11ad/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e", size = 385121, upload-time = "2025-08-27T12:13:13.008Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ea/b306067a712988e2bff00dcc7c8f31d26c29b6d5931b461aa4b60a013e33/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881", size = 398976, upload-time = "2025-08-27T12:13:14.368Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0a/26dc43c8840cb8fe239fe12dbc8d8de40f2365e838f3d395835dde72f0e5/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec", size = 525953, upload-time = "2025-08-27T12:13:15.774Z" }, + { url = "https://files.pythonhosted.org/packages/22/14/c85e8127b573aaf3a0cbd7fbb8c9c99e735a4a02180c84da2a463b766e9e/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb", size = 407915, upload-time = "2025-08-27T12:13:17.379Z" }, + { url = "https://files.pythonhosted.org/packages/ed/7b/8f4fee9ba1fb5ec856eb22d725a4efa3deb47f769597c809e03578b0f9d9/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5", size = 386883, upload-time = "2025-08-27T12:13:18.704Z" }, + { url = "https://files.pythonhosted.org/packages/86/47/28fa6d60f8b74fcdceba81b272f8d9836ac0340570f68f5df6b41838547b/rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a", size = 405699, upload-time = "2025-08-27T12:13:20.089Z" }, + { url = "https://files.pythonhosted.org/packages/d0/fd/c5987b5e054548df56953a21fe2ebed51fc1ec7c8f24fd41c067b68c4a0a/rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444", size = 423713, upload-time = "2025-08-27T12:13:21.436Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ba/3c4978b54a73ed19a7d74531be37a8bcc542d917c770e14d372b8daea186/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a", size = 562324, upload-time = "2025-08-27T12:13:22.789Z" }, + { url = "https://files.pythonhosted.org/packages/b5/6c/6943a91768fec16db09a42b08644b960cff540c66aab89b74be6d4a144ba/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1", size = 593646, upload-time = "2025-08-27T12:13:24.122Z" }, + { url = "https://files.pythonhosted.org/packages/11/73/9d7a8f4be5f4396f011a6bb7a19fe26303a0dac9064462f5651ced2f572f/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998", size = 558137, upload-time = "2025-08-27T12:13:25.557Z" }, + { url = "https://files.pythonhosted.org/packages/6e/96/6772cbfa0e2485bcceef8071de7821f81aeac8bb45fbfd5542a3e8108165/rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39", size = 221343, upload-time = "2025-08-27T12:13:26.967Z" }, + { url = "https://files.pythonhosted.org/packages/67/b6/c82f0faa9af1c6a64669f73a17ee0eeef25aff30bb9a1c318509efe45d84/rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594", size = 232497, upload-time = "2025-08-27T12:13:28.326Z" }, + { url = "https://files.pythonhosted.org/packages/e1/96/2817b44bd2ed11aebacc9251da03689d56109b9aba5e311297b6902136e2/rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502", size = 222790, upload-time = "2025-08-27T12:13:29.71Z" }, + { url = "https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b", size = 361741, upload-time = "2025-08-27T12:13:31.039Z" }, + { url = "https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf", size = 345574, upload-time = "2025-08-27T12:13:32.902Z" }, + { url = "https://files.pythonhosted.org/packages/20/42/ee2b2ca114294cd9847d0ef9c26d2b0851b2e7e00bf14cc4c0b581df0fc3/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83", size = 385051, upload-time = "2025-08-27T12:13:34.228Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e8/1e430fe311e4799e02e2d1af7c765f024e95e17d651612425b226705f910/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf", size = 398395, upload-time = "2025-08-27T12:13:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/82/95/9dc227d441ff2670651c27a739acb2535ccaf8b351a88d78c088965e5996/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2", size = 524334, upload-time = "2025-08-27T12:13:37.562Z" }, + { url = "https://files.pythonhosted.org/packages/87/01/a670c232f401d9ad461d9a332aa4080cd3cb1d1df18213dbd0d2a6a7ab51/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0", size = 407691, upload-time = "2025-08-27T12:13:38.94Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418", size = 386868, upload-time = "2025-08-27T12:13:40.192Z" }, + { url = "https://files.pythonhosted.org/packages/3b/03/8c897fb8b5347ff6c1cc31239b9611c5bf79d78c984430887a353e1409a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d", size = 405469, upload-time = "2025-08-27T12:13:41.496Z" }, + { url = "https://files.pythonhosted.org/packages/da/07/88c60edc2df74850d496d78a1fdcdc7b54360a7f610a4d50008309d41b94/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274", size = 422125, upload-time = "2025-08-27T12:13:42.802Z" }, + { url = "https://files.pythonhosted.org/packages/6b/86/5f4c707603e41b05f191a749984f390dabcbc467cf833769b47bf14ba04f/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd", size = 562341, upload-time = "2025-08-27T12:13:44.472Z" }, + { url = "https://files.pythonhosted.org/packages/b2/92/3c0cb2492094e3cd9baf9e49bbb7befeceb584ea0c1a8b5939dca4da12e5/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2", size = 592511, upload-time = "2025-08-27T12:13:45.898Z" }, + { url = "https://files.pythonhosted.org/packages/10/bb/82e64fbb0047c46a168faa28d0d45a7851cd0582f850b966811d30f67ad8/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002", size = 557736, upload-time = "2025-08-27T12:13:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/00/95/3c863973d409210da7fb41958172c6b7dbe7fc34e04d3cc1f10bb85e979f/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3", size = 221462, upload-time = "2025-08-27T12:13:48.742Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83", size = 232034, upload-time = "2025-08-27T12:13:50.11Z" }, + { url = "https://files.pythonhosted.org/packages/c7/78/3958f3f018c01923823f1e47f1cc338e398814b92d83cd278364446fac66/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d", size = 222392, upload-time = "2025-08-27T12:13:52.587Z" }, + { url = "https://files.pythonhosted.org/packages/01/76/1cdf1f91aed5c3a7bf2eba1f1c4e4d6f57832d73003919a20118870ea659/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228", size = 358355, upload-time = "2025-08-27T12:13:54.012Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6f/bf142541229374287604caf3bb2a4ae17f0a580798fd72d3b009b532db4e/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92", size = 342138, upload-time = "2025-08-27T12:13:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/1a/77/355b1c041d6be40886c44ff5e798b4e2769e497b790f0f7fd1e78d17e9a8/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2", size = 380247, upload-time = "2025-08-27T12:13:57.683Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a4/d9cef5c3946ea271ce2243c51481971cd6e34f21925af2783dd17b26e815/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723", size = 390699, upload-time = "2025-08-27T12:13:59.137Z" }, + { url = "https://files.pythonhosted.org/packages/3a/06/005106a7b8c6c1a7e91b73169e49870f4af5256119d34a361ae5240a0c1d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802", size = 521852, upload-time = "2025-08-27T12:14:00.583Z" }, + { url = "https://files.pythonhosted.org/packages/e5/3e/50fb1dac0948e17a02eb05c24510a8fe12d5ce8561c6b7b7d1339ab7ab9c/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f", size = 402582, upload-time = "2025-08-27T12:14:02.034Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b0/f4e224090dc5b0ec15f31a02d746ab24101dd430847c4d99123798661bfc/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2", size = 384126, upload-time = "2025-08-27T12:14:03.437Z" }, + { url = "https://files.pythonhosted.org/packages/54/77/ac339d5f82b6afff1df8f0fe0d2145cc827992cb5f8eeb90fc9f31ef7a63/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21", size = 399486, upload-time = "2025-08-27T12:14:05.443Z" }, + { url = "https://files.pythonhosted.org/packages/d6/29/3e1c255eee6ac358c056a57d6d6869baa00a62fa32eea5ee0632039c50a3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef", size = 414832, upload-time = "2025-08-27T12:14:06.902Z" }, + { url = "https://files.pythonhosted.org/packages/3f/db/6d498b844342deb3fa1d030598db93937a9964fcf5cb4da4feb5f17be34b/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081", size = 557249, upload-time = "2025-08-27T12:14:08.37Z" }, + { url = "https://files.pythonhosted.org/packages/60/f3/690dd38e2310b6f68858a331399b4d6dbb9132c3e8ef8b4333b96caf403d/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd", size = 587356, upload-time = "2025-08-27T12:14:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/86/e3/84507781cccd0145f35b1dc32c72675200c5ce8d5b30f813e49424ef68fc/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7", size = 555300, upload-time = "2025-08-27T12:14:11.783Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ee/375469849e6b429b3516206b4580a79e9ef3eb12920ddbd4492b56eaacbe/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688", size = 216714, upload-time = "2025-08-27T12:14:13.629Z" }, + { url = "https://files.pythonhosted.org/packages/21/87/3fc94e47c9bd0742660e84706c311a860dcae4374cf4a03c477e23ce605a/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797", size = 228943, upload-time = "2025-08-27T12:14:14.937Z" }, + { url = "https://files.pythonhosted.org/packages/70/36/b6e6066520a07cf029d385de869729a895917b411e777ab1cde878100a1d/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334", size = 362472, upload-time = "2025-08-27T12:14:16.333Z" }, + { url = "https://files.pythonhosted.org/packages/af/07/b4646032e0dcec0df9c73a3bd52f63bc6c5f9cda992f06bd0e73fe3fbebd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33", size = 345676, upload-time = "2025-08-27T12:14:17.764Z" }, + { url = "https://files.pythonhosted.org/packages/b0/16/2f1003ee5d0af4bcb13c0cf894957984c32a6751ed7206db2aee7379a55e/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a", size = 385313, upload-time = "2025-08-27T12:14:19.829Z" }, + { url = "https://files.pythonhosted.org/packages/05/cd/7eb6dd7b232e7f2654d03fa07f1414d7dfc980e82ba71e40a7c46fd95484/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b", size = 399080, upload-time = "2025-08-27T12:14:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/20/51/5829afd5000ec1cb60f304711f02572d619040aa3ec033d8226817d1e571/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7", size = 523868, upload-time = "2025-08-27T12:14:23.485Z" }, + { url = "https://files.pythonhosted.org/packages/05/2c/30eebca20d5db95720ab4d2faec1b5e4c1025c473f703738c371241476a2/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136", size = 408750, upload-time = "2025-08-27T12:14:24.924Z" }, + { url = "https://files.pythonhosted.org/packages/90/1a/cdb5083f043597c4d4276eae4e4c70c55ab5accec078da8611f24575a367/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff", size = 387688, upload-time = "2025-08-27T12:14:27.537Z" }, + { url = "https://files.pythonhosted.org/packages/7c/92/cf786a15320e173f945d205ab31585cc43969743bb1a48b6888f7a2b0a2d/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9", size = 407225, upload-time = "2025-08-27T12:14:28.981Z" }, + { url = "https://files.pythonhosted.org/packages/33/5c/85ee16df5b65063ef26017bef33096557a4c83fbe56218ac7cd8c235f16d/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60", size = 423361, upload-time = "2025-08-27T12:14:30.469Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8e/1c2741307fcabd1a334ecf008e92c4f47bb6f848712cf15c923becfe82bb/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e", size = 562493, upload-time = "2025-08-27T12:14:31.987Z" }, + { url = "https://files.pythonhosted.org/packages/04/03/5159321baae9b2222442a70c1f988cbbd66b9be0675dd3936461269be360/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212", size = 592623, upload-time = "2025-08-27T12:14:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/ff/39/c09fd1ad28b85bc1d4554a8710233c9f4cefd03d7717a1b8fbfd171d1167/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675", size = 558800, upload-time = "2025-08-27T12:14:35.436Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d6/99228e6bbcf4baa764b18258f519a9035131d91b538d4e0e294313462a98/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3", size = 221943, upload-time = "2025-08-27T12:14:36.898Z" }, + { url = "https://files.pythonhosted.org/packages/be/07/c802bc6b8e95be83b79bdf23d1aa61d68324cb1006e245d6c58e959e314d/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456", size = 233739, upload-time = "2025-08-27T12:14:38.386Z" }, + { url = "https://files.pythonhosted.org/packages/c8/89/3e1b1c16d4c2d547c5717377a8df99aee8099ff050f87c45cb4d5fa70891/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3", size = 223120, upload-time = "2025-08-27T12:14:39.82Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/dc7931dc2fa4a6e46b2a4fa744a9fe5c548efd70e0ba74f40b39fa4a8c10/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2", size = 358944, upload-time = "2025-08-27T12:14:41.199Z" }, + { url = "https://files.pythonhosted.org/packages/e6/22/4af76ac4e9f336bfb1a5f240d18a33c6b2fcaadb7472ac7680576512b49a/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4", size = 342283, upload-time = "2025-08-27T12:14:42.699Z" }, + { url = "https://files.pythonhosted.org/packages/1c/15/2a7c619b3c2272ea9feb9ade67a45c40b3eeb500d503ad4c28c395dc51b4/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e", size = 380320, upload-time = "2025-08-27T12:14:44.157Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7d/4c6d243ba4a3057e994bb5bedd01b5c963c12fe38dde707a52acdb3849e7/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817", size = 391760, upload-time = "2025-08-27T12:14:45.845Z" }, + { url = "https://files.pythonhosted.org/packages/b4/71/b19401a909b83bcd67f90221330bc1ef11bc486fe4e04c24388d28a618ae/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec", size = 522476, upload-time = "2025-08-27T12:14:47.364Z" }, + { url = "https://files.pythonhosted.org/packages/e4/44/1a3b9715c0455d2e2f0f6df5ee6d6f5afdc423d0773a8a682ed2b43c566c/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a", size = 403418, upload-time = "2025-08-27T12:14:49.991Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4b/fb6c4f14984eb56673bc868a66536f53417ddb13ed44b391998100a06a96/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8", size = 384771, upload-time = "2025-08-27T12:14:52.159Z" }, + { url = "https://files.pythonhosted.org/packages/c0/56/d5265d2d28b7420d7b4d4d85cad8ef891760f5135102e60d5c970b976e41/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48", size = 400022, upload-time = "2025-08-27T12:14:53.859Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e9/9f5fc70164a569bdd6ed9046486c3568d6926e3a49bdefeeccfb18655875/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb", size = 416787, upload-time = "2025-08-27T12:14:55.673Z" }, + { url = "https://files.pythonhosted.org/packages/d4/64/56dd03430ba491db943a81dcdef115a985aac5f44f565cd39a00c766d45c/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734", size = 557538, upload-time = "2025-08-27T12:14:57.245Z" }, + { url = "https://files.pythonhosted.org/packages/3f/36/92cc885a3129993b1d963a2a42ecf64e6a8e129d2c7cc980dbeba84e55fb/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb", size = 588512, upload-time = "2025-08-27T12:14:58.728Z" }, + { url = "https://files.pythonhosted.org/packages/dd/10/6b283707780a81919f71625351182b4f98932ac89a09023cb61865136244/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0", size = 555813, upload-time = "2025-08-27T12:15:00.334Z" }, + { url = "https://files.pythonhosted.org/packages/04/2e/30b5ea18c01379da6272a92825dd7e53dc9d15c88a19e97932d35d430ef7/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a", size = 217385, upload-time = "2025-08-27T12:15:01.937Z" }, + { url = "https://files.pythonhosted.org/packages/32/7d/97119da51cb1dd3f2f3c0805f155a3aa4a95fa44fe7d78ae15e69edf4f34/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", size = 230097, upload-time = "2025-08-27T12:15:03.961Z" }, + { url = "https://files.pythonhosted.org/packages/7f/6c/252e83e1ce7583c81f26d1d884b2074d40a13977e1b6c9c50bbf9a7f1f5a/rpds_py-0.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c918c65ec2e42c2a78d19f18c553d77319119bf43aa9e2edf7fb78d624355527", size = 372140, upload-time = "2025-08-27T12:15:05.441Z" }, + { url = "https://files.pythonhosted.org/packages/9d/71/949c195d927c5aeb0d0629d329a20de43a64c423a6aa53836290609ef7ec/rpds_py-0.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1fea2b1a922c47c51fd07d656324531adc787e415c8b116530a1d29c0516c62d", size = 354086, upload-time = "2025-08-27T12:15:07.404Z" }, + { url = "https://files.pythonhosted.org/packages/9f/02/e43e332ad8ce4f6c4342d151a471a7f2900ed1d76901da62eb3762663a71/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbf94c58e8e0cd6b6f38d8de67acae41b3a515c26169366ab58bdca4a6883bb8", size = 382117, upload-time = "2025-08-27T12:15:09.275Z" }, + { url = "https://files.pythonhosted.org/packages/d0/05/b0fdeb5b577197ad72812bbdfb72f9a08fa1e64539cc3940b1b781cd3596/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2a8fed130ce946d5c585eddc7c8eeef0051f58ac80a8ee43bd17835c144c2cc", size = 394520, upload-time = "2025-08-27T12:15:10.727Z" }, + { url = "https://files.pythonhosted.org/packages/67/1f/4cfef98b2349a7585181e99294fa2a13f0af06902048a5d70f431a66d0b9/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:037a2361db72ee98d829bc2c5b7cc55598ae0a5e0ec1823a56ea99374cfd73c1", size = 522657, upload-time = "2025-08-27T12:15:12.613Z" }, + { url = "https://files.pythonhosted.org/packages/44/55/ccf37ddc4c6dce7437b335088b5ca18da864b334890e2fe9aa6ddc3f79a9/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5281ed1cc1d49882f9997981c88df1a22e140ab41df19071222f7e5fc4e72125", size = 402967, upload-time = "2025-08-27T12:15:14.113Z" }, + { url = "https://files.pythonhosted.org/packages/74/e5/5903f92e41e293b07707d5bf00ef39a0eb2af7190aff4beaf581a6591510/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fd50659a069c15eef8aa3d64bbef0d69fd27bb4a50c9ab4f17f83a16cbf8905", size = 384372, upload-time = "2025-08-27T12:15:15.842Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e3/fbb409e18aeefc01e49f5922ac63d2d914328430e295c12183ce56ebf76b/rpds_py-0.27.1-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:c4b676c4ae3921649a15d28ed10025548e9b561ded473aa413af749503c6737e", size = 401264, upload-time = "2025-08-27T12:15:17.388Z" }, + { url = "https://files.pythonhosted.org/packages/55/79/529ad07794e05cb0f38e2f965fc5bb20853d523976719400acecc447ec9d/rpds_py-0.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:079bc583a26db831a985c5257797b2b5d3affb0386e7ff886256762f82113b5e", size = 418691, upload-time = "2025-08-27T12:15:19.144Z" }, + { url = "https://files.pythonhosted.org/packages/33/39/6554a7fd6d9906fda2521c6d52f5d723dca123529fb719a5b5e074c15e01/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4e44099bd522cba71a2c6b97f68e19f40e7d85399de899d66cdb67b32d7cb786", size = 558989, upload-time = "2025-08-27T12:15:21.087Z" }, + { url = "https://files.pythonhosted.org/packages/19/b2/76fa15173b6f9f445e5ef15120871b945fb8dd9044b6b8c7abe87e938416/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e202e6d4188e53c6661af813b46c37ca2c45e497fc558bacc1a7630ec2695aec", size = 589835, upload-time = "2025-08-27T12:15:22.696Z" }, + { url = "https://files.pythonhosted.org/packages/ee/9e/5560a4b39bab780405bed8a88ee85b30178061d189558a86003548dea045/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f41f814b8eaa48768d1bb551591f6ba45f87ac76899453e8ccd41dba1289b04b", size = 555227, upload-time = "2025-08-27T12:15:24.278Z" }, + { url = "https://files.pythonhosted.org/packages/52/d7/cd9c36215111aa65724c132bf709c6f35175973e90b32115dedc4ced09cb/rpds_py-0.27.1-cp39-cp39-win32.whl", hash = "sha256:9e71f5a087ead99563c11fdaceee83ee982fd39cf67601f4fd66cb386336ee52", size = 217899, upload-time = "2025-08-27T12:15:25.926Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e0/d75ab7b4dd8ba777f6b365adbdfc7614bbfe7c5f05703031dfa4b61c3d6c/rpds_py-0.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:71108900c9c3c8590697244b9519017a400d9ba26a36c48381b3f64743a44aab", size = 228725, upload-time = "2025-08-27T12:15:27.398Z" }, + { url = "https://files.pythonhosted.org/packages/d5/63/b7cc415c345625d5e62f694ea356c58fb964861409008118f1245f8c3347/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf", size = 371360, upload-time = "2025-08-27T12:15:29.218Z" }, + { url = "https://files.pythonhosted.org/packages/e5/8c/12e1b24b560cf378b8ffbdb9dc73abd529e1adcfcf82727dfd29c4a7b88d/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3", size = 353933, upload-time = "2025-08-27T12:15:30.837Z" }, + { url = "https://files.pythonhosted.org/packages/9b/85/1bb2210c1f7a1b99e91fea486b9f0f894aa5da3a5ec7097cbad7dec6d40f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636", size = 382962, upload-time = "2025-08-27T12:15:32.348Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c9/a839b9f219cf80ed65f27a7f5ddbb2809c1b85c966020ae2dff490e0b18e/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8", size = 394412, upload-time = "2025-08-27T12:15:33.839Z" }, + { url = "https://files.pythonhosted.org/packages/02/2d/b1d7f928b0b1f4fc2e0133e8051d199b01d7384875adc63b6ddadf3de7e5/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc", size = 523972, upload-time = "2025-08-27T12:15:35.377Z" }, + { url = "https://files.pythonhosted.org/packages/a9/af/2cbf56edd2d07716df1aec8a726b3159deb47cb5c27e1e42b71d705a7c2f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8", size = 403273, upload-time = "2025-08-27T12:15:37.051Z" }, + { url = "https://files.pythonhosted.org/packages/c0/93/425e32200158d44ff01da5d9612c3b6711fe69f606f06e3895511f17473b/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc", size = 385278, upload-time = "2025-08-27T12:15:38.571Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1a/1a04a915ecd0551bfa9e77b7672d1937b4b72a0fc204a17deef76001cfb2/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71", size = 402084, upload-time = "2025-08-27T12:15:40.529Z" }, + { url = "https://files.pythonhosted.org/packages/51/f7/66585c0fe5714368b62951d2513b684e5215beaceab2c6629549ddb15036/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad", size = 419041, upload-time = "2025-08-27T12:15:42.191Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7e/83a508f6b8e219bba2d4af077c35ba0e0cdd35a751a3be6a7cba5a55ad71/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab", size = 560084, upload-time = "2025-08-27T12:15:43.839Z" }, + { url = "https://files.pythonhosted.org/packages/66/66/bb945683b958a1b19eb0fe715594630d0f36396ebdef4d9b89c2fa09aa56/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059", size = 590115, upload-time = "2025-08-27T12:15:46.647Z" }, + { url = "https://files.pythonhosted.org/packages/12/00/ccfaafaf7db7e7adace915e5c2f2c2410e16402561801e9c7f96683002d3/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b", size = 556561, upload-time = "2025-08-27T12:15:48.219Z" }, + { url = "https://files.pythonhosted.org/packages/e1/b7/92b6ed9aad103bfe1c45df98453dfae40969eef2cb6c6239c58d7e96f1b3/rpds_py-0.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819", size = 229125, upload-time = "2025-08-27T12:15:49.956Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ed/e1fba02de17f4f76318b834425257c8ea297e415e12c68b4361f63e8ae92/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df", size = 371402, upload-time = "2025-08-27T12:15:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/af/7c/e16b959b316048b55585a697e94add55a4ae0d984434d279ea83442e460d/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3", size = 354084, upload-time = "2025-08-27T12:15:53.219Z" }, + { url = "https://files.pythonhosted.org/packages/de/c1/ade645f55de76799fdd08682d51ae6724cb46f318573f18be49b1e040428/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9", size = 383090, upload-time = "2025-08-27T12:15:55.158Z" }, + { url = "https://files.pythonhosted.org/packages/1f/27/89070ca9b856e52960da1472efcb6c20ba27cfe902f4f23ed095b9cfc61d/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc", size = 394519, upload-time = "2025-08-27T12:15:57.238Z" }, + { url = "https://files.pythonhosted.org/packages/b3/28/be120586874ef906aa5aeeae95ae8df4184bc757e5b6bd1c729ccff45ed5/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4", size = 523817, upload-time = "2025-08-27T12:15:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/70cc197bc11cfcde02a86f36ac1eed15c56667c2ebddbdb76a47e90306da/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66", size = 403240, upload-time = "2025-08-27T12:16:00.923Z" }, + { url = "https://files.pythonhosted.org/packages/cf/35/46936cca449f7f518f2f4996e0e8344db4b57e2081e752441154089d2a5f/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e", size = 385194, upload-time = "2025-08-27T12:16:02.802Z" }, + { url = "https://files.pythonhosted.org/packages/e1/62/29c0d3e5125c3270b51415af7cbff1ec587379c84f55a5761cc9efa8cd06/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c", size = 402086, upload-time = "2025-08-27T12:16:04.806Z" }, + { url = "https://files.pythonhosted.org/packages/8f/66/03e1087679227785474466fdd04157fb793b3b76e3fcf01cbf4c693c1949/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf", size = 419272, upload-time = "2025-08-27T12:16:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/6a/24/e3e72d265121e00b063aef3e3501e5b2473cf1b23511d56e529531acf01e/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf", size = 560003, upload-time = "2025-08-27T12:16:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/26/ca/f5a344c534214cc2d41118c0699fffbdc2c1bc7046f2a2b9609765ab9c92/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6", size = 590482, upload-time = "2025-08-27T12:16:10.137Z" }, + { url = "https://files.pythonhosted.org/packages/ce/08/4349bdd5c64d9d193c360aa9db89adeee6f6682ab8825dca0a3f535f434f/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a", size = 556523, upload-time = "2025-08-27T12:16:12.188Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ea/5463cd5048a7a2fcdae308b6e96432802132c141bfb9420260142632a0f1/rpds_py-0.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa8933159edc50be265ed22b401125c9eebff3171f570258854dbce3ecd55475", size = 371778, upload-time = "2025-08-27T12:16:13.851Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c8/f38c099db07f5114029c1467649d308543906933eebbc226d4527a5f4693/rpds_py-0.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a50431bf02583e21bf273c71b89d710e7a710ad5e39c725b14e685610555926f", size = 354394, upload-time = "2025-08-27T12:16:15.609Z" }, + { url = "https://files.pythonhosted.org/packages/7d/79/b76f97704d9dd8ddbd76fed4c4048153a847c5d6003afe20a6b5c3339065/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78af06ddc7fe5cc0e967085a9115accee665fb912c22a3f54bad70cc65b05fe6", size = 382348, upload-time = "2025-08-27T12:16:17.251Z" }, + { url = "https://files.pythonhosted.org/packages/8a/3f/ef23d3c1be1b837b648a3016d5bbe7cfe711422ad110b4081c0a90ef5a53/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70d0738ef8fee13c003b100c2fbd667ec4f133468109b3472d249231108283a3", size = 394159, upload-time = "2025-08-27T12:16:19.251Z" }, + { url = "https://files.pythonhosted.org/packages/74/8a/9e62693af1a34fd28b1a190d463d12407bd7cf561748cb4745845d9548d3/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2f6fd8a1cea5bbe599b6e78a6e5ee08db434fc8ffea51ff201c8765679698b3", size = 522775, upload-time = "2025-08-27T12:16:20.929Z" }, + { url = "https://files.pythonhosted.org/packages/36/0d/8d5bb122bf7a60976b54c5c99a739a3819f49f02d69df3ea2ca2aff47d5c/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8177002868d1426305bb5de1e138161c2ec9eb2d939be38291d7c431c4712df8", size = 402633, upload-time = "2025-08-27T12:16:22.548Z" }, + { url = "https://files.pythonhosted.org/packages/0f/0e/237948c1f425e23e0cf5a566d702652a6e55c6f8fbd332a1792eb7043daf/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:008b839781d6c9bf3b6a8984d1d8e56f0ec46dc56df61fd669c49b58ae800400", size = 384867, upload-time = "2025-08-27T12:16:24.29Z" }, + { url = "https://files.pythonhosted.org/packages/d6/0a/da0813efcd998d260cbe876d97f55b0f469ada8ba9cbc47490a132554540/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:a55b9132bb1ade6c734ddd2759c8dc132aa63687d259e725221f106b83a0e485", size = 401791, upload-time = "2025-08-27T12:16:25.954Z" }, + { url = "https://files.pythonhosted.org/packages/51/78/c6c9e8a8aaca416a6f0d1b6b4a6ee35b88fe2c5401d02235d0a056eceed2/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a46fdec0083a26415f11d5f236b79fa1291c32aaa4a17684d82f7017a1f818b1", size = 419525, upload-time = "2025-08-27T12:16:27.659Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/5af37e1d71487cf6d56dd1420dc7e0c2732c1b6ff612aa7a88374061c0a8/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8a63b640a7845f2bdd232eb0d0a4a2dd939bcdd6c57e6bb134526487f3160ec5", size = 559255, upload-time = "2025-08-27T12:16:29.343Z" }, + { url = "https://files.pythonhosted.org/packages/40/7f/8b7b136069ef7ac3960eda25d832639bdb163018a34c960ed042dd1707c8/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7e32721e5d4922deaaf963469d795d5bde6093207c52fec719bd22e5d1bedbc4", size = 590384, upload-time = "2025-08-27T12:16:31.005Z" }, + { url = "https://files.pythonhosted.org/packages/d8/06/c316d3f6ff03f43ccb0eba7de61376f8ec4ea850067dddfafe98274ae13c/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2c426b99a068601b5f4623573df7a7c3d72e87533a2dd2253353a03e7502566c", size = 555959, upload-time = "2025-08-27T12:16:32.73Z" }, + { url = "https://files.pythonhosted.org/packages/60/94/384cf54c430b9dac742bbd2ec26c23feb78ded0d43d6d78563a281aec017/rpds_py-0.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4fc9b7fe29478824361ead6e14e4f5aed570d477e06088826537e202d25fe859", size = 228784, upload-time = "2025-08-27T12:16:34.428Z" }, +] + [[package]] name = "secretstorage" version = "3.3.3" @@ -1005,6 +2186,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99", size = 15221, upload-time = "2022-08-13T16:22:44.457Z" }, ] +[[package]] +name = "sentry-sdk" +version = "2.42.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi", marker = "python_full_version < '3.10'" }, + { name = "urllib3", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/04/ec8c1dd9250847303d98516e917978cb1c7083024770d86d657d2ccb5a70/sentry_sdk-2.42.1.tar.gz", hash = "sha256:8598cc6edcfe74cb8074ba6a7c15338cdee93d63d3eb9b9943b4b568354ad5b6", size = 354839, upload-time = "2025-10-20T12:38:40.45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/cb/c21b96ff379923310b4fb2c06e8d560d801e24aeb300faa72a04776868fc/sentry_sdk-2.42.1-py2.py3-none-any.whl", hash = "sha256:f8716b50c927d3beb41bc88439dc6bcd872237b596df5b14613e2ade104aee02", size = 380952, upload-time = "2025-10-20T12:38:38.88Z" }, +] + [[package]] name = "shellingham" version = "1.5.4" @@ -1014,6 +2208,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, ] +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -1023,6 +2226,59 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] +[[package]] +name = "sqlalchemy" +version = "2.0.44" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/f2/840d7b9496825333f532d2e3976b8eadbf52034178aac53630d09fe6e1ef/sqlalchemy-2.0.44.tar.gz", hash = "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22", size = 9819830, upload-time = "2025-10-10T14:39:12.935Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/a7/e9ccfa7eecaf34c6f57d8cb0bb7cbdeeff27017cc0f5d0ca90fdde7a7c0d/sqlalchemy-2.0.44-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c77f3080674fc529b1bd99489378c7f63fcb4ba7f8322b79732e0258f0ea3ce", size = 2137282, upload-time = "2025-10-10T15:36:10.965Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e1/50bc121885bdf10833a4f65ecbe9fe229a3215f4d65a58da8a181734cae3/sqlalchemy-2.0.44-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c26ef74ba842d61635b0152763d057c8d48215d5be9bb8b7604116a059e9985", size = 2127322, upload-time = "2025-10-10T15:36:12.428Z" }, + { url = "https://files.pythonhosted.org/packages/46/f2/a8573b7230a3ce5ee4b961a2d510d71b43872513647398e595b744344664/sqlalchemy-2.0.44-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4a172b31785e2f00780eccab00bc240ccdbfdb8345f1e6063175b3ff12ad1b0", size = 3214772, upload-time = "2025-10-10T15:34:15.09Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d8/c63d8adb6a7edaf8dcb6f75a2b1e9f8577960a1e489606859c4d73e7d32b/sqlalchemy-2.0.44-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9480c0740aabd8cb29c329b422fb65358049840b34aba0adf63162371d2a96e", size = 3214434, upload-time = "2025-10-10T15:47:00.473Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a6/243d277a4b54fae74d4797957a7320a5c210c293487f931cbe036debb697/sqlalchemy-2.0.44-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:17835885016b9e4d0135720160db3095dc78c583e7b902b6be799fb21035e749", size = 3155365, upload-time = "2025-10-10T15:34:17.932Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f8/6a39516ddd75429fd4ee5a0d72e4c80639fab329b2467c75f363c2ed9751/sqlalchemy-2.0.44-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cbe4f85f50c656d753890f39468fcd8190c5f08282caf19219f684225bfd5fd2", size = 3178910, upload-time = "2025-10-10T15:47:02.346Z" }, + { url = "https://files.pythonhosted.org/packages/43/f0/118355d4ad3c39d9a2f5ee4c7304a9665b3571482777357fa9920cd7a6b4/sqlalchemy-2.0.44-cp310-cp310-win32.whl", hash = "sha256:2fcc4901a86ed81dc76703f3b93ff881e08761c63263c46991081fd7f034b165", size = 2105624, upload-time = "2025-10-10T15:38:15.552Z" }, + { url = "https://files.pythonhosted.org/packages/61/83/6ae5f9466f8aa5d0dcebfff8c9c33b98b27ce23292df3b990454b3d434fd/sqlalchemy-2.0.44-cp310-cp310-win_amd64.whl", hash = "sha256:9919e77403a483ab81e3423151e8ffc9dd992c20d2603bf17e4a8161111e55f5", size = 2129240, upload-time = "2025-10-10T15:38:17.175Z" }, + { url = "https://files.pythonhosted.org/packages/e3/81/15d7c161c9ddf0900b076b55345872ed04ff1ed6a0666e5e94ab44b0163c/sqlalchemy-2.0.44-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fe3917059c7ab2ee3f35e77757062b1bea10a0b6ca633c58391e3f3c6c488dd", size = 2140517, upload-time = "2025-10-10T15:36:15.64Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d5/4abd13b245c7d91bdf131d4916fd9e96a584dac74215f8b5bc945206a974/sqlalchemy-2.0.44-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:de4387a354ff230bc979b46b2207af841dc8bf29847b6c7dbe60af186d97aefa", size = 2130738, upload-time = "2025-10-10T15:36:16.91Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3c/8418969879c26522019c1025171cefbb2a8586b6789ea13254ac602986c0/sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3678a0fb72c8a6a29422b2732fe423db3ce119c34421b5f9955873eb9b62c1e", size = 3304145, upload-time = "2025-10-10T15:34:19.569Z" }, + { url = "https://files.pythonhosted.org/packages/94/2d/fdb9246d9d32518bda5d90f4b65030b9bf403a935cfe4c36a474846517cb/sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cf6872a23601672d61a68f390e44703442639a12ee9dd5a88bbce52a695e46e", size = 3304511, upload-time = "2025-10-10T15:47:05.088Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fb/40f2ad1da97d5c83f6c1269664678293d3fe28e90ad17a1093b735420549/sqlalchemy-2.0.44-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:329aa42d1be9929603f406186630135be1e7a42569540577ba2c69952b7cf399", size = 3235161, upload-time = "2025-10-10T15:34:21.193Z" }, + { url = "https://files.pythonhosted.org/packages/95/cb/7cf4078b46752dca917d18cf31910d4eff6076e5b513c2d66100c4293d83/sqlalchemy-2.0.44-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:70e03833faca7166e6a9927fbee7c27e6ecde436774cd0b24bbcc96353bce06b", size = 3261426, upload-time = "2025-10-10T15:47:07.196Z" }, + { url = "https://files.pythonhosted.org/packages/f8/3b/55c09b285cb2d55bdfa711e778bdffdd0dc3ffa052b0af41f1c5d6e582fa/sqlalchemy-2.0.44-cp311-cp311-win32.whl", hash = "sha256:253e2f29843fb303eca6b2fc645aca91fa7aa0aa70b38b6950da92d44ff267f3", size = 2105392, upload-time = "2025-10-10T15:38:20.051Z" }, + { url = "https://files.pythonhosted.org/packages/c7/23/907193c2f4d680aedbfbdf7bf24c13925e3c7c292e813326c1b84a0b878e/sqlalchemy-2.0.44-cp311-cp311-win_amd64.whl", hash = "sha256:7a8694107eb4308a13b425ca8c0e67112f8134c846b6e1f722698708741215d5", size = 2130293, upload-time = "2025-10-10T15:38:21.601Z" }, + { url = "https://files.pythonhosted.org/packages/62/c4/59c7c9b068e6813c898b771204aad36683c96318ed12d4233e1b18762164/sqlalchemy-2.0.44-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72fea91746b5890f9e5e0997f16cbf3d53550580d76355ba2d998311b17b2250", size = 2139675, upload-time = "2025-10-10T16:03:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ae/eeb0920537a6f9c5a3708e4a5fc55af25900216bdb4847ec29cfddf3bf3a/sqlalchemy-2.0.44-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:585c0c852a891450edbb1eaca8648408a3cc125f18cf433941fa6babcc359e29", size = 2127726, upload-time = "2025-10-10T16:03:35.934Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d5/2ebbabe0379418eda8041c06b0b551f213576bfe4c2f09d77c06c07c8cc5/sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b94843a102efa9ac68a7a30cd46df3ff1ed9c658100d30a725d10d9c60a2f44", size = 3327603, upload-time = "2025-10-10T15:35:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/5aa65852dadc24b7d8ae75b7efb8d19303ed6ac93482e60c44a585930ea5/sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:119dc41e7a7defcefc57189cfa0e61b1bf9c228211aba432b53fb71ef367fda1", size = 3337842, upload-time = "2025-10-10T15:43:45.431Z" }, + { url = "https://files.pythonhosted.org/packages/41/92/648f1afd3f20b71e880ca797a960f638d39d243e233a7082c93093c22378/sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0765e318ee9179b3718c4fd7ba35c434f4dd20332fbc6857a5e8df17719c24d7", size = 3264558, upload-time = "2025-10-10T15:35:29.93Z" }, + { url = "https://files.pythonhosted.org/packages/40/cf/e27d7ee61a10f74b17740918e23cbc5bc62011b48282170dc4c66da8ec0f/sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2e7b5b079055e02d06a4308d0481658e4f06bc7ef211567edc8f7d5dce52018d", size = 3301570, upload-time = "2025-10-10T15:43:48.407Z" }, + { url = "https://files.pythonhosted.org/packages/3b/3d/3116a9a7b63e780fb402799b6da227435be878b6846b192f076d2f838654/sqlalchemy-2.0.44-cp312-cp312-win32.whl", hash = "sha256:846541e58b9a81cce7dee8329f352c318de25aa2f2bbe1e31587eb1f057448b4", size = 2103447, upload-time = "2025-10-10T15:03:21.678Z" }, + { url = "https://files.pythonhosted.org/packages/25/83/24690e9dfc241e6ab062df82cc0df7f4231c79ba98b273fa496fb3dd78ed/sqlalchemy-2.0.44-cp312-cp312-win_amd64.whl", hash = "sha256:7cbcb47fd66ab294703e1644f78971f6f2f1126424d2b300678f419aa73c7b6e", size = 2130912, upload-time = "2025-10-10T15:03:24.656Z" }, + { url = "https://files.pythonhosted.org/packages/45/d3/c67077a2249fdb455246e6853166360054c331db4613cda3e31ab1cadbef/sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1", size = 2135479, upload-time = "2025-10-10T16:03:37.671Z" }, + { url = "https://files.pythonhosted.org/packages/2b/91/eabd0688330d6fd114f5f12c4f89b0d02929f525e6bf7ff80aa17ca802af/sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45", size = 2123212, upload-time = "2025-10-10T16:03:41.755Z" }, + { url = "https://files.pythonhosted.org/packages/b0/bb/43e246cfe0e81c018076a16036d9b548c4cc649de241fa27d8d9ca6f85ab/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b61188657e3a2b9ac4e8f04d6cf8e51046e28175f79464c67f2fd35bceb0976", size = 3255353, upload-time = "2025-10-10T15:35:31.221Z" }, + { url = "https://files.pythonhosted.org/packages/b9/96/c6105ed9a880abe346b64d3b6ddef269ddfcab04f7f3d90a0bf3c5a88e82/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c", size = 3260222, upload-time = "2025-10-10T15:43:50.124Z" }, + { url = "https://files.pythonhosted.org/packages/44/16/1857e35a47155b5ad927272fee81ae49d398959cb749edca6eaa399b582f/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:15f3326f7f0b2bfe406ee562e17f43f36e16167af99c4c0df61db668de20002d", size = 3189614, upload-time = "2025-10-10T15:35:32.578Z" }, + { url = "https://files.pythonhosted.org/packages/88/ee/4afb39a8ee4fc786e2d716c20ab87b5b1fb33d4ac4129a1aaa574ae8a585/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e77faf6ff919aa8cd63f1c4e561cac1d9a454a191bb864d5dd5e545935e5a40", size = 3226248, upload-time = "2025-10-10T15:43:51.862Z" }, + { url = "https://files.pythonhosted.org/packages/32/d5/0e66097fc64fa266f29a7963296b40a80d6a997b7ac13806183700676f86/sqlalchemy-2.0.44-cp313-cp313-win32.whl", hash = "sha256:ee51625c2d51f8baadf2829fae817ad0b66b140573939dd69284d2ba3553ae73", size = 2101275, upload-time = "2025-10-10T15:03:26.096Z" }, + { url = "https://files.pythonhosted.org/packages/03/51/665617fe4f8c6450f42a6d8d69243f9420f5677395572c2fe9d21b493b7b/sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl", hash = "sha256:c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e", size = 2127901, upload-time = "2025-10-10T15:03:27.548Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/819435b7cb66dac6192e6af8b7d0896b9507edf798f3826b9590c7e980db/sqlalchemy-2.0.44-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f7027414f2b88992877573ab780c19ecb54d3a536bef3397933573d6b5068be4", size = 2138850, upload-time = "2025-10-10T16:20:45.841Z" }, + { url = "https://files.pythonhosted.org/packages/fd/06/e8637ce5c4fdc027c00a9611052329169ef5a99feb22efd38866e27caf27/sqlalchemy-2.0.44-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fe166c7d00912e8c10d3a9a0ce105569a31a3d0db1a6e82c4e0f4bf16d5eca9", size = 2128842, upload-time = "2025-10-10T16:20:47.209Z" }, + { url = "https://files.pythonhosted.org/packages/c3/5b/86f7cc573254bbfa50b339d8c72c5c026ceaa0adaa114237884886a0e14b/sqlalchemy-2.0.44-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3caef1ff89b1caefc28f0368b3bde21a7e3e630c2eddac16abd9e47bd27cc36a", size = 3216858, upload-time = "2025-10-10T15:38:33.535Z" }, + { url = "https://files.pythonhosted.org/packages/2f/ee/c9e582288edb41a47df7525f9fcae775d9f0b7da8eda8732f9d22f0c383e/sqlalchemy-2.0.44-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc2856d24afa44295735e72f3c75d6ee7fdd4336d8d3a8f3d44de7aa6b766df2", size = 3217019, upload-time = "2025-10-10T15:45:13.678Z" }, + { url = "https://files.pythonhosted.org/packages/71/a1/449f3bea46769b31443eac4152452af684c0ddd64e3c4719b636f85a5604/sqlalchemy-2.0.44-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:11bac86b0deada30b6b5f93382712ff0e911fe8d31cb9bf46e6b149ae175eff0", size = 3155491, upload-time = "2025-10-10T15:38:35.317Z" }, + { url = "https://files.pythonhosted.org/packages/0e/34/f99b584a0bf94ff2e822bcb4951dcc24a7967476b35b9b3c35bc11cbd6bc/sqlalchemy-2.0.44-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4d18cd0e9a0f37c9f4088e50e3839fcb69a380a0ec957408e0b57cff08ee0a26", size = 3179978, upload-time = "2025-10-10T15:45:15.262Z" }, + { url = "https://files.pythonhosted.org/packages/ae/43/71a22aa66a1ef974f4e34982ce55d5f38d4770d2f0091eb210374e860b8e/sqlalchemy-2.0.44-cp39-cp39-win32.whl", hash = "sha256:9e9018544ab07614d591a26c1bd4293ddf40752cc435caf69196740516af7100", size = 2106888, upload-time = "2025-10-10T15:45:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/08/fb/cc98eb1ab3c5ad92b51c0db17ee86d1c33379a7490da376567b902222dcf/sqlalchemy-2.0.44-cp39-cp39-win_amd64.whl", hash = "sha256:8e0e4e66fd80f277a8c3de016a81a554e76ccf6b8d881ee0b53200305a8433f6", size = 2130728, upload-time = "2025-10-10T15:45:59.7Z" }, + { url = "https://files.pythonhosted.org/packages/9c/5e/6a29fa884d9fb7ddadf6b69490a9d45fded3b38541713010dad16b77d015/sqlalchemy-2.0.44-py3-none-any.whl", hash = "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05", size = 1928718, upload-time = "2025-10-10T15:29:45.32Z" }, +] + [[package]] name = "starlette" version = "0.47.2" @@ -1102,6 +2358,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4a/40/d54944eeb5646fb4b1c98d4601fe5e0812dd2e7c0aa94d53fc46457effc8/trove_classifiers-2025.8.26.11-py3-none-any.whl", hash = "sha256:887fb0a402bdbecd4415a52c06e6728f8bdaa506a7143372d2b893e2c5e2d859", size = 14140, upload-time = "2025-08-26T11:30:11.427Z" }, ] +[[package]] +name = "typer" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "rich", marker = "python_full_version < '3.10'" }, + { name = "shellingham", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/28/7c85c8032b91dbe79725b6f17d2fffc595dff06a35c7a30a37bef73a1ab4/typer-0.20.0.tar.gz", hash = "sha256:1aaf6494031793e4876fb0bacfa6a912b551cf43c1e63c800df8b1a866720c37", size = 106492, upload-time = "2025-10-20T17:03:49.445Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl", hash = "sha256:5b463df6793ec1dca6213a3cf4c0f03bc6e322ac5e16e13ddd622a889489784a", size = 47028, upload-time = "2025-10-20T17:03:47.617Z" }, +] + +[[package]] +name = "typer-slim" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/45/81b94a52caed434b94da65729c03ad0fb7665fab0f7db9ee54c94e541403/typer_slim-0.20.0.tar.gz", hash = "sha256:9fc6607b3c6c20f5c33ea9590cbeb17848667c51feee27d9e314a579ab07d1a3", size = 106561, upload-time = "2025-10-20T17:03:46.642Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/dd/5cbf31f402f1cc0ab087c94d4669cfa55bd1e818688b910631e131d74e75/typer_slim-0.20.0-py3-none-any.whl", hash = "sha256:f42a9b7571a12b97dddf364745d29f12221865acef7a2680065f9bb29c7dc89d", size = 47087, upload-time = "2025-10-20T17:03:44.546Z" }, +] + [[package]] name = "typing-extensions" version = "4.14.0" @@ -1123,6 +2407,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, ] +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + [[package]] name = "userpath" version = "1.9.2" @@ -1177,6 +2479,67 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, ] +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "httptools", marker = "python_full_version < '3.10'" }, + { name = "python-dotenv", marker = "python_full_version < '3.10'" }, + { name = "pyyaml", marker = "python_full_version < '3.10'" }, + { name = "uvloop", marker = "python_full_version < '3.10' and platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles", marker = "python_full_version < '3.10'" }, + { name = "websockets", marker = "python_full_version < '3.10'" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/14/ecceb239b65adaaf7fde510aa8bd534075695d1e5f8dadfa32b5723d9cfb/uvloop-0.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c", size = 1343335, upload-time = "2025-10-16T22:16:11.43Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ae/6f6f9af7f590b319c94532b9567409ba11f4fa71af1148cab1bf48a07048/uvloop-0.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792", size = 742903, upload-time = "2025-10-16T22:16:12.979Z" }, + { url = "https://files.pythonhosted.org/packages/09/bd/3667151ad0702282a1f4d5d29288fce8a13c8b6858bf0978c219cd52b231/uvloop-0.22.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac33ed96229b7790eb729702751c0e93ac5bc3bcf52ae9eccbff30da09194b86", size = 3648499, upload-time = "2025-10-16T22:16:14.451Z" }, + { url = "https://files.pythonhosted.org/packages/b3/f6/21657bb3beb5f8c57ce8be3b83f653dd7933c2fd00545ed1b092d464799a/uvloop-0.22.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:481c990a7abe2c6f4fc3d98781cc9426ebd7f03a9aaa7eb03d3bfc68ac2a46bd", size = 3700133, upload-time = "2025-10-16T22:16:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/09/e0/604f61d004ded805f24974c87ddd8374ef675644f476f01f1df90e4cdf72/uvloop-0.22.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a592b043a47ad17911add5fbd087c76716d7c9ccc1d64ec9249ceafd735f03c2", size = 3512681, upload-time = "2025-10-16T22:16:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ce/8491fd370b0230deb5eac69c7aae35b3be527e25a911c0acdffb922dc1cd/uvloop-0.22.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1489cf791aa7b6e8c8be1c5a080bae3a672791fcb4e9e12249b05862a2ca9cec", size = 3615261, upload-time = "2025-10-16T22:16:19.596Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9", size = 1352420, upload-time = "2025-10-16T22:16:21.187Z" }, + { url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77", size = 748677, upload-time = "2025-10-16T22:16:22.558Z" }, + { url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21", size = 3753819, upload-time = "2025-10-16T22:16:23.903Z" }, + { url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702", size = 3804529, upload-time = "2025-10-16T22:16:25.246Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733", size = 3621267, upload-time = "2025-10-16T22:16:26.819Z" }, + { url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473", size = 3723105, upload-time = "2025-10-16T22:16:28.252Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, + { url = "https://files.pythonhosted.org/packages/bd/1b/6fbd611aeba01ef802c5876c94d7be603a9710db055beacbad39e75a31aa/uvloop-0.22.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b45649628d816c030dba3c80f8e2689bab1c89518ed10d426036cdc47874dfc4", size = 1345858, upload-time = "2025-10-16T22:17:11.106Z" }, + { url = "https://files.pythonhosted.org/packages/9e/91/2c84f00bdbe3c51023cc83b027bac1fe959ba4a552e970da5ef0237f7945/uvloop-0.22.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ea721dd3203b809039fcc2983f14608dae82b212288b346e0bfe46ec2fab0b7c", size = 743913, upload-time = "2025-10-16T22:17:12.165Z" }, + { url = "https://files.pythonhosted.org/packages/cc/10/76aec83886d41a88aca5681db6a2c0601622d0d2cb66cd0d200587f962ad/uvloop-0.22.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ae676de143db2b2f60a9696d7eca5bb9d0dd6cc3ac3dad59a8ae7e95f9e1b54", size = 3635818, upload-time = "2025-10-16T22:17:13.812Z" }, + { url = "https://files.pythonhosted.org/packages/d5/9a/733fcb815d345979fc54d3cdc3eb50bc75a47da3e4003ea7ada58e6daa65/uvloop-0.22.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17d4e97258b0172dfa107b89aa1eeba3016f4b1974ce85ca3ef6a66b35cbf659", size = 3685477, upload-time = "2025-10-16T22:17:15.307Z" }, + { url = "https://files.pythonhosted.org/packages/83/fb/bee1eb11cc92bd91f76d97869bb6a816e80d59fd73721b0a3044dc703d9c/uvloop-0.22.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:05e4b5f86e621cf3927631789999e697e58f0d2d32675b67d9ca9eb0bca55743", size = 3496128, upload-time = "2025-10-16T22:17:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/76/ee/3fdfeaa9776c0fd585d358c92b1dbca669720ffa476f0bbe64ed8f245bd7/uvloop-0.22.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:286322a90bea1f9422a470d5d2ad82d38080be0a29c4dd9b3e6384320a4d11e7", size = 3602565, upload-time = "2025-10-16T22:17:17.755Z" }, +] + [[package]] name = "virtualenv" version = "20.31.2" @@ -1191,6 +2554,201 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982, upload-time = "2025-05-08T17:58:21.15Z" }, ] +[[package]] +name = "watchfiles" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/1a/206e8cf2dd86fddf939165a57b4df61607a1e0add2785f170a3f616b7d9f/watchfiles-1.1.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:eef58232d32daf2ac67f42dea51a2c80f0d03379075d44a587051e63cc2e368c", size = 407318, upload-time = "2025-10-14T15:04:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0f/abaf5262b9c496b5dad4ed3c0e799cbecb1f8ea512ecb6ddd46646a9fca3/watchfiles-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03fa0f5237118a0c5e496185cafa92878568b652a2e9a9382a5151b1a0380a43", size = 394478, upload-time = "2025-10-14T15:04:20.297Z" }, + { url = "https://files.pythonhosted.org/packages/b1/04/9cc0ba88697b34b755371f5ace8d3a4d9a15719c07bdc7bd13d7d8c6a341/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca65483439f9c791897f7db49202301deb6e15fe9f8fe2fed555bf986d10c31", size = 449894, upload-time = "2025-10-14T15:04:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/d2/9c/eda4615863cd8621e89aed4df680d8c3ec3da6a4cf1da113c17decd87c7f/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0ab1c1af0cb38e3f598244c17919fb1a84d1629cc08355b0074b6d7f53138ac", size = 459065, upload-time = "2025-10-14T15:04:22.795Z" }, + { url = "https://files.pythonhosted.org/packages/84/13/f28b3f340157d03cbc8197629bc109d1098764abe1e60874622a0be5c112/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bc570d6c01c206c46deb6e935a260be44f186a2f05179f52f7fcd2be086a94d", size = 488377, upload-time = "2025-10-14T15:04:24.138Z" }, + { url = "https://files.pythonhosted.org/packages/86/93/cfa597fa9389e122488f7ffdbd6db505b3b915ca7435ecd7542e855898c2/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e84087b432b6ac94778de547e08611266f1f8ffad28c0ee4c82e028b0fc5966d", size = 595837, upload-time = "2025-10-14T15:04:25.057Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/68c1ed5652b48d89fc24d6af905d88ee4f82fa8bc491e2666004e307ded1/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:620bae625f4cb18427b1bb1a2d9426dc0dd5a5ba74c7c2cdb9de405f7b129863", size = 473456, upload-time = "2025-10-14T15:04:26.497Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dc/1a680b7458ffa3b14bb64878112aefc8f2e4f73c5af763cbf0bd43100658/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:544364b2b51a9b0c7000a4b4b02f90e9423d97fbbf7e06689236443ebcad81ab", size = 455614, upload-time = "2025-10-14T15:04:27.539Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/3d782a666512e01eaa6541a72ebac1d3aae191ff4a31274a66b8dd85760c/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bbe1ef33d45bc71cf21364df962af171f96ecaeca06bd9e3d0b583efb12aec82", size = 630690, upload-time = "2025-10-14T15:04:28.495Z" }, + { url = "https://files.pythonhosted.org/packages/9b/73/bb5f38590e34687b2a9c47a244aa4dd50c56a825969c92c9c5fc7387cea1/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a0bb430adb19ef49389e1ad368450193a90038b5b752f4ac089ec6942c4dff4", size = 622459, upload-time = "2025-10-14T15:04:29.491Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ac/c9bb0ec696e07a20bd58af5399aeadaef195fb2c73d26baf55180fe4a942/watchfiles-1.1.1-cp310-cp310-win32.whl", hash = "sha256:3f6d37644155fb5beca5378feb8c1708d5783145f2a0f1c4d5a061a210254844", size = 272663, upload-time = "2025-10-14T15:04:30.435Z" }, + { url = "https://files.pythonhosted.org/packages/11/a0/a60c5a7c2ec59fa062d9a9c61d02e3b6abd94d32aac2d8344c4bdd033326/watchfiles-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a36d8efe0f290835fd0f33da35042a1bb5dc0e83cbc092dcf69bce442579e88e", size = 287453, upload-time = "2025-10-14T15:04:31.53Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/2c5f479fb531ce2f0564eda479faecf253d886b1ab3630a39b7bf7362d46/watchfiles-1.1.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5", size = 406529, upload-time = "2025-10-14T15:04:32.899Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cd/f515660b1f32f65df671ddf6f85bfaca621aee177712874dc30a97397977/watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741", size = 394384, upload-time = "2025-10-14T15:04:33.761Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c3/28b7dc99733eab43fca2d10f55c86e03bd6ab11ca31b802abac26b23d161/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6", size = 448789, upload-time = "2025-10-14T15:04:34.679Z" }, + { url = "https://files.pythonhosted.org/packages/4a/24/33e71113b320030011c8e4316ccca04194bf0cbbaeee207f00cbc7d6b9f5/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b", size = 460521, upload-time = "2025-10-14T15:04:35.963Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c3/3c9a55f255aa57b91579ae9e98c88704955fa9dac3e5614fb378291155df/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14", size = 488722, upload-time = "2025-10-14T15:04:37.091Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/506447b73eb46c120169dc1717fe2eff07c234bb3232a7200b5f5bd816e9/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d", size = 596088, upload-time = "2025-10-14T15:04:38.39Z" }, + { url = "https://files.pythonhosted.org/packages/82/ab/5f39e752a9838ec4d52e9b87c1e80f1ee3ccdbe92e183c15b6577ab9de16/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff", size = 472923, upload-time = "2025-10-14T15:04:39.666Z" }, + { url = "https://files.pythonhosted.org/packages/af/b9/a419292f05e302dea372fa7e6fda5178a92998411f8581b9830d28fb9edb/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606", size = 456080, upload-time = "2025-10-14T15:04:40.643Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c3/d5932fd62bde1a30c36e10c409dc5d54506726f08cb3e1d8d0ba5e2bc8db/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701", size = 629432, upload-time = "2025-10-14T15:04:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/f7/77/16bddd9779fafb795f1a94319dc965209c5641db5bf1edbbccace6d1b3c0/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10", size = 623046, upload-time = "2025-10-14T15:04:42.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/ef/f2ecb9a0f342b4bfad13a2787155c6ee7ce792140eac63a34676a2feeef2/watchfiles-1.1.1-cp311-cp311-win32.whl", hash = "sha256:de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849", size = 271473, upload-time = "2025-10-14T15:04:43.624Z" }, + { url = "https://files.pythonhosted.org/packages/94/bc/f42d71125f19731ea435c3948cad148d31a64fccde3867e5ba4edee901f9/watchfiles-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4", size = 287598, upload-time = "2025-10-14T15:04:44.516Z" }, + { url = "https://files.pythonhosted.org/packages/57/c9/a30f897351f95bbbfb6abcadafbaca711ce1162f4db95fc908c98a9165f3/watchfiles-1.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e", size = 277210, upload-time = "2025-10-14T15:04:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, + { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, + { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, + { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, + { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, + { url = "https://files.pythonhosted.org/packages/a4/68/a7303a15cc797ab04d58f1fea7f67c50bd7f80090dfd7e750e7576e07582/watchfiles-1.1.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c882d69f6903ef6092bedfb7be973d9319940d56b8427ab9187d1ecd73438a70", size = 409220, upload-time = "2025-10-14T15:05:51.917Z" }, + { url = "https://files.pythonhosted.org/packages/99/b8/d1857ce9ac76034c053fa7ef0e0ef92d8bd031e842ea6f5171725d31e88f/watchfiles-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d6ff426a7cb54f310d51bfe83fe9f2bbe40d540c741dc974ebc30e6aa238f52e", size = 396712, upload-time = "2025-10-14T15:05:53.437Z" }, + { url = "https://files.pythonhosted.org/packages/41/7a/da7ada566f48beaa6a30b13335b49d1f6febaf3a5ddbd1d92163a1002cf4/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79ff6c6eadf2e3fc0d7786331362e6ef1e51125892c75f1004bd6b52155fb956", size = 451462, upload-time = "2025-10-14T15:05:54.742Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b2/7cb9e0d5445a8d45c4cccd68a590d9e3a453289366b96ff37d1075aaebef/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c1f5210f1b8fc91ead1283c6fd89f70e76fb07283ec738056cf34d51e9c1d62c", size = 460811, upload-time = "2025-10-14T15:05:55.743Z" }, + { url = "https://files.pythonhosted.org/packages/04/9d/b07d4491dde6db6ea6c680fdec452f4be363d65c82004faf2d853f59b76f/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9c4702f29ca48e023ffd9b7ff6b822acdf47cb1ff44cb490a3f1d5ec8987e9c", size = 490576, upload-time = "2025-10-14T15:05:56.983Z" }, + { url = "https://files.pythonhosted.org/packages/56/03/e64dcab0a1806157db272a61b7891b062f441a30580a581ae72114259472/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acb08650863767cbc58bca4813b92df4d6c648459dcaa3d4155681962b2aa2d3", size = 597726, upload-time = "2025-10-14T15:05:57.986Z" }, + { url = "https://files.pythonhosted.org/packages/5c/8e/a827cf4a8d5f2903a19a934dcf512082eb07675253e154d4cd9367978a58/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08af70fd77eee58549cd69c25055dc344f918d992ff626068242259f98d598a2", size = 474900, upload-time = "2025-10-14T15:05:59.378Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a6/94fed0b346b85b22303a12eee5f431006fae6af70d841cac2f4403245533/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c3631058c37e4a0ec440bf583bc53cdbd13e5661bb6f465bc1d88ee9a0a4d02", size = 457521, upload-time = "2025-10-14T15:06:00.419Z" }, + { url = "https://files.pythonhosted.org/packages/c4/64/bc3331150e8f3c778d48a4615d4b72b3d2d87868635e6c54bbd924946189/watchfiles-1.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cf57a27fb986c6243d2ee78392c503826056ffe0287e8794503b10fb51b881be", size = 632191, upload-time = "2025-10-14T15:06:01.621Z" }, + { url = "https://files.pythonhosted.org/packages/e4/84/f39e19549c2f3ec97225dcb2ceb9a7bb3c5004ed227aad1f321bf0ff2051/watchfiles-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d7e7067c98040d646982daa1f37a33d3544138ea155536c2e0e63e07ff8a7e0f", size = 623923, upload-time = "2025-10-14T15:06:02.671Z" }, + { url = "https://files.pythonhosted.org/packages/0e/24/0759ae15d9a0c9c5fe946bd4cf45ab9e7bad7cfede2c06dc10f59171b29f/watchfiles-1.1.1-cp39-cp39-win32.whl", hash = "sha256:6c9c9262f454d1c4d8aaa7050121eb4f3aea197360553699520767daebf2180b", size = 274010, upload-time = "2025-10-14T15:06:03.779Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3b/eb26cddd4dfa081e2bf6918be3b2fc05ee3b55c1d21331d5562ee0c6aaad/watchfiles-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:74472234c8370669850e1c312490f6026d132ca2d396abfad8830b4f1c096957", size = 289090, upload-time = "2025-10-14T15:06:04.821Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4c/a888c91e2e326872fa4705095d64acd8aa2fb9c1f7b9bd0588f33850516c/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:17ef139237dfced9da49fb7f2232c86ca9421f666d78c264c7ffca6601d154c3", size = 409611, upload-time = "2025-10-14T15:06:05.809Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/5420d1943c8e3ce1a21c0a9330bcf7edafb6aa65d26b21dbb3267c9e8112/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:672b8adf25b1a0d35c96b5888b7b18699d27d4194bac8beeae75be4b7a3fc9b2", size = 396889, upload-time = "2025-10-14T15:06:07.035Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e5/0072cef3804ce8d3aaddbfe7788aadff6b3d3f98a286fdbee9fd74ca59a7/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a13aea58bc2b90173bc69f2a90de8e282648939a00a602e1dc4ee23e26b66d", size = 451616, upload-time = "2025-10-14T15:06:08.072Z" }, + { url = "https://files.pythonhosted.org/packages/83/4e/b87b71cbdfad81ad7e83358b3e447fedd281b880a03d64a760fe0a11fc2e/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b495de0bb386df6a12b18335a0285dda90260f51bdb505503c02bcd1ce27a8b", size = 458413, upload-time = "2025-10-14T15:06:09.209Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8e/e500f8b0b77be4ff753ac94dc06b33d8f0d839377fee1b78e8c8d8f031bf/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88", size = 408250, upload-time = "2025-10-14T15:06:10.264Z" }, + { url = "https://files.pythonhosted.org/packages/bd/95/615e72cd27b85b61eec764a5ca51bd94d40b5adea5ff47567d9ebc4d275a/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336", size = 396117, upload-time = "2025-10-14T15:06:11.28Z" }, + { url = "https://files.pythonhosted.org/packages/c9/81/e7fe958ce8a7fb5c73cc9fb07f5aeaf755e6aa72498c57d760af760c91f8/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24", size = 450493, upload-time = "2025-10-14T15:06:12.321Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546, upload-time = "2025-10-14T15:06:13.372Z" }, + { url = "https://files.pythonhosted.org/packages/00/db/38a2c52fdbbfe2fc7ffaaaaaebc927d52b9f4d5139bba3186c19a7463001/watchfiles-1.1.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdab464fee731e0884c35ae3588514a9bcf718d0e2c82169c1c4a85cc19c3c7f", size = 409210, upload-time = "2025-10-14T15:06:14.492Z" }, + { url = "https://files.pythonhosted.org/packages/d1/43/d7e8b71f6c21ff813ee8da1006f89b6c7fff047fb4c8b16ceb5e840599c5/watchfiles-1.1.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3dbd8cbadd46984f802f6d479b7e3afa86c42d13e8f0f322d669d79722c8ec34", size = 397286, upload-time = "2025-10-14T15:06:16.177Z" }, + { url = "https://files.pythonhosted.org/packages/1f/5d/884074a5269317e75bd0b915644b702b89de73e61a8a7446e2b225f45b1f/watchfiles-1.1.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5524298e3827105b61951a29c3512deb9578586abf3a7c5da4a8069df247cccc", size = 451768, upload-time = "2025-10-14T15:06:18.266Z" }, + { url = "https://files.pythonhosted.org/packages/17/71/7ffcaa9b5e8961a25026058058c62ec8f604d2a6e8e1e94bee8a09e1593f/watchfiles-1.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b943d3668d61cfa528eb949577479d3b077fd25fb83c641235437bc0b5bc60e", size = 458561, upload-time = "2025-10-14T15:06:19.323Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" }, + { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" }, + { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" }, + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/36/db/3fff0bcbe339a6fa6a3b9e3fbc2bfb321ec2f4cd233692272c5a8d6cf801/websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5", size = 175424, upload-time = "2025-03-05T20:02:56.505Z" }, + { url = "https://files.pythonhosted.org/packages/46/e6/519054c2f477def4165b0ec060ad664ed174e140b0d1cbb9fafa4a54f6db/websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a", size = 173077, upload-time = "2025-03-05T20:02:58.37Z" }, + { url = "https://files.pythonhosted.org/packages/1a/21/c0712e382df64c93a0d16449ecbf87b647163485ca1cc3f6cbadb36d2b03/websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b", size = 173324, upload-time = "2025-03-05T20:02:59.773Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cb/51ba82e59b3a664df54beed8ad95517c1b4dc1a913730e7a7db778f21291/websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770", size = 182094, upload-time = "2025-03-05T20:03:01.827Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0f/bf3788c03fec679bcdaef787518dbe60d12fe5615a544a6d4cf82f045193/websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb", size = 181094, upload-time = "2025-03-05T20:03:03.123Z" }, + { url = "https://files.pythonhosted.org/packages/5e/da/9fb8c21edbc719b66763a571afbaf206cb6d3736d28255a46fc2fe20f902/websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054", size = 181397, upload-time = "2025-03-05T20:03:04.443Z" }, + { url = "https://files.pythonhosted.org/packages/2e/65/65f379525a2719e91d9d90c38fe8b8bc62bd3c702ac651b7278609b696c4/websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee", size = 181794, upload-time = "2025-03-05T20:03:06.708Z" }, + { url = "https://files.pythonhosted.org/packages/d9/26/31ac2d08f8e9304d81a1a7ed2851c0300f636019a57cbaa91342015c72cc/websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed", size = 181194, upload-time = "2025-03-05T20:03:08.844Z" }, + { url = "https://files.pythonhosted.org/packages/98/72/1090de20d6c91994cd4b357c3f75a4f25ee231b63e03adea89671cc12a3f/websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880", size = 181164, upload-time = "2025-03-05T20:03:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/2d/37/098f2e1c103ae8ed79b0e77f08d83b0ec0b241cf4b7f2f10edd0126472e1/websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411", size = 176381, upload-time = "2025-03-05T20:03:12.77Z" }, + { url = "https://files.pythonhosted.org/packages/75/8b/a32978a3ab42cebb2ebdd5b05df0696a09f4d436ce69def11893afa301f0/websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4", size = 176841, upload-time = "2025-03-05T20:03:14.367Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" }, + { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" }, + { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" }, + { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/b7/48/4b67623bac4d79beb3a6bb27b803ba75c1bdedc06bd827e465803690a4b2/websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940", size = 173106, upload-time = "2025-03-05T20:03:29.404Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f0/adb07514a49fe5728192764e04295be78859e4a537ab8fcc518a3dbb3281/websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e", size = 173339, upload-time = "2025-03-05T20:03:30.755Z" }, + { url = "https://files.pythonhosted.org/packages/87/28/bd23c6344b18fb43df40d0700f6d3fffcd7cef14a6995b4f976978b52e62/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9", size = 174597, upload-time = "2025-03-05T20:03:32.247Z" }, + { url = "https://files.pythonhosted.org/packages/6d/79/ca288495863d0f23a60f546f0905ae8f3ed467ad87f8b6aceb65f4c013e4/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b", size = 174205, upload-time = "2025-03-05T20:03:33.731Z" }, + { url = "https://files.pythonhosted.org/packages/04/e4/120ff3180b0872b1fe6637f6f995bcb009fb5c87d597c1fc21456f50c848/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f", size = 174150, upload-time = "2025-03-05T20:03:35.757Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c3/30e2f9c539b8da8b1d76f64012f3b19253271a63413b2d3adb94b143407f/websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123", size = 176877, upload-time = "2025-03-05T20:03:37.199Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + [[package]] name = "wrapt" version = "1.17.2"